Log4net appenders are the components that determine where we need to keep the logs. Using appenders, we can send log4net logs to different destinations such as the console, files, databases, etc.

In this article, we are going to discuss a few of the most common and useful log4net appenders in detail with examples. 

To download the source code for this article, you can visit our GitHub repository.

For an introduction to using log4net in ASP.NET Core, check out our Structured Logging in ASP.NET Core With log4net article.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

So let’s get going.

Log4net Appenders to Write Logs Into the Console

To write log4net logs to the application’s console, the simplest choice is to use the ConsoleAppender

So let’s add the console appender in the log4net.config file:

<log4net>
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
        <layout type="log4net.Layout.PatternLayout">
            <conversionPattern value="%date{dd MMM yyyy HH:mm:ss} %-5level - %message%newline" />
        </layout>
    </appender>
    <root>
        <level value="ALL" />
        <appender-ref ref="ConsoleAppender" />
    </root>
</log4net>

After that, let’s add different types of logs such as Debug, Info, Warn, and Error in the controller action method:

public class LogTestController : ControllerBase
{
    private readonly ILog _logger;

    public LogTestController(ILog logger)
    {
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    [HttpGet]
    public IActionResult Get()
    {
        _logger.Debug("This is a debug log.");
        _logger.Info("This is an informational log.");
        _logger.Warn("This is a warning log.");
        _logger.Error("This is an error log.");

        return Ok("Test success!");
    }
}

Here, we create a new controller and inject the ILog interface into the constructor. Then, in our Get() method, we write a set of logs.

Now let’s run the application and navigate to the /logtest endpoint.

We notice that the application now writes logs into the console window:

26 May 2023 18:51:03 DEBUG - This is a debug log.
26 May 2023 18:51:03 INFO  - This is an informational log.
26 May 2023 18:51:03 WARN  - This is a warning log.
26 May 2023 18:51:03 ERROR - This is an error log.

Notice that the logging is based on the layout format that we specified in the log4net configuration file.

It is possible to configure text and background colors for different log levels in the console using the ManagedColoredConsoleAppender. So let’s replace the ConsoleAppender with it:

<appender name="ManagedColoredConsoleAppender" type="log4net.Appender.ManagedColoredConsoleAppender">
    <mapping>
        <level value="ERROR" />
        <foreColor value="DarkRed" />
        <backColor value="White" />
    </mapping>
    <mapping>
        <level value="WARN" />
        <foreColor value="Yellow" />
    </mapping>
    <mapping>
        <level value="INFO" />
        <foreColor value="White" />
    </mapping>
    <mapping>
        <level value="DEBUG" />
        <foreColor value="Blue" />
    </mapping>

    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date{dd MMM yyyy HH:mm:ss} %-5level - %message%newline"/>
    </layout>
</appender>
<root>
    <level value="ALL" />
    <appender-ref ref="ManagedColoredConsoleAppender" />
</root>

Here we add mappings for each log level and configured errors in red color with white background. Similarly, we have configured warnings in yellow, informational logs in white, and debug logs in blue color.

Now let’s run the application once again and navigate to the /logtest endpoint. We can see that the application still writes logs into the console, but it formats the log messages with the different colors that we configured:

log4net appender colored console

This way we can produce a customized colored console output that will help us to easily distinguish between different types of logs.

Write Debug Logs With log4net Appenders

If we want to write log4net logs into the debug system, we can use DebugAppender.

So let’s configure the debug appender in the log4net.config file:

<appender name="DebugAppender" type="log4net.Appender.DebugAppender">
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date{dd MMM yyyy HH:mm:ss} %-5level - %message%newline" />
    </layout>
</appender>
<root>
    <level value="ALL" />
    <appender-ref ref="DebugAppender" />
</root>

Here, we replace the console appender with a debug appender. This will send the log4net logs to the debug output instead of the console.

Now if we run the application in debug mode and navigate to the /logtest endpoint, it will write logs into the application’s debug information. While debugging in Visual Studio, we can view this in the output window by filtering on the debug output:

Log4netAppenders.Program: 31 May 2023 15:56:38 DEBUG - This is a debug log.
Log4netAppenders.Program: 31 May 2023 15:56:38 INFO  - This is an informational log.
Log4netAppenders.Program: 31 May 2023 15:56:38 WARN  - This is a warning log.
Log4netAppenders.Program: 31 May 2023 15:56:38 ERROR - This is an error log.

Similarly, it is possible to write logs into a trace system by using the TraceAppender. For that, we need to replace the debug appender with the trace appender in the log4net.config file:

<appender name="TraceAppender" type="log4net.Appender.TraceAppender">
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date{dd MMM yyyy HH:mm:ss} %-5level - %message%newline" />
    </layout>
</appender>
<root>
    <level value="ALL" />
    <appender-ref ref="TraceAppender" />
</root>

Once we configure the trace appender, the application will convert logs into traces and write them into the trace listeners. We have the option to configure different trace listeners for our application and the logs will go into the trace system that we configure.

While debugging the application in Visual Studio, we can find the traces as part of the debug output in the output window:

Log4netAppenders.Program: 31 May 2023 16:18:07 DEBUG - This is a debug log.
Log4netAppenders.Program: 31 May 2023 16:18:07 INFO  - This is an informational log.
Log4netAppenders.Program: 31 May 2023 16:18:07 WARN  - This is a warning log.
Log4netAppenders.Program: 31 May 2023 16:18:07 ERROR - This is an error log.

While we use debug systems mostly to investigate any issues in the application, a trace listener usually tracks all the application events step by step. Using appropriate log4net appenders, it is possible to write logs into both a debug system and a trace listener.

In-Memory Logging With log4net 

The MemoryAppender stores the log4net logs as an array in memory. This is an excellent choice when we want to write tests for verifying the logging functionality. For testing the log4net logging functionality, we just need to create a MemoryAppender instance and initialize the log4net system using it. After that, once we execute the method under test, we can verify the log entries in the appender events.

Since MemoryAppender is mostly used for writing tests, we usually configure it through code even though it is possible to configure it with the log4net configuration file as well.

Let’s write a test method for testing our endpoint that writes logs:

public class Log4netAppendersTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;

