Skip to content

Conversation

@snakefoot
Copy link
Contributor

@snakefoot snakefoot commented May 4, 2025

Followup to #5800 to resolve #5537

When NLog needs to render message template upfront, then it will now skip Parameters-array-allocation, and instead rely on Properties-dictionary (Now matches NLog.Extensions.Logging)

@coderabbitai
Copy link

coderabbitai bot commented May 4, 2025

Warning

Rate limit exceeded

@snakefoot has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 2 minutes and 31 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 12f5623 and 5a720db.

📒 Files selected for processing (10)
  • src/NLog/ILoggerExtensions.cs (6 hunks)
  • src/NLog/Internal/LogMessageTemplateFormatter.cs (5 hunks)
  • src/NLog/LogEventInfo.cs (2 hunks)
  • src/NLog/LogFactory.cs (2 hunks)
  • src/NLog/Logger-V1Compat.cs (30 hunks)
  • src/NLog/Logger-generated.cs (48 hunks)
  • src/NLog/Logger-generated.tt (8 hunks)
  • src/NLog/Logger.cs (14 hunks)
  • src/NLog/MessageTemplates/LiteralHole.cs (1 hunks)
  • src/NLog/MessageTemplates/TemplateEnumerator.cs (1 hunks)

Walkthrough

This set of changes enhances the NLog logging framework to support logging methods that accept ReadOnlySpan<object> parameters, primarily targeting .NET Standard 2.1 and .NET 9.0 or greater. Internal logic in the Logger, Logger-generated.cs, and related files is refactored to use new span-based overloads, reducing unnecessary array allocations when logging is disabled. Conditional compilation ensures compatibility with older platforms. Additional improvements include more efficient message formatting, clearer method naming, and minor API surface adjustments such as new properties and constructor parameter updates.

Changes

