Serilog.Formatting.Log4Net is an add-on for Serilog to format log events as log4net or log4j compatible XML format.
You can use Log4View to look at log files produced with this formatter.
Add the Serilog.Formatting.Log4Net NuGet package to your project using the NuGet Package Manager or run the following command:
dotnet add package Serilog.Formatting.Log4Net
Serilog.Formatting.Log4Net provides the Log4NetTextFormatter
class which implements Serilog's ITextFormatter interface.
Here's how to use it with a file sink in a simple Hello World app:
using System;
using Serilog;
using Serilog.Formatting.Log4Net;
static class Program
{
static void Main(string[] args)
{
var logger = new LoggerConfiguration()
.Enrich.WithProperty("AppName", "Program")
.WriteTo.File(new Log4NetTextFormatter(c => c.UseCDataMode(CDataMode.Never)), "logs.xml")
.CreateLogger();
logger.Information("Start app with {Args}", args);
Console.WriteLine("Hello World!");
logger.Information("Stop app");
}
}
Running this app writes the following XML events into the logs.xml
file in the current working directory:
<log4net:event timestamp="2021-02-24T18:23:40.4496605+01:00" level="INFO" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2/">
<log4net:properties>
<log4net:data name="Args[0]" value="--first-argument" />
<log4net:data name="Args[1]" value="--second-argument" />
<log4net:data name="AppName" value="Program" />
</log4net:properties>
<log4net:message>Start app with ["--first-argument", "--second-argument"]</log4net:message>
</log4net:event>
<log4net:event timestamp="2021-02-24T18:23:40.5086666+01:00" level="INFO" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2/">
<log4net:properties>
<log4net:data name="AppName" value="Program" />
</log4net:properties>
<log4net:message>Stop app</log4net:message>
</log4net:event>
You can configure Log4NetTextFormatter
in multiple ways, the fluent options builder will help you discover all the possibilities.
By default, Log4NetTextFormatter formats exception by calling ToString(). You can customise this behaviour by setting your own formatting delegate. For example, you could use Ben.Demystifier like this:
new Log4NetTextFormatter(c => c.UseExceptionFormatter(exception => exception.ToStringDemystified()))
By default, Log4NetTextFormatter writes all messages and exceptions with a CDATA section. It is possible to configure it to use CDATA sections only when the message or exception contain &
, <
or >
by using CDataMode.IfNeeded
or to never write CDATA sections by using CDataMode.Never
:
new Log4NetTextFormatter(c => c.UseCDataMode(CDataMode.Never))
You can remove the log4net
XML namespace by calling UseNoXmlNamespace()
on the options builder. This is useful if you want to spare some bytes and your log reader supports log4net XML events without namespace, like Log4View does.
new Log4NetTextFormatter(c => c.UseNoXmlNamespace())
By default, Log4NetTextFormatter uses the line feed (LF) character for line ending between XML elements. You can choose to use CRLF if you need to:
new Log4NetTextFormatter(c => c.UseLineEnding(LineEnding.CarriageReturn | LineEnding.LineFeed))
By default, Log4NetTextFormatter indents XML elements with two spaces. You can configure it to use either spaces or tabs. For example, indent XML elements with one tab:
new Log4NetTextFormatter(c => c.UseIndentationSettings(new IndentationSettings(Indentation.Tab, 1)))
Or you can use no indentation at all, having log4net events written on a single line:
new Log4NetTextFormatter(c => c.UseNoIndentation())
By default, Log4NetTextFormatter uses the invariant culture (Serilog's default) when formatting Serilog properties that implement the IFormattable
interface. It can be configured to use culture-specific formatting information. For example, to use the Swiss French culture:
new Log4NetTextFormatter(c => c.UseFormatProvider(CultureInfo.GetCultureInfo("fr-CH")))
By default, Log4NetTextFormatter serializes all Serilog properties. You can filter out some properties by configuring a custom property filter delegate:
new Log4NetTextFormatter(c => c.UsePropertyFilter((_, name) => name != "MySecretProperty"))
The formatter also supports a log4j compatibility mode. Log4Net and Log4j XML formats are very similar but have a few key differences.
- The
event
elements are in different XML namespaces - The
timestamp
attribute is a number of milliseconds (log4j) vs an ISO 8061 formatted date (log4net) - Exception elements are named
throwable
vsexception
In order to enable the compatibility mode, call UseLog4JCompatibility()
:
new Log4NetTextFormatter(c => c.UseLog4JCompatibility())
Note that unlike other fluent configuration methods, this one can not be chained because you should not change options after enabling the log4j compatibility mode.
Alternatively, you can use the Log4NetTextFormatter.Log4JFormatter
static property which is configured for the log4j XML layout. This static property is also useful when using the Serilog.Settings.Configuration package where it can be used with the following accessor:
Serilog.Formatting.Log4Net.Log4NetTextFormatter::Log4JFormatter, Serilog.Formatting.Log4Net
You can also combine options, for example, using Ben.Demystifier for exception formatting, filtering properties and using the log4j compatibility mode. This sample configuration sends logs as UDP packages over the network with Serilog.Sinks.Udp and are viewable with Loginator:
var appFileName = Path.GetFileName(Environment.GetCommandLineArgs()[0]);
var processId = Process.GetCurrentProcess().Id;
var formatter = new Log4NetTextFormatter(c => c
.UseExceptionFormatter(exception => exception.ToStringDemystified())
.UsePropertyFilter((_, name) => name.StartsWith("log4j"))
.UseLog4JCompatibility()
);
Log.Logger = new LoggerConfiguration()
.Enrich.WithProperty("log4japp", $"{appFileName}({processId})")
.Enrich.WithProperty("log4jmachinename", Environment.MachineName)
.Enrich.WithThreadId()
.WriteTo.Udp("localhost", 7071, AddressFamily.InterNetwork, formatter)
.CreateLogger();
The log4Net XML format defines some special attributes which are not included by default in Serilog events. They can be added by using the appropriate Serilog enrichers.
Include the thread id in log4net events by using Serilog.Enrichers.Thread:
var loggerConfiguration = new LoggerConfiguration().Enrich.WithThreadId();
Include the domain and user name in log4net events by using Serilog.Enrichers.Environment:
var loggerConfiguration = new LoggerConfiguration().Enrich.WithEnvironmentUserName();
Include the machine name in log4net events by using Serilog.Enrichers.Environment:
var loggerConfiguration = new LoggerConfiguration().Enrich.WithMachineName();
Include caller information (class, method, file, line) by using Serilog.Enrichers.WithCaller:
var loggerConfiguration = new LoggerConfiguration().Enrich.WithCaller(includeFileInfo: true);
Combining these four enrichers will produce a log event including thread
, domain
and username
attributes, a log4net:HostName
property containing the machine name and a locationInfo
element:
<event timestamp="2020-06-28T10:07:33.314159+02:00" level="INFO" thread="1" domain="TheDomainName" username="TheUserName">
<properties>
<data name="log4net:HostName" value="TheMachineName" />
</properties>
<message>The message</message>
<locationInfo class="Program" method="Main(System.String[])" file="/Absolute/Path/To/Program.cs" line="29" />
</event>
The Serilog.Sinks.Log4Net project is similar but depends on the log4net NuGet package whereas Serilog.Formatting.Log4Net does not. Also, Serilog.Sinks.Log4Net is a sink so you have to configure log4net in addition to configuring Serilog.
The Serilog.Sinks.Udp project also provides a Log4Net formatter but it writes XML manually (without using an XmlWriter), completely ignores Serilog properties, is not configurable at all (indentation, newlines, namespaces etc.) and is not documented.