    public Log4netAppendersTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
    }

    [Fact]
    public async Task GivenLog4NetConfigured_WhenLogTestIsInvoked_ThenAllEventsAreLogged()
    {
        // arrange
        var client = _factory.CreateClient();

        var appender = new log4net.Appender.MemoryAppender();
        BasicConfigurator.Configure(appender);

        // act
        var response = await client.GetAsync("api/logtest");

        //assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);

        var logEntries = appender.GetEvents();
        Assert.Equal(4, logEntries.Length);
    }
}

First, we create a HttpClient instance and configure a MemoryAppender for the log4net. After that, we send an HTTP GET request to the endpoint that we want to test. Along with verifying the HTTP status code, we can get the log events from the appender and verify those as well.

For learning more on how to test an ASP.NET Core Web API using the WebApplicationFactory class, please refer to our great article Integration Testing in ASP.NET Core.

Next up, let’s see how we can use a log4net appender to write logs to files.

Log4net Appenders to Write Logs Into Files

It is possible to write log4net logs into a file in the file system as well. For that, the most simple way is to use the FileAppender. So let’s replace the TraceAppender with FileAppender in the log4net.config file:

<appender name="FileAppender" type="log4net.Appender.FileAppender">
    <file value="logfile.txt" />
    <appendToFile value="true" />
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date{dd MMM yyyy HH:mm:ss} %-5level - %message%newline" />
    </layout>
</appender>
<root>
    <level value="ALL" />
    <appender-ref ref="FileAppender" />
</root>

Here we specify the path and name for the log file in the file node. We configure it to create the log file named logfile.txt in the application’s root path. The appendToFile value determines if we need to append to the specified file every time or overwrite it, which we have set to true.

