Skip to content

Commit fde4670

Browse files
committed
Add support for Serilog.Enrichers.WithCaller / locationInfo
Also update README and CHANGELOG for version 1.1.0
1 parent 510d6ae commit fde4670

8 files changed

+135
-2
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
44

55
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).
66

7+
## [1.1.0][1.1.0] - 2023-05-02
8+
9+
- Add support for caller information (class, method, file, line) through the [Serilog.Enrichers.WithCaller](https://www.nuget.org/packages/Serilog.Enrichers.WithCaller/) package.
10+
711
## [1.0.2][1.0.2] - 2023-02-11
812

913
- 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:
@@ -80,6 +84,7 @@ Still trying to figure out how to make everything fit together with [MinVer](htt
8084

8185
- Implement log4j compatibility mode.
8286

87+
[1.1.0]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.0.2...1.1.0
8388
[1.0.2]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.0.1...1.0.2
8489
[1.0.1]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.0.0...1.0.1
8590
[1.0.0]: https://github.com/serilog-contrib/serilog-formatting-log4net/compare/1.0.0-rc.4...1.0.0

README.md

+12-1
Original file line numberDiff line numberDiff line change
@@ -192,14 +192,25 @@ Include the machine name in log4net events by using [Serilog.Enrichers.Environme
192192
var loggerConfiguration = new LoggerConfiguration().Enrich.WithMachineName();
193193
```
194194

195-
Combining these three enrichers will produce a log event including `thread`, `domain` and `username` attributes plus a `log4net:HostName` property containing the machine name:
195+
### Caller
196+
197+
Include caller information (class, method, file, line) by using [Serilog.Enrichers.WithCaller](https://www.nuget.org/packages/Serilog.Enrichers.WithCaller/):
198+
199+
```c#
200+
var loggerConfiguration = new LoggerConfiguration().Enrich.WithCaller(includeFileInfo: true);
201+
```
202+
203+
### All together
204+
205+
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:
196206

197207
```xml
198208
<event timestamp="2020-06-28T10:07:33.314159+02:00" level="INFO" thread="1" domain="TheDomainName" username="TheUserName">
199209
<properties>
200210
<data name="log4net:HostName" value="TheMachineName" />
201211
</properties>
202212
<message>The message</message>
213+
<locationInfo class="Program" method="Main(System.String[])" file="/Absolute/Path/To/Program.cs" line="29" />
203214
</event>
204215
```
205216

src/Log4NetTextFormatter.cs

+39-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Diagnostics.CodeAnalysis;
44
using System.IO;
55
using System.Linq;
6+
using System.Text.RegularExpressions;
67
using System.Xml;
78
using Serilog.Core;
89
using Serilog.Events;
@@ -25,7 +26,7 @@ public class Log4NetTextFormatter : ITextFormatter
2526
/// </summary>
2627
private static readonly string[] SpecialProperties = {
2728
Constants.SourceContextPropertyName, OutputProperties.MessagePropertyName,
28-
ThreadIdPropertyName, UserNamePropertyName, MachineNamePropertyName
29+
ThreadIdPropertyName, UserNamePropertyName, MachineNamePropertyName, CallerPropertyName
2930
};
3031

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

50+
/// <summary>
51+
/// The name of the caller property, set by <a href="https://www.nuget.org/packages/Serilog.Enrichers.WithCaller/">Serilog.Enrichers.WithCaller</a>
52+
/// </summary>
53+
/// <remarks>https://github.com/pmetz-steelcase/Serilog.Enrichers.WithCaller/blob/1.2.0/Serilog.Enrichers.WithCaller/CallerEnricher.cs#L66</remarks>
54+
private const string CallerPropertyName = "Caller";
55+
56+
/// <summary>
57+
/// The regular exception matching "class", "method", and optionally "file" and "line" for the caller property.
58+
/// </summary>
59+
private static readonly Regex CallerRegex = new(@"(?<class>.*)\.(?<method>.*\(.*\))(?: (?<file>.*):(?<line>\d+))?", RegexOptions.Compiled);
60+
4961
private readonly Log4NetTextFormatterOptions _options;
5062
private readonly bool _usesLog4JCompatibility;
5163

@@ -139,6 +151,7 @@ private void WriteEvent(LogEvent logEvent, XmlWriter writer)
139151
}
140152
WriteMessage(logEvent, writer);
141153
WriteException(logEvent, writer);
154+
WriteLocationInfo(logEvent, writer);
142155
writer.WriteEndElement();
143156
}
144157

@@ -423,6 +436,31 @@ private void WriteException(LogEvent logEvent, XmlWriter writer)
423436
}
424437
}
425438

439+
/// <summary>
440+
/// Write location information associated to the log event, i.e. class, method, file and line where the event originated.
441+
/// If the event was not enriched with caller information then nothing is written.
442+
/// </summary>
443+
/// <param name="logEvent">The log event.</param>
444+
/// <param name="writer">The XML writer.</param>
445+
/// <remarks>https://github.com/apache/logging-log4net/blob/rel/2.0.8/src/Layout/XmlLayout.cs#L297-L307</remarks>
446+
/// <remarks>https://github.com/apache/log4j/blob/v1_2_17/src/main/java/org/apache/log4j/xml/XMLLayout.java#L170-L181</remarks>
447+
private void WriteLocationInfo(LogEvent logEvent, XmlWriter writer)
448+
{
449+
if (logEvent.Properties.TryGetValue(CallerPropertyName, out var callerProperty) && callerProperty is ScalarValue { Value: string caller })
450+
{
451+
var match = CallerRegex.Match(caller);
452+
if (match.Success)
453+
{
454+
WriteStartElement(writer, "locationInfo");
455+
writer.WriteAttributeString("class", match.Groups[1].Value);
456+
writer.WriteAttributeString("method", match.Groups[2].Value);
457+
writer.WriteAttributeString("file", match.Groups[3].Value);
458+
writer.WriteAttributeString("line", match.Groups[4].Value);
459+
writer.WriteEndElement();
460+
}
461+
}
462+
}
463+
426464
/// <summary>
427465
/// Start writing an XML element, taking into account the configured <see cref="Log4NetTextFormatterOptions.XmlNamespace"/>.
428466
/// </summary>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<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/">
2+
<log4net:message><![CDATA[Hello from Serilog]]></log4net:message>
3+
<log4net:locationInfo class="Fully.Qualified.ClassName" method="MethodName(System.String)" file="" line="" />
4+
</log4net:event>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<log4j:event timestamp="1041689366535" level="INFO">
2+
<log4j:message><![CDATA[Hello from Serilog]]></log4j:message>
3+
<log4j:locationInfo class="Fully.Qualified.ClassName" method="MethodName(System.String)" file="" line="" />
4+
</log4j:event>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<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/">
2+
<log4net:message><![CDATA[Hello from Serilog]]></log4net:message>
3+
</log4net:event>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<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/">
2+
<log4net:message><![CDATA[Hello from Serilog]]></log4net:message>
3+
<log4net:locationInfo class="Fully.Qualified.ClassName" method="MethodName(System.String)" file="/Absolute/Path/To/FileName.cs" line="123" />
4+
</log4net:event>

tests/Log4NetTextFormatterTest.cs

+64
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,70 @@ public Task MachineNamePropertyStructureValue()
583583
return Verify(output);
584584
}
585585

586+
[Fact]
587+
public Task Caller()
588+
{
589+
// Arrange
590+
using var output = new StringWriter();
591+
var logEvent = CreateLogEvent(properties: new LogEventProperty("Caller", new ScalarValue("Fully.Qualified.ClassName.MethodName(System.String)")));
592+
593+
var formatter = new Log4NetTextFormatter();
594+
595+
// Act
596+
formatter.Format(logEvent, output);
597+
598+
// Assert
599+
return Verify(output);
600+
}
601+
602+
[Fact]
603+
public Task CallerNonScalar()
604+
{
605+
// Arrange
606+
using var output = new StringWriter();
607+
var logEvent = CreateLogEvent(properties: new LogEventProperty("Caller", new CustomLogEventPropertyValue("")));
608+
609+
var formatter = new Log4NetTextFormatter();
610+
611+
// Act
612+
formatter.Format(logEvent, output);
613+
614+
// Assert
615+
return Verify(output);
616+
}
617+
618+
[Fact]
619+
public Task CallerWithFile()
620+
{
621+
// Arrange
622+
using var output = new StringWriter();
623+
var logEvent = CreateLogEvent(properties: new LogEventProperty("Caller", new ScalarValue("Fully.Qualified.ClassName.MethodName(System.String) /Absolute/Path/To/FileName.cs:123")));
624+
625+
var formatter = new Log4NetTextFormatter();
626+
627+
// Act
628+
formatter.Format(logEvent, output);
629+
630+
// Assert
631+
return Verify(output);
632+
}
633+
634+
[Fact]
635+
public Task CallerLog4J()
636+
{
637+
// Arrange
638+
using var output = new StringWriter();
639+
var logEvent = CreateLogEvent(properties: new LogEventProperty("Caller", new ScalarValue("Fully.Qualified.ClassName.MethodName(System.String)")));
640+
641+
var formatter = new Log4NetTextFormatter(c => c.UseLog4JCompatibility());
642+
643+
// Act
644+
formatter.Format(logEvent, output);
645+
646+
// Assert
647+
return Verify(output);
648+
}
649+
586650
[Fact]
587651
public Task SequenceProperty()
588652
{

0 commit comments

Comments
 (0)