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!
Great! Thanks a lot for this. I had some issues with usings however in the end this worked like a charm!
how do you inject the log4net to another class in order to log in it?
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));
}
}
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?
@Remy, sorry, no idea. I would need to see the code. Is it available on any Github account?
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);
});
Hey, Thanks for the great code. I had a question. how do you register the service in Program.cs file?
Hi @MRebati, thanks for the comment. Take a look at the end of the post.
LoggerFactor
is crated. Also, you can take a look at the code I added to make it work: https://github.com/mikuam/console-app-net-core/commit/650ac5348886d3e0238dfec07076b959d62bd4baSo 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.
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.
Hi Petko,
I just added a working example to my GitHub repo, check that out!
https://github.com/mikuam/console-app-net-core/commit/650ac5348886d3e0238dfec07076b959d62bd4ba
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);