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 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.
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();
Combining these three enrichers will produce a log event including thread
, domain
and username
attributes plus a log4net:HostName
property containing the machine name:
<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>
</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.
Serilog.Formatting.Log4Net uses MinVer for its versioning, so a tag must exist with the chosen semantic version number in order to create an official release.
- Update the CHANGELOG Unreleased section to the chosen version and copy the release notes for step #2.
- Add the release date
- Update the link from
HEAD
to the chosen version
- Create an annotated tag, the (multi-line) message of the annotated tag will be the content of the GitHub release. Markdown (copied from step #1) should be used.
git tag --annotate 1.0.0-rc.1
git push --follow-tags
Once pushed, the GitHub Continuous Integration workflow takes care of building, running the tests, creating the NuGet package, creating the GitHub release and finally publishing the produced NuGet package.
After the NuGet package is succesfully published:
- Update the
ContractVersion
version in theSerilog.Formatting.Log4Net.csproj
file to the newly released version. - Delete the
ApiCompatBaseline.txt
file if there's one.