Skip to content

Avoid boxing when using positional logging parameters #787

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 33 additions & 27 deletions src/NLog.Extensions.Logging/Logging/NLogLogger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,44 +64,29 @@ public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId
switch (((IReadOnlyList<KeyValuePair<string, object?>>)state).Count)
{
case 0:
if (captureEventId)
{
var formattedMessage = formatter(state, exception);
var eventIdParameterCount = GetEventIdMessageParameters(eventId, out var eventIdArg1, out var eventIdArg2);
if (eventIdParameterCount == 0)
return new LogEventInfo(nLogLogLevel, _logger.Name, formattedMessage);
else if (eventIdParameterCount == 1)
return new LogEventInfo(nLogLogLevel, _logger.Name, formattedMessage, formattedMessage, [eventIdArg1]);
else
return new LogEventInfo(nLogLogLevel, _logger.Name, formattedMessage, formattedMessage, [eventIdArg1, eventIdArg2]);
return CreateLogEventWithoutParameters(nLogLogLevel, eventId, captureEventId, formattedMessage);
}
return new LogEventInfo(nLogLogLevel, _logger.Name, formatter(state, exception));
case 1:
parameterCount = 1;
if (OriginalFormatPropertyName.Equals(((IReadOnlyList<KeyValuePair<string, object?>>)state)[0].Key))
{
var formattedMessage = formatter(state, exception);
if (captureEventId)
{
var eventIdParameterCount = GetEventIdMessageParameters(eventId, out var eventIdArg1, out var eventIdArg2);
if (eventIdParameterCount == 0)
return new LogEventInfo(nLogLogLevel, _logger.Name, formattedMessage);
else if (eventIdParameterCount == 1)
return new LogEventInfo(nLogLogLevel, _logger.Name, formattedMessage, formattedMessage, [eventIdArg1]);
else
return new LogEventInfo(nLogLogLevel, _logger.Name, formattedMessage, formattedMessage, [eventIdArg1, eventIdArg2]);
}
return new LogEventInfo(nLogLogLevel, _logger.Name, formattedMessage);
return CreateLogEventWithoutParameters(nLogLogLevel, eventId, captureEventId, formattedMessage);
}
break;
case 2:
parameterCount = 2;
if (_options.CaptureMessageTemplates && !_options.CaptureMessageParameters && !_options.ParseMessageTemplates && OriginalFormatPropertyName.Equals(((IReadOnlyList<KeyValuePair<string, object?>>)state)[1].Key))
{
var arg1 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[0]);
var arg1 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[0], 0);
if (arg1.Name is not null)
{
var formattedMessage = formatter(state, exception);
if ("0".Equals(arg1.Name))
return CreateLogEventWithoutParameters(nLogLogLevel, eventId, captureEventId, formattedMessage);

if (captureEventId)
{
var eventIdParameterCount = GetEventIdMessageParameters(eventId, out var eventIdArg1, out var eventIdArg2);
Expand All @@ -118,13 +103,16 @@ public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId
break;
case 3:
parameterCount = 3;
if (_options.CaptureMessageTemplates && !_options.CaptureMessageParameters && !_options.ParseMessageTemplates && OriginalFormatPropertyName.Equals(((IReadOnlyList<KeyValuePair<string, object?>>)state)[1].Key))
if (_options.CaptureMessageTemplates && !_options.CaptureMessageParameters && !_options.ParseMessageTemplates && OriginalFormatPropertyName.Equals(((IReadOnlyList<KeyValuePair<string, object?>>)state)[2].Key))
{
var arg1 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[0]);
var arg2 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[1]);
var arg1 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[0], 0);
var arg2 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[1], 1);
if (arg1.Name is not null && arg2.Name is not null)
{
var formattedMessage = formatter(state, exception);
if ("0".Equals(arg1.Name) && "1".Equals(arg2.Name))
return CreateLogEventWithoutParameters(nLogLogLevel, eventId, captureEventId, formattedMessage);

if (captureEventId)
{
var eventIdParameterCount = GetEventIdMessageParameters(eventId, out var eventIdArg1, out var eventIdArg2);
Expand All @@ -144,12 +132,15 @@ public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId
parameterCount = 4;
if (_options.CaptureMessageTemplates && !_options.CaptureMessageParameters && !_options.ParseMessageTemplates && OriginalFormatPropertyName.Equals(((IReadOnlyList<KeyValuePair<string, object?>>)state)[3].Key))
{
var arg1 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[0]);
var arg2 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[1]);
var arg3 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[1]);
var arg1 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[0], 0);
var arg2 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[1], 1);
var arg3 = NLogMessageParameterList.GetMessageTemplateParameter(((IReadOnlyList<KeyValuePair<string, object?>>)state)[2], 2);
if (arg1.Name is not null && arg2.Name is not null && arg3.Name is not null)
{
var formattedMessage = formatter(state, exception);
if ("0".Equals(arg1.Name) && "1".Equals(arg2.Name) && "2".Equals(arg3.Name))
return CreateLogEventWithoutParameters(nLogLogLevel, eventId, captureEventId, formattedMessage);

if (captureEventId)
{
var eventIdParameterCount = GetEventIdMessageParameters(eventId, out var eventIdArg1, out var eventIdArg2);
Expand All @@ -175,6 +166,21 @@ public void Log<TState>(Microsoft.Extensions.Logging.LogLevel logLevel, EventId

return null;
}

private LogEventInfo CreateLogEventWithoutParameters(LogLevel nLogLogLevel, in EventId eventId, bool captureEventId, string formattedMessage)
{
if (captureEventId)
{
var eventIdParameterCount = GetEventIdMessageParameters(eventId, out var eventIdArg1, out var eventIdArg2);
if (eventIdParameterCount == 0)
return new LogEventInfo(nLogLogLevel, _logger.Name, formattedMessage);
else if (eventIdParameterCount == 1)
return new LogEventInfo(nLogLogLevel, _logger.Name, formattedMessage, formattedMessage, [eventIdArg1]);
else
return new LogEventInfo(nLogLogLevel, _logger.Name, formattedMessage, formattedMessage, [eventIdArg1, eventIdArg2]);
}
return new LogEventInfo(nLogLogLevel, _logger.Name, formattedMessage);
}
#endif

private LogEventInfo CreateLogEventInfo<TState>(LogLevel nLogLogLevel, in EventId eventId, in TState state, Exception? exception, Func<TState, Exception?, string> formatter)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -187,13 +187,16 @@ public MessageTemplateParameter this[int index]
set => throw new NotSupportedException();
}

internal static MessageTemplateParameter GetMessageTemplateParameter(in KeyValuePair<string, object?> propertyValue)
internal static MessageTemplateParameter GetMessageTemplateParameter(in KeyValuePair<string, object?> propertyValue, int index)
{
var propertyName = propertyValue.Key;
if (propertyName is null || propertyName.Length == 0)
return default;
var firstChar = propertyName[0];
if (char.IsDigit(firstChar) || GetCaptureType(firstChar) != CaptureType.Normal)
if (char.IsDigit(firstChar) && (propertyName.Length != 1 || (firstChar - '0') != index))
return default;

if (GetCaptureType(firstChar) != CaptureType.Normal)
return default;

return new MessageTemplateParameter(propertyName, propertyValue.Value, null, CaptureType.Normal);
Expand Down
9 changes: 9 additions & 0 deletions test/NLog.Extensions.Logging.Tests/LoggerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ public void TestTwoReverseParameters()
Assert.Equal("NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|message with id and 2 parameters|", runner.LastTargetMessage);
}

[Fact]
public void TestThreeParameters()
{
var runner = GetRunner();
runner.Logger.LogDebug("message with {0} and {1} and {2} parameters", "id", "username", 3);

Assert.Equal("NLog.Extensions.Logging.Tests.LoggerTests.Runner|DEBUG|message with id and username and 3 parameters|", runner.LastTargetMessage);
}

[Fact]
public void TestStructuredLogging()
{
Expand Down