I am using serilog in my C# .NET 8 Worker Service project. I want a special behavior and have written a sink to filter the logging.
In my Program.cs
file, I call the following function to create my logger:
// Method to configure Serilog with DuplicateFilterSink
Serilog.ILogger CreateSerilogLogger()
{
LoggerConfiguration loggerConfig = new LoggerConfiguration()
.WriteTo.Console() // for debugging
.WriteTo.File(
path: Settings.Default.LogFileSpec, // base file name
rollingInterval: RollingInterval.Day, // automatically creates a new file daily
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
);
// Add Event Log only for Windows
if (OperatingSystem.IsWindows())
{
loggerConfig.WriteTo.EventLog(
source: Settings.Default.EventLogSource,
logName: Settings.Default.EventLogName,
restrictedToMinimumLevel: Serilog.Events.LogEventLevel.Warning
);
}
DuplicateFilterSink duplicateFilterSink = new DuplicateFilterSink(loggerConfig.CreateLogger());
return new LoggerConfiguration()
.WriteTo.Sink(duplicateFilterSink)
.CreateLogger();
}
My DuplicateFilterSink
class looks like this:
public class DuplicateFilterSink : ILogEventSink
{
private readonly ILogEventSink _innerSink;
private LogEvent? _lastLogEvent;
private int _duplicateCount = 0;
public DuplicateFilterSink(ILogEventSink innerSink)
{
_innerSink = innerSink;
}
public void Emit(LogEvent logEvent)
{
// Check if the message is the same as the previous one
if (_lastLogEvent != null && logEvent.MessageTemplate.Text == _lastLogEvent.MessageTemplate.Text)
{
_duplicateCount++;
}
else
{
// If the message has changed, log the previous duplicate count
if (_duplicateCount > 0)
{
List<LogEventProperty> properties = new List<LogEventProperty>();
foreach (var thing in logEvent.Properties)
{
LogEventProperty property = new LogEventProperty(thing.Key, thing.Value);
properties.Add(property);
}
string dupmsg = $"The previous message occurred {_duplicateCount} times";
MessageTemplate duplicateTemplate = new MessageTemplate(dupmsg, Array.Empty<MessageTemplateToken>());
LogEvent duplicateMessage = new LogEvent(
logEvent.Timestamp,
logEvent.Level,
logEvent.Exception,
duplicateTemplate,
properties.ToArray()
);
_innerSink.Emit(duplicateMessage);
_duplicateCount = 0;
}
// Log the new message
_innerSink.Emit(logEvent);
_lastLogEvent = logEvent;
}
}
}
The idea is that if the LogEvent
matches the previous LogEvent
, it will not be emitted but only counted. As soon as the LogEvent
DOESN'T match the previous LogEvent
AND the count is greater than zero, it will emit a message saying the previous message occurred X times and then it would emit the new message. This will prevent runaway messages in a tight loop from filling up my log files.
This all works except when the duplicateMessage
LogEvent
is emitted, the text is blank. I believe it is because I am using an empty MessageTemplateToken
array. That is the only difference between the duplicatMessage
LogEvent
and the LogEvent
that is the parameter.
If this is the problem, I don't know how to correct it. The MessageTemplateToken
is an abstract class so I can't instantiate it and I can't find any class that derives from it. I also can't use the existing MessageTemplateTokens
from the LogEvent
parameter's MessageTemplate
because I would have to change the text inside it and the Text property is read-only.
Question #1: Is this my problem? Is the blank MessageTemplateToken
array causing my emitted LogEvent
to write a blank line?
Question #2: is there any way to create a valid MessageTemplate
with a valid MessageTemplateToken
array?
It's possible to just construct message template tokens, e.g.:
var template = new MessageTemplate(new[] { new TextToken("This is a message") });
For templates with structured data you might want ILogger.BindMessageTemplate()
:
Log.Logger.BindMessageTemplate(
"The previous message occurred {DuplicateCount} times",
new object[] { _duplicateCount },
out var template,
out var properties);
The output template
and properties
values are useful when constructing a log event.