It took me a while to figure out logging in EF Core, and I missed the simple way you could add logging for EF6. Remember in EF6 it's as easy as
using (var db = new BloggingContext()) { db.Database.Log = s => Console.WriteLine(s); //. . . }
In EF Core it's more code, and more complicated, as documented here: https://docs.microsoft.com/en-us/ef/core/miscellaneous/logging
using (var db = new BloggingContext()) { var serviceProvider = db.GetInfrastructure(); var loggerFactory = serviceProvider.GetService(); loggerFactory.AddProvider(new MyLoggerProvider()); //. . . }
And what's worse, the log message handling is buried inside the guts of MyLoggerProvider. So I took that sample and reworked so you can add a logger for your DbContext like this:
using (var db = new BloggingContext()) { db.ConfigureLogging( s => Console.WriteLine(s) ); //. . . }
You can also filter the logger by LogLevel and/or category with an optional second parameter. Here the logger will capture all Errors and all Queries like this:
using (var db = new BloggingContext()) { db.ConfigureLogging( s => Console.WriteLine(s) , (c,l) => l == LogLevel.Error || c == DbLoggerCategory.Query.Name); //. . . }
And there's a shortcut to log the Query, Command, and Update categories to capture the all the generated SQL and execution statistics.
using (var db = new BloggingContext()) { db.ConfigureLogging(s => Console.WriteLine(s), LoggingCategories.SQL); //. . . }
Once you configure logging on a DbContext instance it will be enabled on all instances of that DbContext type. Repeated calls to ConfigureLogging() will change the logging for the DbContext type. Behind the scenes there is a single LogProvider for each DbContext type.
Note in a multi-threaded environment changing the logging will affect all new instances of your DbContext type.
It's an extension method so you need to import the namespace with:
using Microsoft.Samples.EFLogging;
And here's the code:
namespace Microsoft.Samples.EFLogging { using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.Extensions.Logging; using System; using System.Collections.Concurrent; using System.Collections.Generic; public enum LoggingCategories { All = 0, SQL = 1 } public static class DbContextLoggingExtensions { public static void ConfigureLogging(this DbContext db, Action logger, Func filter ) { var serviceProvider = db.GetInfrastructure(); var loggerFactory = (ILoggerFactory)serviceProvider.GetService(typeof(ILoggerFactory)); LogProvider.CreateOrModifyLoggerForDbContext(db.GetType(), loggerFactory, logger, filter); } public static void ConfigureLogging(this DbContext db, Action logger, LoggingCategories categories = LoggingCategories.All) { var serviceProvider = db.GetInfrastructure(); var loggerFactory = (Microsoft.Extensions.Logging.LoggerFactory)serviceProvider.GetService(typeof(ILoggerFactory)); if (categories == LoggingCategories.SQL) { var SqlCategories = new List { DbLoggerCategory.Database.Command.Name, DbLoggerCategory.Query.Name, DbLoggerCategory.Update.Name }; LogProvider.CreateOrModifyLoggerForDbContext(db.GetType(), loggerFactory, logger, (c, l) => SqlCategories.Contains(c)); } else if (categories == LoggingCategories.All) { LogProvider.CreateOrModifyLoggerForDbContext(db.GetType(), loggerFactory, logger, (c, l) => true); } } } internal class LogProvider : ILoggerProvider { static ConcurrentDictionary providers = new ConcurrentDictionary(); public static void CreateOrModifyLoggerForDbContext(Type DbContextType, ILoggerFactory loggerFactory, Action logger, Func filter = null) { var provider = providers.GetOrAdd(DbContextType, t => { var p = new LogProvider(); loggerFactory.AddProvider(p); return p; } ); provider.logger = logger; provider.filter = filter ?? DefaultFilter; } Action logger; Func filter; public static bool DefaultFilter(string CategoryName, LogLevel level) => true; private LogProvider() { } private LogProvider(Action logger, Func filter = null) { this.filter = filter ?? DefaultFilter; this.logger = logger; } public ILogger CreateLogger(string categoryName) { return new Logger(categoryName, this); } public void Dispose() { } private class Logger : ILogger { string categoryName; LogProvider provider; public Logger(string categoryName, LogProvider provider) { this.provider = provider; this.categoryName = categoryName; } public bool IsEnabled(LogLevel logLevel) { return true; } public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { if (provider.filter(categoryName, logLevel)) { provider.logger(formatter(state, exception)); } } public IDisposable BeginScope(TState state) { return null; } } } }