Now if we run the application once again and navigate to the /logtest endpoint, it will write logs into the log file with the name logfile.txt in the application’s root directory:

31 May 2023 18:32:26 DEBUG - This is a debug log.
31 May 2023 18:32:26 INFO  - This is an informational log.
31 May 2023 18:32:26 WARN  - This is a warning log.
31 May 2023 18:32:26 ERROR - This is an error log.

Note that the application will continue to append logs into the same file if we run the endpoint multiple times as we have configured it to append logs to the file.

Writing log4net logs into a file is a very nice option, but what if the log file keeps on growing infinitely? Of course, we don’t want that to happen and need a mechanism to split log files based on size, date, etc. The RollingFileAppender will help us to achieve this functionality.

Split Log Files With RollingFileAppender

By using a RollingFileAppender, we can split log files based on size, date, or both. So let’s replace the FileAppender with  RollingFileAppender and explore the various configuration options:

<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender">
    <file value="log-" />
    <appendToFile value="true" />
    <rollingStyle value="Date" />
    <datePattern value="yyyyMMdd-HHmm'.txt'" />
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
    </layout>
</appender>

We have already discussed the purpose of the file and appendToFile property with the FileAppender

The RollingStyle property determines how we need to split the log files. If we want to split the log files based on size, we can set its value to size and specify the maximum file size in the maximumFileSize property. On the other hand, if we want to split the log files based on date, we can set its value to date and specify the date boundary in the datePattern property. By setting its value to Composite, we can still split log files based on the date boundary we specify in the datePattern property. However, within a date boundary, the file will be split once it exceeds the size limit specified in the maximumFileSize property as well.

Here, we configure the application to split log files based on date and time by setting the datePattern format to yyyyMMdd-HHmm which will create new log files every minute. However, if we want to change that and create just one log file per day, we can change its format to yyyyMMdd and so on.

Notice that we have suffixed the date pattern with the file extension and made its value yyyyMMdd-HHmm'.txt'.  This is to preserve the file extension while it generates the log file names dynamically. The appender will name the log file by joining the file value and the datePattern value. So this will produce log files with the name format log-yyyyMMdd-HHmm.txt.

Now let’s run the application once again and navigate to the /logtest endpoint. This time it will create a separate log file for each minute that the application writes logs. Also note that it names the log file by appending the values of file and datePattern , For eg, log-20230531-1920.txtlog-20230531-1921.txt, etc.

Write log4net Logs Into a Database

Log4net provides a default AdoNetAppender for writing logs to the database. But it doesn’t officially support .NET Core as of date. However, there is a third-party Log4NetAdoNetAppender NuGet package that extends AdoNetAppender and works pretty well with the latest .NET Core versions. We are going to use that to write logs to a SQL database.

For that, first, let’s create a database table to store the logs:

CREATE TABLE [dbo].[Log] ( 
  [ID] [int] IDENTITY (1, 1) NOT NULL ,
  [Date] [datetime] NOT NULL ,
  [Thread] [varchar] (255) NOT NULL ,
  [Level] [varchar] (20) NOT NULL ,
  [Logger] [varchar] (255) NOT NULL ,
  [Message] [varchar] (4000) NOT NULL 
) ON [PRIMARY]

The next step is to add the NuGet reference to our project:

dotnet add package MicroKnights.Log4NetAdoNetAppender

Then let’s replace the RollingFileAppender with the Log4NetAdoNetAppender in the log4net.config file:

