How to Log4Net Logging in ASP.NET Core

In this article, let's talk about how we can configure and implement Log4net file logging in an ASP.NET Core application in a simple way.

Logging is one of those most essential components to build in an application, which can later help us in quick troubleshooting in case the application runs into any issues during runtime.

Since an application can’t be guaranteed error-free in production loads, where it comes across varieties of requests and scenarios which may not be even reproduced in test environments, logging is quite important for developers and troubleshooters alike.

There are a variety of ways in which logs can be captured in applications, say like a data store (database logging), file logging and even cloud providers (such as Azure Application Insights or AWS CloudWatch to name a few).

File logging is one of the oldest approaches which always finds a section of developers who prefer over a database logging and which is the way for applications which don’t have a database layer for itself (such as TCP Listeners or Routing applications).

There are various providers which offer file logging options by means of frameworks which help developers implement file logging without worrying about threading and other file I/O related issues.

A few examples for these providers are Serilog, Log4net, Loggr, Sentry to name a few. In this article, let’s talk about how we can configure and implement Log4net file logging in an AspNetCore application in a simple way.

AspNetCore provides various approaches to register and use third-party Logging providers by means of its ILoggerFactory interface. To add a new custom provider we would need to create a new LoggerProvider that implements ILoggerProvider with logic to setup and configure the custom Logging provider.

Few providers such as Serilog come with this setup built-in so that we can just add Serilog logging service by chaining Serilog with the LoggerFactory and Serilog starts logging onto the file immediately during runtime.

Whereas Log4net doesn’t have such support and we would need to go through all this to create Log4net logging provider onto AspNetCore application. To solve this, we’ll skip adding Log4net to LoggerFactory and instead use our own service to Log using Log4net.

About Log4Net

The Apache log4net library is a tool to output log statements to a variety of output targets. log4net is a port of log4j framework to the .NET ecosystem. The framework is similar to the original log4j while taking advantage of new features in the .NET runtime.

– Documentation

A Log4net logging provider depends on an xml configuration file called log4net.config which contains instructions about how a file should be created, where it should be created and in which format the created logs are written onto the Log files.

Log4net picks up this config file and digests it into its provider instance, using which the Logging functionality works.

Getting Started – Installation

To get started with adding Log4net capabilities, we begin by installing the Log4net nuget package to our application as below:

> dotnet add package log4net

or to the csproj

<PackageReference Include="log4net" Version="2.0.8" />

Implementing Log4Net – Singleton Approach

For our implementation, let’s create an interface ILoggerManager which defines a single method LogInformation() that accepts a string arguement that shall be written onto the file. Let’s add an implementation of ILoggerManager that implements this method with functionality to write to files using Log4net.

namespace TestApi.Providers
{
    public interface ILoggerManager
    {
        void LogInformation(string message);
    }

    public class LoggerManager : ILoggerManager
    {
        // Logging functionality happens here
    }
}

Inside the LoggerManager class, add an instance variable _logger of type ILog which holds the Log4net logger setup. We create a new Logger instance and assign it to _logger, to use it for the logging functionality.

private readonly ILog _logger = LogManager.GetLogger(typeof(LoggerManager));

Let’s add a constructor to LoggerManager() which contains the configuration to create and configure Log4net logging provider.

public LoggerManager()
{
    try
    {
        XmlDocument log4netConfig = new XmlDocument();

        using (var fs = File.OpenRead("log4net.config"))
        {
            log4netConfig.Load(fs);
            
            var repo = LogManager.CreateRepository(
                    Assembly.GetEntryAssembly(), 
                    typeof(log4net.Repository.Hierarchy.Hierarchy));
            
            XmlConfigurator.Configure(repo, log4netConfig["log4net"]);
            
            // The first log to be written 
            _logger.Info("Log System Initialized");
        }
    }
    catch (Exception ex)
    {
        _logger.Error("Error", ex);
    }
}

The above code snippet reads the config file log4net.config which contains the instructions on writing logs to the files as mentioned before. It then configures the Log4net.LoggerRepository with the loaded configuration file and this empowers logger to write logs according to the prescribed configuration.

The log4net.config is configured as below:

<?xml version="1.0" encoding="utf-8" ?>
<log4net>
  <appender name="RollingLogFileAppender" 
      type="log4net.Appender.RollingFileAppender">
    <lockingModel type="log4net.Appender.FileAppender+MinimalLock"/>
    <file value="Logs" />
    <datePattern value="yyyy-MM-dd.'txt'"/>
    <staticLogFileName value="false"/>
    <appendToFile value="true"/>
    <rollingStyle value="Date"/>
    <maxSizeRollBackups value="100"/>
    <maximumFileSize value="15MB"/>
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern 
        value="%date [%thread] %-5level App  %newline %message %newline %newline"/>
    </layout>
  </appender>
  <root>
    <level value="INFO"/>
    <appender-ref ref="RollingLogFileAppender"/>
  </root>
</log4net>

Log4net configures Loggers in the name of “Appenders” which contains configuration about how the log needs to be written; including the file name, format, log location and attributes that needs to be appended along with the log message.

At the root, we have the log level along with that points to an specified.

We implement our LogInformation() method from the ILoggerManager interface as below:

public void LogInformation(string message)
{
    _logger.Info(message);
}

Apart from Info() method, Log4net supports these type of Logs:

  • Info()
  • Debug()
  • Warn()
  • Error()
  • Fatal()

All the logs generated from the application are written onto log file YYYY-MM-DD.txt inside the /Logs folder and all the logs created for a single day are appended into the same file, creating one single file per day.

<file value="Logs" />
<datePattern value="yyyy-MM-dd.'txt'"/>
<staticLogFileName value="false"/>
<appendToFile value="true"/>
<rollingStyle value="Date"/>

And the log is written inside the file per message as:

2020-06-07 14:45:48,048 [1] INFO  App  
 Starting the Host 

which is due to the log pattern specification given inside the log4net.config file within the element as:

<conversionPattern 
  value="%date [%thread] %-5level App  %newline %message %newline %newline"/>

Finally, we put this setup into action by registering the ILoggerManager and its implementation LoggerManager as a “Singleton” service in the startup class as below:

services.AddSingleton<ILoggerManager, LoggerManager>();

Since we register the service as a “Singleton”, the constructor is called only once in its lifetime and hence the Logging works as expected without being reinstantiated each time.

We can inject this service in any other component such as a Controller and then pass the log message to the Log4net logger via the LogInformation() method which is further appended onto the log file.

public class ReadersController : ControllerBase 
{
    private readonly ILoggerManager logger;
    private readonly IReaderStore store;
    
    public ReadersController(IReaderStore store, ILoggerManager logger)
    {
        this.logger = logger;
        this.store = store;

        this.logger.LogInformation("On the ReadersController constructor");
    }
}

To use this service inside the main method, we can make use of the host instance to get the service instance and call LogInformation() on it.

public static void Main(string[] args)
{
    var host = CreateHostBuilder(args).Build();

    var logger = host.Services.GetRequiredService<ILoggerManager>();

    logger.LogInformation("Starting the Host");

    host.Run();
}

Buy Me A Coffee

Found this article helpful? Please consider supporting!

In this way, we can implement a simple Log4net logger and use it to log messages in AspNetCore application. The complete example with the configuration mentioned here is available at https://github.com/referbruv/log4net-sample


Buy Me A Coffee

Found this article helpful? Please consider supporting!

Ram
Ram

I'm a full-stack developer and a software enthusiast who likes to play around with cloud and tech stack out of curiosity. You can connect with me on Medium, Twitter or LinkedIn.

Leave a Reply

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