-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Logger with support for params ReadOnlySpan #5800
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
Conversation
WalkthroughThis update introduces span-based overloads for logging methods throughout the codebase, targeting .NET Standard 2.1 and .NET 9.0 or greater. New overloads for all major logging levels (Trace, Debug, Info, Warn, Error, Fatal) in the Changes
Sequence Diagram(s)sequenceDiagram
participant Caller
participant Logger
participant InternalLogger
participant LogEventBuilder
alt Span-based logging (Logger)
Caller->>Logger: LogLevel(message, ReadOnlySpan<object> args)
Logger->>Logger: Convert args to array if not empty
Logger->>Logger: Create LogEventInfo
Logger->>Logger: Write LogEventInfo to targets
end
alt Span-based logging (InternalLogger)
Caller->>InternalLogger: LogLevel(message, ReadOnlySpan<object> args)
InternalLogger->>InternalLogger: Convert args to array if not empty
InternalLogger->>InternalLogger: Write log entry
end
alt Span-based properties (LogEventBuilder)
Caller->>LogEventBuilder: Properties(ReadOnlySpan<(string, object)>)
LogEventBuilder->>LogEventBuilder: Set properties from span
end
alt Span-based message (LogEventBuilder)
Caller->>LogEventBuilder: Message(message, ReadOnlySpan<object> args)
LogEventBuilder->>LogEventBuilder: Set message and parameters
end
Poem
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/NLog/Logger-generated.cs (1)
187-1478: Excellent performance optimization using ReadOnlySpan across all log levels.The implementation throughout the logger class consistently applies modern C# features to improve performance:
- Avoids unnecessary array allocations by using
ReadOnlySpan<object>instead ofparams object[]- Only converts spans to arrays when necessary (when not empty)
- Efficiently checks for logger targets directly
- Maintains feature parity with existing array-based methods
The conditional compilation ensures these optimizations are only available on platforms that support them (.NET Standard 2.1+ or .NET Core 3.1+).
Consider adding similar span-based overloads for methods that accept both exceptions and arguments in a future update to complete the API surface.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro📒 Files selected for processing (13)
src/NLog/Common/InternalLogger-generated.cs(6 hunks)src/NLog/Common/InternalLogger-generated.tt(2 hunks)src/NLog/Common/InternalLogger.cs(1 hunks)src/NLog/ILogger-V1.cs(181 hunks)src/NLog/ILoggerBase-V1.cs(31 hunks)src/NLog/Internal/OverloadResolutionPriorityAttribute.cs(1 hunks)src/NLog/LogEventBuilder.cs(1 hunks)src/NLog/Logger-Conditional.cs(1 hunks)src/NLog/Logger-generated.cs(6 hunks)src/NLog/Logger-generated.tt(3 hunks)src/NLog/Logger.cs(1 hunks)src/NLog/NLog.csproj(2 hunks)tests/NLog.UnitTests/Fluent/LogEventBuilderTests.cs(1 hunks)🔇 Additional comments (30)
src/NLog/Logger-Conditional.cs (1)
39-39: Adding required namespace for overload resolution attribute.This adds the
System.Runtime.CompilerServicesnamespace which is needed for theOverloadResolutionPriorityAttributethat will be used to control method overload selection priority in the logging methods.src/NLog/NLog.csproj (2)
14-14: Updated supported platforms description for accuracy.The description now clearly specifies .NET Standard 2.0 and 2.1 support instead of the previously more ambiguous ".NET Standard 1.3+ and 2.0+" wording, which better reflects the actual supported platforms.
67-67: Setting C# language version 13 for .NET Standard 2.1 target.This configuration enables C# 13 language features specifically for the .NET Standard 2.1 target, which is required for the new
ReadOnlySpan<object>parameter support in logging methods. This aligns with the PR objectives to leverage C# 13 features for more efficient parameter handling.tests/NLog.UnitTests/Fluent/LogEventBuilderTests.cs (1)
314-314: Updated test to clarify overload resolution with explicit cast.The explicit cast to
KeyValuePair<string, object>[]ensures the test targets the correct method overload when passingnull. This change is necessary because of newReadOnlySpan-based overloads being added elsewhere in the codebase.src/NLog/Internal/OverloadResolutionPriorityAttribute.cs (1)
34-60: Well-implemented internal attribute for controlling overload resolution.This custom implementation of
OverloadResolutionPriorityAttributeprovides overload resolution control for frameworks that don't include this attribute natively. The implementation follows best practices with:
- Proper conditional compilation for compatibility
- Appropriate attribute usage targeting (methods, constructors, properties)
- Comprehensive XML documentation
- Clean implementation with proper parameter validation
This attribute enables the codebase to use
[OverloadResolutionPriority(-1)]on legacy method overloads to ensure the new span-based methods are preferred during overload resolution when available.src/NLog/ILogger-V1.cs (3)
38-38: Addition of System.Runtime.CompilerServices namespace is required for OverloadResolutionPriority attributeThe added namespace import enables the use of the
OverloadResolutionPriorityattribute which is essential for controlling method overload resolution with the new ReadOnlySpan-based logging methods.
54-57: Strategic use of OverloadResolutionPriority for improved method resolutionThe addition of the
[OverloadResolutionPriority(-1)]attribute to legacy logging methods ensures correct overload resolution when introducing span-based alternatives. This pattern is applied consistently to all logging method overloads throughout the interface.
77-80: Consistent application of OverloadResolutionPriority to multi-parameter methodsThe attribute is correctly applied to multi-parameter logging methods as well, maintaining a consistent approach to overload resolution throughout the interface.
src/NLog/Common/InternalLogger.cs (1)
184-211: Efficient ReadOnlySpan overloads for Log methodsThese new overloads using
ReadOnlySpan<object>will improve performance by avoiding array allocations. Key implementation points:
- Only converts span to array when not empty (
args.IsEmpty ? null : args.ToArray())- Guards execution with
IsLogLevelEnabledcheck before parameter processing- Properly compiled for NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER
src/NLog/ILoggerBase-V1.cs (3)
38-38: Required namespace import for OverloadResolutionPriorityThe System.Runtime.CompilerServices namespace is required for the OverloadResolutionPriority attribute that's used throughout this file to adjust method resolution priority.
54-58: Consistent method resolution approach matches ILogger implementationThe OverloadResolutionPriority approach used here matches that used in ILogger-V1.cs, ensuring a consistent overload resolution strategy across the library's interface hierarchy.
450-453: Comprehensive implementation covers all numeric typesThe attribute has been systematically applied to all legacy method overloads, including this one for ulong parameters, ensuring complete coverage across all data types.
src/NLog/Logger.cs (1)
385-421: Well-implemented ReadOnlySpan overloads for improved performance.These new overloads of the
Logmethod leverageReadOnlySpan<object>to reduce unnecessary array allocations when passing formatting arguments. The code correctly checks whether targets are available before creating the log event, and only converts the span to an array if it's not empty, which is an excellent optimization.The conditional compilation (
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP3_1_OR_GREATER) ensures compatibility with older platforms while enabling the more efficient API on newer frameworks.src/NLog/Logger-generated.tt (3)
48-48: Added NLog.Internal namespace import for Guard.The addition of the
NLog.Internalnamespace import is needed to support theGuard.ThrowIfNullusage in the template.
117-117: Improved null validation using Guard pattern.Good refactoring to use
Guard.ThrowIfNullinstead of inline null check, making the code more consistent with the codebase's validation patterns.
163-179: Added span-based logging methods for more efficient parameter passing.These new conditional overloads for each log level use
ReadOnlySpan<object>to reduce unnecessary array allocations. The implementation correctly checks for the log level's enabled status and only converts the span to an array if not empty.The method signatures and implementation match the pattern used in the main
Logger.csfile, maintaining consistency throughout the codebase.src/NLog/Common/InternalLogger-generated.tt (1)
59-84: Well-designed span-based logging overloads for internal logger.These new overloads mirror the changes in the public Logger API, enabling more efficient parameter passing without array allocations. The code properly checks if the log level is enabled before proceeding with logging operations, and only converts spans to arrays when necessary.
The conditional compilation ensures backward compatibility while enabling modern API improvements on supported platforms.
src/NLog/Logger-generated.cs (6)
187-203: Well-implemented span-based Trace overload.This new overload for .NET Standard 2.1+ provides a more efficient way to pass logging arguments without unnecessary array allocations.
442-458: Well-implemented span-based Debug overload.Follows the same efficient pattern as the Trace method, providing memory allocation benefits on supported platforms.
697-713: Consistent implementation of span-based Info overload.The Info level implementation maintains the same pattern used throughout the class, ensuring consistent behavior.
952-968: Consistent implementation of span-based Warn overload.Maintains the pattern established across other log levels, providing efficient argument handling.
1207-1223: Consistent implementation of span-based Error overload.The Error level implementation follows the established pattern, providing consistent API with performance benefits.
1462-1478: Consistent implementation of span-based Fatal overload.Completes the set of span-based overloads with consistent implementation across all logging levels.
src/NLog/Common/InternalLogger-generated.cs (7)
73-98: Well-implemented span-based Trace overloads.These new overloads (with and without exception parameter) provide a more efficient way to pass logging arguments without unnecessary array allocations.
214-239: Well-implemented span-based Debug overloads.Both overloads follow the same efficient pattern established in the Trace methods, providing memory allocation benefits on supported platforms.
355-380: Consistent implementation of span-based Info overloads.Both Info level implementations maintain the same pattern used throughout the class, ensuring consistent behavior.
496-521: Consistent implementation of span-based Warn overloads.Both Warn level implementations follow the established pattern, providing the same efficient API as other log levels.
637-662: Consistent implementation of span-based Error overloads.The Error level implementations follow the established pattern, maintaining API consistency with other log levels.
778-803: Consistent implementation of span-based Fatal overloads.Completes the set of span-based overloads with consistent implementation across all logging levels.
73-803: Excellent performance optimization using ReadOnlySpan across all log levels in the InternalLogger.The implementation consistently applies modern C# features to improve performance:
- Avoids unnecessary array allocations by using
ReadOnlySpan<object>instead ofparams object[]- Only converts spans to arrays when necessary (when not empty)
- Maintains the same level-checking logic as existing methods
- Provides complete API coverage with both exception and non-exception overloads for all log levels
The conditional compilation ensures these optimizations are only available on platforms that support them (.NET Standard 2.1+ or .NET Core 3.1+). This is a well-executed enhancement that maintains backward compatibility while enabling better performance on newer platforms.
15e72e4 to
ccb35d1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/NLog/Logger-Conditional.cs (3)
39-39: Unconditionalusing System.Runtime.CompilerServicesis safe, but consider conditional inclusion
OverloadResolutionPriorityAttributeis only referenced inside the#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATERblocks.
To avoid “unused‐using” diagnostics on older target frameworks you could move thisusinginside the same compilation conditions.- using System.Runtime.CompilerServices; +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + using System.Runtime.CompilerServices; +#endif
170-182: Minor optimisation: avoid the extra span copy
Debug(message, args)will receiveargsby value, causing a defensive copy of theReadOnlySpan<object>struct (16 bytes).
You can forward it byinto prevent that:- Debug(message, args); + Debug(message, in args);(This is purely micro-optimisation; feel free to skip if you prefer code consistency.)
283-304: Attribute ordering: placeOverloadResolutionPriorityfirst for maximum effectAccording to the Roslyn implementation notes, overload-resolution attributes are inspected in declaration order. Placing
[OverloadResolutionPriority]before[EditorBrowsable]guarantees it is processed even if another analyser stops walking after the first attribute.- [EditorBrowsable(EditorBrowsableState.Never)] -#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER - [OverloadResolutionPriority(-1)] -#endif +#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER + [OverloadResolutionPriority(-1)] +#endif + [EditorBrowsable(EditorBrowsableState.Never)]
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (13)
src/NLog/Common/InternalLogger-generated.cs(7 hunks)src/NLog/Common/InternalLogger-generated.tt(2 hunks)src/NLog/Common/InternalLogger.cs(1 hunks)src/NLog/ILogger-V1.cs(181 hunks)src/NLog/ILoggerBase-V1.cs(31 hunks)src/NLog/Internal/OverloadResolutionPriorityAttribute.cs(1 hunks)src/NLog/LogEventBuilder.cs(2 hunks)src/NLog/Logger-Conditional.cs(53 hunks)src/NLog/Logger-generated.cs(7 hunks)src/NLog/Logger-generated.tt(3 hunks)src/NLog/Logger.cs(1 hunks)src/NLog/NLog.csproj(2 hunks)tests/NLog.UnitTests/Fluent/LogEventBuilderTests.cs(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
- tests/NLog.UnitTests/Fluent/LogEventBuilderTests.cs
- src/NLog/NLog.csproj
- src/NLog/Logger.cs
- src/NLog/Logger-generated.tt
- src/NLog/Internal/OverloadResolutionPriorityAttribute.cs
- src/NLog/Common/InternalLogger.cs
- src/NLog/Common/InternalLogger-generated.tt
- src/NLog/LogEventBuilder.cs
- src/NLog/ILogger-V1.cs
- src/NLog/ILoggerBase-V1.cs
- src/NLog/Common/InternalLogger-generated.cs
- src/NLog/Logger-generated.cs
🔇 Additional comments (1)
src/NLog/Logger-Conditional.cs (1)
106-119: Great to see the new C# 13params ReadOnlySpan<object>overloadThis will eliminate heap allocations for most call-sites.
Two follow-ups to keep in mind:
- Unit-tests – make sure there is at least one test that exercises these span-based overloads to guard against accidental regression when LangVersion or TFMs change.
- Call-site ambiguity – a call such as
ConditionalDebug(ex, "msg", default);can bind both to theobject[]and theReadOnlySpan<object>overload (whendefaultis used). Current Roslyn picks the array version, but please add a unit-test to freeze the behaviour you expect.
ccb35d1 to
0922493
Compare
c46f03d to
58251fa
Compare
|
LangVersion=13 is only supported with NET9, so had to update AppVeyor from Ubuntu2004 to Ubuntu2204 for NET9-support. But the console-output from xUnit-tests looks garbled after the change to Ubuntu2204. Seems this is caused by NET9 enabling Terminal Console by default: And it is fixed by setting environment variable environment:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
MSBUILDTERMINALLOGGER: off |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🧹 Nitpick comments (1)
tests/NLog.UnitTests/LayoutRenderers/ProcessNameLayoutRendererTests.cs (1)
58-60: Consider making the “allowed process names” list less brittleEach time the test is executed under a different host (IDE, runner, OS-specific wrapper, etc.) we tend to append another hard-coded string. Over time the list becomes long and still risks missing future hosts.
A more future-proof approach would be:
- Compare against
Process.GetCurrentProcess().ProcessNamedirectly, instead of maintaining a manual allow-list.- Or fall back to asserting that the rendered value equals (case-insensitive) the current process name or contains it as a substring.
Example sketch:
var expected = System.Diagnostics.Process.GetCurrentProcess().ProcessName.ToLowerInvariant(); Assert.Contains(expected, lower);This removes the need to keep this list in sync and avoids false negatives on new runners.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
run-tests.ps1(3 hunks)src/NLog/Internal/XmlHelper.cs(1 hunks)tests/ManuallyLoadedExtension/ManuallyLoadedExtension.csproj(1 hunks)tests/NLog.UnitTests/LayoutRenderers/AssemblyVersionTests.cs(0 hunks)tests/NLog.UnitTests/LayoutRenderers/ProcessNameLayoutRendererTests.cs(1 hunks)tests/NLog.UnitTests/NLog.UnitTests.csproj(3 hunks)tests/NLog.UnitTests/Properties/AssemblyInfo.cs(1 hunks)tests/NLogAutoLoadExtension/NLogAutoLoadExtension.csproj(1 hunks)tests/SampleExtensions/SampleExtensions.csproj(1 hunks)
💤 Files with no reviewable changes (1)
- tests/NLog.UnitTests/LayoutRenderers/AssemblyVersionTests.cs
✅ Files skipped from review due to trivial changes (4)
- tests/ManuallyLoadedExtension/ManuallyLoadedExtension.csproj
- tests/SampleExtensions/SampleExtensions.csproj
- tests/NLogAutoLoadExtension/NLogAutoLoadExtension.csproj
- run-tests.ps1
🔇 Additional comments (2)
src/NLog/Internal/XmlHelper.cs (1)
283-283: Excellent improvement for culture-invariant DateTime formattingThis change ensures the DateTime string representation is always formatted using the invariant culture, preventing any potential inconsistencies that could arise from different thread or system culture settings. This is particularly important for XML operations where consistent formatting is critical.
tests/NLog.UnitTests/NLog.UnitTests.csproj (1)
5-6: String comparison onVisualStudioVersionmay mis-detect versions
'$(VisualStudioVersion)' < '17.0'performs a lexical comparison, so"9.0">"17.0"(because'9'>'1').
If someone builds with an older VS (e.g. “9.0” or “10.0”) the condition could yield an unexpected result and select the wrong target framework.Safer alternatives:
<!-- Convert to integer --> <PropertyGroup> <_VSVersion>$([System.Int32]::Parse('$([System.String]::Copy($(VisualStudioVersion)).Split('.')[0])'))</_VSVersion> <TargetFrameworks Condition=" '$(TargetFrameworks)' == '' and $(_VSVersion) < 17 ">net472</TargetFrameworks> </PropertyGroup>or consider dropping the VS‐based branching and rely on an explicit
TargetFrameworksvalue.
d31bb98 to
be1c73a
Compare
be1c73a to
cd7e158
Compare
3cd7f02 to
4a831bb
Compare
4a831bb to
ea3bf73
Compare
|


Skip params
object[]-allocation when LogLevel is not enabled.First step for resolving #5537 (Still missing the logic to skip
object[]-allocation when message-template with properties like with Microsoft Extension Logging)Changed NETSTANDARD2.1 to use LangVersion=13 to support
params ReadOnlySpanandOverloadResolutionPriority:Maybe the
[OverloadResolutionPriority(-1)]can be a long-game approach for cleaning up the V1 Logger-API with less users affected by the breaking changes (Can cleanup when NET8 has reached end-of-support, some time in 2027)