In this article, we will learn how to implement Log4net, a logging framework for C# applications, and how to use it effectively.

Knowing how to log properly helps us to be more efficient while debugging, monitoring, and maintaining our applications.

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

Let’s begin.

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

Why Log4net?

Log4net is a popular logging framework for .NET applications. It’s flexible and works with different output formats and destinations. It’s also fast and doesn’t slow down our application much. We can easily change how it logs without changing our code. Plus, it works well with many .NET frameworks and libraries.

While Log4net is a good choice, other popular .NET logging frameworks exist, such as Serilog and NLog.

To learn more about these logging frameworks check out our other articles Structured Logging in ASP.NET Core With Serilog and ASP.NET Core Web API – Logging With NLog.

Before we start, let’s talk about some good practices when using Log4net.

Good Practices When Using Log4net

First, we need to use the right log levels like DEBUG, INFO, WARN, ERROR, and FATAL. This helps us sort our logs easily.

It’s also important to add helpful details to our log messages. This way, we’ll know what is happening in our app when something goes wrong.

We should also set up log rotation. This keeps our log files from getting too big and taking up too much space.

Lastly, it’s a good idea to name our loggers in a consistent way. When we have a good naming system, it’s easier to tell which part of our app a log message is coming from. This is especially helpful in bigger projects.

If we stick to these practices, we’ll end up with a well-organized logging system. This will make our lives easier both while we’re developing and when our app is up and running.

Now that we know a bit about Log4net, let’s set it up in our project.

Setting Up Log4net

To start with Log4net, we first need to install it:

dotnet add package log4net

Then, we need to create a log4net.config file in our project root. This file will contain the configuration for Log4net.

Configuring Log4net in C#

First, let’s start with a basic configuration:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" />
  </configSections>
  
  <log4net>
    <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
      </layout>
    </appender>
    
    <appender name="FileAppender" type="log4net.Appender.RollingFileAppender">
      <file value="logs/application.log" />
      <appendToFile value="true" />
      <rollingStyle value="Size" />
      <maxSizeRollBackups value="5" />
      <maximumFileSize value="10MB" />
      <staticLogFileName value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date [%thread] %-5level %logger - %message%newline" />
      </layout>
    </appender>
    
    <root>
      <level value="ALL" />
      <appender-ref ref="ConsoleAppender" />
      <appender-ref ref="FileAppender" />
    </root>
  </log4net>
</configuration>

Here, we set up two appenders: a console appender, which will output logs to the console, and a file appender, which will write logs to a file.

The <appender> elements define where the log messages will be sent. In this case, we’re using a ConsoleAppender to output logs to the console and a RollingFileAppender to write logs to a file. Within each appender, the <layout> element specifies how each log message should be formatted. By using PatternLayout, we can customize the format using a pattern string.

To learn more about appenders and logging to different destinations like files, databases, and email, read our article on Log4net Appenders Introduction With C# Examples.

Next, the <conversionPattern> element is where we define the actual format of our log messages. The value attribute contains placeholders that the system replaces with actual data when it writes a log.

The placeholders we use are:

  • %date: The date and time when the log was created
  • [%thread]: The name of the thread that generated the log
  • %-5level: The log level (DEBUG, INFO, WARN, ERROR, FATAL)
  • %logger: The name of the logger
  • %message: The log message
  • %newline: A new line character

Finally, the <root> element sets up the main logger. By setting the level to “ALL”, we’re telling it to process all log levels and the <appender-ref> element tells the root logger to use our ConsoleAppender.

File Appender Configuration in Log4net

For the file appender, we add some additional configuration. First, we set the location and name of the log file with <file value="logs/application.log" />. Then, we have Log4net append to the existing file rather than overwrite it using <appendToFile value="true" />. After that, we enable log rotation based on file size with <rollingStyle value="Size" />.

We also specify that we want to keep up to 5 backup files using <maxSizeRollBackups value="5" />. The maximum size of each of these log files is set to 10MB with <maximumFileSize value="10MB" />; this is to prevent the files from taking up too much disk space. Then, we make sure that the filenames are consistent by using <staticLogFileName value="true" />.

Now we have a good starting point to implement Log4net in our C# app. We’ll see detailed logs, helping us keep track of what’s happening in our app.

Implement Log4net in C#

With the configuration in place, it’s time to implement Log4net in a simple C# project.

Let’s start with our simple Calculator example:

public static class Calculator
{
    private static readonly ILog log = LogManager.GetLogger(typeof(Calculator));

    public static int Add(int a, int b)
    {
        log.Debug($"Adding {a} and {b}");
        int result = a + b;
        log.Info($"Result of {a} + {b} = {result}");

        return result;
    }
}

First, we initialize a logger for the Calculator class using Log4net’s LogManager.GetLogger() method. The typeof(Calculator) parameter associates the logger with the Calculator class, which helps us figure out the source of log messages.

In the Add() method, we use log.Debug() level to log the values being added.

After we calculate the result, the log.Info() logs an informational message that indicates the result. We use the info level for significant events in the application’s flow.

Since we’re making a calculator, let’s also add our Subtract, Multiply, and Divide methods:

public static int Subtract(int a, int b)
{
    log.Debug($"Subtracting {b} from {a}");

    if (b > a)
    {
        log.Warn($"Subtraction may result in a negative number: {a} - {b}");
    }

    int result = a - b;

    log.Info($"Result of {a} - {b} = {result}");

    return result;
}