Files/Groups Change Summary
src/NLog/ILoggerExtensions.cs Refactored ConditionalDebug and ConditionalTrace to use generic overloads for logging methods, replacing object array usage with direct generic calls.
src/NLog/Internal/LogMessageTemplateFormatter.cs Renamed internal fields/parameters for clarity, refactored message rendering logic, separated positional hole rendering, and added a new Render overload for ReadOnlySpan<object>.
src/NLog/LogEventInfo.cs Refactored NeedToPreformatMessage for clarity and added a new overload supporting ReadOnlySpan<object>.
src/NLog/LogFactory.cs Added internal field for caching LogMessageTemplateFormatter and updated logic in RefreshMessageFormatter to conditionally cache formatter instances.
src/NLog/Logger-V1Compat.cs
src/NLog/Logger-generated.cs
src/NLog/Logger-generated.tt
Added conditional compilation to use span-based logging methods on supported platforms, refactored method bodies to optimize argument handling, and unified logging control flow.
src/NLog/Logger.cs Added new public and private methods for span-based logging, refactored internal dispatching logic, and renamed internal methods for clarity.
src/NLog/MessageTemplates/LiteralHole.cs Updated LiteralHole constructor to take parameters by in reference for efficiency.
src/NLog/MessageTemplates/TemplateEnumerator.cs Added public Template property to expose the underlying template string.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Logger
    participant LogMessageTemplateFormatter
    participant LogFactory
    participant Targets

    Caller->>Logger: Log(LogLevel, string message, ReadOnlySpan<object> args)
    alt .NET Standard 2.1+ or .NET 9.0+
        Logger->>Logger: WriteToTargetsWithSpan(...)
        Logger->>LogFactory: Check LogMessageTemplateFormatter
        alt Preformatting needed
            Logger->>LogMessageTemplateFormatter: Render(..., ReadOnlySpan<object> args)
            LogMessageTemplateFormatter-->>Logger: Formatted message
            Logger->>Targets: Write formatted message
        else
            Logger->>Targets: Write with deferred formatting
        end
    else
        Logger->>Logger: WriteToTargets(..., object[] args)
        Logger->>Targets: Write message
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
Support params ReadOnlySpan with NetStandard2.1 and lang version 13 (#5537)
Avoid params-array-allocation upfront when LogLevel is not enabled (#5537)

Poem

In the warren where loggers leap and bound,
Spans now hop with barely a sound.
Arrays no longer gather in fright—
Only when needed, they join the night.
Templates are parsed with carrot-sharp flair,
As messages sprint through the springtime air!
🥕✨


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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🔭 Outside diff range comments (1)
src/NLog/Logger-generated.cs (1)

188-199: ⚠️ Potential issue

Compilation blocker replicated in generated code

All span-based overloads emitted by the T4 template keep the invalid signature:

public void Trace(string message, params ReadOnlySpan<object> args)

This prevents the entire assembly from compiling.
Fix in the template and regenerate rather than editing this file directly.

Additionally, make sure every internal call site is updated:

- WriteToTargetsWithSpan(..., message, args);
+ WriteToTargetsWithSpan(..., message, args);   // unchanged

(the call itself is fine once the signature is corrected).

Also applies to: 194-199

♻️ Duplicate comments (2)
src/NLog/Logger-V1Compat.cs (2)

127-133: Duplicate of the first comment – same pattern, same concern.


506-512: Duplicate of the first comment – same pattern, same concern.

🧹 Nitpick comments (2)
src/NLog/Logger-generated.cs (1)

274-279: Unnecessary stack allocation fallback still allocates on heap

In the #else branch you wrap a single generic argument into new object[] { argument }.
Because logging is disabled most of the time, consider replacing this with:

- WriteToTargets(..., new object[] { argument });
+ WriteToTargets(..., ArrayHelper.Wrap(argument)); // or pooled array / stackalloc

to avoid per-call heap allocation on old TFMs.

src/NLog/Internal/LogMessageTemplateFormatter.cs (1)

299-307: Minor: collapse duplicate overloads

RenderHolePositional and RenderHole share the same early-null path.
You can remove duplication by letting one call the other after the null-check.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 36943d3 and 34af77b.

📒 Files selected for processing (10)
  • src/NLog/ILoggerExtensions.cs (6 hunks)
  • src/NLog/Internal/LogMessageTemplateFormatter.cs (6 hunks)
  • src/NLog/LogEventInfo.cs (2 hunks)
  • src/NLog/LogFactory.cs (2 hunks)
  • src/NLog/Logger-V1Compat.cs (30 hunks)
  • src/NLog/Logger-generated.cs (48 hunks)
  • src/NLog/Logger-generated.tt (8 hunks)
  • src/NLog/Logger.cs (15 hunks)
  • src/NLog/MessageTemplates/LiteralHole.cs (1 hunks)
  • src/NLog/MessageTemplates/TemplateEnumerator.cs (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/NLog/MessageTemplates/LiteralHole.cs (2)
src/NLog/MessageTemplates/Literal.cs (1)
  • Literal (52-56)
src/NLog/MessageTemplates/Hole.cs (1)
  • Hole (48-55)
src/NLog/ILoggerExtensions.cs (1)
src/NLog/Logger-generated.cs (10)
  • Debug (403-409)
  • Debug (417-423)
  • Debug (429-436)
  • Debug (457-463)
  • Debug (515-521)
  • Trace (111-117)
  • Trace (125-131)
  • Trace (137-144)
  • Trace (165-171)
  • Trace (223-229)
src/NLog/Internal/LogMessageTemplateFormatter.cs (3)
src/NLog/MessageTemplates/TemplateEnumerator.cs (2)
  • MoveNext (105-136)
  • TemplateEnumerator (65-72)
src/NLog/MessageTemplates/Hole.cs (1)
  • Hole (48-55)
src/NLog/MessageTemplates/ValueFormatter.cs (1)
  • FormatToString (378-401)
🔇 Additional comments (18)
src/NLog/MessageTemplates/TemplateEnumerator.cs (1)

57-57: LGTM - Adding property accessor for template string.

The newly added Template property provides external access to the original template string being parsed, which can be useful for components that need to access the raw template string directly.

src/NLog/MessageTemplates/LiteralHole.cs (1)

59-59: Good optimization for struct parameter passing.

Using the in keyword for struct parameters is a performance improvement that avoids copying the struct values when passed to the constructor. Since Literal and Hole are structs, this reduces memory allocations and improves performance in code paths that create many LiteralHole instances.

src/NLog/ILoggerExtensions.cs (6)

255-255: LGTM - Using generic overload instead of object array.

This change improves performance by calling the generic Debug<object> method directly instead of packing arguments into a temporary object array. This reduces memory allocations, especially when logging is disabled.


274-274: LGTM - Using generic overload for multiple arguments.

Similar optimization for the two-argument case, calling Debug<object, object> instead of creating an object array.


295-295: LGTM - Using generic overload for three arguments.

Similar optimization for the three-argument case, using Debug<object, object, object> to avoid array allocations.


422-422: LGTM - Using generic Trace overload instead of object array.

The same optimizations applied to the Trace method, replacing object array creation with direct generic method calls.


441-441: LGTM - Using generic Trace overload for two arguments.

Similar optimization for the two-argument case for Trace calls.


462-462: LGTM - Using generic Trace overload for three arguments.

Similar optimization for the three-argument case for Trace calls.

src/NLog/LogFactory.cs (3)

72-72: LGTM - Adding field for caching LogMessageTemplateFormatter.

Adding an internal field to cache the LogMessageTemplateFormatter instance, which will support the span-based optimization improvements.


309-312: LGTM - Conditionally caching the message formatter.

This improvement caches the LogMessageTemplateFormatter only when message template parsing is disabled. When parsing is enabled, it sets the cache to null since each message might need different parsing logic. The SingleTargetMessageFormatter is always assigned regardless of the EnableMessageTemplateParser setting.


316-316: LGTM - Proper null handling in else branch.

Ensures the LogMessageTemplateFormatter field is explicitly set to null when the messageFormatter is not a LogMessageTemplateFormatter.

src/NLog/LogEventInfo.cs (2)

612-629: Approve the refactored method for enhanced readability.

The refactoring of NeedToPreformatMessage(object[] parameters) improves readability with early returns and clearer control flow while maintaining the same functionality.


631-648: Well-implemented span-based performance optimization.

This new overload leverages ReadOnlySpan<object> with the in modifier to avoid unnecessary array allocations, particularly beneficial when logging is disabled. The conditional compilation ensures compatibility with older frameworks while allowing modern .NET applications to benefit from this optimization.

src/NLog/Logger-V1Compat.cs (2)

722-724: Good use of the new span overload

Here the traditional IsTraceEnabled guard is retained, so no unnecessary work is done when the level is disabled. 👍


98-104:

Details

✅ Verification successful

Consider restoring the IsEnabled(level) guard (affects every new WriteToTargetsWithSpan call)

For the span-based branch we now gate on

var targetsForLevel = GetTargetsForLevelSafe(level);
if (targetsForLevel != null)
{ ... }

where the legacy code used a cheap IsEnabled(level) check.

If GetTargetsForLevelSafe internally performs the same check this is fine, but if it merely returns an empty array the extra lookup/allocation happens for every disabled log-call, partly defeating the allocation-saving goal of this PR.

+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
+            if (IsEnabled(level))   // keeps the fast short-circuit
+            {
+                var targetsForLevel = GetTargetsForLevelSafe(level);
+                if (targetsForLevel != null)
+                {
+                    WriteToTargetsWithSpan(targetsForLevel, level, null,
+                        Factory.DefaultCultureInfo, message, arg1, arg2);
+                }
+            }
+#else
             if (IsEnabled(level))
             {
                 WriteToTargets(level, message, new[] { arg1, arg2 });
             }
-#endif
+#endif

Please verify the behaviour of GetTargetsForLevelSafe (and whether it allocates) before applying – the same pattern recurs in many overloads.


🏁 Script executed:

#!/bin/bash
set -e
# Locate and display the implementation of GetTargetsForLevelSafe in Logger-V1Compat.cs
rg -n "GetTargetsForLevelSafe" -A15 -B5 src/NLog/Logger-V1Compat.cs

Length of output: 3758


🏁 Script executed:

#!/bin/bash
set -e
# Search for the definition of GetTargetsForLevelSafe across the codebase
rg -n "GetTargetsForLevelSafe" -g "*.cs" -A50 -B5

Length of output: 44095


No changes needed for the span-based guard

Verified that IsEnabled(level) is implemented as

public bool IsEnabled(LogLevel level) => GetTargetsForLevelSafe(level) != null;

and GetTargetsForLevelSafe simply does an array lookup (no allocations). The new code already invokes GetTargetsForLevelSafe exactly once and short-circuits on null, matching the legacy behavior without extra overhead.

src/NLog/Logger.cs (3)

254-254: Good refactor – avoids one virtual-call hop

Replacing the direct WriteToTargets invocation with the slimmer WriteLogEventToTargets helper removes an extra condition/overload resolution step and keeps all target dispatch logic in one place. Nice micro-cleanup.


428-456: Potential infinite recursion / ambiguous overload after signature change

Both overloads of WriteToTargetsWithSpan call each other in certain
paths, e.g.:

WriteToTargetsWithSpan(level,)WriteToTargetsWithSpan(targetsForLevel,)

After the signature corrections, ensure
(1) the first overload is only a convenience wrapper that retrieves
targetsForLevel and does not re-enter the same overload chain, and
(2) there is no accidental overload hiding once params is removed.

A quick guard:

private void WriteToTargetsWithSpan(LogLevel level,)
{
    var targets = GetTargetsForLevel(level);
    if (targets is null) return;
    // NOTE: do not call itself again – call the <ITargetWithFilterChain,…> variant
    WriteToTargetsWithSpan(targets, level,);
}

Validate with unit tests or Roslyn analyzers to avoid stack-overflow.


805-806: Consistent use of the new helper—nice

Every internal WriteToTargets overload has been updated to delegate to
WriteLogEventToTargets, centralising common exception handling and
context property merging. This improves maintainability and reduces code
duplication.

Also applies to: 817-818, 827-828, 840-841, 850-851

Comment on lines 170 to 176
public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan<object> args)
{
var targetsForLevel = GetTargetsForLevel(LogLevel.<#=level#>);
if (targetsForLevel != null)
if (Is<#=level#>Enabled)
{
var logEvent = LogEventInfo.Create(LogLevel.<#=level#>, Name, Factory.DefaultCultureInfo, message, args.IsEmpty ? null : args.ToArray());
WriteToTargets(logEvent, targetsForLevel);
WriteToTargets(LogLevel.<#=level#>, null, Factory.DefaultCultureInfo, message, args);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

params ReadOnlySpan<object> is illegal C# syntax – will not compile

params can only be applied to array types (T[]).
Using it with ReadOnlySpan<T> causes a compiler error (CS0225).
This mistake is repeated for every span‐based overload generated by the template.

- public void <#=level#>(..., params ReadOnlySpan<object> args)
+ public void <#=level#>(..., ReadOnlySpan<object> args)

Please remove the params modifier (and regenerate the .cs file), or switch back to object[] if you need the params behaviour.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan<object> args)
{
var targetsForLevel = GetTargetsForLevel(LogLevel.<#=level#>);
if (targetsForLevel != null)
if (Is<#=level#>Enabled)
{
var logEvent = LogEventInfo.Create(LogLevel.<#=level#>, Name, Factory.DefaultCultureInfo, message, args.IsEmpty ? null : args.ToArray());
WriteToTargets(logEvent, targetsForLevel);
WriteToTargets(LogLevel.<#=level#>, null, Factory.DefaultCultureInfo, message, args);
}
}
public void <#=level#>([Localizable(false)][StructuredMessageTemplate] string message, ReadOnlySpan<object> args)
{
if (Is<#=level#>Enabled)
{
WriteToTargets(LogLevel.<#=level#>, null, Factory.DefaultCultureInfo, message, args);
}
}

Comment on lines 392 to 401
[MessageTemplateFormatMethod("message")]
public void Log(LogLevel level, [Localizable(false)][StructuredMessageTemplate] string message, params ReadOnlySpan<object> args)
{
var targetsForLevel = GetTargetsForLevel(level);
var targetsForLevel = GetTargetsForLevelSafe(level);
if (targetsForLevel != null)
{
var logEvent = LogEventInfo.Create(level, Name, Factory.DefaultCultureInfo, message, args.IsEmpty ? null : args.ToArray());
WriteToTargets(logEvent, targetsForLevel);
WriteToTargetsWithSpan(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, args);
}
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

params ReadOnlySpan<object> is illegal – will not compile

C# currently only permits params on single-dimension arrays.
Declaring or consuming params ReadOnlySpan<T> therefore yields CS0225 / CS1736.

All affected members:

  • Log(LogLevel, string, params ReadOnlySpan<object> args)
  • Log(LogLevel, Exception, string, params ReadOnlySpan<object> args)
  • WriteToTargetsWithSpan(…) (both overloads)

will break the build on every target framework.

Suggested fix (example for the first overload – apply similarly to the rest):

- public void Log(LogLevel level, string message, params ReadOnlySpan<object> args)
+ public void Log(LogLevel level, string message, ReadOnlySpan<object> args)

and drop the params modifier from every call-site & method definition that uses a ReadOnlySpan<T>.

If a call-site must forward n standalone arguments, compose the span explicitly:

var tmp = new object[] { arg1, arg2 };          // unavoidable allocation
Log(level, message, tmp);                       // implicit array->span

or stay on the existing object[] overloads.
Failing to address this will make the library uncompilable.

Also applies to: 410-417, 419-456

@snakefoot snakefoot force-pushed the skip_params_array branch 2 times, most recently from 8d91243 to 59bdd55 Compare May 4, 2025 14:54
Copy link

@coderabbitai coderabbitai bot left a 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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8d91243 and 59bdd55.

📒 Files selected for processing (10)
  • src/NLog/ILoggerExtensions.cs (6 hunks)
  • src/NLog/Internal/LogMessageTemplateFormatter.cs (5 hunks)
  • src/NLog/LogEventInfo.cs (2 hunks)
  • src/NLog/LogFactory.cs (2 hunks)
  • src/NLog/Logger-V1Compat.cs (30 hunks)
  • src/NLog/Logger-generated.cs (48 hunks)
  • src/NLog/Logger-generated.tt (8 hunks)
  • src/NLog/Logger.cs (14 hunks)
  • src/NLog/MessageTemplates/LiteralHole.cs (1 hunks)
  • src/NLog/MessageTemplates/TemplateEnumerator.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/NLog/MessageTemplates/TemplateEnumerator.cs
  • src/NLog/MessageTemplates/LiteralHole.cs
  • src/NLog/ILoggerExtensions.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/LogEventInfo.cs
  • src/NLog/LogFactory.cs
  • src/NLog/Logger-V1Compat.cs
  • src/NLog/Logger-generated.cs
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/NLog/Logger.cs (3)
src/NLog/Logger-generated.cs (16)
  • MessageTemplateFormatMethod (152-159)
  • MessageTemplateFormatMethod (178-185)
  • MessageTemplateFormatMethod (193-200)
  • MessageTemplateFormatMethod (208-215)
  • MessageTemplateFormatMethod (237-244)
  • MessageTemplateFormatMethod (253-260)
  • MessageTemplateFormatMethod (269-280)
  • MessageTemplateFormatMethod (288-299)
  • MessageTemplateFormatMethod (310-321)
  • MessageTemplateFormatMethod (331-342)
  • MessageTemplateFormatMethod (355-366)
  • MessageTemplateFormatMethod (378-389)
  • MessageTemplateFormatMethod (444-451)
  • MessageTemplateFormatMethod (470-477)
  • MessageTemplateFormatMethod (485-492)
  • MessageTemplateFormatMethod (500-507)
src/NLog/Internal/LogMessageTemplateFormatter.cs (4)
  • LogMessageTemplateFormatter (44-333)
  • LogMessageTemplateFormatter (65-71)
  • Render (139-207)
  • Render (210-278)
src/NLog/LogEventInfo.cs (12)
  • LogEventInfo (51-795)
  • LogEventInfo (79-82)
  • LogEventInfo (90-93)
  • LogEventInfo (102-116)
  • LogEventInfo (126-131)
  • LogEventInfo (141-148)
  • LogEventInfo (159-162)
  • LogEventInfo (173-182)
  • LogEventInfo (444-447)
  • LogEventInfo (456-459)
  • LogEventInfo (484-496)
  • LogEventInfo (507-510)
🔇 Additional comments (8)
src/NLog/Internal/LogMessageTemplateFormatter.cs (5)

55-55: LGTM: Improved naming consistency with _forceMessageTemplateRenderer

The rename from _forceTemplateRenderer to _forceMessageTemplateRenderer improves naming consistency and better reflects its purpose in relation to message templates.

Also applies to: 62-69, 78-78


141-207: Refactored Render method for better separation of positional and non-positional holes

The refactoring creates a cleaner structure with better logical separation between positional and named template holes. The new flow correctly determines the message format type and handles template parameters accordingly.

Some key improvements:

  • Initializing messageTemplateParameters to null upfront
  • Clearly separating the positional template path from the named template path
  • Refactoring the shared hole rendering logic to the new RenderHolePositional method
  • Adding proper handling for rewind scenarios
  • Improved null checks when truncating parameters

209-279: LGTM: New span-based Render overload for modern .NET versions

The new span-based rendering method is a good optimization that leverages ReadOnlySpan<object> to reduce allocations. It maintains the same logical structure as the array-based version while properly utilizing spans.


281-295: Good extraction of positional hole rendering logic

Extracting the positional hole rendering logic into a separate method is a good refactoring that improves code maintainability and readability.


297-312: LGTM: Simplified RenderHole methods

The refactored RenderHole methods provide a cleaner separation of concerns, with the primary method delegating to a more specialized implementation.

src/NLog/Logger.cs (3)

254-254: LGTM: Improved naming clarity with WriteLogEventToTargets

The rename from WriteToTargets to WriteLogEventToTargets provides better clarity about the method's purpose - specifically writing a LogEventInfo object to targets.

Also applies to: 272-272, 803-803, 815-815, 825-825, 838-838, 848-848, 852-890


413-425: LGTM: Conditional span-based logging for generic arguments

The conditional compilation approach allows efficient span-based logging for modern .NET frameworks while maintaining backward compatibility with older frameworks. The direct use of WriteToTargetsWithSpan for generic arguments avoids unnecessary array allocations.

Also applies to: 437-449, 464-476, 490-502, 519-531, 547-559


595-602: LGTM: Smart message formatting optimization

The WriteToTargetsWithSpan method intelligently determines whether to:

  1. Defer message formatting completely
  2. Convert parameters to properties for non-positional templates (skipping array allocation)
  3. Fall back to traditional string.Format with array allocation for positional templates

This optimization reduces unnecessary allocations in high-throughput logging scenarios.

However, similar to the public methods, remove the params modifier:

- private void WriteToTargetsWithSpan(LogLevel level, Exception exception, IFormatProvider formatProvider, string message, params ReadOnlySpan<object> args)
+ private void WriteToTargetsWithSpan(LogLevel level, Exception exception, IFormatProvider formatProvider, string message, ReadOnlySpan<object> args)
- private void WriteToTargetsWithSpan(ITargetWithFilterChain targetsForLevel, LogLevel level, Exception exception, IFormatProvider formatProvider, string message, params ReadOnlySpan<object> args)
+ private void WriteToTargetsWithSpan(ITargetWithFilterChain targetsForLevel, LogLevel level, Exception exception, IFormatProvider formatProvider, string message, ReadOnlySpan<object> args)

Also applies to: 604-632

@snakefoot snakefoot force-pushed the skip_params_array branch 2 times, most recently from ce99697 to 12f5623 Compare May 4, 2025 15:07
Copy link

@coderabbitai coderabbitai bot left a 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 (1)
src/NLog/Internal/LogMessageTemplateFormatter.cs (1)

141-279: Consider extracting common rendering logic

There's significant duplication between the array-based and span-based Render methods. Consider extracting the common template processing logic into a shared private method to improve maintainability.

You could create a generic helper method that accepts the template enumerator, format provider, and a callback for parameter handling:

private void RenderTemplate<T>(ref TemplateEnumerator enumerator, IFormatProvider formatProvider, 
    T parameters, StringBuilder sb, ref int holeIndex, ref IList<MessageTemplateParameter> messageTemplateParameters,
    Func<int, T, object> getParameter, Action resetAction)
{
    string template = enumerator.Template;
    int pos = 0;
    int holeStartPosition = 0;

    do
    {
        var literal = enumerator.Current.Literal;
        sb.Append(template, pos, literal.Print);
        pos += literal.Print;
        if (literal.Skip == 0)
        {
            pos++;
            continue;
        }

        pos += literal.Skip;
        var hole = enumerator.Current.Hole;
        
        // Rest of the common logic...
    } while (enumerator.MoveNext());
}

Then both Render methods could call this helper with appropriate parameters and callbacks.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce99697 and 12f5623.

📒 Files selected for processing (10)
  • src/NLog/ILoggerExtensions.cs (6 hunks)
  • src/NLog/Internal/LogMessageTemplateFormatter.cs (5 hunks)
  • src/NLog/LogEventInfo.cs (2 hunks)
  • src/NLog/LogFactory.cs (2 hunks)
  • src/NLog/Logger-V1Compat.cs (30 hunks)
  • src/NLog/Logger-generated.cs (48 hunks)
  • src/NLog/Logger-generated.tt (8 hunks)
  • src/NLog/Logger.cs (14 hunks)
  • src/NLog/MessageTemplates/LiteralHole.cs (1 hunks)
  • src/NLog/MessageTemplates/TemplateEnumerator.cs (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/NLog/MessageTemplates/TemplateEnumerator.cs
  • src/NLog/MessageTemplates/LiteralHole.cs
  • src/NLog/ILoggerExtensions.cs
  • src/NLog/LogFactory.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Logger-generated.cs
  • src/NLog/Logger-V1Compat.cs
  • src/NLog/Logger.cs
🔇 Additional comments (4)
src/NLog/Internal/LogMessageTemplateFormatter.cs (4)

55-56: Good name improvement for clarity

The renaming of the field and parameter from _forceTemplateRenderer to _forceMessageTemplateRenderer improves clarity by aligning with MessageTemplate terminology used throughout the codebase. The EnableMessageTemplateParser property update is consistent with this change.

Also applies to: 62-63, 68-69, 78-78


141-207: Improved message template rendering logic

The refactored Render method provides several improvements:

  1. Better structure with early exit for non-message-template formats
  2. Improved handling of mixed format templates with rewind capability
  3. Proper null initialization and cleanup for parameter arrays
  4. Do-while loop provides clearer control flow

The implementation now handles both positional and named parameters more efficiently, with proper rewinding when mixed templates are detected.


209-279: Good optimization for modern .NET platforms

The span-based overload for .NET Standard 2.1+ and .NET 9.0+ provides performance benefits by:

  1. Accepting ReadOnlySpan<object> to avoid array allocations
  2. Using the pooled StringBuilder pattern efficiently
  3. Providing equivalent functionality to the array-based version

The null-check on line 268 properly prevents the potential NullReferenceException identified in a previous review.


281-295: Well-structured method breakdown for hole rendering

The separation of rendering logic into distinct methods improves code organization:

  1. New RenderHolePositional method handles the specific case of positional holes
  2. Refactored RenderHole methods provide clear overloads for different parameter types
  3. Common rendering logic is properly shared

This structure supports both the original array-based and new span-based implementations efficiently.

Also applies to: 297-300, 302-312

@snakefoot snakefoot force-pushed the skip_params_array branch from 12f5623 to 5a720db Compare May 4, 2025 15:22
@sonarqubecloud
Copy link

sonarqubecloud bot commented May 4, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
20.2% Coverage on New Code (required ≥ 80%)
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@snakefoot snakefoot merged commit 336e584 into NLog:dev May 4, 2025
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support params ReadOnlySpan with NetStandard2.1 and lang version 13

1 participant