<appender name="AdoNetAppender" type="MicroKnights.Logging.AdoNetAppender, MicroKnights.Log4NetAdoNetAppender">
    <connectionType value="Microsoft.Data.SqlClient.SqlConnection, Microsoft.Data.SqlClient, 
                Version=1.0.0.0,Culture=neutral,PublicKeyToken=23ec7fc2d6eaa4a5" />
    <connectionString value="{Provide the DB connection string here}" />
    <commandText value="INSERT INTO Log ([Date],[Thread],[Level],[Logger],[Message]) 
                VALUES (@log_date, @thread, @log_level, @logger, @message)" />
    <parameter>
        <parameterName value="@log_date" />
        <dbType value="DateTime" />
        <layout type="log4net.Layout.PatternLayout" value="%date{yyyy'-'MM'-'dd HH':'mm':'ss'.'fff}" />
    </parameter>
    <parameter>
        <parameterName value="@thread" />
        <dbType value="String" />
        <size value="255" />
        <layout type="log4net.Layout.PatternLayout" value="%thread" />
    </parameter>
    <parameter>
        <parameterName value="@log_level" />
        <dbType value="String" />
        <size value="50" />
        <layout type="log4net.Layout.PatternLayout" value="%level" />
    </parameter>
    <parameter>
        <parameterName value="@logger" />
        <dbType value="String" />
        <size value="255" />
        <layout type="log4net.Layout.PatternLayout" value="%logger" />
    </parameter>
    <parameter>
        <parameterName value="@message" />
        <dbType value="String" />
        <size value="4000" />
        <layout type="log4net.Layout.PatternLayout" value="%message" />
    </parameter>
</appender>
<root>
    <level value="ALL" />
    <appender-ref ref="AdoNetAppender" />
</root>

For the connectionType property, we need to specify the type and library details of the SqlConnection class. In the connectionString property, we provide our DB connection string. Similarly, in the commandText, we provide the SQL statement to insert the log data into the database table.

After that, we provide the list of parameters by specifying the parameter name, type, and size. Also, note that we use the layout property to format the parameter values before inserting them into the table.

That’s all the changes needed. Our application will now write log4net logs into the database table:

log4net appender adonet

We can see the logs in the database table now. 

Send log4net Logs to Azure Application Insights

Azure Application Insights is an excellent tool that can monitor live applications and capture logs, telemetry data, etc. Application Insights Log4Net Appender is a custom appender that allows us to send log4net logs to Azure Application Insights. Let’s see how to use it in our application.

First, let’s create an Azure Application Insights resource from the Azure Portal. After creating the Application Insights, we need to copy the connection string and keep it aside. We’ll need to add the connection string to our application later so that it can connect with this resource and send data to it.

The next step is adding the NuGet packages to our application:

dotnet add package Microsoft.ApplicationInsights.Log4NetAppender
dotnet add package Microsoft.ApplicationInsights.AspNetCore

While the Microsoft.ApplicationInsights.Log4NetAppender NuGet package allows us to send log4net logs to Azure Application Insights, Microsoft.ApplicationInsights.AspNetCore NuGet package is needed for enabling code-based monitoring for Azure Application Insights in an ASP.NET Core application.

After that, let’s add the Application Insights connection string in the appsettings.json file:

{
  "Logging": {
    "LogLevel": {
...
    }
  },
  "AllowedHosts": "*",
  "ApplicationInsights": {
    "ConnectionString": "{Provide the Application Insights connection string here}"
  }
}

Next, let’s add the Application Insights services to the service collection in the Program class:

builder.Services.AddApplicationInsightsTelemetry(new ApplicationInsightsServiceOptions
{
    ConnectionString = builder.Configuration["ApplicationInsights:ConnectionString"],
    EnableActiveTelemetryConfigurationSetup = true
});

Now, let’s replace the Log4NetAdoNetAppender with the ApplicationInsightsAppender in the log4net.config file:

<appender name="AiAppender" type="Microsoft.ApplicationInsights.Log4NetAppender.ApplicationInsightsAppender, 
            Microsoft.ApplicationInsights.Log4NetAppender">
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%message%newline"/>
    </layout>
</appender>
<root>
    <level value="ALL" />
    <appender-ref ref="AiAppender" />
</root>

Our application is now ready to send log4net logs to Azure Application Insights.

