Skip to content

Commit

Permalink
feat: support custom SD-ID in RFC5424 messages (#78)
Browse files Browse the repository at this point in the history
* Unit test for custom SD-ID definition
* Format message with custom SD-ID value
* Declare parameter in configuration methods
* Restore test project targets
* Document format restriction

---------

Co-authored-by: Nicolas MANGUE <n.mangue@cnr.tm.fr>
  • Loading branch information
nmangue and Nicolas MANGUE authored Nov 2, 2024
1 parent 45466a9 commit 8b3dba3
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 11 deletions.
13 changes: 10 additions & 3 deletions src/Serilog.Sinks.Syslog/Sinks/Formatters/Rfc5424Formatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class Rfc5424Formatter : SyslogFormatterBase

private readonly string applicationName;
private readonly string messageIdPropertyName;
private readonly string structuredDataId;

internal const string DefaultMessageIdPropertyName = "SourceContext";

Expand All @@ -53,11 +54,13 @@ public class Rfc5424Formatter : SyslogFormatterBase
/// <param name="messageIdPropertyName">Where the Id number of the message will be derived from. Defaults to the "SourceContext" property of the syslog event. Property name and value must be all printable ASCII characters with max length of 32.</param>
/// <param name="sourceHost"><inheritdoc cref="SyslogFormatterBase.Host" path="/summary"/></param>
/// <param name="severityMapping"><inheritdoc cref="SyslogLoggerConfigurationExtensions.LocalSyslog" path="/param[@name='severityMapping']"/></param>
/// <param name="structuredDataId">Structured Data ID name (SD-ID)</param>
public Rfc5424Formatter(Facility facility = Facility.Local0, string applicationName = null,
ITextFormatter templateFormatter = null,
string messageIdPropertyName = DefaultMessageIdPropertyName,
string sourceHost = null,
Func<LogEventLevel, Severity> severityMapping = null)
Func<LogEventLevel, Severity> severityMapping = null,
string structuredDataId = null)
: base(facility, templateFormatter, sourceHost, severityMapping)
{
this.applicationName = applicationName ?? ProcessName;
Expand All @@ -71,6 +74,10 @@ public Rfc5424Formatter(Facility facility = Facility.Local0, string applicationN
this.messageIdPropertyName = (messageIdPropertyName ?? DefaultMessageIdPropertyName)
.AsPrintableAscii()
.WithMaxLength(32);

this.structuredDataId = (structuredDataId ?? STRUCTURED_DATA_ID)
.AsPrintableAscii()
.WithMaxLength(32);
}

// NOTE: For the rsyslog daemon to correctly handle RFC5424, you need to change your /etc/rsyslog.conf to use:
Expand Down Expand Up @@ -113,13 +120,13 @@ private string GetMessageId(LogEvent logEvent)
: NILVALUE;
}

