Quantcast
Channel: MSDN Blogs
Viewing all articles
Browse latest Browse all 5308

Simple EF6-style Logging for EF Core

$
0
0

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;
            }
        }
    }
}

Viewing all articles
Browse latest Browse all 5308

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>