Let’s run the application once again and navigate to the /logtest endpoint. Notice that the ApplicationInsightsAppender will convert the logs into traces and send them to Application Insights. That means the logs will be available in the traces section of the Azure Application Insights from where we can query and analyze the results:

Log4net Appender Application Insights

We can now see the logs in the traces section of the Azure Application Insights. 

Log4net Appender for Seq

Seq is a centralized structured logging system that provides powerful searching, filtering, and analyzing capabilities.  It provides a log4net appender that can write events to Seq via HTTP.

So, let’s see how to configure the Seq appender in our application. But before that, let’s download and install Seq on our local machine. Once the Seq service is installed and configured, we can access the Seq UI using the configured port. The default URL will be http://localhost:5341

After that, we need to install the Seq.Client.Log4Net NuGet package in our application:

dotnet add package Seq.Client.Log4Net

The next step is to replace the ApplicationInsightsAppender with the SeqAppender in the log4net.config file:

<appender name="SeqAppender" type="Seq.Client.Log4Net.SeqAppender, Seq.Client.Log4Net" >
    <bufferSize value="1" />
    <serverUrl value="http://localhost:5341" />
</appender>
<root>
    <level value="ALL" />
    <appender-ref ref="SeqAppender" />
</root>

We need to provide the address of the Seq server in the serverURL property. 

That’s it. Our application will now start sending log4net logs into Seq.

We can verify it by opening the Seq UI:

log4net appender seq

We can now see the logs in Seq.

Use Multiple log4net Appenders Simultaneously

So far we have learned how to configure any one of the log4net appenders in our application. What if we want to write logs into multiple appenders at the same time?

Fortunately, log4net supports writing logs into multiple appenders simultaneously. We just need to define multiple appenders and add them to the root element in the log4net.config. For instance, if we want to write logs into both the console and a log file, we can add both the console appender and the file appender to the log4net.config file:

<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date{dd MMM yyyy HH:mm:ss} %-5level - %message%newline" />
    </layout>
</appender>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
    <file value="logfile.txt" />
    <appendToFile value="true" />
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date{dd MMM yyyy HH:mm:ss} %-5level - %message%newline" />
    </layout>
</appender>
<root>
    <level value="ALL" />
    <appender-ref ref="ConsoleAppender" />
    <appender-ref ref="FileAppender" />
</root>

This will write all logs to both the console and the log file. But what’s more interesting is that we can configure log4net to write different types of logs to different appenders

For instance, if we want to log messages with lower log levels such as DEBUG and INFO into the console and higher log levels such as WARN, ERROR, FATAL, etc. into the log file, we can configure it that way using level range filters:

<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date{dd MMM yyyy HH:mm:ss} %-5level - %message%newline" />
    </layout>
    <filter type="log4net.Filter.LevelRangeFilter">
        <levelMin value="DEBUG" />
        <levelMax value="INFO" />
    </filter>
</appender>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
    <file value="logfile.txt" />
    <appendToFile value="true" />
    <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date{dd MMM yyyy HH:mm:ss} %-5level - %message%newline" />
    </layout>
    <filter type="log4net.Filter.LevelRangeFilter">
        <levelMin value="WARN" />
        <levelMax value="FATAL" />
    </filter>
</appender>
<root>
    <level value="ALL" />
    <appender-ref ref="ConsoleAppender" />
    <appender-ref ref="FileAppender" />
</root>

Here we add a level range filter for the console appender by specifying the minimum level as DEBUG and the maximum level as INFO. Similarly, for the file appender, we have added a level range filter with the minimum level as WARN and the maximum level as FATAL.

Now our application will send logs with DEBUG and INFO levels to the console and higher levels such as WARN, ERROR, and FATAL to the log file. 

Conclusion

In this article, we learned about some of the most common and useful log4net appenders that can write logs into debug, trace, memory, console, file, database, etc. Additionally, we learned how to send log4net logs into Azure Application Insights and Seq. Finally, we touched upon how to use multiple appenders simultaneously and use level range filters as well.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!