public static int Multiply(int a, int b)
{
    log.Debug($"Multiplying {a} and {b}");

    int result = a * b;

    if (result == 0)
    {
        log.Warn($"Multiplication resulted in zero: {a} * {b}");
    }
    else if (result < 0)
    {
        log.Warn($"Multiplication resulted in a negative number: {a} * {b} = {result}");
    }

    log.Info($"Result of {a} * {b} = {result}");

    return result;
}

public static int Divide(int a, int b)
{
    log.Debug($"Dividing {a} by {b}");

    if (b == 0)
    {
        log.Error("Division by zero attempted");
        throw new DivideByZeroException("Cannot divide by zero");
    }

    int result = a / b;

    log.Info($"Result of {a} / {b} = {result}");

    return result;
}

Here, we introduce some new logging levels.

The first is log.Warn(), which we use when subtracting a larger number from a smaller one. In the Subtract() method, we use it when b is greater than a, and in the Multiply() method, if the result is zero or negative.

Then, in the Divide() method, we utilize another new log level, log.Error() before throwing an exception when dividing by zero. This lets us log the error before the program potentially crashes or moves to exception handling.

We are ready to move to the Program class:

var log = LogManager.GetLogger(typeof(Program));
var logRepository = LogManager.GetRepository(Assembly.GetEntryAssembly());
XmlConfigurator.Configure(logRepository, new FileInfo("log4net.config"));

log.Info("Application Started");

int addResult = Calculator.Add(5, 3);
Console.WriteLine($"Result of 5 + 3 = {addResult}");

int subtractResult = Calculator.Subtract(5, 10);
Console.WriteLine($"Result of 5 - 10 = {subtractResult}");

int multiplyResult = Calculator.Multiply(4, 6);
Console.WriteLine($"Result of 4 * 6 = {multiplyResult}");

try
{
    int divideResult = Calculator.Divide(10, 2);
    Console.WriteLine($"Result of 10 / 2 = {divideResult}");

    Calculator.Divide(5, 0);
}
catch (DivideByZeroException ex)
{
    log.Fatal("A critical error occurred during division", ex);
    Console.WriteLine($"Caught exception: {ex.Message}");
}

log.Info("Application Ended");

First, we create a logger for the Program class using LogManager.GetLogger(typeof(Program)). 

Next, the logger repository is configured with LogManager.GetRepository() and XmlConfigurator.Configure(). These lines read the configuration from the log4net.config file and set up the logging system accordingly.

After that, an informational message log.Info("Application Started") is logged to show that the application has started.

We have a try-catch block attempting to perform a division by zero so we can intentionally trigger DivideByZeroException and log a fatal error using log.Fatal(). 

Finally, an informational message is logged to show that the application has ended with log.Info("Application Ended").

Log4net in the Console

After running our code, we should see something like this in the console:

2024-08-10 12:28:27,371 [1] INFO  Program - Application Started
2024-08-10 12:28:27,376 [1] DEBUG LoggingInCSharp.Calculator - Adding 5 and 3
2024-08-10 12:28:27,377 [1] INFO  LoggingInCSharp.Calculator - Result of 5 + 3 = 8
Result of 5 + 3 = 8
2024-08-10 12:28:27,377 [1] DEBUG LoggingInCSharp.Calculator - Subtracting 10 from 5
2024-08-10 12:28:27,377 [1] WARN  LoggingInCSharp.Calculator - Subtraction may result in a negative number: 5 - 10
2024-08-10 12:28:27,377 [1] INFO  LoggingInCSharp.Calculator - Result of 5 - 10 = -5
Result of 5 - 10 = -5
2024-08-10 12:28:27,377 [1] DEBUG LoggingInCSharp.Calculator - Multiplying 4 and 6
2024-08-10 12:28:27,377 [1] INFO  LoggingInCSharp.Calculator - Result of 4 * 6 = 24
Result of 4 * 6 = 24
2024-08-10 12:28:27,378 [1] DEBUG LoggingInCSharp.Calculator - Dividing 10 by 2
2024-08-10 12:28:27,378 [1] INFO  LoggingInCSharp.Calculator - Result of 10 / 2 = 5
Result of 10 / 2 = 5
2024-08-10 12:28:27,378 [1] DEBUG LoggingInCSharp.Calculator - Dividing 5 by 0
2024-08-10 12:28:27,378 [1] ERROR LoggingInCSharp.Calculator - Division by zero attempted
2024-08-10 12:28:27,378 [1] FATAL Program - A critical error occurred during division
System.DivideByZeroException: Cannot divide by zero
   at LoggingInCSharp.Calculator.Divide(Int32 a, Int32 b) in C:\Users\user\Documents\GitHub\CodeMazeGuides\dotnet-logging\Log4NetImplementationCSharp\Log4NetImplementationCSharp\Calculator.cs:line 63
   at Program.<Main>$(String[] args) in C:\Users\user\Documents\GitHub\CodeMazeGuides\dotnet-logging\Log4NetImplementationCSharp\Log4NetImplementationCSharp\Program.cs:line 26
Caught exception: Cannot divide by zero
2024-08-10 12:28:27,384 [1] INFO  Program - Application Ended

Here, we see log messages that show our application’s flow. 

The DEBUG messages show the Calculator class preparing to perform operations, while the INFO messages give us information about the results. When an error occurs, we see ERROR and FATAL log messages, along with the exception details.

Also, if we look at our log file it is almost identical to this console output. The only difference is that the file output won’t include the Console.WriteLine statements.

Conclusion

In this article, we learned how to implement Log4net in C# applications, focusing on setup, configuration, and best practices. Log4net’s flexibility makes it a useful tool for logging, debugging, monitoring, and maintaining our applications.

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