
Posted on • Edited on • Originally published atblog.elmah.io
log4net Tutorial - The Complete Guide for beginners and pros
log4net is still one of the most commonly used logging frameworks for .NET. While originally being a port of the log4j logging framework for Java, a lot has happened over the years, to make log4net a unique logging option for .NET/C# developers. This post is a collection of descriptions, examples and best practices I would have liked to have had when starting out with log4net 10 years ago.
To make sure everyone follows, let me start by explaining what log4net is. Whether you are just getting started or have years of experience writing software, you probably know the concept of logging. Small pieces of text, written to the console, a file, a database, you name it, which will track what your software is doing and help you debug errors after they happen. log4net is one of three very popular frameworks for implementing log messages in your application (Serilog and NLog being the other two). log4net works with almost any version of .NET (including .NET Core).
I don't recommend log4net for new projects. Check outNLog vs log4net andSerilog vs log4net for more details.
Logging
I guess you are here to learn about logging messages with log4net, so I'll start by showing you how to do just that. In order to understand log4net, you will need some basic knowledge about a concept in log4net calledLevels.
Log levels
All log messages written through log4net must be assigned one of the five following levels:
- Debug
- Info
- Warn
- Error
- Fatal
By assigning a log level to each message, you will be able to filter on level, notify on specific levels, etc. More about this later in this tutorial.
Logging messages
Now that you understand log levels, let's write some log messages. Messages are logged using a log4net class namedILog
. To get an instance of theILog
class, use theLogManager
as illustrated here:
ILoglog=LogManager.GetLogger("mylog")
In the example, I retrieved an instance ofILog
by calling theGetLogger
-method with a name of the logger. I won't go into details about named loggers just yet, so don't worry too much about themylog
-parameter.
With our new logger, logging messages through log4net is easy using one of the provided methods:
log.Info("This is my first log message");
Notice the name of the method beingInfo
. This tell log4net that the log message specified in the parameter is an information message (remember the log levels?). There are log methods available for all 5 log levels:
log.Debug("This is a debug message");log.Info("This is an information message");log.Warn("This is a warning message");log.Error("This is an error message");log.Fatal("This is a fatal message");
Log messages can be constructed dynamically by either using string interpolation or by using the provided*Format
-methods available onILog
:
varname="Thomas";log.Info($"Hello from{name}");log.InfoFormat("Hello from {0}",name);// Same result as above
Both lines result in the lineHello from Thomas
being logged.
Logging is often used to log that errors are happening, why you may have anException
object you want to log. Rather than callingToString
manually, there are overloads accepting exceptions too:
try{// Something dangerous!}catch(Exceptione){log.Error("An error happened",e);}
Logging the exception using this approach, will open up a range of possibilities which I will show you later in this tutorial.
If you want full control of the log message being logged, you can use theLog
-method on theLogger
property:
log.Logger.Log(newLoggingEvent(newLoggingEventData{Level=Level.Error,Message="An error happened"}));
You normally don't need to use this method, since message details can be set using other and better (IMO) features provided by log4net.
Contexts
You might want to include repeating properties or pieces of information on all log messages. An example of this could be an application name, if multiple applications are logging to the same destination. log4net provides a number of context objects in order for you to define contextual information like this.
In the example of adding an application name to all messages logged through log4net, use theGlobalContext
class:
GlobalContext.Properties["Application"]="My application";
By setting theApplication
property like this,My application
will automatically be written as part of each log message (if configured correctly).
Like global properties, you can set up custom properties visible from the current thread only using theThreadContext
class:
ThreadContext.Properties["ThreadId"]=Thread.CurrentThread.ManagedThreadId;
In the example I stored the current thread id in the variableThreadId
. Storing thread IDs like this, can help you debug a problem later on.
In the case where you want to attach a custom property to one or more log message of your choice, but don't want the property value as part of the text message, you can utilize theusing
construct in C#:
using(ThreadContext.Stacks["url"].Push("http://localhost/about")){log.Info("This is an information message");...log.Debug("This is a debug message");}log.Info("This won't get url");
In the example, I pushed a variable namedurl
to all log messages written inside the using. In the example, theurl
property is included in both log messages inside the using but not the log message outside.
Configuration
Up until this point, I didn't add a single line of configuration. I told log4net which messages to log, but never where and how to log them. Let's change that! Like logging messages, there are some key features you will need to understand in order to configure log4net. Let's dig in.
Appenders
Appenders are the most important feature to understand when dealing with log4net configuration. If you understand appenders, you can read and understand most log4net configuration files out there.
So what is an appender? In order for log4net to know where to store your log messages, you add one or more appenders to your configuration. An appender is a C# class that can transform a log message, including its properties, and persist it somewhere. Examples of appenders are the console, a file, a database, an API call, etc. There are appenders for pretty much every database technology out there. Check out a search forlog4net.appender
on nuget.org to see some of them:https://www.nuget.org/packages?q=log4net.appender.
Look ma, I'm configured in XML
log4net is typically configured using XML in either theweb/app.config
file or in a separatelog4net.config
file. Later, I'll show you how to configure it in code, but that isn't common practice. The simplest possible configuration, writes all log messages to the console. It's a great starting point for checking that logging configuration is correctly picked up by log4net:
<log4net><appendername="ConsoleAppender"type="log4net.Appender.ConsoleAppender"><layouttype="log4net.Layout.PatternLayout"></layout></appender><root><levelvalue="ALL"/><appender-refref="ConsoleAppender"/></root></log4net>
Let's go through each element. All of the configuration is wrapped inside thelog4net
element. This element will be the root element when using alog4net.config
file and nested beneath theconfiguration
element when included in anapp/web.config
file. I'll be using anapp.config
file for this example, that looks similar to this:
<?xml version="1.0" encoding="utf-8" ?><configuration><configSections><sectionname="log4net"type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/></configSections><log4net><appendername="ConsoleAppender"type="log4net.Appender.ConsoleAppender"><layouttype="log4net.Layout.PatternLayout"></layout></appender><root><levelvalue="ALL"/><appender-refref="ConsoleAppender"/></root></log4net></configuration>
To get IntelliSense on log4net config, download this XML schema:http://csharptest.net/downloads/schema/log4net.xsd. Open your config file and clickXML |Schemas... inside Visual Studio. Add the downloaded XML schema and you IntelliSense is provided. There are some other schemas floating around on GitHub too, but these doesn't seem to be working insideapp/web.config
files.
That was quite a detour. Inside thelog4net
element, there are two key elements:appender
androot
. You can add as manyappender
elements as you'd like. As we learned in the previous section, appenders are used to tell log4net about where to store log messages. In the example, I'm using thelog4net.Appender.ConsoleAppender
which (surprise!) show log messages on the console. Thelayout
element insideappender
is used to tell log4net how to format each log message. I'm not specifying any format here, why log4net simply outputs the log message.
In theroot
element I tell log4net to log the log levelALL
to ConsoleAppender. ALL is a shortcut for all 5 log levels.
To show you a couple of example outputs, I'll re-use the following C# code:
log.Debug("This is a debug message");log.Info("This is an information message");log.Warn("This is a warning message");log.Error("This is an error message");log.Fatal("This is a fatal message");
When running the program with these lines and the default XML configuration above, the output is the following:
This is a debug messageThis is an information messageThis is a warning messageThis is an error messageThis is a fatal message
To spice things up, let's define better layout in the config file:
<appendername="ConsoleAppender"type="log4net.Appender.ConsoleAppender"><layouttype="log4net.Layout.PatternLayout"><conversionPatternvalue="%date %level %message%newline"/></layout></appender>
Including the%date
and%level
variables makes the log output more informative:
2019-05-02 14:03:30,556 DEBUG This is a debug message2019-05-02 14:03:30,565 INFO This is an information message2019-05-02 14:03:30,565 WARN This is a warning message2019-05-02 14:03:30,566 ERROR This is an error message2019-05-02 14:03:30,566 FATAL This is a fatal message
Configuration in appSettings
While thelog4net
element contains everything you need to configure elmah.io, it is sometimes preferred to specify some settings asappSettings
. Doing so will make it a lot easier to replace on deploy time using Octopus Deploy, Azure DevOps, config transformations, and similar. OverwritingappSettings
on Azure is also a lot easier than trying to replace elements buried deep within thelog4net
element.
log4net provides a feature named pattern strings to address this. Let's look at a quick example to see how patterns can be applied to the configuration file:
<?xml version="1.0" encoding="utf-8" ?><configuration> ...<appSettings><addkey="layout"value="%date %level %message%newline"/></appSettings><log4net><appendername="ConsoleAppender"type="log4net.Appender.ConsoleAppender"><layouttype="log4net.Layout.PatternLayout"><conversionPatterntype="log4net.Util.PatternString"value="%appSetting{layout}"/></layout></appender><root><levelvalue="ALL"/><appender-refref="ConsoleAppender"/></root></log4net></configuration>
In the example, I've moved the value of theconversionPattern
from the previous example to a newappSettings
value namedlayout
. To reference thelayout
setting, addtype="log4net.Util.PatternString"
to the element you want to replace values (in this case theconversionPattern
element) and reference the setting using its name:value="%appSetting{layout}"
.
Having the option to mix and match the configuration in both thelog4net
andappSettings
elements quickly gets addictive.
Commonly used appenders
Logging to the console is great when developing locally. Once your code hits a server, you always want to store your log messages in some kind of persistent storage. This can be everything from a simple file directly on the file system, over database products like SQL Server and Elasticsearch, to clouding logging platforms likeelmah.io.
Console and file
Let's start simple. In the following example, I'm sending all log messages to both the console and a file:
<log4net><appendername="ConsoleAppender"type="log4net.Appender.ConsoleAppender"><layouttype="log4net.Layout.PatternLayout"><conversionPatternvalue="%date %level %message%newline"/></layout></appender><appendername="FileAppender"type="log4net.Appender.FileAppender"><filevalue="log-file.txt"/><appendToFilevalue="true"/><layouttype="log4net.Layout.PatternLayout"><conversionPatternvalue="%date %level %message%newline"/></layout></appender><root><levelvalue="ALL"/><appender-refref="ConsoleAppender"/><appender-refref="FileAppender"/></root></log4net>
TheConsoleAppender
part matches the one from the previous example. I've added a newappender
element, which references thelog4net.Appender.FileAppender
in thetype
attribute. As you probably already guessed, this appender writes log messages to a file. The appender is configured using sub-elements, like thefile
element to tell the appender which file to log to. Finally, notice that both appender names are referenced in theroot
element.
ColoredConsole and RollingFile
Logging to the console and a file are the simplest appenders included in log4net. While they work great for a tutorial like this, I would recommend you to use two alternatives:
<log4net><appendername="ColoredConsoleAppender"type="log4net.Appender.ColoredConsoleAppender"><mapping><levelvalue="ERROR"/><foreColorvalue="White"/><backColorvalue="Red, HighIntensity"/></mapping><layouttype="log4net.Layout.PatternLayout"><conversionPatternvalue="%date %level %message%newline"/></layout></appender><appendername="RollingFileAppender"type="log4net.Appender.RollingFileAppender"><filevalue="logfile"/><appendToFilevalue="true"/><rollingStylevalue="Date"/><datePatternvalue="yyyyMMdd-HHmm"/><layouttype="log4net.Layout.PatternLayout"><conversionPatternvalue="%date %level %message%newline"/></layout></appender><root><levelvalue="ALL"/><appender-refref="ColoredConsoleAppender"/><appender-refref="RollingFileAppender"/></root></log4net>
ColoredConsoleAppender
is basicallyConsoleAppender
on steroids. Using themapping
element, you can control the colors of the log messages in the console. In the example above, errors are marked with a red background color:
A simple thing as to be able to quickly spot errors happening in a 500 line log output can be a game changer.
Likewise,RollingFileAppender
is a specialization ofFileAppender
which does rolling logs. If you are not familiar with the termrolling log, let me spend a few words elaborating on that. Applications typically logA LOT of data. If you keep logging messages to the same file, your file will quickly grow to GBs or even TBs in size. File isn't an optimal solution to store data that you want to be able to search in, but searching in large files is pretty much impossible. Using rolling files, you tell log4net to split up your files in chunks. Rolling logging can be configured by size, dates, and more. You often see people configuring a log file per day.
elmah.io
You can log to elmah.io through log4net too. To do so, install theelmah.io.log4net
NuGet package:
Install-Packageelmah.io.log4net
Configure the elmah.io log4net appender in your config file:
<log4net><appendername="ElmahIoAppender"type="elmah.io.log4net.ElmahIoAppender, elmah.io.log4net"><logIdvalue="LOG_ID"/><apiKeyvalue="API_KEY"/></appender><root><levelvalue="WARN"/><appender-refref="ElmahIoAppender"/></root></log4net>
Remember to replaceLOG_ID
andAPI_KEY
with the correct values from elmah.io. That's it! All warn, error and fatal messages are logged to elmah.io.
Elasticsearch
If you want to self host everything and need to create custom dashboards over your log data, Elasticsearch is a great option. We've used Elasticsearch since founding elmah.io back in 2013 and are very satisfied with that choice.
log4net supports Elasticsearch through a couple of appenders available on NuGet. The best one (IMO) islog4net.ElasticSearch
by JP Toto. To get started, install the NuGet package:
Install-Packagelog4net.ElasticSearch
Then add configuration to yourweb/app.config
orlog4net.config
:
<log4net><appendername="ElasticSearchAppender"type="log4net.ElasticSearch.ElasticSearchAppender, log4net.ElasticSearch"><connectionStringvalue="Scheme=http;Server=localhost;Index=log;Port=9200;rolling=true"/><bufferSizevalue="1"/></appender><root><levelvalue="ALL"/><appender-refref="ElasticSearchAppender"/></root></log4net>
There are a lot of configuration options available with the package, why I encourage you to check out thedocumentation. For this example, I've included a minimal set of options. TheconnectionString
element contains the information needed for the appender to communicate with Elasticsearch. I've includedrolling=true
to create a new index per day. This is especially handy when needing to clean up old log messages. Finally, I've setbufferSize
to1
to ensure that every log statement is stored in Elasticsearch without any delay. It is not recommended to do this in production, though. Analyze your need and set the value to something between 10-100.
When running my demo application, log messages are now successfully persisted in Elasticsearch:
Configuration in C
I promised you an example of log4net configuration in C#. You won't find a lot of documentation or blog posts utilizing this approach, but it is definitely possible. Let's configure the example from previously in C#:
varhierarchy=(Hierarchy)LogManager.GetRepository();PatternLayoutpatternLayout=newPatternLayout{ConversionPattern="%date %level %message%newline"};patternLayout.ActivateOptions();varcoloredConsoleAppender=newColoredConsoleAppender();coloredConsoleAppender.AddMapping(newColoredConsoleAppender.LevelColors{BackColor=ColoredConsoleAppender.Colors.Red,ForeColor=ColoredConsoleAppender.Colors.White,Level=Level.Error});coloredConsoleAppender.Layout=patternLayout;varrollingFileAppender=newRollingFileAppender{File="logfile",AppendToFile=true,RollingStyle=RollingFileAppender.RollingMode.Date,DatePattern="yyyyMMdd-HHmm",Layout=patternLayout};hierarchy.Root.AddAppender(coloredConsoleAppender);hierarchy.Root.AddAppender(rollingFileAppender);hierarchy.Root.Level=Level.All;hierarchy.Configured=true;BasicConfigurator.Configure(hierarchy);
I won't go into details on each line. If you look through the previous example in XML and this C#-based example, you should be able to see the mapping. The important part here is to callBasicConfigurator.Configure(hierarchy)
instead of XML configuration inAssemblyInfo.cs
.
Dependency injection
Until now, we have created a new instance of theILog
implementation by calling theGetLogger
method on theLogManager
class. While I normally swear to dependency injection, I believe this is a nice and simple way to do logging: Declare astatic ILog
variable in the top of the class and done. Some prefer injecting theILog
class instead, which is perfectly valid with log4net too.
How you implement dependency injection of the logger is up to the IoC container you are using. For ASP.NET Core and the feature set available in theMicrosoft.Extensions.DependencyInjection.Abstractions
package, you can implement code similar to this:
services.AddScoped(factory=>LogManager.GetLogger(GetType()));
This will add a scoped instance ofILog
available for injection:
publicclassHomeController:Controller{privatereadonlyILoglog;publicHomeController(ILoglog){this.log=log;}}
The code above serves as illustrations only. If logging with log4net from ASP.NET Core, I would recommend you to log throughMicrosoft.Extensions.Logging
like illustrated inthis sample.
Would your users appreciate fewer errors?
elmah.io is the easy error logging and uptime monitoring service for .NET. Take back control of your errors with support for all .NET web and logging frameworks.
➡️Error Monitoring for .NET Web Applications ⬅️
This article first appeared on the elmah.io blog athttps://blog.elmah.io/log4net-tutorial-the-complete-guide-for-beginners-and-pros/
Top comments(5)

- LocationKyiv, Ukraine
- EducationTheoretical physics
- WorkSenior Software Engineer
- Joined
Thanks for the article, Thomas. I use log4net for a couple of years, with log4stash appender to write logs to Elasticsearch. It works pretty well, but I miss the support of native Microsoft.Extensions.Logging.ILogger in my codebase - we use our own wrapper around log4net.ILog interface.
Can log4net be used through native Microsoft.Extensions.Logging.ILogger interface? If so, what should I do with my custom properties which I add to almost every log message?

- LocationAarhus, Denmark
- WorkFounder at elmah.io
- Joined
Not sure exactly what you mean there. You want to use Microsoft.Extensions.Logging.ILogger as the main interface for logging messages. And then configure log4net as the implementation for that and control were to persist the log messages with log4net config?

- LocationKyiv, Ukraine
- EducationTheoretical physics
- WorkSenior Software Engineer
- Joined
Yes, my goal is to use Microsoft.Extensions.Logging.ILogger everywhere in my solution. I would rather use Microsoft's interface ILogger than my own wrapper or directly log4net interface. Unfortunately, I haven't found any existed Microsoft.Extensions.Logging.ILogger implementation that uses log4net or any kind of adapter. Maybe I'm using old log4net nuget package and now they implement Microsoft's ILogger interface.

- LocationAarhus, Denmark
- WorkFounder at elmah.io
- Joined
You could use a NuGet package like this one:nuget.org/packages/Microsoft.Exten....

- LocationKyiv, Ukraine
- EducationTheoretical physics
- WorkSenior Software Engineer
- Joined
😳how could I missed that?
Thank a lot!
For further actions, you may consider blocking this person and/orreporting abuse