Tag Archives: log4net

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!