Skip to content

Format Serilog events in log4net or log4j compatible XML format

License

Notifications You must be signed in to change notification settings

serilog-contrib/serilog-formatting-log4net

Repository files navigation

Serilog.Formatting.Log4Net is an add-on for Serilog to format log events as log4net or log4j compatible XML format.

NuGet Continuous Integration Coverage Code Quality Mutation Score

You can use Log4View to look at log files produced with this formatter.

Getting started

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>

Configuration

You can configure Log4NetTextFormatter in multiple ways, the fluent options builder will help you discover all the possibilities.

Exception formatting

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()))

CDATA

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))

XML Namespace

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())

Line ending

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))

Indentation

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())

Format provider

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")))

Property filter

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"))

Log4j compatibility mode

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 vs exception

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

Combining options

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();

Enrichers

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.

Thread Id

Include the thread id in log4net events by using Serilog.Enrichers.Thread:

var loggerConfiguration = new LoggerConfiguration().Enrich.WithThreadId();

Domain and User Name

Include the domain and user name in log4net events by using Serilog.Enrichers.Environment:

var loggerConfiguration = new LoggerConfiguration().Enrich.WithEnvironmentUserName();

Machine Name

Include the machine name in log4net events by using Serilog.Enrichers.Environment:

var loggerConfiguration = new LoggerConfiguration().Enrich.WithMachineName();

Caller

Include caller information (class, method, file, line) by using Serilog.Enrichers.WithCaller:

var loggerConfiguration = new LoggerConfiguration().Enrich.WithCaller(includeFileInfo: true);

All together

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>

Related projects

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.