private static string RenderStructuredData(LogEvent logEvent)
private string RenderStructuredData(LogEvent logEvent)
{
var properties = logEvent.Properties.Select(kvp =>
new KeyValuePair<string, string>(RenderPropertyKey(kvp.Key), RenderPropertyValue(kvp.Value)));

var structuredDataKvps = String.Join(" ", properties.Select(t => $@"{t.Key}=""{t.Value}"""));
var structuredData = String.IsNullOrEmpty(structuredDataKvps) ? NILVALUE : $"[{STRUCTURED_DATA_ID} {structuredDataKvps}]";
var structuredData = String.IsNullOrEmpty(structuredDataKvps) ? NILVALUE : $"[{this.structuredDataId} {structuredDataKvps}]";

return structuredData;
}
Expand Down
23 changes: 15 additions & 8 deletions src/Serilog.Sinks.Syslog/SyslogLoggerConfigurationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ public static class SyslogLoggerConfigurationExtensions
/// <param name="formatter">The message formatter</param>
/// <param name="levelSwitch">A switch allowing the pass-through minimum level
/// to be changed at runtime.</param>
/// <param name="structuredDataId">The structured data ID (SD-ID). Only applicable when <paramref name="format"/> is <see cref="SyslogFormat.RFC5424"/>. Defaults to "meta".</param>
/// <seealso cref="!:https://github.com/serilog/serilog/wiki/Formatting-Output"/>
public static LoggerConfiguration LocalSyslog(this LoggerSinkConfiguration loggerSinkConfig,
string appName = null, Facility facility = Facility.Local0, string outputTemplate = null,
LogEventLevel restrictedToMinimumLevel = LevelAlias.Minimum,
Func<LogEventLevel, Severity> severityMapping = null,ITextFormatter formatter = null,
LoggingLevelSwitch levelSwitch = null)
LoggingLevelSwitch levelSwitch = null, string structuredDataId = null)
{
if (!LocalSyslogService.IsAvailable)
{
Expand All @@ -68,7 +69,7 @@ public static LoggerConfiguration LocalSyslog(this LoggerSinkConfiguration logge
}

var messageFormatter = GetFormatter(SyslogFormat.Local, appName, facility, outputTemplate,
severityMapping: severityMapping, formatter: formatter);
severityMapping: severityMapping, formatter: formatter, structuredDataId: structuredDataId);
var syslogService = new LocalSyslogService(facility, appName);
syslogService.Open();

Expand Down Expand Up @@ -96,6 +97,7 @@ public static LoggerConfiguration LocalSyslog(this LoggerSinkConfiguration logge
/// <param name="formatter">The message formatter</param>
/// <param name="levelSwitch">A switch allowing the pass-through minimum level
/// to be changed at runtime.</param>
/// <param name="structuredDataId">The structured data ID (SD-ID). Only applicable when <paramref name="format"/> is <see cref="SyslogFormat.RFC5424"/>. Defaults to "meta".</param>
/// <see cref="!:https://github.com/serilog/serilog/wiki/Formatting-Output"/>
public static LoggerConfiguration UdpSyslog(this LoggerSinkConfiguration loggerSinkConfig,
string host, int port = 514, string appName = null, SyslogFormat format = SyslogFormat.RFC3164,
Expand All @@ -104,13 +106,14 @@ public static LoggerConfiguration UdpSyslog(this LoggerSinkConfiguration loggerS
string messageIdPropertyName = Rfc5424Formatter.DefaultMessageIdPropertyName,
string sourceHost = null,
Func<LogEventLevel, Severity> severityMapping = null, ITextFormatter formatter = null,
LoggingLevelSwitch levelSwitch = null)
LoggingLevelSwitch levelSwitch = null,
string structuredDataId = null)
{
if (String.IsNullOrWhiteSpace(host))
throw new ArgumentException(nameof(host));

batchConfig ??= DefaultBatchOptions;
var messageFormatter = GetFormatter(format, appName, facility, outputTemplate, messageIdPropertyName, sourceHost, severityMapping, formatter);
var messageFormatter = GetFormatter(format, appName, facility, outputTemplate, messageIdPropertyName, sourceHost, severityMapping, formatter, structuredDataId);
var endpoint = ResolveIP(host, port);

var syslogUdpSink = new SyslogUdpSink(endpoint, messageFormatter);
Expand Down Expand Up @@ -172,6 +175,7 @@ public static LoggerConfiguration TcpSyslog(this LoggerSinkConfiguration loggerS
/// <param name="formatter">The message formatter</param>
/// <param name="levelSwitch">A switch allowing the pass-through minimum level
/// to be changed at runtime.</param>
/// <param name="structuredDataId">The structured data ID (SD-ID). Only applicable when <paramref name="format"/> is <see cref="SyslogFormat.RFC5424"/>. Defaults to "meta".</param>
/// <seealso cref="!:https://github.com/serilog/serilog/wiki/Formatting-Output"/>
public static LoggerConfiguration TcpSyslog(this LoggerSinkConfiguration loggerSinkConfig,
string host, int port = 1468, string appName = null, FramingType framingType = FramingType.OCTET_COUNTING,
Expand All @@ -184,10 +188,11 @@ public static LoggerConfiguration TcpSyslog(this LoggerSinkConfiguration loggerS
PeriodicBatchingSinkOptions batchConfig = null,
string sourceHost = null,
Func<LogEventLevel, Severity> severityMapping = null, ITextFormatter formatter = null,
LoggingLevelSwitch levelSwitch = null)
LoggingLevelSwitch levelSwitch = null,
string structuredDataId = null)
{
var messageFormatter = GetFormatter(format, appName, facility, outputTemplate, messageIdPropertyName,
sourceHost, severityMapping, formatter);
sourceHost, severityMapping, formatter, structuredDataId);

var config = new SyslogTcpConfig
{
Expand Down Expand Up @@ -228,7 +233,9 @@ private static ISyslogFormatter GetFormatter(SyslogFormat format, string appName
string outputTemplate,
string messageIdPropertyName = null,
string sourceHost = null,
Func<LogEventLevel, Severity> severityMapping = null, ITextFormatter formatter = null)
Func<LogEventLevel, Severity> severityMapping = null,
ITextFormatter formatter = null,
string structuredDataId = null)
{
ITextFormatter templateFormatter;

Expand All @@ -246,7 +253,7 @@ private static ISyslogFormatter GetFormatter(SyslogFormat format, string appName
return format switch
{
SyslogFormat.RFC3164 => new Rfc3164Formatter(facility, appName, templateFormatter, sourceHost, severityMapping),
SyslogFormat.RFC5424 => new Rfc5424Formatter(facility, appName, templateFormatter, messageIdPropertyName, sourceHost, severityMapping),
SyslogFormat.RFC5424 => new Rfc5424Formatter(facility, appName, templateFormatter, messageIdPropertyName, sourceHost, severityMapping, structuredDataId),
SyslogFormat.Local => new LocalFormatter(facility, templateFormatter, severityMapping),
_ => throw new ArgumentException($"Invalid format: {format}")
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ public void Should_format_message_with_structured_data()
match.Groups["proc"].Value.ToInt().ShouldBeGreaterThan(0);
match.Groups["msgid"].Value.ShouldBe(SOURCE_CONTEXT);
match.Groups["sd"].Value.ShouldNotBe(NILVALUE);
match.Groups["sdid"].Value.ShouldBe("meta");
match.Groups["msg"].Value.ShouldBe($"This is a test message with val \"{testVal}\"");
}

Expand Down Expand Up @@ -193,5 +194,28 @@ public void Should_override_log_host_name()

match.Groups["host"].Value.ShouldBe(hostname);
}

[Fact]
public void Should_format_message_with_custom_sdid()
{
const string sdId = "ourSDID@32473";
var customFormatter = new Rfc5424Formatter(Facility.User, APP_NAME, structuredDataId: sdId);

var properties = new List<LogEventProperty>
{
new LogEventProperty("MyProperty", new ScalarValue("Lorem Ipsum")),
};

var template = new MessageTemplateParser().Parse("This is a test message with val {MyProperty}");
var warnEvent = new LogEvent(this.timestamp, LogEventLevel.Warning, null, template, properties);

var formatted = customFormatter.FormatMessage(warnEvent);
this.output.WriteLine($"RFC5424 with structured data: {formatted}");

var match = this.regex.Match(formatted);
match.Success.ShouldBeTrue();

match.Groups["sdid"].Value.ShouldBe(sdId);
}
}
}

0 comments on commit 8b3dba3

Please sign in to comment.