Adding a log4Net provider in .net core console app

I recently was developing a console application in .net core, where I had to use log4net logging.

In the standard asp.net core approach we can use:

    public void Configure(IApplicationBuilder app, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory)
    {
        loggerFactory.AddLog4Net();
    }

But this is .net core console application, where I’m creating LoggerFactory on my own, so it would not work.

In order to solve it, I had to implement my own Log4NetProvider, that would implement ILoggerProvider.

    public class Log4NetProvider : ILoggerProvider
    {
        private readonly string _log4NetConfigFile;

        private readonly bool _skipDiagnosticLogs;

        private readonly ConcurrentDictionary<string, ILogger> _loggers =
            new ConcurrentDictionary<string, ILogger>();

        public Log4NetProvider(string log4NetConfigFile, bool skipDiagnosticLogs)
        {
            _log4NetConfigFile = log4NetConfigFile;
            _skipDiagnosticLogs = skipDiagnosticLogs;
        }

        public ILogger CreateLogger(string categoryName)
        {
            return _loggers.GetOrAdd(categoryName, CreateLoggerImplementation);
        }

        public void Dispose()
        {
            _loggers.Clear();
        }

        private ILogger CreateLoggerImplementation(string name)
        {
            return new Log4NetLogger(name, new FileInfo(_log4NetConfigFile), _skipDiagnosticLogs);
        }
    }

And the implementation of an actual logger:

    public class Log4NetLogger : ILogger
    {
        private readonly string _name;

        private readonly ILog _log;

        private readonly bool _skipDiagnosticLogs;

        private ILoggerRepository _loggerRepository;

        public Log4NetLogger(string name, FileInfo fileInfo, bool skipDiagnosticLogs)
        {
            _name = name;
            _loggerRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
            _log = LogManager.GetLogger(_loggerRepository.Name, name);
            _skipDiagnosticLogs = skipDiagnosticLogs;

            log4net.Config.XmlConfigurator.Configure(_loggerRepository, fileInfo);
        }

        public IDisposable BeginScope<TState>(TState state)
        {
            return null;
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            switch (logLevel)
            {
                case LogLevel.Critical:
                    return _log.IsFatalEnabled;
                case LogLevel.Debug:
                case LogLevel.Trace:
                    return _log.IsDebugEnabled && AllowDiagnostics();
                case LogLevel.Error:
                    return _log.IsErrorEnabled;
                case LogLevel.Information:
                    return _log.IsInfoEnabled && AllowDiagnostics();
                case LogLevel.Warning:
                    return _log.IsWarnEnabled;
                default:
                    throw new ArgumentOutOfRangeException(nameof(logLevel));
            }
        }

        public void Log<TState>(
            LogLevel logLevel,
            EventId eventId,
            TState state,
            Exception exception,
            Func<TState, Exception, string> formatter)
        {
            if (!IsEnabled(logLevel))
            {
                return;
            }

            if (formatter == null)
            {
                throw new ArgumentNullException(nameof(formatter));
            }

            string message = $"{formatter(state, exception)} {exception}";

            if (!string.IsNullOrEmpty(message) || exception != null)
            {
                switch (logLevel)
                {
                    case LogLevel.Critical:
                        _log.Fatal(message);
                        break;
                    case LogLevel.Debug:
                    case LogLevel.Trace:
                        _log.Debug(message);
                        break;
                    case LogLevel.Error:
                        _log.Error(message);
                        break;
                    case LogLevel.Information:
                        _log.Info(message);
                        break;
                    case LogLevel.Warning:
                        _log.Warn(message);
                        break;
                    default:
                        _log.Warn($"Encountered unknown log level {logLevel}, writing out as Info.");
                        _log.Info(message, exception);
                        break;
                }
            }
        }

        private bool AllowDiagnostics()
        {
            if (!_skipDiagnosticLogs)
            {
                return true;
            }

            return !(_name.ToLower().StartsWith("microsoft")
                || _name == "IdentityServer4.AccessTokenValidation.Infrastructure.NopAuthenticationMiddleware");
        }
    }

One last touch is adding an extension for ILoggerFactory to be able to use AddLog4Net.

    public static class Log4netExtensions
    {
        public static ILoggerFactory AddLog4Net(this ILoggerFactory factory, bool skipDiagnosticLogs)
        {
            factory.AddProvider(new Log4NetProvider("log4net.config", skipDiagnosticLogs));
            return factory;
        }
    }

In my DI container registration, I added code:

var loggerFactory = new Microsoft.Extensions.Logging.LoggerFactory();
loggerFactory.AddLog4Net(true);
Container.RegisterInstance<Microsoft.Extensions.Logging.ILoggerFactory>(loggerFactory);

Now it will all works!

To see the whole code, go to my GitHub repository and check this commit: https://github.com/mikuam/console-app-net-core/commit/650ac5348886d3e0238dfec07076b959d62bd4ba

Hope that works for you!

12 thoughts on “Adding a log4Net provider in .net core console app

  1. Tomas Grolmus

    Great! Thanks a lot for this. I had some issues with usings however in the end this worked like a charm!

    Reply
    1. Michał Białecki Post author

      Hi Jocelyne,
      If you follow all the steps, you can inject it in your class constructor:

      public class CustomService : ICustomService
      {
      private readonly ILogger _logger;

      public CustomService(ILoggerFactory loggerFactory)
      {
      _logger = loggerFactory.CreateLogger(typeof(CustomService));
      }
      }

      Reply
  2. Remy

    Hi

    I tried your classes and have a problem with debug messages.
    All messages are displayed except debug ones.

    I put _skipDiagnosticLogs=false;

    In my log4net.config I tried with

    or

    Nothing works

    What is strange is If I make a “step by step” on a Line Logger.LogDebug(“hello debug !”); it doesn’t enter in Log4NetLogger class.
    If I make a “step by step” on a Line Logger.LogInformation(“hello info !”); it enters in Log4NetLogger class.

    Any idea?

    Reply
    1. Michał Białecki Post author

      @Remy, sorry, no idea. I would need to see the code. Is it available on any Github account?

      Reply
    2. Péter Szatmári

      Hello Remy!

      Not sure if you solved it or worked around this, but I’ll leave my findings here.
      Apparently the default LoggingFactory you instantiate has the minimum logging level set. The below code will set the log level back to the lowest level of trace.

      ILoggerFactory factory = LoggerFactory.Create(builder =>
      {
      builder
      .SetMinimumLevel(LogLevel.Trace)
      .AddProvider(yourProvider);
      });

      Reply
    1. Vishal

      So a quick and dirty way would be to create things manually.

      In the program.cs Main method:

      var logger = new Log4NetProvider(“log4net.config”, false);
      var log = logger.CreateLogger(“Program”);
      log.Log(LogLevel.Information, “Hello world”);

      You can change the log4net.config file to:

      This will create a log.txt file in the folder running the exe.

      Make sure that the config file is set to “COPY IF NEWER”

      Hope this helps.

      Reply
  3. Petko

    Hi Michal,

    I am struggling running the log4net in my console app. Do you have this code in github so I could check it out and figure out, what is wrong in my implementation.

    Reply
      1. Petko

        Thanks a lot. It’s been very helpful. I am using the IServiceCollection for registering my services and all I had to add was this line of code: services.AddSingleton(loggerFactory);

        Reply

Leave a Reply

Your email address will not be published. Required fields are marked *