Skip to content

Commit

Permalink
Add support for Serilog.Enrichers.WithCaller / locationInfo
Browse files Browse the repository at this point in the history
Also update README and CHANGELOG for version 1.1.0
  • Loading branch information
0xced committed May 1, 2023
1 parent 510d6ae commit fde4670
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 2 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [1.1.0][1.1.0] - 2023-05-02

- Add support for caller information (class, method, file, line) through the [Serilog.Enrichers.WithCaller](https://www.nuget.org/packages/Serilog.Enrichers.WithCaller/) package.

## [1.0.2][1.0.2] - 2023-02-11

- Add a new `Log4NetTextFormatter.Log4JFormatter` static property which is configured for the log4j XML layout. This static property is also useful when using the [Serilog.Settings.Configuration](https://github.com/serilog/serilog-settings-configuration/) package where it can be used with the following accessor:
Expand Down Expand Up @@ -80,6 +84,7 @@ Still trying to figure out how to make everything fit together with [MinVer](htt

- Implement log4j compatibility mode.

[1.1.0]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.0.2...1.1.0
[1.0.2]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.0.1...1.0.2
[1.0.1]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.0.0...1.0.1
[1.0.0]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.0.0-rc.4...1.0.0
Expand Down
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,14 +192,25 @@ Include the machine name in log4net events by using [Serilog.Enrichers.Environme
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:
### Caller

Include caller information (class, method, file, line) by using [Serilog.Enrichers.WithCaller](https://www.nuget.org/packages/Serilog.Enrichers.WithCaller/):

```c#
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:

```xml
<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>
```

Expand Down
40 changes: 39 additions & 1 deletion src/Log4NetTextFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml;
using Serilog.Core;
using Serilog.Events;
Expand All @@ -25,7 +26,7 @@ public class Log4NetTextFormatter : ITextFormatter
/// </summary>
private static readonly string[] SpecialProperties = {
Constants.SourceContextPropertyName, OutputProperties.MessagePropertyName,
ThreadIdPropertyName, UserNamePropertyName, MachineNamePropertyName
ThreadIdPropertyName, UserNamePropertyName, MachineNamePropertyName, CallerPropertyName
};

/// <summary>
Expand All @@ -46,6 +47,17 @@ public class Log4NetTextFormatter : ITextFormatter
/// <remarks>https://github.com/serilog/serilog-enrichers-environment/blob/v2.1.3/src/Serilog.Enrichers.Environment/Enrichers/MachineNameEnricher.cs#L36</remarks>
private const string MachineNamePropertyName = "MachineName";

/// <summary>
/// The name of the caller property, set by <a href="https://www.nuget.org/packages/Serilog.Enrichers.WithCaller/">Serilog.Enrichers.WithCaller</a>
/// </summary>
/// <remarks>https://github.com/pmetz-steelcase/Serilog.Enrichers.WithCaller/blob/1.2.0/Serilog.Enrichers.WithCaller/CallerEnricher.cs#L66</remarks>
private const string CallerPropertyName = "Caller";

/// <summary>
/// The regular exception matching "class", "method", and optionally "file" and "line" for the caller property.
/// </summary>
private static readonly Regex CallerRegex = new(@"(?<class>.*)\.(?<method>.*\(.*\))(?: (?<file>.*):(?<line>\d+))?", RegexOptions.Compiled);

private readonly Log4NetTextFormatterOptions _options;
private readonly bool _usesLog4JCompatibility;

Expand Down Expand Up @@ -139,6 +151,7 @@ private void WriteEvent(LogEvent logEvent, XmlWriter writer)
}
WriteMessage(logEvent, writer);
WriteException(logEvent, writer);
WriteLocationInfo(logEvent, writer);
writer.WriteEndElement();
}

Expand Down Expand Up @@ -423,6 +436,31 @@ private void WriteException(LogEvent logEvent, XmlWriter writer)
}
}

/// <summary>
/// Write location information associated to the log event, i.e. class, method, file and line where the event originated.
/// If the event was not enriched with caller information then nothing is written.
/// </summary>
/// <param name="logEvent">The log event.</param>
/// <param name="writer">The XML writer.</param>
/// <remarks>https://github.com/apache/logging-log4net/blob/rel/2.0.8/src/Layout/XmlLayout.cs#L297-L307</remarks>
/// <remarks>https://github.com/apache/log4j/blob/v1_2_17/src/main/java/org/apache/log4j/xml/XMLLayout.java#L170-L181</remarks>
private void WriteLocationInfo(LogEvent logEvent, XmlWriter writer)
{
if (logEvent.Properties.TryGetValue(CallerPropertyName, out var callerProperty) && callerProperty is ScalarValue { Value: string caller })
{
var match = CallerRegex.Match(caller);
if (match.Success)
{
WriteStartElement(writer, "locationInfo");
writer.WriteAttributeString("class", match.Groups[1].Value);
writer.WriteAttributeString("method", match.Groups[2].Value);
writer.WriteAttributeString("file", match.Groups[3].Value);
writer.WriteAttributeString("line", match.Groups[4].Value);
writer.WriteEndElement();
}
}
}

/// <summary>
/// Start writing an XML element, taking into account the configured <see cref="Log4NetTextFormatterOptions.XmlNamespace"/>.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions tests/Log4NetTextFormatterTest.Caller.verified.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<log4net:event timestamp="2003-01-04T15:09:26.535+01:00" level="INFO" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2/">
<log4net:message><![CDATA[Hello from Serilog]]></log4net:message>
<log4net:locationInfo class="Fully.Qualified.ClassName" method="MethodName(System.String)" file="" line="" />
</log4net:event>
4 changes: 4 additions & 0 deletions tests/Log4NetTextFormatterTest.CallerLog4J.verified.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<log4j:event timestamp="1041689366535" level="INFO">
<log4j:message><![CDATA[Hello from Serilog]]></log4j:message>
<log4j:locationInfo class="Fully.Qualified.ClassName" method="MethodName(System.String)" file="" line="" />
</log4j:event>
3 changes: 3 additions & 0 deletions tests/Log4NetTextFormatterTest.CallerNonScalar.verified.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<log4net:event timestamp="2003-01-04T15:09:26.535+01:00" level="INFO" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2/">
<log4net:message><![CDATA[Hello from Serilog]]></log4net:message>
</log4net:event>
4 changes: 4 additions & 0 deletions tests/Log4NetTextFormatterTest.CallerWithFile.verified.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<log4net:event timestamp="2003-01-04T15:09:26.535+01:00" level="INFO" xmlns:log4net="http://logging.apache.org/log4net/schemas/log4net-events-1.2/">
<log4net:message><![CDATA[Hello from Serilog]]></log4net:message>
<log4net:locationInfo class="Fully.Qualified.ClassName" method="MethodName(System.String)" file="/Absolute/Path/To/FileName.cs" line="123" />
</log4net:event>
64 changes: 64 additions & 0 deletions tests/Log4NetTextFormatterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,70 @@ public Task MachineNamePropertyStructureValue()
return Verify(output);
}

[Fact]
public Task Caller()
{
// Arrange
using var output = new StringWriter();
var logEvent = CreateLogEvent(properties: new LogEventProperty("Caller", new ScalarValue("Fully.Qualified.ClassName.MethodName(System.String)")));

var formatter = new Log4NetTextFormatter();

// Act
formatter.Format(logEvent, output);

// Assert
return Verify(output);
}

[Fact]
public Task CallerNonScalar()
{
// Arrange
using var output = new StringWriter();
var logEvent = CreateLogEvent(properties: new LogEventProperty("Caller", new CustomLogEventPropertyValue("")));

var formatter = new Log4NetTextFormatter();

// Act
formatter.Format(logEvent, output);

// Assert
return Verify(output);
}

[Fact]
public Task CallerWithFile()
{
// Arrange
using var output = new StringWriter();
var logEvent = CreateLogEvent(properties: new LogEventProperty("Caller", new ScalarValue("Fully.Qualified.ClassName.MethodName(System.String) /Absolute/Path/To/FileName.cs:123")));

var formatter = new Log4NetTextFormatter();

// Act
formatter.Format(logEvent, output);

// Assert
return Verify(output);
}

[Fact]
public Task CallerLog4J()
{
// Arrange
using var output = new StringWriter();
var logEvent = CreateLogEvent(properties: new LogEventProperty("Caller", new ScalarValue("Fully.Qualified.ClassName.MethodName(System.String)")));

var formatter = new Log4NetTextFormatter(c => c.UseLog4JCompatibility());

// Act
formatter.Format(logEvent, output);

// Assert
return Verify(output);
}

[Fact]
public Task SequenceProperty()
{
Expand Down

0 comments on commit fde4670

Please sign in to comment.