Skip to content

Fix nullable warnings from NET10 annotations#6067

Merged
snakefoot merged 1 commit intoNLog:devfrom
snakefoot:SpanNoParams
Dec 29, 2025
Merged

Fix nullable warnings from NET10 annotations#6067
snakefoot merged 1 commit intoNLog:devfrom
snakefoot:SpanNoParams

Conversation

@snakefoot
Copy link
Contributor

@snakefoot snakefoot commented Dec 28, 2025

NET10 have added more nullable annotations for the .NET API. Trying to fix most warnings, but not all. Since being multi-target complicates things.

@coderabbitai
Copy link

coderabbitai bot commented Dec 28, 2025

Walkthrough

Broad nullable-reference annotation updates, added null-safety guards for string/path conversions, widened NET conditional-compilation for span-based paths, Equals/ToString nullability/return adjustments, and packaging arguments into span-array forms for span-based logging calls.

Changes

Cohort / File(s) Summary
Nullable signatures & Equals updates
src/NLog/Common/AsyncLogEventInfo.cs, src/NLog/Internal/PropertiesDictionary.cs, src/NLog/Internal/SingleItemOptimizedHashSet.cs, src/NLog/Internal/TargetWithFilterChain.cs, src/NLog/Internal/TimeoutContinuation.cs, src/NLog/Filters/WhenRepeatedFilter.cs, src/NLog/Layouts/Typed/Layout.cs, src/NLog/Targets/LineEndingMode.cs, src/NLog/Internal/DictionaryEntryEnumerable.cs, src/NLog/Internal/ExceptionMessageFormatProvider.cs, src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs, src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs, src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
Many overrides/implementations made nullable-aware (e.g., Equals(object?), object?, Type?, Assembly?, string?); some private handlers accept object?. EmptyDictionaryEnumerator.Key changed to non-null object (empty string).
IAsyncDisposable / LogFactory changes
src/NLog/LogFactory.cs
Conditionally implements IAsyncDisposable and adds DisposeAsync() under updated NET guard; several internal Equals and event handler sender parameters made nullable-aware.
Path and string null-coalescing guards
src/NLog/Config/AssemblyExtensionLoader.cs, src/NLog/Internal/AppEnvironmentWrapper.cs, src/NLog/Internal/MultiFileWatcher.cs, src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs, src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs, src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs, src/NLog/Targets/FileArchiveHandlers/..., src/NLog/Targets/FileTarget.cs, src/NLog/Internal/XmlHelper.cs, src/NLog/Targets/DefaultJsonSerializer.cs, src/NLog/Internal/FormatHelper.cs
Added ?? string.Empty and null-coalescing guards around Path.GetDirectoryName(...), Assembly.FullName, and Convert.ToString results to ensure non-null strings for paths, assembly names, XML/JSON conversions.
Widened conditional compilation to NET
src/NLog/Internal/LogMessageTemplateFormatter.cs, src/NLog/Internal/StringBuilderExt.cs, src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs, src/NLog/Layouts/SimpleLayout.cs, src/NLog/LogEventBuilder.cs, src/NLog/LogEventInfo.cs, src/NLog/Logger*.cs, src/NLog/Targets/ColoredConsole*.cs, tests/**
Replaced framework-specific preprocessor symbols with NET (or `NETSTANDARD2_1_OR_GREATER
Span-based logging argument packaging
src/NLog/Logger-V1Compat.cs, src/NLog/Logger-generated.cs, src/NLog/Logger-generated.tt, src/NLog/Logger.cs
Converted span-path call sites to package arguments as array-literal spans (e.g., [arg1, arg2]) for span-based WriteToTargetsWithSpan under NET guard; adjusted private span parameter signatures to in ReadOnlySpan<object?>.
ToString and formatting fallbacks
src/NLog/Internal/ScopeContextAsyncState.cs, src/NLog/ScopeContext.cs, src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs, src/NLog/Targets/TargetWithContext.cs, src/NLog/Internal/FormatHelper.cs, src/NLog/Layouts/Typed/Layout.cs
Added null-coalescing fallbacks in ToString and Convert.ToString flows to guarantee non-null string outputs; some ToString() now fallback to type name.
Conditions, comparisons and name matching null handling
src/NLog/Conditions/ConditionMethods.cs, src/NLog/Conditions/ConditionLiteralExpression.cs, src/NLog/Conditions/ConditionRelationalExpression.cs, src/NLog/Config/LoggerNameMatcher.cs
Added explicit null checks/early returns and null-coalescing fallbacks for Contains/StartsWith/EndsWith, literal-to-string conversion, type promotion to string/LogLevel, and logger-name matching.
Reflection / property / assembly null-safety
src/NLog/Internal/ObjectReflectionCache.cs, src/NLog/Internal/PropertyHelper.cs, src/NLog/Internal/AssemblyHelpers.cs, src/NLog/Internal/CallSiteInformation.cs, src/NLog/Internal/StackTraceUsageUtils.cs, src/NLog/Internal/ObjectGraphScanner.cs, src/NLog/Internal/ReflectionHelpers.cs
Tightened null checks when inspecting PropertyInfo, MethodBase, Assembly, or enumerables; skip/early-return when getters/return types are missing; several method signatures updated for nullable inputs/returns.
MethodFactory and nullable propagation
src/NLog/Config/MethodFactory.cs
Propagated nullability: ResolveDefaultMethodParameters now returns object?[], InvokeMethodInfo returns object?, and related locals updated to propagate nullable values.
Serialization & value formatting
src/NLog/Targets/DefaultJsonSerializer.cs, src/NLog/MessageTemplates/ValueFormatter.cs
Guarded string conversions with ?? string.Empty; allowed nullable parameters for collection/item serialization and string formatting methods.
Misc small refactors & behavior tweaks
src/NLog/Common/InternalLogger.cs, src/NLog/Config/LoggingConfiguration.cs, src/NLog/Internal/PropertyHelper.cs, src/NLog/Filters/WhenRepeatedFilter.cs, src/NLog/Internal/ScopeContextAsyncState.cs, src/NLog/Internal/TimeoutContinuation.cs, src/NLog/Targets/AsyncTaskTarget.cs, src/NLog/Targets/MethodCallTarget.cs, src/NLog/Targets/Wrappers/*, tests/**
Use of var, inline out variables, private parameter nullability set to object?, safer exception rethrow when InnerException may be null, skip null elements when copying enumerables, and other small reorganizations to avoid null dereferences.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application
    participant Logger as Logger
    participant Factory as LogFactory
    participant Writer as WriteToTargetsWithSpan
    participant Target as Target

    Note over App,Logger: Call-site packages arguments and invokes Logger
    App->>Logger: Log(level, message, args...)
    alt NET (span path)
        Logger->>Factory: Prepare span-packed args as [arg1, arg2, ...]
        Factory->>Writer: WriteToTargetsWithSpan(in ReadOnlySpan<object?>)
        Writer->>Target: Deliver formatted message
    else Legacy (array path)
        Logger->>Factory: Prepare object[] args
        Factory->>Writer: WriteToTargets(object[] args)
        Writer->>Target: Deliver formatted message
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐇
I hopped through nulls to stitch each seam,
Coalesced the strings and smoothed the stream.
Equals grew kinder, spans stacked tight,
Async disposal tucked in at night.
Carrots for tests — a cozy code dream.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.19% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix nullable warnings from NET10 annotations' directly summarizes the main change - addressing nullable reference type warnings introduced by NET10's new annotations.
Description check ✅ Passed The description explains that NET10 added nullable annotations and this PR attempts to fix most resulting warnings, acknowledging multi-targeting complications. It relates directly to the changeset.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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

🧹 Nitpick comments (5)
src/NLog/Targets/MethodCallTarget.cs (1)

270-272: Good null-safety improvement!

The null check before rethrowing the inner exception correctly prevents potential issues when InnerException is null, aligning well with the PR's nullable annotation objectives.

Consider using ExceptionDispatchInfo to preserve the full stack trace when rethrowing the inner exception. The current approach loses some context about where the TargetInvocationException was originally caught.

🔎 Optional refactor to preserve stack traces
+using System.Runtime.ExceptionServices;

Then in the catch block:

 catch (TargetInvocationException ex)
 {
     InternalLogger.Warn("{0}: Failed to invoke method - {1}", this, ex.Message);
     if (ex.InnerException != null)
-        throw ex.InnerException;
+        ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
     throw;
 }
src/NLog/Logger-generated.tt (1)

163-163: Minor: Consider documenting the NET symbol scope.

The conditional compilation now uses the broader NET symbol instead of NET9_0_OR_GREATER. While this correctly enables span-based overloads for more target frameworks, consider adding a comment explaining which .NET versions this encompasses (NET5.0+) to help future maintainers understand the intent.

Optional: Add clarifying comment
-#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
+#if NETSTANDARD2_1_OR_GREATER || NET  // NET = NET5.0+ span-based overloads
         /// <summary>
src/NLog/Internal/ReflectionHelpers.cs (1)

183-195: Nullable adjustments for PropertyInfo helpers look correct

Allowing IsValidPublicProperty(this PropertyInfo? p) and returning object? from GetPropertyValue aligns with reflection semantics and avoids nullability warnings; behavior is unchanged for non-null PropertyInfo values.

If you want to be fully null-usage-consistent, consider GetPropertyValue(this PropertyInfo p, object? instance) so static properties can be invoked without nullable warnings.

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

96-4188: Span-based V1 compat overloads are wired correctly

The updated V1-compat Log/Trace/Debug/Info/Warn/Error/Fatal overloads that now delegate to WriteToTargetsWithSpan(..., [arg1, arg2, arg3]) under #if NETSTANDARD2_1_OR_GREATER || NET preserve the previous culture/exception behavior while enabling the new span-based pipeline when available, with clean fallbacks to the array-based path otherwise.

Given how repetitive these patterns are, consider centralizing the span-vs-array choice in a small helper to keep future changes to the logging pipeline localized.

src/NLog/Logger.cs (1)

606-668: WriteToTargetsWithSpan and RenderPreformattedLogEvent mirror array-based behavior

The span-based pipeline:

  • Uses LogEventInfo.NeedToPreformatMessage(in ReadOnlySpan<object?>) to decide between deferred vs upfront formatting.
  • Falls back to LogEventInfo.Create(..., args.ToArray()) and LogMessageStringFormatter.Default.MessageFormatter in the same cases as the array path.
  • Converts template parameters into properties when using AutoMessageTemplateFormatter, matching the existing message-template behavior.

Overall the logic faithfully mirrors the pre-existing non-span implementation while avoiding unnecessary array allocations where possible.

You could pass messageTemplateParameters into the LogEventInfo ctor directly and skip the subsequent CreatePropertiesInternal call, but that’s a minor micro-optimization and not required.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f06b012 and 6cc0dc3.

📒 Files selected for processing (68)
  • src/NLog/Common/AsyncLogEventInfo.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Conditions/ConditionExpression.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/Config/MethodFactory.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Internal/DictionaryEntryEnumerable.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Internal/ScopeContextAsyncState.cs
  • src/NLog/Internal/SingleItemOptimizedHashSet.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/LogFactory.cs
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Logger-V1Compat.cs
  • src/NLog/Logger-generated.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Logger.cs
  • src/NLog/MessageTemplates/ValueFormatter.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/Targets/MethodCallTarget.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-12T01:22:52.437Z
Learnt from: thatrajeevkr
Repo: NLog/NLog PR: 5999
File: src/NLog/Layouts/JSON/JsonLayout.cs:162-166
Timestamp: 2025-09-12T01:22:52.437Z
Learning: When suggesting StringBuilder optimizations for NLog layouts marked as [ThreadAgnostic], avoid shared StringBuilder fields as they create thread safety issues. Prefer simple string operations or ThreadLocal<StringBuilder> if allocation profiling shows a real performance impact.

Applied to files:

  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Layouts/SimpleLayout.cs
🧬 Code graph analysis (22)
src/NLog/Internal/TargetWithFilterChain.cs (7)
src/NLog/Common/AsyncLogEventInfo.cs (2)
  • Equals (89-89)
  • Equals (92-92)
src/NLog/Filters/WhenRepeatedFilter.cs (2)
  • Equals (390-406)
  • Equals (408-411)
src/NLog/Internal/PropertiesDictionary.cs (3)
  • Equals (748-748)
  • Equals (750-758)
  • Equals (772-775)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/LogFactory.cs (2)
  • Equals (1113-1116)
  • Equals (1118-1121)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/LogLevel.cs (2)
  • Equals (363-366)
  • Equals (374-377)
src/NLog/Internal/AssemblyHelpers.cs (2)
src/NLog/Config/AssemblyExtensionLoader.cs (1)
  • Assembly (482-503)
src/NLog/Internal/StackTraceUsageUtils.cs (1)
  • Assembly (219-240)
src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs (8)
src/NLog/Layouts/SimpleLayout.cs (1)
  • IStringValueRenderer (483-486)
src/NLog/LayoutRenderers/Wrappers/WhenEmptyLayoutRendererWrapper.cs (1)
  • IStringValueRenderer (101-104)
src/NLog/LayoutRenderers/EventPropertiesLayoutRenderer.cs (1)
  • IStringValueRenderer (136-148)
src/NLog/LayoutRenderers/EnvironmentLayoutRenderer.cs (1)
  • IStringValueRenderer (84-92)
src/NLog/LayoutRenderers/LevelLayoutRenderer.cs (1)
  • IStringValueRenderer (142-150)
src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs (1)
  • IStringValueRenderer (178-178)
src/NLog/LayoutRenderers/ThreadIdLayoutRenderer.cs (1)
  • IStringValueRenderer (62-65)
src/NLog/Internal/IStringValueRenderer.cs (1)
  • GetFormattedString (49-49)
src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs (1)
src/NLog/Internal/StackTraceUsageUtils.cs (2)
  • System (174-178)
  • Assembly (219-240)
src/NLog/Conditions/ConditionLiteralExpression.cs (8)
src/NLog/Conditions/ConditionExpression.cs (1)
  • ToString (92-92)
src/NLog/Conditions/ConditionRelationalExpression.cs (1)
  • ToString (79-82)
src/NLog/Layouts/SimpleLayout.cs (1)
  • ToString (274-282)
src/NLog/Conditions/ConditionAndExpression.cs (1)
  • ToString (66-69)
src/NLog/Conditions/ConditionLayoutExpression.cs (1)
  • ToString (65-68)
src/NLog/Conditions/ConditionNotExpression.cs (1)
  • ToString (57-60)
src/NLog/Conditions/ConditionOrExpression.cs (1)
  • ToString (65-68)
src/NLog/LayoutRenderers/LayoutRenderer.cs (1)
  • CultureInfo (232-235)
src/NLog/Config/PropertyTypeConverter.cs (1)
src/NLog/Internal/StringHelpers.cs (1)
  • StringHelpers (45-170)
src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (5)
src/NLog/Layouts/Typed/Layout.cs (3)
  • ToString (362-365)
  • ToString (374-377)
  • ToString (624-627)
src/NLog/Targets/Target.cs (1)
  • ToString (283-286)
src/NLog/Layouts/JSON/JsonLayout.cs (1)
  • ToString (475-483)
src/NLog/Layouts/JSON/JsonArrayLayout.cs (1)
  • ToString (169-172)
src/NLog/Layouts/CSV/CsvLayout.cs (1)
  • ToString (331-334)
src/NLog/Targets/TargetWithContext.cs (6)
src/NLog/Layouts/SimpleLayout.cs (1)
  • ToString (274-282)
src/NLog/Layouts/Typed/Layout.cs (3)
  • ToString (362-365)
  • ToString (374-377)
  • ToString (624-627)
src/NLog/LogEventInfo.cs (1)
  • ToString (585-588)
src/NLog/Targets/Target.cs (1)
  • ToString (283-286)
src/NLog/Layouts/JSON/JsonLayout.cs (1)
  • ToString (475-483)
src/NLog/Layouts/XML/XmlElementBase.cs (1)
  • ToString (800-812)
src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs (1)
src/NLog/Internal/ReflectionHelpers.cs (1)
  • ReflectionHelpers (45-196)
src/NLog/Internal/PropertiesDictionary.cs (6)
src/NLog/Common/AsyncLogEventInfo.cs (2)
  • Equals (89-89)
  • Equals (92-92)
src/NLog/Filters/WhenRepeatedFilter.cs (2)
  • Equals (390-406)
  • Equals (408-411)
src/NLog/Internal/TargetWithFilterChain.cs (2)
  • Equals (376-379)
  • Equals (381-386)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/LogFactory.cs (2)
  • Equals (1113-1116)
  • Equals (1118-1121)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/Logger-V1Compat.cs (2)
src/NLog/Logger.cs (7)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
  • WriteToTargets (827-831)
  • WriteToTargets (833-841)
  • WriteToTargets (843-853)
  • WriteToTargets (855-863)
  • WriteToTargets (866-874)
src/NLog/Logger-generated.cs (10)
  • Trace (111-117)
  • Trace (125-131)
  • Trace (137-144)
  • Trace (165-171)
  • Trace (223-229)
  • Info (695-701)
  • Info (709-715)
  • Info (721-728)
  • Info (749-755)
  • Info (807-813)
src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (2)
src/NLog/Targets/Wrappers/LogEventDroppedEventArgs.cs (2)
  • LogEventDroppedEventArgs (41-53)
  • LogEventDroppedEventArgs (47-47)
src/NLog/Targets/Wrappers/LogEventQueueGrowEventArgs.cs (2)
  • LogEventQueueGrowEventArgs (44-66)
  • LogEventQueueGrowEventArgs (51-55)
src/NLog/Logger-generated.cs (1)
src/NLog/Logger.cs (2)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
src/NLog/Config/LoggingConfiguration.cs (1)
src/NLog/Internal/ThreadSafeDictionary.cs (1)
  • TryGetValue (156-159)
src/NLog/LogLevelTypeConverter.cs (2)
src/NLog/Targets/LineEndingMode.cs (2)
  • CanConvertFrom (207-210)
  • ConvertFrom (213-217)
src/NLog/Config/PropertyTypeConverter.cs (1)
  • CultureInfo (199-208)
src/NLog/Targets/LineEndingMode.cs (4)
src/NLog/Common/AsyncLogEventInfo.cs (2)
  • Equals (89-89)
  • Equals (92-92)
src/NLog/Internal/TargetWithFilterChain.cs (2)
  • Equals (376-379)
  • Equals (381-386)
src/NLog/LogFactory.cs (2)
  • Equals (1113-1116)
  • Equals (1118-1121)
src/NLog/LogLevel.cs (2)
  • Equals (363-366)
  • Equals (374-377)
src/NLog/Internal/CallSiteInformation.cs (1)
src/NLog/Internal/StackTraceUsageUtils.cs (1)
  • StackTraceUsageUtils (44-268)
src/NLog/Internal/PropertyHelper.cs (1)
src/NLog/Internal/ReflectionHelpers.cs (1)
  • IsValidPublicProperty (183-186)
src/NLog/Layouts/Typed/Layout.cs (6)
src/NLog/Layouts/SimpleLayout.cs (1)
  • ToString (274-282)
src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (2)
  • ToString (250-253)
  • ToString (272-275)
src/NLog/Targets/TargetWithContext.cs (1)
  • ToString (727-730)
src/NLog/Layouts/JSON/JsonLayout.cs (1)
  • ToString (475-483)
src/NLog/Layouts/Layout.cs (1)
  • T (530-533)
src/NLog/Layouts/Typed/LayoutTypedExtensions.cs (1)
  • T (53-56)
src/NLog/Config/AssemblyExtensionLoader.cs (2)
src/NLog/Internal/PathHelpers.cs (2)
  • PathHelpers (38-98)
  • TrimDirectorySeparators (71-78)
src/NLog/Internal/AssemblyHelpers.cs (1)
  • System (45-113)
src/NLog/Internal/ReflectionHelpers.cs (1)
src/NLog/Internal/ObjectReflectionCache.cs (1)
  • PropertyInfo (222-249)
src/NLog/LogFactory.cs (6)
src/NLog/Config/ServiceRepositoryUpdateEventArgs.cs (2)
  • ServiceRepositoryUpdateEventArgs (42-57)
  • ServiceRepositoryUpdateEventArgs (48-51)
src/NLog/Common/AsyncHelpers.cs (1)
  • AsyncHelpers (46-382)
src/NLog/Internal/ObjectReflectionCache.cs (1)
  • Equals (553-556)
src/NLog/Internal/TargetWithFilterChain.cs (2)
  • Equals (376-379)
  • Equals (381-386)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/LogLevel.cs (2)
  • Equals (363-366)
  • Equals (374-377)
🔇 Additional comments (93)
src/NLog/Common/InternalLogger.cs (1)

425-429: Proper nullable type handling with guard clause.

The change from explicit string to inferred var correctly aligns the variable declaration with the actual return type of Path.GetDirectoryName(), which returns string?. The null/empty check guard on line 426 safely narrows the type before usage, eliminating nullable warnings while maintaining correctness.

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

96-96: LGTM! Nullable annotation aligns with Timer callback signature.

The nullable annotation correctly matches the TimerCallback delegate signature in modern .NET, which expects object? state. Since the parameter is unused in the method body, this change safely eliminates nullable warnings without any behavioral impact.

src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs (1)

274-274: LGTM! Correct nullable annotation.

The parameter type change from object to object? correctly reflects that the state parameter can be null. The existing null handling on line 280 with the null-coalescing operator (?? 0) already handles this case safely.

src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs (1)

89-95: LGTM! Excellent null-safety improvement.

The null check for getGetMethodInfo ensures that CreateLateBoundMethod receives a valid non-null MethodInfo, preventing potential null reference issues. The explicit exception with a descriptive message improves debuggability by catching configuration errors early during initialization rather than failing later at runtime.

src/NLog/Config/MethodFactory.cs (2)

173-173: LGTM!

Using var here correctly infers the object?[] type from the updated ResolveDefaultMethodParameters return type. The downstream usages in ResolveMethodParameters overloads are already compatible.


208-229: Nullable annotations are correctly applied.

The changes appropriately reflect nullable semantics:

  • InvokeMethodInfo returning object? aligns with MethodInfo.Invoke's nullable return type in .NET
  • ResolveDefaultMethodParameters returning object?[] correctly models that ParameterInfo.DefaultValue can be null for optional parameters
  • The local array on line 229 matches the return type

These align with the existing delegate signatures in MethodDetails which already use object? return types.

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

591-591: LGTM: Defensive null-safety for NET10 annotations.

The null-coalescing fallback to GetType().ToString() safely addresses nullable reference type warnings from NET10 BCL annotations. While base.ToString() should never return null in practice, this defensive guard ensures a non-null result and aligns with the PR's objective to fix nullable warnings.

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

360-362: LGTM! Proper null-safety guard added.

The null-check correctly handles cases where Convert.ToString returns null, which is possible according to .NET's nullable reference type annotations. Returning string.Empty is consistent with the exception handling behavior at line 367.

src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (1)

469-469: LGTM! Nullable annotations correctly applied.

The nullable parameter annotations on these private methods accurately reflect their actual usage patterns:

  • ProcessPendingEvents receives null from the Timer constructor and never uses the state parameter
  • FlushEventsInQueue safely handles nullable state with as cast and null-conditional operators
  • OnRequestQueueDropItem and OnRequestQueueGrow follow standard event handler patterns where sender can be nullable

These changes improve null-safety awareness without altering behavior.

Also applies to: 523-523, 617-617, 622-622

src/NLog/Targets/AsyncTaskTarget.cs (2)

83-83: LGTM! Nullable annotations correctly applied to delegate and method.

The delegate type and method signature changes correctly reflect that the continuation state parameter can be null. The method already handles null appropriately through the cast and null-check at lines 758-762.

Also applies to: 707-707


786-786: LGTM! Nullable annotation correctly applied.

The nullable annotation correctly reflects that null is passed as the state parameter when initializing the timer (line 174). The unused parameter is a standard pattern for timer callbacks.

src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs (2)

173-180: LGTM: Correct nullable annotation for StackFrame.

The nullable return type correctly reflects that StackTrace.GetFrame(int) can return null when the index is out of range. This internal change improves null-safety awareness without affecting the public API.


197-198: LGTM: Proper null-safe handling of nullable StackFrame.

The null-conditional operator correctly handles the nullable StackFrame returned by the indexer. When a frame is null, StringBuilder.Append(null) safely appends nothing, making the raw format more defensive. This behavior aligns with AppendFlat and AppendDetailedFlat, which already skip frames without valid methods.

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

113-113: LGTM! Null-safety improvement aligns with PR objectives.

The null-coalescing guard prevents potential null reference issues when Path.GetDirectoryName returns null (e.g., for root directories or filenames without directory components in .NET 5+). Falling back to string.Empty resolves to the current directory via Path.GetFullPath, which is reasonable default behavior. Edge cases are handled gracefully by the Directory.Exists check and logging on lines 116-119.

src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs (1)

80-80: LGTM! Conditional compilation symbol modernized.

The change from NETCOREAPP3_0_OR_GREATER to NET modernizes the conditional compilation to use the unified .NET platform symbol (5.0+). Since .NET Core 3.0+ is already covered by NETSTANDARD2_1_OR_GREATER, this change maintains equivalent behavior while aligning with modern .NET conventions.

src/NLog/Config/LoggerNameMatcher.cs (1)

187-192: Null guard enhances safety and satisfies nullable analyzer.

The added null check for both loggerName and _matchingArgument improves null safety. While _matchingArgument should never be null based on the factory method logic (Line 186 always produces a non-null substring), this defensive guard satisfies the nullable reference type analyzer and protects against potential future refactoring.

src/NLog/Config/PropertyTypeConverter.cs (1)

199-208: LGTM! Explicit null check improves nullable reference type compliance.

The explicit is null check ensures the compiler's nullable flow analysis correctly recognizes stringValue as non-null for the CultureInfo constructor at line 207. This change fixes nullable warnings without altering runtime behavior.

src/NLog/Config/LoggingConfiguration.cs (1)

118-118: LGTM! Correct nullable annotation for TryGetValue pattern.

The nullable annotation on the out Target? parameter correctly reflects that the parameter will be null when the method returns false. This aligns with standard .NET nullable reference type best practices for the TryGetValue pattern. The single call site at line 253 properly handles the nullable return value.

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

263-265: LGTM! Null-safety improvements align with PR objectives.

The null-conditional operator and null checks correctly handle properties without public getters. GetGetMethod() can return null for non-public or non-existent getters, and this change safely skips such properties in the fast lookup, preventing potential NullReferenceExceptions while satisfying nullable reference type analysis.

src/NLog/Layouts/SimpleLayout.cs (1)

191-191: The project targets net35, net46, netstandard2.0, and netstandard2.1 — not .NET Core 3.x. This change from NETCOREAPP3_0_OR_GREATER to NET has no practical impact since the project doesn't support those frameworks. The NotNullIfNotNull attribute remains applied for netstandard2.1 targets via the NETSTANDARD2_1_OR_GREATER condition, and the operator implementation correctly handles the nullable contract.

Likely an incorrect or invalid review comment.

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

109-109: Nullable warning fix is correct and appropriately aligned with the interface contract.

The change from object? to object and returning string.Empty correctly implements the IDictionaryEnumerator.Key interface signature, which defines Key as non-nullable. Returning string.Empty instead of null prevents nullable reference warnings while maintaining the contract.

Minor note: Entry.Key (from default(DictionaryEntry)) would be null, but the Key property now returns string.Empty. Since MoveNext() always returns false, this inconsistency is harmless in practice.

src/NLog/Targets/DefaultJsonSerializer.cs (4)

437-437: LGTM: Clearer null-safety check ordering.

Checking value is null before objTypeCode == TypeCode.Empty improves readability and aligns with null-safety best practices for nullable reference types.


444-444: LGTM: Defensive null-coalescing for ToString().

The null-coalescing guard addresses nullable reference type annotations on ToString(), even though the method should never return null for non-null String or Char values. This defensive approach ensures type safety.


511-519: LGTM: Null-coalescing guard for Convert.ToString().

The null-coalescing at line 515 addresses nullable annotations on Convert.ToString(), which should not return null for Enum values in practice. This ensures type safety for .NET 10 nullable reference types.


750-750: LGTM: Essential null-safety guard.

The null-coalescing guard is necessary here since Convert.ToString() can legitimately return null for certain object types. This prevents potential null reference issues when calling AppendStringEscape().

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

424-424: The NET conditional compilation symbol change is correct and intentional.

The symbol change from NETCOREAPP3_0_OR_GREATER to NETSTANDARD2_1_OR_GREATER || NET is verified. The NET symbol correctly represents .NET 5.0+ platforms as defined by Microsoft's compiler. This change is necessary because the guarded code uses collection expression syntax (builder.Append([...])), a C# 12 feature only available in .NET 5+. .NET Core 3.0/3.1, which reached end-of-support in 2019 and 2022 respectively, will appropriately use the fallback path with four separate Append calls. This pattern is consistent throughout the codebase (30+ occurrences) and aligns with Microsoft's deprecation of NETCOREAPP* symbols in favor of the unified NET symbol for .NET 5+.

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

750-758: LGTM! Nullable annotation aligns with .NET best practices.

The nullable parameter annotation on the Equals override correctly reflects that object.Equals can receive null. The existing type-checking logic already handles null safely by returning false.

src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1)

56-59: LGTM! Null-coalescing ensures non-null directory path.

The null-coalescing guard prevents potential null propagation from Path.GetDirectoryName. The downstream method DeleteOldFilesBeforeArchive already checks for empty strings at line 69, so this change preserves existing behavior while improving null-safety.

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

246-254: LGTM! Null-coalescing aligns with method contract.

The null-coalescing operator ensures the method always returns a non-null string, consistent with the fallback at line 253. This change properly handles cases where Path.GetDirectoryName returns null for root paths or edge cases.

src/NLog/Common/AsyncLogEventInfo.cs (1)

92-92: LGTM! Nullable annotation follows framework conventions.

The nullable parameter annotation correctly reflects that object.Equals accepts null inputs. The pattern matching expression (obj is AsyncLogEventInfo) safely handles null by returning false.

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

65-65: LGTM! Null-coalescing ensures non-null string result.

The guard prevents potential null returns from Convert.ToString, ensuring the method contract is honored. This aligns with other return paths in the method (lines 52, 54) that also return non-null strings.

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

242-245: LGTM! Interface implementation updated for nullable context.

The nullable parameter annotations align with the IEqualityComparer<T> interface requirements under nullable reference types. The implementation using ReferenceEquals correctly handles null inputs.

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

376-379: LGTM! Nullable annotation aligns with base class contract.

The nullable parameter annotation correctly reflects that object.Equals can receive null. The pattern matching (obj is CallSiteKey) handles null safely by returning false.

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

46-53: LGTM! Nullable parameter with consistent null handling.

The nullable Assembly? parameter combined with the null check ensures safe handling across both NETFRAMEWORK and non-NETFRAMEWORK paths. The non-NETFRAMEWORK path already used null-conditional operator (assembly?.Location), and the explicit check at lines 52-53 provides equivalent safety for the NETFRAMEWORK path.

src/NLog/Filters/WhenRepeatedFilter.cs (3)

151-151: LGTM!

Modern inline variable declaration pattern for TryGetValue. No behavioral change, just cleaner syntax.


381-381: LGTM!

Proper null-safety for hash code computation. When stringBuffer is null and stringValue is also null, returning 0 is the correct default hash code behavior.


408-411: LGTM!

Nullable annotation for Equals(object? obj) aligns with .NET nullable reference type patterns and is consistent with other Equals overrides in the codebase (e.g., LogLevel.cs, AsyncLogEventInfo.cs).

src/NLog/Config/AssemblyExtensionLoader.cs (4)

315-318: LGTM!

Defensive null-coalescing for Assembly.FullName which can be null for dynamic or in-memory assemblies. Prevents null values in the HashSet<string>.


328-328: LGTM!

Consistent null-coalescing for Assembly.FullName when adding to the already-registered set.


352-352: LGTM!

Same null-coalescing pattern applied consistently in RegisterAppDomainAssemblies.


398-398: LGTM!

Null-coalescing for Path.GetDirectoryName result aligns with the pattern used in PathHelpers.TrimDirectorySeparators which also handles null paths with ?? string.Empty.

src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs (1)

89-90: LGTM!

Null-coalescing for Path.GetDirectoryName ensures a non-null directory string is passed to DeleteOldFilesBeforeArchive. This is consistent with similar null-safety adjustments across the codebase.

src/NLog/Internal/ExceptionMessageFormatProvider.cs (2)

45-56: LGTM!

Nullable annotations for ICustomFormatter.Format parameters align with the interface contract in .NET. The implementation already handles null arg safely by formatting it as "{0}".


80-83: LGTM!

Nullable annotation for IFormatProvider.GetFormat parameter matches the .NET interface contract. The implementation returns null for unsupported format types, which is correct behavior.

src/NLog/MessageTemplates/ValueFormatter.cs (2)

266-272: LGTM!

Nullable annotation for stringValue parameter reflects that this method can receive null values. StringBuilder.Append(string?) handles null safely by appending nothing.


355-363: LGTM!

Nullable annotation for item parameter. The method logic handles null correctly - when item is null, it won't match IConvertible or IEnumerable, and SerializeSimpleObject will output "NULL" for null values (line 189-192).

src/NLog/Targets/FileTarget.cs (2)

1018-1018: LGTM!

Nullable annotation for timer callback's state parameter aligns with TimerCallback delegate signature which accepts nullable state.


1248-1248: LGTM!

Null-coalescing for Path.GetDirectoryName result ensures a non-null directory name is used in the subsequent Directory.CreateDirectory call. This prevents potential ArgumentNullException.

src/NLog/Targets/LineEndingMode.cs (3)

188-191: LGTM!

Nullable annotation for Equals(object? obj) is consistent with other Equals overrides in the codebase (e.g., LogLevel.cs line 362-365, AsyncLogEventInfo.cs line 91).


196-199: LGTM!

Nullable annotation for Equals(LineEndingMode? other) with proper null-conditional access other?._newLineCharacters. This matches the pattern in LogLevel.Equals(LogLevel? other) which uses other?._ordinal.


213-217: LGTM!

Nullable return type for ConvertFrom is correct since base.ConvertFrom can return null. The implementation properly handles the nullable name variable.

src/NLog/Internal/StackTraceUsageUtils.cs (2)

107-108: LGTM!

Null-coalescing fallback for method.ToString() is a defensive measure. While ToString() rarely returns null, this ensures the method never returns an unexpected null value.


219-221: LGTM!

The parameter signature change from MethodBase? to MethodBase is safe because all callers already perform null checks before invoking this method (e.g., line 250: if (method != null && LookupAssemblyFromMethod(method) != null)).

The internal logic correctly uses null-conditional access for DeclaringType?.Assembly and Module?.Assembly since these can still be null even with a non-null MethodBase.

src/NLog/Internal/CallSiteInformation.cs (2)

308-322: LGTM! Null-safety improvements are correct.

The refactoring properly handles null inputs:

  • Early return when stackMethod is null prevents downstream null reference issues
  • Explicit null check for declaringType before calling IsHiddenClassType eliminates potential NPEs
  • The explicit return false at the end improves code clarity

327-332: LGTM! Minor refactor for consistency.

Inlining the declaringType variable declaration aligns with the pattern used in the updated SkipStackFrameWhenHidden method above.

src/NLog/LogLevelTypeConverter.cs (1)

46-80: LGTM! Nullable annotations align with base class.

The updated method signatures correctly adopt nullable reference type annotations to match the TypeConverter base class contract in modern .NET frameworks. The internal logic remains unchanged, and the IsNumericType helper appropriately guards against null inputs.

src/NLog/LogFactory.cs (5)

54-56: LGTM! Conditional compilation updated for broader .NET target.

The change from NETCOREAPP3_1_OR_GREATER to NET appropriately broadens the target to include all modern .NET versions (.NET 5+) while maintaining NETSTANDARD2_1_OR_GREATER for compatibility. This aligns with the availability of IAsyncDisposable across these platforms.

Also applies to: 376-411


288-296: LGTM! Nullable annotation for event handler parameter.

The nullable object? sender parameter aligns with standard .NET event handler patterns where the sender can be null.


655-655: LGTM! Fully qualified namespace prevents ambiguity.

Using NLog.Common.AsyncHelpers.PreventMultipleCalls instead of the unqualified AsyncHelpers improves code clarity and prevents potential namespace conflicts.


1113-1116: LGTM! Nullable annotation matches base class.

The object? parameter correctly aligns with the System.Object.Equals(object?) signature for nullable reference type compliance.


1227-1247: LGTM! Event handler signatures updated for null-safety.

Both OnLoggerShutdown and OnStopLogging correctly accept nullable object? sender parameters, consistent with standard .NET event handler patterns.

Also applies to: 1249-1274

src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs (1)

84-87: LGTM! Null-coalescing guard for path operations.

The null-coalescing operator properly handles cases where Path.GetDirectoryName returns null (e.g., root directory, invalid path), ensuring _processDir is always initialized to a non-null value. This aligns with similar null-safety patterns in related layout renderers like BaseDirLayoutRenderer and NLogDirLayoutRenderer.

src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs (1)

155-158: LGTM! Consistent null-safety pattern.

The null-coalescing operator matches the pattern used in ProcessDirLayoutRenderer and ensures GetProcessDir always returns a non-null string. This prevents potential null reference issues in downstream path operations.

src/NLog/ScopeContext.cs (1)

525-529: LGTM! Defensive null-safety for ToString override.

The null-coalescing operator ensures the ToString method always returns a non-null string by falling back to the type name when _collapsed.ToString() returns null. This aligns with similar null-safety patterns throughout the PR in other ToString implementations.

src/NLog/Conditions/ConditionLiteralExpression.cs (1)

64-82: LGTM! ToString always returns non-null string.

The null-coalescing operator on line 81 ensures that Convert.ToString returning null is handled gracefully with an empty string fallback. This guarantees the ToString override always returns a non-null string, consistent with nullable reference type best practices.

src/NLog/Conditions/ConditionRelationalExpression.cs (1)

217-228: LGTM! Null-safe type promotion for comparisons.

The null-coalescing operators on lines 220 and 226 ensure that type promotions to LogLevel and string always produce non-null values, preventing null reference issues during condition evaluation. The explicit string? declaration on line 219 makes the nullable flow clear and intentional.

src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs (1)

93-93: LGTM! Null-safety improvement.

The null-coalescing operator ensures ResolveNLogDir always returns a non-null string, preventing potential null reference issues when Path.GetDirectoryName returns null.

src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (1)

272-275: LGTM! Consistent null-safety pattern.

The null-coalescing fallback ensures ToString never returns null, aligning with similar patterns in Layout.cs and other components where type name serves as a sensible fallback.

src/NLog/Targets/ColoredConsoleSystemPrinter.cs (1)

89-94: Verify span API availability (same concern as ColoredConsoleAnsiPrinter.cs).

This change mirrors the conditional compilation update in ColoredConsoleAnsiPrinter.cs. Ensure that AsSpan is available on all target frameworks covered by the NET symbol.

src/NLog/Internal/PropertyHelper.cs (2)

175-183: LGTM! Correct Try-pattern implementation.

The logic reversal properly implements the Try-pattern by returning false when the property is not found (null) and true when found, with the result properly assigned in both cases.


439-439: LGTM! Defensive null check.

The explicit null check before calling IsValidPublicProperty improves null-safety and clarity, even though IsValidPublicProperty handles null internally (as shown in ReflectionHelpers.cs:182-185).

tests/NLog.UnitTests/LogMessageFormatterTests.cs (1)

103-109: Verify that production code targets match test framework coverage.

The test enables the span-based LogEventInfo path for NETSTANDARD2_1_OR_GREATER || NET. The main project targets netstandard2.1 but does not have an explicit .NET 5+ target (like net5.0, net6.0, net8.0), while the test project runs on net8.0. Confirm whether using netstandard2.1 for all .NET 5+ platforms is intentional, or if the main project should include a dedicated .NET 5+ target.

src/NLog/Conditions/ConditionExpression.cs (1)

56-58: The conditional compilation change is correct. The NET symbol is automatically defined by the .NET SDK for .NET 5.0 and later versions, all of which support the NotNullIfNotNull attribute. Combined with NETSTANDARD2_1_OR_GREATER, this condition correctly covers all target frameworks where the attribute is available (netstandard2.1 and .NET 5+), while appropriately excluding frameworks without support (net35, net46, netstandard2.0).

src/NLog/LogEventBuilder.cs (1)

123-138: The conditional compilation is correct and safely enables ReadOnlySpan<T> support across all intended targets.

The NET symbol (representing .NET 5.0 and later) is appropriate here. ReadOnlySpan is available in all .NET 5.0+ versions as well as .NET Standard 2.1, making #if NETSTANDARD2_1_OR_GREATER || NET a proper guard that correctly includes the method for frameworks that support span types while excluding those that don't (net35, net46, netstandard2.0).

Broadening from NET9_0_OR_GREATER to NET safely extends the feature to .NET 5.0, 6.0, 7.0, and 8.0 without compatibility risk.

src/NLog/Targets/ColoredConsoleAnsiPrinter.cs (1)

88-95: AsSpan is available on all frameworks covered by the condition—no action required.

The conditional #if NETSTANDARD2_1_OR_GREATER || NET correctly ensures string.AsSpan is only used where it's available:

  • NETSTANDARD2_1_OR_GREATER: covers netstandard2.1 where AsSpan was introduced
  • NET: covers .NET 5.0+ where AsSpan is available

Frameworks without AsSpan support (net35, net46, netstandard2.0) fall through to the #else block using the character-by-character loop, which is correct.

tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs (1)

389-394: LGTM! Conditional compilation symbol updated to broaden span-based API support.

The change from NETCOREAPP3_0_OR_GREATER to NETSTANDARD2_1_OR_GREATER || NET aligns with the PR's objective to enable span-based overloads under the broader NET target framework while maintaining compatibility.

tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs (1)

167-172: LGTM! Consistent conditional compilation update.

This change mirrors the update in the main test file, ensuring span-based overloads are available under the broader NET target framework.

src/NLog/Conditions/ConditionMethods.cs (3)

79-85: LGTM! Explicit null handling improves clarity.

The explicit null check and early return make the null-handling behavior clear and explicit, aligning with the PR's nullable reference type enhancements.


95-101: LGTM! Consistent null handling with Contains.

The explicit null check pattern is consistently applied across string comparison methods.


111-117: LGTM! Completes the consistent null handling pattern.

All three string comparison methods now use the same explicit null-checking approach, improving code consistency and null-safety.

src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs (1)

178-188: LGTM! Correct nullable annotation for Assembly return.

The nullable annotation accurately reflects that GetEntryAssembly() can return null. The caller GetVersion() already handles the null case via null-conditional operators (lines 157, 160, 163).

src/NLog/Logger-generated.cs (1)

274-278: LGTM! Broadened NET support with collection expression syntax.

The change from NET9_0_OR_GREATER to NET enables span-based logging paths for a wider range of .NET targets. The array-literal syntax [argument] is cleaner and aligns with modern C# collection expressions.

src/NLog/Layouts/Typed/Layout.cs (4)

362-365: LGTM! Null-safe ToString with type fallback.

The null-coalescing fallback to GetType().ToString() ensures a non-null return value, aligning with similar patterns in related layout types.


380-396: LGTM! Nullable annotation for Equals parameter.

The nullable annotation on the obj parameter correctly reflects that Equals can receive null values.


401-405: LGTM! Consistent nullable annotation for generic Equals.

The nullable annotation on the typed Equals method maintains consistency with the object-based overload.


624-627: LGTM! Consistent ToString fallback pattern.

The null-coalescing fallback is consistently applied across layout value types, ensuring reliable string representations.

src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs (1)

55-60: LGTM! Correct nullable annotation for thread name.

The nullable return type correctly reflects that Thread.CurrentThread.Name can be null. This aligns with the IStringValueRenderer.GetFormattedString interface signature which returns string?.

src/NLog/Logger-generated.tt (1)

250-255: LGTM! Template updated to broaden NET support.

The T4 template correctly updates the conditional compilation from NET9_0_OR_GREATER to NET, and uses collection expression syntax [argument] for span-based calls. This change will propagate to all generated logging method overloads.

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

74-89: RuntimeFeature guard broadened appropriately

Using #if NETSTANDARD2_1_OR_GREATER || NET around the RuntimeFeature.IsDynamicCodeSupported check keeps the AOT-safe fallback available on all modern TFMs without changing behavior where dynamic code is supported.

src/NLog/LogEventInfo.cs (2)

136-159: Span-based overloads correctly unified under NET

Moving the ReadOnlySpan<MessageTemplateParameter> ctor and NeedToPreformatMessage(in ReadOnlySpan<object?>) under #if NETSTANDARD2_1_OR_GREATER || NET makes these span paths available on all modern TFMs that support them, with logic kept in sync with the array-based overload.

Also applies to: 675-692


161-186: Guarding IReadOnlyList ctor against NET35/NET40

The updated #if !NET35 && !NET40 around the IReadOnlyList<KeyValuePair<object, object?>> ctor is appropriate, since IReadOnlyList<> isn’t available on those legacy frameworks and this avoids compile-time issues there.

src/NLog/Logger.cs (2)

439-519: Generic Log<T...> overloads now using span pipeline correctly

Routing the generic single/dual/triple-argument Log<T...> overloads through WriteToTargetsWithSpan(..., [argument[..]]) under #if NETSTANDARD2_1_OR_GREATER || NET keeps behavior identical while enabling the lower-allocation span path on modern TFMs, with the existing array-based path retained for older targets.


571-604: New Log overloads with params ReadOnlySpan<object?> integrate cleanly

The new span-based Log overloads:

  • Reuse GetTargetsForLevelSafe for enablement checks.
  • Delegate directly to the internal WriteToTargetsWithSpan path.
  • Leave the existing params object?[] overloads untouched for callers on older compilers/TFMs.

This is a good extension point for span-aware callers without breaking existing APIs.

}

#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
#if NETSTANDARD2_1_OR_GREATER || NET
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's check the actual state of line 202 in the file
cat -n src/NLog/Internal/LogMessageTemplateFormatter.cs | sed -n '195,210p'

Repository: NLog/NLog

Length of output: 946


🏁 Script executed:

# Check the method that uses ReadOnlySpan<object?> to understand API requirements
cat -n src/NLog/Internal/LogMessageTemplateFormatter.cs | sed -n '202,220p'

Repository: NLog/NLog

Length of output: 1016


🏁 Script executed:

# Search for NET symbol definitions in project files
fd -e csproj -e props -e vbproj | head -20

Repository: NLog/NLog

Length of output: 1615


🏁 Script executed:

# Check for custom DefineConstants or NET symbol definitions
rg -n 'DefineConstants|<NET>' --type xml | head -30

Repository: NLog/NLog

Length of output: 35


🏁 Script executed:

# Search for usages of the NET symbol to understand the pattern
rg -n '#if.*\bNET\b' --type cs | head -20

Repository: NLog/NLog

Length of output: 1430


🏁 Script executed:

# Let's check the main NLog project file to see target frameworks
cat src/NLog/NLog.csproj | head -50

Repository: NLog/NLog

Length of output: 2639


🏁 Script executed:

# Search for TargetFramework definitions
rg -n 'TargetFramework' src/NLog/NLog.csproj -A 2

Repository: NLog/NLog

Length of output: 1996


🏁 Script executed:

# Check if NET is a predefined symbol by looking at project properties
cat src/NLog/NLog.csproj | grep -A 5 -B 5 'TargetFramework'

Repository: NLog/NLog

Length of output: 3379


🏁 Script executed:

# Look for any DefineConstants in the NLog.csproj
rg -n 'DefineConstants' src/NLog/NLog.csproj

Repository: NLog/NLog

Length of output: 119


🏁 Script executed:

# Let's check the .NET SDK documentation for predefined symbols
# First, let's see what symbols are actually defined for different target frameworks
# by looking at git history to see what this line was before

git log -p --all -- src/NLog/Internal/LogMessageTemplateFormatter.cs | head -200

Repository: NLog/NLog

Length of output: 35


🏁 Script executed:

# Let's also check if NET is used elsewhere and if there's any documentation
rg -n 'define.*NET' --type cs --type xml --type props

Repository: NLog/NLog

Length of output: 82


🏁 Script executed:

# Check if there's any build configuration that defines NET symbol
find . -name "*.props" -o -name "*.targets" | xargs grep -l "NET" 2>/dev/null | head -10

Repository: NLog/NLog

Length of output: 35


🏁 Script executed:

# Let's check if NET is a standard MSBuild predefined symbol by looking at .NET SDK documentation
# First, let's search for any custom property definitions in Directory.Build.props or similar
fd -type f -name "Directory.Build.*" -o -name "*.props" | head -20

Repository: NLog/NLog

Length of output: 223


🏁 Script executed:

# Let's check if there's a Directory.Build.props at the root
ls -la | grep -i build

Repository: NLog/NLog

Length of output: 191


🏁 Script executed:

# Let's examine the actual condition more carefully by looking at both versions in git
git log --all -S "NETSTANDARD2_1_OR_GREATER || NET" --oneline -- src/NLog/Internal/LogMessageTemplateFormatter.cs

Repository: NLog/NLog

Length of output: 171


🏁 Script executed:

# Let's check the git diff to see what changed
git show HEAD:src/NLog/Internal/LogMessageTemplateFormatter.cs | sed -n '200,210p'

Repository: NLog/NLog

Length of output: 554


🏁 Script executed:

# Let's search for any documentation about the NET symbol in comments or code
rg -n "NET.*symbol|define.*NET" --type cs -B 2 -A 2 | head -40

Repository: NLog/NLog

Length of output: 35


🏁 Script executed:

# Let's check the commit that made this change to see what the previous state was
git show 6cc0dc3f:src/NLog/Internal/LogMessageTemplateFormatter.cs | sed -n '200,210p'

Repository: NLog/NLog

Length of output: 554


🏁 Script executed:

# Let's check the full diff of that commit for this file
git show 6cc0dc3f -- src/NLog/Internal/LogMessageTemplateFormatter.cs | head -100

Repository: NLog/NLog

Length of output: 4617


🏁 Script executed:

# Let's understand what NET means by checking Microsoft documentation patterns
# Let's see if there's any reference to NET being defined as a shorthand
rg "NETSTANDARD2_1_OR_GREATER.*NET[^0-9]" --type cs -B 3 -A 3 | head -60

Repository: NLog/NLog

Length of output: 660


🏁 Script executed:

# The git show indicates this was a NEW file. Let's check if this file existed before this commit
git log --all --follow -- src/NLog/Internal/LogMessageTemplateFormatter.cs | head -20

Repository: NLog/NLog

Length of output: 445


🏁 Script executed:

# Let's also check that suspicious NETNET9_0_OR_GREATER we found - this might be a typo in the codebase
rg "NETNET" --type cs

Repository: NLog/NLog

Length of output: 134


🏁 Script executed:

# Let's understand what NET symbol actually means - let's search for how it's used in conditions
rg "#if.*\|\s*NET[^0-9_]" --type cs -A 1 -B 1 | head -50

Repository: NLog/NLog

Length of output: 280


🏁 Script executed:

# Check what the standard Microsoft predefined symbols are
# Let's see if we can find any documentation comments in the code about this
rg "NET.*predefined|predefined.*NET" --type cs -i

Repository: NLog/NLog

Length of output: 35


🏁 Script executed:

# Let's check if NET is perhaps just meant as a catch-all for all .NET versions 5+
# by examining similar patterns in the codebase
rg "#if.*NETSTANDARD.*\|\|\s*NET[^0-9_]" --type cs

Repository: NLog/NLog

Length of output: 134


🏁 Script executed:

# Let's check what TargetFramework identifiers are actually defined in the project
# and see if the description mentions which .NET versions are supported
cat src/NLog/NLog.csproj | grep -A 20 "Supported platforms"

Repository: NLog/NLog

Length of output: 833


🏁 Script executed:

# Let's verify the NET symbol is a standard MSBuild predefined symbol
# by checking Microsoft documentation reference in comments or seeing how it's used consistently
rg "NETSTANDARD2_1_OR_GREATER.*NET[^0-9_]" src/NLog/ --type cs | wc -l

Repository: NLog/NLog

Length of output: 54


🏁 Script executed:

# Let's count how many times this pattern appears to see if it's intentional
rg "#if NETSTANDARD2_1_OR_GREATER \|\| NET$" --type cs | wc -l

Repository: NLog/NLog

Length of output: 56


🏁 Script executed:

# Let's check if there's a version history showing what NET should be
# by looking at a few of these files to see what's being guarded
rg -B 5 "#if NETSTANDARD2_1_OR_GREATER \|\| NET$" --type cs | head -80

Repository: NLog/NLog

Length of output: 5221


🏁 Script executed:

# Let's check what the actual current target frameworks are in the current repo state
git show HEAD:src/NLog/NLog.csproj | grep -A 2 "TargetFrameworks"

Repository: NLog/NLog

Length of output: 390


🏁 Script executed:

# Let's see if NET is a Microsoft-defined predefined symbol by checking if it's used in the commit that introduced these changes
git show 6cc0dc3f --stat | head -20

Repository: NLog/NLog

Length of output: 1100


🏁 Script executed:

# Let's verify if this is actually using a newer MSBuild convention where NET is predefined
# by looking at whether there are net5.0, net6.0 style TFMs in the actual commit
git show 6cc0dc3f:src/NLog/NLog.csproj | grep -E "TargetFramework|net[0-9]"

Repository: NLog/NLog

Length of output: 1188


🏁 Script executed:

# Check if maybe the NET symbol needs to be defined as a custom symbol
# Let's look for any DefineConstants that might be missing
git show 6cc0dc3f:src/NLog/NLog.csproj | grep -B 5 -A 5 "DefineConstants"

Repository: NLog/NLog

Length of output: 415


🏁 Script executed:

# Let's check if the NET symbol is defined as a standard MSBuild symbol by looking at .NET SDK documentation
# First, let's see what symbols would be predefined for the current target frameworks
# The project targets: net35;net46;netstandard2.0;netstandard2.1
# Let's verify if NET is a custom symbol that needs to be defined

# Check if there are any conditional properties that define NET
git show 6cc0dc3f:src/NLog/NLog.csproj | grep -E "PropertyGroup|NET[^a-zA-Z0-9_]" | head -40

Repository: NLog/NLog

Length of output: 1183


🏁 Script executed:

# Let's check what NET symbol represents - search for comments explaining it
rg "NET.*symbol|symbol.*NET" --type cs -i

Repository: NLog/NLog

Length of output: 35


🏁 Script executed:

# Let's also check the exact error we'd get if NET is undefined
# by looking at what symbols are available for each target framework
# According to Microsoft docs, for netstandard2.1, the predefined symbols would be NETSTANDARD and NETSTANDARD2_1
# For NET Framework, they would be NET* but not a bare NET
# Let's verify by checking the MSBuild documentation references in code

rg "NETSTANDARD2_1_OR_GREATER" src/NLog/ --type cs | head -5

Repository: NLog/NLog

Length of output: 468


🏁 Script executed:

# Check if this is possibly a new convention in newer SDK versions
# by looking at when this pattern was introduced across the codebase
git log --all --oneline -S "NETSTANDARD2_1_OR_GREATER || NET" -- src/NLog/ | head -5

Repository: NLog/NLog

Length of output: 171


🏁 Script executed:

# Let's verify that NET is actually undefined and check what happens with undefined symbols
# by looking at the Logger-V1Compat.cs file where we found the NETNET9_0_OR_GREATER error

cat -n src/NLog/Logger-V1Compat.cs | sed -n '1,50p'

Repository: NLog/NLog

Length of output: 2554


🏁 Script executed:

# Let's check if the symbol NET is supposed to be defined somewhere for modern .NET versions
# but isn't actually present in the current target frameworks
cat -n src/NLog/Logger-V1Compat.cs | grep -B 3 -A 3 "NETNET9"

Repository: NLog/NLog

Length of output: 513


🏁 Script executed:

# Let's verify the actual issue - if NET is undefined, code using it won't compile correctly
# Let's check if the current codebase even compiles or if there are known issues
fd -type f -name "*.md" | xargs grep -l "NET.*symbol\|undefined.*symbol" 2>/dev/null | head -5

Repository: NLog/NLog

Length of output: 223


🏁 Script executed:

# Let's also check the actual comment/documentation about what NET means
rg "NET\b" src/NLog/NLog.csproj | head -20

Repository: NLog/NLog

Length of output: 604


🏁 Script executed:

# Let's check if NET symbol is maybe supposed to be defined as a custom symbol
# by looking for any PropertyGroup that defines it conditionally

rg "PropertyGroup.*NET" src/NLog/NLog.csproj

Repository: NLog/NLog

Length of output: 35


🏁 Script executed:

# Let's also check if there's a Directory.Build.props at repo root that might define it
ls -la Directory.Build.* 2>/dev/null || echo "No Directory.Build files found"

Repository: NLog/NLog

Length of output: 83


🏁 Script executed:

# Let's verify the exact issue by checking what happens when undefined symbols are used in preprocessor directives
# Check if there are other undefined symbols being used this way

rg "#if.*\|\|.*[A-Z]+[^0-9_\s]*$" --type cs | head -20

Repository: NLog/NLog

Length of output: 1612


🏁 Script executed:

# Let me verify what NET symbol should actually be
# by checking the Microsoft documentation pattern for modern .NET versions
# The pattern shows NET9_0_OR_GREATER, NET6_0_OR_GREATER, etc. are the real symbols

# Let's confirm that NET is truly undefined and not a valid predefined symbol
rg "#if.*\bNET\b[^0-9_]" --type cs -c

Repository: NLog/NLog

Length of output: 35


🏁 Script executed:

# Let's also check if this was intentional by looking at the actual file state before and after the commit
git show 6cc0dc3f^:src/NLog/Internal/LogMessageTemplateFormatter.cs 2>/dev/null | head -5 || echo "File did not exist in parent commit"

Repository: NLog/NLog

Length of output: 35


🏁 Script executed:

# Let's verify the semantics: if NET is undefined, the condition evaluates based only on NETSTANDARD2_1_OR_GREATER
# This means for modern .NET versions like net5.0, net6.0, net9.0 WITHOUT explicit symbol definition, the code would NOT be included
# Let's check if there's supposed to be a custom symbol definition

grep -r "DefineConstants.*NET[^0-9]" . 2>/dev/null | grep -v ".git" | head -10

Repository: NLog/NLog

Length of output: 35


The NET preprocessor symbol is undefined and must be corrected.

The condition uses #if NETSTANDARD2_1_OR_GREATER || NET, but NET is not a standard MSBuild-defined symbol and is not defined in the project configuration. This means the NET part of the condition evaluates to false, and the span-based Render overload is only compiled for the netstandard2.1 target, not for modern .NET versions (5.0+) as the PR description suggests.

Replace NET with one of:

  • NET5_0_OR_GREATER (to include all .NET 5+)
  • Specific version symbols like NET6_0_OR_GREATER || NET9_0_OR_GREATER (to target specific versions)
  • Or remove it entirely if only netstandard2.1 support is intended

This same pattern appears ~104 times across the codebase and needs to be corrected consistently.

🤖 Prompt for AI Agents
In src/NLog/Internal/LogMessageTemplateFormatter.cs around line 202 the
preprocessor condition uses an undefined symbol ("NET"), so the span-based
Render overload is not compiled for modern .NET; replace the `NET` part with a
proper target symbol such as NET5_0_OR_GREATER (or a set of specific
NETx_0_OR_GREATER symbols you need) so the block is included for .NET 5+ and
apply the same replacement consistently to the ~104 other occurrences across the
codebase; verify no custom NET symbol is relied on in the csproj and update
tests/build to ensure the span overload is compiled for intended targets.

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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/NLog/LogFactory.cs (1)

376-411: Review async disposal timeout implementation.

The timeout pattern has potential issues:

  1. Background execution after timeout: Line 403 uses Task.WhenAny to race between disposal and timeout, but doesn't cancel the losing task. If the timeout completes first, DisposeInternal() continues running in the background, which could lead to:

    • Unpredictable behavior accessing disposed resources
    • Locks held longer than expected
    • Inconsistent object state
  2. GC.SuppressFinalize placement: Line 404 only executes if the await completes successfully. If an exception occurs (including timeout), the finalizer may still run, potentially causing double-disposal.

Consider:

  • Using CancellationTokenSource to properly cancel DisposeInternal() on timeout
  • Moving GC.SuppressFinalize(this) outside the try block or adding it to a finally block to ensure it executes even on exception
src/NLog/Internal/ExceptionMessageFormatProvider.cs (1)

45-56: Fix IFormatProvider.GetFormat signature to match .NET BCL definition.

The ICustomFormatter.Format method at line 45 correctly matches the .NET 10 BCL signature. However, IFormatProvider.GetFormat at line 80 does not conform to the BCL interface definition:

  • BCL spec: object GetFormat(Type formatType)
  • Current code: object? IFormatProvider.GetFormat(Type? formatType)

Remove the nullable annotations from both the return type and the formatType parameter to match the required BCL interface contract.

♻️ Duplicate comments (3)
src/NLog/Internal/LogMessageTemplateFormatter.cs (1)

202-202: The NET preprocessor symbol issue from previous review remains unaddressed.

As thoroughly documented in the previous review, NET (without a version suffix) is not a standard MSBuild-defined preprocessor symbol. The span-based Render overload will not be compiled for modern .NET 5+ targets as intended. The correct symbols are NET5_0_OR_GREATER, NET6_0_OR_GREATER, etc.

This same pattern appears in ~104 locations across the codebase and needs systematic correction.

src/NLog/Logger-generated.cs (1)

274-275: Verify C# language version compatibility for collection expressions on .NET 5-7.

The conditional compilation symbol NET includes .NET 5+ targets, but collection expression syntax [argument] requires C# 12, which is only the default language version for .NET 8+. Projects targeting .NET 5-7 will need an explicit <LangVersion>12</LangVersion> or later in the project file to compile this code.

Run the following verification to check the project's C# language version configuration:

#!/bin/bash
# Check if LangVersion is explicitly set in project files
echo "=== Checking C# language version in project files ==="
fd -e csproj -x cat {} \; | rg -i "LangVersion"

echo -e "\n=== Checking target frameworks that will use the NET conditional ==="
fd -e csproj -x cat {} \; | rg -i "TargetFramework"

echo -e "\n=== Verifying collection expression usage in generated file ==="
rg -n "\[argument.*\]" src/NLog/Logger-generated.cs | head -5

Also applies to: 293-294, 315-316, 336-337, 360-361, 566-567, 585-586, 607-608, 628-629, 652-653, 675-676, 858-859, 877-878, 899-900, 920-921, 944-945, 967-968, 1150-1151, 1169-1170, 1191-1192, 1212-1213, 1236-1237, 1259-1260, 1442-1443, 1461-1462, 1483-1484, 1504-1505, 1528-1529, 1551-1552, 1734-1735, 1753-1754, 1775-1776, 1796-1797, 1820-1821, 1843-1844

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

2041-2054: Fix typo in preprocessor symbol NETNET9_0_OR_GREATER

This directive:

#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATER
    [OverloadResolutionPriority(-1)]
#endif

contains a typo in the symbol name (NETNET9_0_OR_GREATER). As written, the attribute is never compiled, unlike other neighboring overloads that use either NET9_0_OR_GREATER or NET.

You likely want this to be consistent with the rest of the file, for example:

-#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATER
+#if NETSTANDARD2_1_OR_GREATER || NET
     [OverloadResolutionPriority(-1)]
 #endif

(or || NET9_0_OR_GREATER if you prefer the narrower condition).

🧹 Nitpick comments (6)
src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs (1)

89-95: Null-safety improvement looks good; consider refining the error message.

The addition of the null-check for GetGetMethod() properly addresses nullable reference type warnings and provides fail-fast behavior. The two-step validation pattern (fetch, check, use) is consistent with ReflectionHelpers.IsValidPublicProperty shown in the related code.

The error message could be slightly clearer: since the property was already found at line 84, the message "Property '{Property}' not found in System.Diagnostics.Process with valid getter-method" might confuse readers. Consider rewording to emphasize that the property exists but lacks a getter.

🔎 Optional refinement for error message clarity
-                throw new ArgumentException($"Property '{Property}' not found in System.Diagnostics.Process with valid getter-method");
+                throw new ArgumentException($"Property '{Property}' in System.Diagnostics.Process does not have a public getter method");
src/NLog/Conditions/ConditionMethods.cs (2)

97-100: Null safety improvement; consider removing redundant == true comparison.

The null guards improve safety. However, line 100's == true comparison is redundant since StartsWith returns bool, not bool?. Also verify that returning false for null inputs is the intended behavior.

🔎 Optional simplification
-            return haystack.StartsWith(needle, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == true;
+            return haystack.StartsWith(needle, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);

113-116: Null safety improvement; consider removing redundant == true comparison.

The null guards improve safety and maintain consistency with the other methods. However, line 116's == true comparison is redundant since EndsWith returns bool, not bool?. Also verify that returning false for null inputs is the intended behavior.

🔎 Optional simplification
-            return haystack.EndsWith(needle, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal) == true;
+            return haystack.EndsWith(needle, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal);
src/NLog/Internal/DictionaryEntryEnumerable.cs (1)

108-109: Consider aligning Entry.Key with the Key property for consistency.

The Entry property returns a default DictionaryEntry, which has a null Key, while the Key property now returns string.Empty. This creates an inconsistency where Entry.Key and Key represent different values.

While this shouldn't matter during normal iteration (since MoveNext() always returns false for an empty enumerator), the inconsistency could be confusing if these properties are accessed directly.

🔎 Option: Align Entry.Key with Key property
 private sealed class EmptyDictionaryEnumerator : IDictionaryEnumerator
 {
     public static readonly IDictionaryEnumerator Default = new EmptyDictionaryEnumerator();
-    public DictionaryEntry Entry => default;
+    public DictionaryEntry Entry => new DictionaryEntry(string.Empty, default);
     public object Key => string.Empty;
     public object? Value => default;

Alternatively, document that these properties should not be accessed on an empty enumerator.

src/NLog/Targets/MethodCallTarget.cs (1)

270-272: Good null safety fix for InnerException handling.

The null check correctly prevents a potential NullReferenceException when InnerException is null, and falls back to rethrowing the original TargetInvocationException.

Optional: Preserve stack trace with ExceptionDispatchInfo

When you throw ex.InnerException, the stack trace is reset to the current throw location. To preserve the original stack trace, consider using ExceptionDispatchInfo:

+using System.Runtime.ExceptionServices;
 catch (TargetInvocationException ex)
 {
     InternalLogger.Warn("{0}: Failed to invoke method - {1}", this, ex.Message);
     if (ex.InnerException != null)
-        throw ex.InnerException;
+        ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
     throw;
 }

This is a nice-to-have improvement for better debugging experience, but not essential for this PR's null safety objectives.

src/NLog/Config/AssemblyExtensionLoader.cs (1)

398-398: Simplify the ternary operator for better readability.

The ternary operator is unnecessary because PathHelpers.TrimDirectorySeparators already handles null input (it returns string.Empty for null). The current logic also allows nlogAssemblyDirectory to be null when nlogAssemblyLocation is null, which seems inconsistent with the variable name suggesting it should be a directory path.

🔎 Proposed simplification
-var nlogAssemblyDirectory = string.IsNullOrEmpty(nlogAssemblyLocation) ? nlogAssemblyLocation : PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation) ?? string.Empty);
+var nlogAssemblyDirectory = PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation) ?? string.Empty);

This ensures nlogAssemblyDirectory is always a non-null string and aligns with the AI summary's claim of "ensuring a non-null directory string for path computations."

Based on the code at [src/NLog/Internal/PathHelpers.cs] lines 67-77, TrimDirectorySeparators already handles null input safely.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6cc0dc3 and b8cb19b.

📒 Files selected for processing (68)
  • src/NLog/Common/AsyncLogEventInfo.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Conditions/ConditionExpression.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/Config/MethodFactory.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Internal/DictionaryEntryEnumerable.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Internal/ScopeContextAsyncState.cs
  • src/NLog/Internal/SingleItemOptimizedHashSet.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/LogFactory.cs
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Logger-V1Compat.cs
  • src/NLog/Logger-generated.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Logger.cs
  • src/NLog/MessageTemplates/ValueFormatter.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/Targets/MethodCallTarget.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
🚧 Files skipped from review as they are similar to previous changes (35)
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Internal/ScopeContextAsyncState.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Internal/AssemblyHelpers.cs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-12T01:22:52.437Z
Learnt from: thatrajeevkr
Repo: NLog/NLog PR: 5999
File: src/NLog/Layouts/JSON/JsonLayout.cs:162-166
Timestamp: 2025-09-12T01:22:52.437Z
Learning: When suggesting StringBuilder optimizations for NLog layouts marked as [ThreadAgnostic], avoid shared StringBuilder fields as they create thread safety issues. Prefer simple string operations or ThreadLocal<StringBuilder> if allocation profiling shows a real performance impact.

Applied to files:

  • src/NLog/Internal/StringBuilderExt.cs
🧬 Code graph analysis (10)
src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs (2)
src/NLog/Internal/ReflectionHelpers.cs (1)
  • ReflectionHelpers (45-196)
src/NLog/Targets/MethodCallTarget.cs (2)
  • ReflectionHelpers (184-197)
  • ReflectionHelpers (206-220)
src/NLog/ScopeContext.cs (3)
src/NLog/Internal/ObjectReflectionCache.cs (2)
  • ToString (331-331)
  • ToString (423-426)
src/NLog/Internal/ScopeContextAsyncState.cs (4)
  • ToString (326-329)
  • ToString (388-391)
  • ToString (489-492)
  • ToString (586-592)
src/NLog/Targets/TargetWithContext.cs (1)
  • ToString (727-730)
src/NLog/Logger-V1Compat.cs (1)
src/NLog/Logger.cs (7)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
  • WriteToTargets (827-831)
  • WriteToTargets (833-841)
  • WriteToTargets (843-853)
  • WriteToTargets (855-863)
  • WriteToTargets (866-874)
src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (3)
src/NLog/Layouts/Typed/Layout.cs (3)
  • ToString (362-365)
  • ToString (374-377)
  • ToString (624-627)
src/NLog/Layouts/JSON/JsonLayout.cs (1)
  • ToString (475-483)
src/NLog/Layouts/XML/XmlElementBase.cs (1)
  • ToString (800-812)
src/NLog/Common/AsyncLogEventInfo.cs (4)
src/NLog/Internal/TargetWithFilterChain.cs (2)
  • Equals (376-379)
  • Equals (381-386)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/LogFactory.cs (2)
  • Equals (1113-1116)
  • Equals (1118-1121)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/Logger-generated.cs (1)
src/NLog/Logger.cs (2)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
src/NLog/Internal/CallSiteInformation.cs (1)
src/NLog/Internal/StackTraceUsageUtils.cs (1)
  • StackTraceUsageUtils (44-268)
src/NLog/Config/AssemblyExtensionLoader.cs (1)
src/NLog/Internal/PathHelpers.cs (2)
  • PathHelpers (38-98)
  • TrimDirectorySeparators (71-78)
src/NLog/Targets/LineEndingMode.cs (4)
src/NLog/Internal/PropertiesDictionary.cs (3)
  • Equals (748-748)
  • Equals (750-758)
  • Equals (772-775)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/LogLevel.cs (2)
  • Equals (363-366)
  • Equals (374-377)
src/NLog/LogLevelTypeConverter.cs (1)
  • ConvertFrom (52-61)
src/NLog/LogFactory.cs (1)
src/NLog/Common/AsyncHelpers.cs (1)
  • AsyncHelpers (46-382)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: task-list-completed
🔇 Additional comments (35)
src/NLog/Internal/TimeoutContinuation.cs (1)

96-99: LGTM!

The nullable annotation on the state parameter is appropriate since null is explicitly passed when constructing the Timer on line 57, and the parameter is unused in the method body.

src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs (1)

274-274: LGTM! Nullable annotation aligns with actual usage.

The nullable annotation correctly reflects that the Timer callback at line 195 passes (int?)flushTimeout as state, and the existing null-safe handling at line 280 using ?? 0 ensures correct behavior.

src/NLog/ScopeContext.cs (1)

525-528: LGTM! Defensive null-safety pattern for ToString.

The addition of the null-coalescing operator ensures the method always returns a non-null string, which is essential for nullable reference type annotations. This pattern is consistent with similar ToString implementations throughout the codebase (e.g., ScopeContextAsyncState.ToString and TargetWithContext.ToString).

src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (1)

272-275: LGTM! Null-safety improvement consistent with codebase patterns.

The addition of the null-coalescing fallback ?? GetType().ToString() correctly addresses nullable reference type warnings while maintaining consistency with similar ToString overrides in the codebase (e.g., Layout.cs lines 361-364). The fallback to the type name is appropriate and ensures a non-null return value.

src/NLog/Config/MethodFactory.cs (3)

173-173: LGTM!

Using var here correctly adapts to the nullable return type of ResolveDefaultMethodParameters. The inferred type object?[] properly flows through to downstream ResolveMethodParameters calls which already accept nullable element arrays.


208-221: LGTM!

The return type change to object? correctly reflects that MethodInfo.Invoke can return null (for void methods or methods that explicitly return null). This aligns with the delegate signatures in MethodDetails which already specify object? return types.


223-245: LGTM!

The nullable annotations are correct:

  • ParameterInfo.DefaultValue (line 233) can return null for optional parameters with null defaults
  • The return type object?[] properly indicates that array elements may be null
  • This propagates correctly to all call sites via the var inference at line 173
src/NLog/Internal/AppEnvironmentWrapper.cs (1)

251-251: LGTM! Null-safety fix is correct and consistent.

The null-coalescing operator appropriately handles the nullable return from Path.GetDirectoryName, ensuring a non-null string is returned. This fix aligns with the nullable reference type annotations and matches the defensive pattern used throughout this file.

src/NLog/Targets/LineEndingMode.cs (3)

188-191: LGTM! Nullable parameter handled correctly.

The nullable annotation on the parameter is appropriate, and the implementation correctly handles null values using pattern matching, which returns false when obj is null. This pattern aligns with similar changes in LogLevel.cs.


196-199: LGTM! Nullable parameter handled correctly.

The nullable annotation is appropriate, and the implementation correctly handles null values using the null-conditional operator (other?._newLineCharacters). This pattern is consistent with similar implementations in LogLevel.cs.


213-217: LGTM! Nullable return type is appropriate.

The nullable return type annotation correctly reflects that base.ConvertFrom(context, culture, value) can return null when the conversion is not supported. This matches the same pattern used in LogLevelTypeConverter.ConvertFrom.

src/NLog/Conditions/ConditionMethods.cs (1)

81-84: Null safety improvement, verify intended behavior for null inputs.

The explicit null guards improve safety by preventing exceptions. However, please verify that returning false for null inputs aligns with the intended semantics—particularly edge cases like whether null should be considered to contain null.

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

360-362: LGTM! Proper null-safety guard added.

The null check correctly handles the case where Convert.ToString returns null, which aligns with .NET nullable reference type annotations. The early return with string.Empty is consistent with the catch block's fallback behavior.

src/NLog/LogFactory.cs (5)

54-56: LGTM: Standard IAsyncDisposable pattern.

The conditional compilation and interface addition follow the standard .NET pattern for async disposal support.


288-288: LGTM: Nullable sender annotation.

The nullable sender parameter aligns with standard .NET event handler conventions and nullable reference type best practices.


655-655: LGTM: Namespace qualification.

Explicit namespace qualification improves code clarity and helps with disambiguation.


1113-1116: LGTM: Standard Equals nullable override.

The nullable parameter annotation correctly reflects the standard Object.Equals signature and the implementation safely handles null/non-matching types.


1227-1247: LGTM: Event handler nullable sender annotations.

The nullable sender parameters are correct for event handlers and consistent with the actual usage on Line 1231 where null is explicitly passed as the sender.

Also applies to: 1249-1274

src/NLog/Internal/CallSiteInformation.cs (3)

310-311: LGTM! Good defensive null guard.

The early null check prevents potential NullReferenceException on subsequent property accesses and aligns with the PR's null safety objectives. Returning true (skip frame) when the method cannot be determined is the correct behavior.


317-321: LGTM! Explicit null handling improves safety and clarity.

The explicit null check for declaringType before calling IsHiddenClassType prevents violating the method's non-nullable contract (line 52). The logic correctly handles the edge case where DeclaringType is null by not skipping the frame (since we cannot determine if it's hidden). The explicit return false on line 321 makes the control flow clearer.


329-329: LGTM! Proper null-safe handling.

The null-conditional operator stackMethod?.DeclaringType combined with the null check ensures safe execution even if stackMethod is null. While the call site at line 253 ensures stackMethod is non-null when this method is invoked, this defensive approach correctly honors the method's nullable signature.

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

113-113: Null-safety guard is appropriate and consistent with the codebase.

The ?? string.Empty addition correctly handles the nullable return from Path.GetDirectoryName and aligns with the PR's null-safety objectives. This pattern is used consistently throughout the codebase in similar contexts (ProcessDirLayoutRenderer, BaseDirLayoutRenderer, FileTarget, and file archive handlers). The subsequent Directory.Exists check on line 116 validates the directory, making the approach safe even when GetDirectoryName returns null for edge cases like bare filenames without directory components.

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

242-245: LGTM! Nullable annotation correctly applied.

The updated signature for IEqualityComparer<T>.Equals now accepts nullable parameters, which aligns with .NET nullable reference type conventions. The ReferenceEquals implementation correctly handles null inputs.

src/NLog/Common/AsyncLogEventInfo.cs (1)

92-92: LGTM! Standard nullable annotation for Equals override.

The parameter is now correctly annotated as nullable, matching the base object.Equals(object?) signature. The pattern matching implementation handles null inputs correctly.

src/NLog/MessageTemplates/ValueFormatter.cs (2)

266-272: LGTM! StringBuilder safely handles nullable strings.

The nullable annotation is appropriate here. StringBuilder.Append correctly handles null strings by treating them as empty, and the calling context at Line 203 can pass a nullable result from IConvertible.ToString().


355-363: LGTM! Null handling is correct.

The nullable annotation is appropriate. The pattern matching checks (is IConvertible, is IEnumerable) correctly return false for null items, and SerializeSimpleObject handles null explicitly at Line 189.

src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs (1)

178-188: LGTM! Nullable return type accurately reflects Assembly.GetEntryAssembly behavior.

The nullable annotation is correct. Assembly.GetEntryAssembly() can return null in certain hosting scenarios (e.g., unmanaged hosts), and the caller at Line 152 properly handles this with null-conditional operators.

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

424-432: No changes needed. The preprocessor condition is correct and safe.

The code uses a defensive conditional compilation strategy: the collection expression syntax [...] is guarded behind #if NETSTANDARD2_1_OR_GREATER || NET, which only activates for targets that support C# 12+. For netstandard2.1, the project explicitly sets LangVersion=13 (C# 13), so collection expressions are fully supported. All other targets (net35, net46, netstandard2.0) fall through to the #else block, which uses individual Append() calls without collection expressions. The NET symbol is unused for current target frameworks (which don't include .NET 5+), making this change future-proof rather than problematic.

Likely an incorrect or invalid review comment.

src/NLog/Conditions/ConditionExpression.cs (1)

56-58: Change is correct and follows established patterns.

The NotNullIfNotNull attribute is available in .NET Standard 2.1 and later, so the conditional compilation #if NETSTANDARD2_1_OR_GREATER || NET correctly applies the attribute only where the framework provides it. This pattern is already used consistently in SimpleLayout.cs and appropriately handles NLog's multi-framework targeting (net35, net46, netstandard2.0, netstandard2.1, and NET 5.0+).

src/NLog/Targets/ColoredConsoleAnsiPrinter.cs (1)

88-88: Conditional broadened to include all .NET targets.

The change from NETCOREAPP3_0_OR_GREATER to NET expands the span-based write path to all .NET 5+ targets, aligning with the PR's goal of broadening NET-targeted code paths. The Span APIs are available in both target sets, so this is functionally correct.

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

96-376: Span-based routing for V1 compat overloads looks consistent

All the updated Log/Trace/Debug/Info/Warn/Error/Fatal overloads that now call WriteToTargetsWithSpan(..., [args]) under #if NETSTANDARD2_1_OR_GREATER || NET (with WriteToTargets(..., new object?[]{...}) in the #else) preserve the prior exception/formatProvider/culture and level gating semantics while aligning with the core span pipeline in Logger. I don’t see functional or overload-resolution issues in these compat shims.

Also applies to: 714-1154, 1297-1840, 1888-2427, 2475-3014, 3058-3601, 3649-4188

src/NLog/Targets/ColoredConsoleSystemPrinter.cs (1)

87-93: Broadening span-based WriteSubString path to all NET targets

Switching the guard to #if NETSTANDARD2_1_OR_GREATER || NET means the AsSpan/Write(ReadOnlySpan<char>) path is used on all modern .NET / .NET Core TFMs, with Substring kept as the fallback. That’s a good perf win and should be safe given those APIs are available wherever NET is defined. Please just confirm your targeted TFMs all meet that requirement.

tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs (1)

167-172: Span overload override keeps tests aligned with production writer

Adding Write(ReadOnlySpan<char> buffer) to PartsWriter under #if NETSTANDARD2_1_OR_GREATER || NET nicely tracks the production change to use TextWriter.Write(ReadOnlySpan<char>), so the tests still capture the same segments for both string and span writes. Just ensure all TFMs where this test is built expose that virtual overload on StringWriter.

src/NLog/Logger.cs (2)

571-604: New params ReadOnlySpan<object?> Log overloads – check language version and binding impact

Introducing

public void Log(LogLevel level, string message, params ReadOnlySpan<object?> args)
public void Log(LogLevel level, Exception? exception, string message, params ReadOnlySpan<object?> args)

under #if NETSTANDARD2_1_OR_GREATER || NET is a good way to gain span-based params perf benefits, but it assumes the code is compiled with a C# version that supports params on non-array collections (notably ReadOnlySpan<T>).(developers.redhat.com)

Also, with both params object?[] and params ReadOnlySpan<object?> overloads in scope, C# 13+ overload resolution prefers the span overload when both are applicable, which can be a behavioral change versus earlier compilers, especially inside expression trees.(learn.microsoft.com) That seems intentional and aligns with the BCL, but it’s worth double-checking that any callers relying on expression-tree capture of Logger.Log are OK with the new binding.


440-471: Span-based internal pipeline matches existing behavior and looks correct

The updated generic Log<TArgument[1..3]> overloads and the internal WriteToTargetsWithSpan/RenderPreformattedLogEvent pipeline:

  • Share the same GetTargetsForLevelSafe / GetTargetsForLevel gating as the object[] path.
  • Use NeedToPreformatMessage(args) and AutoMessageTemplateFormatter consistently, only allocating arrays via args.ToArray() when necessary (empty span, preformat fallback, or template parse failure).
  • Don’t let the ReadOnlySpan<object?> escape the call stack; spans are only used synchronously within these methods, so there are no lifetime issues.

Given that, the refactor to route the generic and compat overloads through the span-based path under NETSTANDARD2_1_OR_GREATER || NET looks sound and backwards compatible aside from the expected params-overload binding differences noted earlier.

Also applies to: 484-518, 533-569, 606-642, 644-668

Comment on lines +189 to +190
if (loggerName is null || _matchingArgument is null)
return _matchingArgument is null;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent null handling across matchers.

The three matchers (StartsWith, EndsWith, Contains) now return true when _matchingArgument is null and loggerName is not null, while EqualsLoggerNameMatcher (line 171-175) returns false in the same scenario.

Although _matchingArgument should never be null in practice for these matchers (since they're constructed via Substring operations on non-null patterns), the inconsistent behavior is a code smell.

🔎 Suggested fix for consistent behavior

Consider aligning the behavior with EqualsLoggerNameMatcher:

 public override bool NameMatches(string loggerName)
 {
-    if (loggerName is null || _matchingArgument is null)
-        return _matchingArgument is null;
+    if (loggerName is null) return _matchingArgument is null;
+    if (_matchingArgument is null) return false;
     return loggerName.StartsWith(_matchingArgument, StringComparison.Ordinal);
 }

Apply the same pattern to EndsWithLoggerNameMatcher and ContainsLoggerNameMatcher.

Alternatively, if you're confident _matchingArgument is never null for these matchers, consider adding a debug assertion or using the null-forgiving operator:

 public override bool NameMatches(string loggerName)
 {
     if (loggerName is null) return _matchingArgument is null;
-    return loggerName.StartsWith(_matchingArgument, StringComparison.Ordinal);
+    return loggerName.StartsWith(_matchingArgument!, StringComparison.Ordinal);
 }

Also applies to: 206-207, 223-224

🤖 Prompt for AI Agents
In src/NLog/Config/LoggerNameMatcher.cs around lines 189-190 (and similarly at
206-207 and 223-224), the StartsWith/EndsWith/Contains matchers return true when
_matchingArgument is null and loggerName is not, which is inconsistent with
EqualsLoggerNameMatcher; change the null-check logic so that if loggerName is
null or _matchingArgument is null the method returns false (matching
EqualsLoggerNameMatcher), and optionally add a Debug.Assert or contract
precondition to document that _matchingArgument is expected non-null (or use the
null-forgiving operator) rather than treating null as a successful match.

Comment on lines +201 to +203
var item = list[i];
if (item != null)
elements.Add(item);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

Script executed:

#!/bin/bash
# Find tests for ObjectGraphScanner that verify null element handling
rg -n -C3 "ObjectGraphScanner" --type cs -g "*Test*.cs"

Repository: NLog/NLog


Repository: NLog/NLog
Exit code: 1


Script executed:

#!/bin/bash
# Let's examine the actual code at the lines in question
cat -n src/NLog/Internal/ObjectGraphScanner.cs | sed -n '155,225p'

Repository: NLog/NLog


Repository: NLog/NLog
Exit code: 0

stdout:

   155	            }
   156	            else
   157	            {
   158	                ScanProperties(configFactory, aggressiveSearch, propValue, result, level + 1, visitedObjects);
   159	            }
   160	        }
   161	
   162	        private static void ScanPropertiesList<T>(ConfigurationItemFactory configFactory, bool aggressiveSearch, IList list, List<T> result, int level, HashSet<object> visitedObjects) where T : class
   163	        {
   164	            var firstElement = list[0];
   165	            if (firstElement is null || PropertyHelper.IsConfigurationItemType(configFactory, firstElement.GetType()))
   166	            {
   167	                ScanProperties(configFactory, aggressiveSearch, firstElement, result, level, visitedObjects);
   168	
   169	                for (int i = 1; i < list.Count; i++)
   170	                {
   171	                    var element = list[i];
   172	                    ScanProperties(configFactory, aggressiveSearch, element, result, level, visitedObjects);
   173	                }
   174	            }
   175	        }
   176	
   177	        private static IList ConvertEnumerableToList(IEnumerable enumerable, HashSet<object> visitedObjects)
   178	        {
   179	            if (enumerable is ICollection collection && collection.Count == 0)
   180	            {
   181	                return ArrayHelper.Empty<object>();
   182	            }
   183	
   184	            if (visitedObjects.Contains(enumerable))
   185	            {
   186	                return ArrayHelper.Empty<object>();
   187	            }
   188	
   189	            visitedObjects.Add(enumerable);
   190	
   191	            if (enumerable is IList list)
   192	            {
   193	                if (!list.IsReadOnly && !(list is Array))
   194	                {
   195	                    // Protect against collection was modified
   196	                    List<object> elements = new List<object>(list.Count);
   197	                    lock (list.SyncRoot)
   198	                    {
   199	                        for (int i = 0; i < list.Count; i++)
   200	                        {
   201	                            var item = list[i];
   202	                            if (item != null)
   203	                                elements.Add(item);
   204	                        }
   205	                    }
   206	                    return elements;
   207	                }
   208	
   209	                return list;
   210	            }
   211	
   212	            //new list to prevent: Collection was modified after the enumerator was instantiated.
   213	            //note .Cast is tread-unsafe! But at least it isn't a ICollection / IList
   214	            return enumerable.Cast<object>().Where(o => o != null).ToList();
   215	        }
   216	    }
   217	}

Script executed:

#!/bin/bash
# Search for tests that specifically test null elements in collections
rg -n "null" --type cs -g "*Test*.cs" | rg -i "objectgraph|enumerable|convertenumarable" | head -20

Repository: NLog/NLog


Repository: NLog/NLog
Exit code: 0

stdout:

tests/NLog.UnitTests/Targets/Wrappers/SplitGroupTargetTests.cs:102:            wrapper.WriteAsyncLogEvents(Enumerable.Range(3, LogEventBatchSize).Select(i => LogEventInfo.Create(LogLevel.Info, "", null, i).WithContinuation(exceptions.Add)).ToArray());
tests/NLog.UnitTests/Targets/Wrappers/AsyncTargetWrapperTests.cs:857:            AsyncHelpers.ForEachItemInParallel(System.Linq.Enumerable.Range(1, 6), (ex) => Interlocked.Exchange(ref allTasksCompleted, 1), (value, cont) => { for (int i = 0; i < 100; ++i) logger.Info("Hello {0}", value); cont(null); });
tests/NLog.UnitTests/Contexts/ScopeContextTest.cs:182:            IEnumerable<KeyValuePair<string, object>> allPropertiesState = null;

Script executed:

#!/bin/bash
# Check ScanProperties method to see if it handles nulls
cat -n src/NLog/Internal/ObjectGraphScanner.cs | sed -n '1,100p'

Repository: NLog/NLog


Repository: NLog/NLog
Exit code: 0

stdout:

     1	//
     2	// Copyright (c) 2004-2024 Jaroslaw Kowalski <[email protected]>, Kim Christensen, Julian Verdurmen
     3	//
     4	// All rights reserved.
     5	//
     6	// Redistribution and use in source and binary forms, with or without
     7	// modification, are permitted provided that the following conditions
     8	// are met:
     9	//
    10	// * Redistributions of source code must retain the above copyright notice,
    11	//   this list of conditions and the following disclaimer.
    12	//
    13	// * Redistributions in binary form must reproduce the above copyright notice,
    14	//   this list of conditions and the following disclaimer in the documentation
    15	//   and/or other materials provided with the distribution.
    16	//
    17	// * Neither the name of Jaroslaw Kowalski nor the names of its
    18	//   contributors may be used to endorse or promote products derived from this
    19	//   software without specific prior written permission.
    20	//
    21	// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    22	// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    23	// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    24	// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
    25	// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    26	// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    27	// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    28	// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    29	// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    30	// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
    31	// THE POSSIBILITY OF SUCH DAMAGE.
    32	//
    33	
    34	namespace NLog.Internal
    35	{
    36	    using System;
    37	    using System.Collections;
    38	    using System.Collections.Generic;
    39	    using System.Linq;
    40	    using System.Reflection;
    41	    using NLog.Common;
    42	    using NLog.Config;
    43	
    44	    /// <summary>
    45	    /// Scans (breadth-first) the object graph following all the edges whose are
    46	    /// instances have <see cref="NLogConfigurationItemAttribute"/> attached and returns
    47	    /// all objects implementing a specified interfaces.
    48	    /// </summary>
    49	    internal static class ObjectGraphScanner
    50	    {
    51	        /// <summary>
    52	        /// Finds the objects which have attached <see cref="NLogConfigurationItemAttribute"/> which are reachable
    53	        /// from any of the given root objects when traversing the object graph over public properties.
    54	        /// </summary>
    55	        /// <typeparam name="T">Type of the objects to return.</typeparam>
    56	        /// <param name="configFactory">Configuration Reflection Helper</param>
    57	        /// <param name="aggressiveSearch">Also search the properties of the wanted objects.</param>
    58	        /// <param name="rootObjects">The root objects.</param>
    59	        /// <returns>Ordered list of objects implementing T.</returns>
    60	        public static List<T> FindReachableObjects<T>(ConfigurationItemFactory configFactory, bool aggressiveSearch, params object[] rootObjects)
    61	            where T : class
    62	        {
    63	            if (InternalLogger.IsTraceEnabled)
    64	            {
    65	                InternalLogger.Trace("FindReachableObject<{0}>:", typeof(T));
    66	            }
    67	            var result = new List<T>();
    68	            var visitedObjects = new HashSet<object>(SingleItemOptimizedHashSet<object>.ReferenceEqualityComparer.Default);
    69	            foreach (var rootObject in rootObjects)
    70	            {
    71	                if (PropertyHelper.IsConfigurationItemType(configFactory, rootObject.GetType()))
    72	                {
    73	                    ScanProperties(configFactory, aggressiveSearch, rootObject, result, 0, visitedObjects);
    74	                }
    75	            }
    76	            return result;
    77	        }
    78	
    79	        private static void ScanProperties<T>(ConfigurationItemFactory configFactory, bool aggressiveSearch, object? targetObject, List<T> result, int level, HashSet<object> visitedObjects)
    80	            where T : class
    81	        {
    82	            if (targetObject is null)
    83	            {
    84	                return;
    85	            }
    86	
    87	            if (visitedObjects.Contains(targetObject))
    88	            {
    89	                return;
    90	            }
    91	
    92	            if (targetObject is T t)
    93	            {
    94	                result.Add(t);
    95	                if (!aggressiveSearch)
    96	                {
    97	                    return;
    98	                }
    99	            }
   100	

Script executed:

#!/bin/bash
# Search for ScanProperties method definition
rg -n "private static void ScanProperties" src/NLog/Internal/ObjectGraphScanner.cs -A 10

Repository: NLog/NLog


Repository: NLog/NLog
Exit code: 0

stdout:

79:        private static void ScanProperties<T>(ConfigurationItemFactory configFactory, bool aggressiveSearch, object? targetObject, List<T> result, int level, HashSet<object> visitedObjects)
80-            where T : class
81-        {
82-            if (targetObject is null)
83-            {
84-                return;
85-            }
86-
87-            if (visitedObjects.Contains(targetObject))
88-            {
89-                return;
--
162:        private static void ScanPropertiesList<T>(ConfigurationItemFactory configFactory, bool aggressiveSearch, IList list, List<T> result, int level, HashSet<object> visitedObjects) where T : class
163-        {
164-            var firstElement = list[0];
165-            if (firstElement is null || PropertyHelper.IsConfigurationItemType(configFactory, firstElement.GetType()))
166-            {
167-                ScanProperties(configFactory, aggressiveSearch, firstElement, result, level, visitedObjects);
168-
169-                for (int i = 1; i < list.Count; i++)
170-                {
171-                    var element = list[i];
172-                    ScanProperties(configFactory, aggressiveSearch, element, result, level, visitedObjects);

Script executed:

#!/bin/bash
# Search for calls to ConvertEnumerableToList to understand usage
rg -n "ConvertEnumerableToList" src/NLog/Internal/ObjectGraphScanner.cs -B 3 -A 2

Repository: NLog/NLog


Repository: NLog/NLog
Exit code: 0

stdout:

147-
148-            if (propValue is IEnumerable enumerable)
149-            {
150:                var propListValue = ConvertEnumerableToList(enumerable, visitedObjects);
151-                if (propListValue.Count > 0)
152-                {
--
174-            }
175-        }
176-
177:        private static IList ConvertEnumerableToList(IEnumerable enumerable, HashSet<object> visitedObjects)
178-        {
179-            if (enumerable is ICollection collection && collection.Count == 0)

Script executed:

#!/bin/bash
# Search for any documentation or comments about nullable handling in this file
rg -n "null|nullable" src/NLog/Internal/ObjectGraphScanner.cs | head -30

Repository: NLog/NLog


Repository: NLog/NLog
Exit code: 0

stdout:

82:            if (targetObject is null)
117:                if (propValue is null)
129:                return propInfo.GetValue(targetObject, null);
137:                return null;
165:            if (firstElement is null || PropertyHelper.IsConfigurationItemType(configFactory, firstElement.GetType()))
202:                            if (item != null)
214:            return enumerable.Cast<object>().Where(o => o != null).ToList();

Script executed:

#!/bin/bash
# Look at the git history of this file to understand the change
git log --oneline src/NLog/Internal/ObjectGraphScanner.cs | head -10

Repository: NLog/NLog


Repository: NLog/NLog
Exit code: 0

stdout:

b8cb19b7 Fix nullable warnings from NET10 annotations

Script executed:

#!/bin/bash
# Check the recent changes to this specific method
git log -p --follow src/NLog/Internal/ObjectGraphScanner.cs | grep -A 30 "ConvertEnumerableToList" | head -80

Repository: NLog/NLog


Repository: NLog/NLog
Exit code: 0


Script executed:

#!/bin/bash
# Check if there are any unit tests in the repository for ObjectGraphScanner
find tests -name "*.cs" -type f | xargs rg -l "ObjectGraphScanner|FindReachableObjects" 2>/dev/null

Repository: NLog/NLog


Repository: NLog/NLog
Exit code: 123


Script executed:

#!/bin/bash
# Let me verify the exact code logic flow by looking at ScanPropertiesList more carefully
cat -n src/NLog/Internal/ObjectGraphScanner.cs | sed -n '140,175p'

Repository: NLog/NLog


Repository: NLog/NLog
Exit code: 0

stdout:

   140	
   141	        private static void ScanPropertyForObject<T>(ConfigurationItemFactory configFactory, bool aggressiveSearch, object propValue, PropertyInfo prop, List<T> result, int level, HashSet<object> visitedObjects) where T : class
   142	        {
   143	            if (InternalLogger.IsTraceEnabled)
   144	            {
   145	                InternalLogger.Trace("{0}Scanning Property {1} '{2}' {3}", new string(' ', level + 1), prop.Name, propValue, prop.PropertyType);
   146	            }
   147	
   148	            if (propValue is IEnumerable enumerable)
   149	            {
   150	                var propListValue = ConvertEnumerableToList(enumerable, visitedObjects);
   151	                if (propListValue.Count > 0)
   152	                {
   153	                    ScanPropertiesList(configFactory, aggressiveSearch, propListValue, result, level + 1, visitedObjects);
   154	                }
   155	            }
   156	            else
   157	            {
   158	                ScanProperties(configFactory, aggressiveSearch, propValue, result, level + 1, visitedObjects);
   159	            }
   160	        }
   161	
   162	        private static void ScanPropertiesList<T>(ConfigurationItemFactory configFactory, bool aggressiveSearch, IList list, List<T> result, int level, HashSet<object> visitedObjects) where T : class
   163	        {
   164	            var firstElement = list[0];
   165	            if (firstElement is null || PropertyHelper.IsConfigurationItemType(configFactory, firstElement.GetType()))
   166	            {
   167	                ScanProperties(configFactory, aggressiveSearch, firstElement, result, level, visitedObjects);
   168	
   169	                for (int i = 1; i < list.Count; i++)
   170	                {
   171	                    var element = list[i];
   172	                    ScanProperties(configFactory, aggressiveSearch, element, result, level, visitedObjects);
   173	                }
   174	            }
   175	        }

Address the logic inconsistency created by null filtering.

The null filtering at lines 201-203 and 214 creates a critical logic flaw:

  1. Unreachable code: ConvertEnumerableToList now filters all nulls in both code paths, making the if (firstElement is null || ...) check at line 165 unreachable. The firstElement can never be null when it arrives at that line.

  2. Behavioral change inconsistent with PR objective: The PR aims to "Fix nullable warnings from NET10 annotations," but this change filters nulls at runtime—a behavioral change, not just a compile-time null-safety fix. The null guard in ScanProperties (line 82) was designed to handle nulls gracefully.

  3. No test coverage: No unit tests exist for ObjectGraphScanner to verify this behavioral change is intentional and safe.

Either:

  • Remove the null filtering (lines 201-203, 214) to preserve original behavior and allow ScanProperties to handle nulls as designed, OR
  • Remove the null check at line 165 and adjust ScanPropertiesList logic if filtering nulls is intentional

Add test coverage to verify the chosen approach.

Comment on lines +250 to +251
#if NETSTANDARD2_1_OR_GREATER || NET
WriteToTargetsWithSpan(LogLevel.<#=level#>, null, formatProvider, message, [argument]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Consider using NET8_0_OR_GREATER instead of NET for C# 12 collection expressions.

The template uses collection expression syntax [argument] which requires C# 12. While the NET symbol includes .NET 5-7, C# 12 is only the default language version starting with .NET 8. To ensure compatibility without requiring explicit <LangVersion> configuration, consider using NET8_0_OR_GREATER instead of NET:

-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET8_0_OR_GREATER

This aligns the conditional compilation with the default C# language version for the target framework and addresses the concern from the past review about specifying a complete .NET version.

🔎 Alternative: Keep NET and verify language version

If the intent is to support .NET 5-7 with collection expressions, ensure the project explicitly sets <LangVersion>12</LangVersion> or later, and verify that the collection expression overhead is acceptable on older runtimes.

Also applies to: 269-270, 291-292, 312-313, 336-337, 359-360

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/NLog/Logger.cs (1)

571-604: Change Logger.cs conditional from NET to NET9_0_OR_GREATER for params ReadOnlySpan overloads.

The params ReadOnlySpan<object?> syntax is a C# 13 feature available only in .NET 9+. Logger.cs (line 571) guards these overloads with NETSTANDARD2_1_OR_GREATER || NET, but the generated Logger-generated.cs (line 187) and Logger-generated.tt (line 163) correctly use NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER. This inconsistency will cause compilation errors on .NET 5-8 targets. Update Logger.cs to match the more restrictive conditional.

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

2041-2056: Fix typo in preprocessor symbol NETNET9_0_OR_GREATER

#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATER contains a duplicated NET and references a symbol that will never be defined, so [OverloadResolutionPriority(-1)] won’t be applied for NET builds on this overload even though similar overloads do get it.

This matches an earlier review comment and should likely be || NET (as you’ve done elsewhere in this file) or || NET9_0_OR_GREATER, depending on intent.

#!/bin/bash
# Verify no other occurrences of the typo remain.
rg -n "NETNET9_0_OR_GREATER" src/NLog || echo "No other occurrences of NETNET9_0_OR_GREATER found"
src/NLog/Logger-generated.tt (1)

250-251: Collection expression syntax requires C# 12 verification.

This concern was already flagged in a previous review. The collection expression syntax [argument] requires C# 12, which is the default starting with .NET 8. Using NET instead of NET8_0_OR_GREATER may cause compilation issues on .NET 5-7 unless the project explicitly sets <LangVersion>12</LangVersion> or later.

Please verify the project's language version configuration:

#!/bin/bash
# Check for LangVersion settings in project files
fd -e csproj -e props | xargs grep -l "LangVersion" 2>/dev/null | head -10
fd -e csproj -e props | xargs grep -h "<LangVersion>" 2>/dev/null | head -10

# Check target frameworks to understand which NET versions are supported
fd -e csproj | xargs grep -h "TargetFramework" 2>/dev/null | head -20

Also applies to: 269-270, 291-292, 312-313, 336-337, 359-360

src/NLog/Logger.cs (1)

444-448: Same collection expression C# 12 concern as the template.

These lines use collection expression syntax [argument], [argument1, argument2], etc., under the NET conditional. As noted in the template file review, this requires C# 12 which is default only in .NET 8+. Ensure consistency with the template's resolution.

Also applies to: 465-469, 489-493, 512-516, 538-542, 563-567

🧹 Nitpick comments (2)
src/NLog/Config/MethodFactory.cs (1)

247-300: Minor: Consider consistent array instantiation.

Lines 252, 262, 271, 281, and 292 use new object[...] while line 229 uses new object?[...]. While both work due to array covariance, using new object?[...] consistently would improve clarity.

Optional: Consistent array instantiation

Consider updating the array instantiations for consistency:

-object?[] methodParameters = new object[defaultMethodParameters.Length];
+object?[] methodParameters = new object?[defaultMethodParameters.Length];

Apply this pattern to lines 252, 262, 271, 281, and 292.

src/NLog/LogFactory.cs (1)

655-655: Consider removing unnecessary namespace qualification.

The fully-qualified NLog.Common.AsyncHelpers.PreventMultipleCalls can be simplified to AsyncHelpers.PreventMultipleCalls since NLog.Common is already imported (line 45). Unless there's a specific ambiguity concern, the shorter form is more consistent with the rest of the codebase.

🔎 Proposed simplification
-asyncContinuation = asyncContinuation is null ? null : NLog.Common.AsyncHelpers.PreventMultipleCalls(asyncContinuation);
+asyncContinuation = asyncContinuation is null ? null : AsyncHelpers.PreventMultipleCalls(asyncContinuation);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8cb19b and ae86481.

📒 Files selected for processing (68)
  • src/NLog/Common/AsyncLogEventInfo.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Conditions/ConditionExpression.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/Config/MethodFactory.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Internal/DictionaryEntryEnumerable.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Internal/ScopeContextAsyncState.cs
  • src/NLog/Internal/SingleItemOptimizedHashSet.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/LogFactory.cs
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Logger-V1Compat.cs
  • src/NLog/Logger-generated.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Logger.cs
  • src/NLog/MessageTemplates/ValueFormatter.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/Targets/MethodCallTarget.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
🚧 Files skipped from review as they are similar to previous changes (35)
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/Targets/MethodCallTarget.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/Common/AsyncLogEventInfo.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Targets/FileTarget.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Internal/SingleItemOptimizedHashSet.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Internal/PropertyHelper.cs
🧰 Additional context used
🧬 Code graph analysis (7)
src/NLog/Logger-generated.cs (1)
src/NLog/Logger.cs (2)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
src/NLog/Conditions/ConditionLiteralExpression.cs (4)
src/NLog/Conditions/ConditionExpression.cs (1)
  • ToString (92-92)
src/NLog/Conditions/ConditionRelationalExpression.cs (1)
  • ToString (79-82)
src/NLog/Conditions/ConditionAndExpression.cs (1)
  • ToString (66-69)
src/NLog/Conditions/ConditionNotExpression.cs (1)
  • ToString (57-60)
src/NLog/Targets/TargetWithContext.cs (3)
src/NLog/Layouts/Typed/Layout.cs (3)
  • ToString (362-365)
  • ToString (374-377)
  • ToString (624-627)
src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (2)
  • ToString (250-253)
  • ToString (272-275)
src/NLog/Layouts/XML/XmlElementBase.cs (1)
  • ToString (800-812)
src/NLog/Targets/LineEndingMode.cs (7)
src/NLog/Common/AsyncLogEventInfo.cs (2)
  • Equals (89-89)
  • Equals (92-92)
src/NLog/Filters/WhenRepeatedFilter.cs (2)
  • Equals (390-406)
  • Equals (408-411)
src/NLog/Internal/PropertiesDictionary.cs (3)
  • Equals (748-748)
  • Equals (750-758)
  • Equals (772-775)
src/NLog/Internal/TargetWithFilterChain.cs (2)
  • Equals (376-379)
  • Equals (381-386)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/LogFactory.cs (2)
  • Equals (1113-1116)
  • Equals (1118-1121)
src/NLog/LogLevel.cs (2)
  • Equals (363-366)
  • Equals (374-377)
src/NLog/Internal/ScopeContextAsyncState.cs (3)
src/NLog/Layouts/Typed/Layout.cs (3)
  • ToString (362-365)
  • ToString (374-377)
  • ToString (624-627)
src/NLog/ScopeContext.cs (2)
  • ToString (525-528)
  • ToString (872-875)
src/NLog/Targets/TargetWithContext.cs (1)
  • ToString (727-730)
src/NLog/Layouts/Typed/Layout.cs (2)
src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (2)
  • ToString (250-253)
  • ToString (272-275)
src/NLog/Targets/TargetWithContext.cs (1)
  • ToString (727-730)
src/NLog/ScopeContext.cs (3)
src/NLog/Internal/ScopeContextAsyncState.cs (4)
  • ToString (326-329)
  • ToString (388-391)
  • ToString (489-492)
  • ToString (586-592)
src/NLog/Targets/TargetWithContext.cs (1)
  • ToString (727-730)
src/NLog/NestedDiagnosticsLogicalContext.cs (1)
  • ToString (207-211)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: task-list-completed
🔇 Additional comments (77)
src/NLog/Internal/TimeoutContinuation.cs (1)

96-96: LGTM! Correct nullable annotation for Timer callback.

The nullable annotation on the state parameter is appropriate since the Timer is constructed with null as the state value (line 57), and the parameter is unused within the method body.

src/NLog/Targets/LineEndingMode.cs (3)

188-191: LGTM! Nullable annotation correctly applied.

The nullable annotation on the object? parameter is appropriate, and the implementation correctly handles null values via pattern matching.


196-199: LGTM! Nullable annotation and null-safe implementation.

The nullable annotation correctly reflects that the method can accept null, and the implementation safely handles null values using the null-conditional operator.


213-217: LGTM! Nullable return type correctly reflects base class behavior.

The nullable return type annotation is appropriate since base.ConvertFrom can potentially return null, making this override correctly reflect the actual return possibilities.

src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs (1)

93-93: LGTM!

The null-coalescing operator correctly handles the case where Path.GetDirectoryName returns null (e.g., for root paths), ensuring the method always returns a non-null string as expected by the return type.

src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs (1)

80-84: LGTM!

The updated preprocessor directive correctly expands the Span-based optimization to all modern .NET (5+) targets. The NET symbol is defined for .NET 5 and later, which all support StringBuilder.Append(ReadOnlySpan<char>), making this a valid broadening of the optimized code path.

src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs (2)

173-180: LGTM!

The nullable return type StackFrame? correctly reflects the reality that StackTrace.GetFrame() can return null in certain scenarios (e.g., .NET Native, heavily optimized code). This aligns with the .NET 10 nullable annotations and makes the API contract explicit.


197-198: LGTM!

The null-conditional operator f?.ToString() properly handles potential null frames. When f is null, Append(null) simply appends nothing, maintaining the existing behavior while satisfying nullable analysis.

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

108-110: Pragmatic nullable fix, but note the minor inconsistency.

The change to return string.Empty for Key satisfies the non-nullable annotation from .NET 10. However, this creates a subtle inconsistency: Entry.Key (from the default DictionaryEntry struct) remains null, while the Key property now returns string.Empty.

This is acceptable because:

  1. MoveNext() always returns false, so these properties should never be accessed in correct enumeration patterns.
  2. Allocating a new DictionaryEntry just for consistency would be unnecessary overhead for an empty enumerator.
src/NLog/Internal/CallSiteInformation.cs (2)

310-321: LGTM! Improved null safety handling.

The refactored null checks properly handle nullable reference types:

  • Explicit null check for stackMethod at the start ensures safe access to its members
  • Defensive check for declaringType before calling IsHiddenClassType
  • Explicit return false statement improves code clarity

These changes align well with the PR objective of fixing nullable warnings from NET10 annotations.


329-329: LGTM! Proper use of null-conditional operator.

The null-conditional operator ?. correctly handles the case where stackMethod might be null, aligning with nullable reference type best practices in .NET.

src/NLog/ScopeContext.cs (1)

525-528: LGTM! Null-safe ToString() implementation.

The addition of the GetType().ToString() fallback ensures this method never returns null, addressing nullable warnings from NET10 annotations. This pattern is consistent with similar implementations elsewhere in the codebase.

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

586-592: LGTM! Null-safe ToString() fallback.

The addition of the GetType().ToString() fallback in the else branch ensures this method never returns null, addressing nullable warnings from NET10 annotations. This is consistent with the same pattern applied in ScopeContext.cs line 527.

src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (4)

469-469: LGTM! Nullable annotation aligns with framework delegate signature.

The nullable annotation on the state parameter correctly matches the TimerCallback delegate signature in .NET, which expects object? state. This change improves null-safety compliance without affecting behavior.


523-523: LGTM! Nullable annotation aligns with framework delegate signature.

The nullable annotation on the state parameter correctly matches the WaitCallback delegate signature. The method safely handles null values using the as operator (line 525) and null checks (line 531).


617-620: LGTM! Nullable annotation aligns with framework event handler signature.

The nullable annotation on the sender parameter correctly matches the EventHandler<LogEventDroppedEventArgs> delegate signature in .NET, where sender is nullable. This change improves null-safety compliance.


622-625: LGTM! Nullable annotation aligns with framework event handler signature.

The nullable annotation on the sender parameter correctly matches the EventHandler<LogEventQueueGrowEventArgs> delegate signature in .NET, where sender is nullable. This change improves null-safety compliance.

src/NLog/LogEventBuilder.cs (1)

123-123: LGTM! Conditional compilation broadened appropriately.

The change from NET9_0_OR_GREATER to NET correctly broadens the availability of the Properties(ReadOnlySpan<(string, object?)>) overload to all .NET 5.0+ targets, since this method doesn't use the params modifier with ReadOnlySpan. This is intentionally different from line 253, which correctly retains NET9_0_OR_GREATER because the params modifier with ReadOnlySpan requires C# 13 (.NET 9.0+).

src/NLog/Layouts/Typed/Layout.cs (4)

362-365: LGTM! Defensive null-coalescing in ToString.

The null-coalescing operator ensures ToString() never returns null, which aligns with nullable reference type best practices and is consistent with similar patterns throughout the codebase.


380-396: LGTM! Correct nullable annotation for Equals override.

The nullable annotation on the obj parameter is the correct signature for overriding Object.Equals and aligns with .NET nullable reference type conventions. The implementation properly handles null values.


401-405: LGTM! Appropriate nullable annotation for IEquatable.

The nullable annotation on the other parameter is appropriate for IEquatable<T> where T might be nullable. The implementation correctly handles null values using object.Equals.


624-627: LGTM! Consistent defensive null-coalescing.

The null-coalescing operator is consistent with the pattern used in FuncMethodValue.ToString() (line 364) and ensures the method never returns null.

src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (1)

272-275: LGTM! Consistent null-coalescing pattern.

The null-coalescing operator follows the same defensive pattern used in Layout.cs (lines 364 and 626), ensuring ToString() never returns null. This change is consistent with the PR objective to fix nullable warnings from .NET nullable reference type annotations.

src/NLog/MessageTemplates/ValueFormatter.cs (2)

266-272: LGTM! Nullable annotation correctly added.

The signature change from string to string? appropriately reflects that the method can receive null values (e.g., from IConvertible.ToString() at line 203). The implementation safely handles null since StringBuilder.Append(string?) treats null as an empty string.


355-363: LGTM! Nullable annotation correctly added.

The signature change from object to object? appropriately reflects that collection items (dictionary keys, values, or list elements) can be null. The implementation correctly handles null through the cascading checks—ultimately reaching SerializeSimpleObject which appends "NULL" (lines 189-193).

src/NLog/Conditions/ConditionExpression.cs (1)

56-58: LGTM! Conditional compilation updated for modern .NET targets.

The change from NETCOREAPP3_0_OR_GREATER to NET appropriately broadens support to all modern .NET versions (5+) while maintaining .NET Standard 2.1+ compatibility. The NotNullIfNotNull attribute is available in both target groups.

src/NLog/Conditions/ConditionMethods.cs (3)

81-84: LGTM! Null safety improved.

The explicit null checks prevent exceptions and provide sensible fallback behavior (returning false when either operand is null).


97-100: LGTM! Null safety improved.

Consistent with the Contains method, this prevents exceptions when either parameter is null.


113-116: LGTM! Null safety improved.

Consistent with the other string predicate methods, this prevents exceptions when either parameter is null.

src/NLog/Conditions/ConditionRelationalExpression.cs (2)

219-220: LGTM! Null-safe LogLevel conversion.

The intermediate variable and null-coalescing operator ensure LogLevel.FromString receives a non-null argument, preventing potential exceptions when Convert.ToString returns null.


226-226: LGTM! Null-safe string promotion.

The null-coalescing operator ensures type promotion to string always produces a non-null value, aligning with nullable reference type expectations.

src/NLog/Conditions/ConditionLiteralExpression.cs (1)

81-81: LGTM! Ensures non-null return value.

The null-coalescing operator guarantees ToString() always returns a non-null string, satisfying the method signature contract when Convert.ToString returns null for edge cases.

src/NLog/Targets/TargetWithContext.cs (1)

727-730: LGTM! Nullable warning properly addressed.

The implementation correctly ensures a non-null return value through the triple fallback chain. While the final ?? GetType().ToString() is technically redundant since base.ToString() never returns null, the defensive approach is good practice and aligns with the PR's objective to fix nullable warnings from NET10 annotations.

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

113-113: Null-safety improvement approved.

The null-coalescing operator appropriately handles the nullable return value from Path.GetDirectoryName, ensuring type-safe behavior for downstream calls to Path.GetFullPath and Directory.Exists. The existing exception handling at lines 127-132 provides additional protection.

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

46-50: No changes needed - nullable parameter change is correct and all callers are compatible.

The nullable annotation on the assembly parameter correctly aligns with .NET nullable reference types. The implementation safely handles null input in both code paths (lines 50 and 52-53), and all five callers are compatible with this signature change. Two callers pass Assembly.GetEntryAssembly() which can legitimately be null, and three callers pass typeof(...).Assembly which is never null—both patterns work correctly with the nullable parameter.

src/NLog/Targets/ColoredConsoleSystemPrinter.cs (1)

89-89: The original concern about excluding .NET Core 3.0/3.1 from the AsSpan path is not applicable—the project targets only net35, net46, netstandard2.0, and netstandard2.1, not any .NET Core versions. The change from NETCOREAPP3_0_OR_GREATER to NET has no impact on supported frameworks, as neither directive activates in the current build matrix. Only the NETSTANDARD2_1_OR_GREATER part of the condition is effective for existing targets.

Likely an incorrect or invalid review comment.

src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs (1)

89-90: Null-safety improvement is correct.

The null-coalescing operator properly addresses the nullable warning. The DeleteOldFilesBeforeArchive method in the parent class explicitly handles the empty directory case by returning false (line 18-19 in BaseFileArchiveHandler.cs), so passing an empty string is safe.

src/NLog/LogEventInfo.cs (3)

161-161: LGTM: Correct API availability guard.

The additional !NET40 exclusion is appropriate since IReadOnlyList<T> was introduced in .NET Framework 4.5 and is not available in .NET Framework 4.0. This change ensures the constructor is only available on platforms that support the required API.


675-675: LGTM: Consistent conditional compilation broadening.

The change from NET9_0_OR_GREATER to NET broadens the availability of the ReadOnlySpan<object?> overload of NeedToPreformatMessage to all .NET 5.0+ targets, which is consistent with the change on line 136. Since ReadOnlySpan<T> is supported in all .NET 5.0+ targets, this change is appropriate.

The verification script provided for line 136 will also validate this change.


136-136: The conditional compilation changes are correct and safe.

The NET preprocessor symbol is defined only for .NET 5.0+ (the unified platform) and does not include older .NET Framework versions (net35, net46). The pattern NETSTANDARD2_1_OR_GREATER || NET correctly enables these code paths only on platforms that support ReadOnlySpan<T>:

  • .NET Standard 2.1+ (includes netstandard2.1)
  • .NET 5.0+ (unified platform)

This correctly excludes net35 and net46, which lack ReadOnlySpan<T> support. The additional !NET40 exclusion at line 161 is also correct, as IReadOnlyList<T> was introduced in .NET Framework 4.5, not 4.0.

src/NLog/Config/MethodFactory.cs (3)

173-173: LGTM: Appropriate use of type inference.

Using var here correctly accommodates the new nullable return type (object?[]) from ResolveDefaultMethodParameters while maintaining readability.


208-221: LGTM: Correct nullable return type.

Changing the return type from object to object? accurately reflects that MethodInfo.Invoke can return null, improving null-safety annotations without altering behavior.


223-245: LGTM: Correct nullable array type for optional parameters.

The return type change from object[] to object?[] and the corresponding array instantiation on line 229 correctly reflect that optional parameters can have null as their default value (line 233). This aligns well with the existing delegate signatures in MethodDetails.

src/NLog/LogFactory.cs (6)

288-288: LGTM: Correct nullable annotation for event handler.

The nullable sender parameter aligns with the standard EventHandler<T> pattern where the sender can be null.


376-411: LGTM: Async disposal implementation with timeout.

The DisposeAsync implementation properly flushes before disposing and uses a timeout to prevent indefinite blocking. Note that if the timeout expires (line 402), the background DisposeInternal task will continue running but the method returns. This is acceptable for disposal scenarios where blocking indefinitely is worse than allowing background cleanup to complete.


1113-1116: LGTM: Correct override signature with nullable parameter.

The nullable obj parameter matches the base Object.Equals(object?) signature and is properly handled by the pattern matching on line 1115.


1227-1247: LGTM: Correct nullable annotation for event handler.

The nullable sender parameter is appropriate for the event handler pattern and matches the usage on line 1231 where null is explicitly passed as the sender.


1249-1274: LGTM: Correct nullable annotation for event handler.

The nullable sender parameter aligns with the event handler pattern and is consistent with the OnLoggerShutdown method signature.


54-56: The NET preprocessor symbol is non-standard and undefined in the project configuration.

The conditional compilation uses || NET, but this bare symbol is not a Microsoft standard preprocessor symbol (standard symbols are version-specific like NET5_0_OR_GREATER, NET6_0_OR_GREATER). The symbol is also not explicitly defined in NLog.csproj. Since NET is undefined, the condition #if NETSTANDARD2_1_OR_GREATER || NET effectively only matches when compiling for .NET Standard 2.1, not for other target frameworks (net35, net46, netstandard2.0).

Define the NET symbol explicitly in the project configuration if it's intended as a catch-all for modern .NET versions, or use version-specific symbols instead.

src/NLog/Logger-generated.cs (6)

269-389: Trace span-based overload routing looks consistent

Trace generic overloads now delegate to WriteToTargetsWithSpan under NETSTANDARD2_1_OR_GREATER || NET with [argument] / [arg1, arg2, arg3] and fall back to new object?[] { ... } otherwise. The guards and culture handling are preserved; routing is consistent and safe.


562-681: Debug span-based overload routing matches Trace pattern

Debug generic overloads mirror the Trace changes: same IsDebugEnabled guards, same culture arguments, and [argument...] span packaging behind NETSTANDARD2_1_OR_GREATER || NET, with object-array fallback otherwise. No functional regressions spotted.


854-973: Info span-based overloads correctly expanded to NET

Info generic overloads are updated to use span-based routing under NETSTANDARD2_1_OR_GREATER || NET and maintain the existing object-array path for older targets. Argument order, culture handling, and enable checks remain unchanged.


1146-1265: Warn generic overloads span-routing is coherent with other levels

Warn generic overloads follow the same pattern as Trace/Debug/Info: the only change is how arguments are packaged ([arg...] vs new object?[]{...}). Control flow and observable behavior are preserved.


1438-1557: Error generic overloads: span vs array paths correctly conditioned

Error generic overloads now use WriteToTargetsWithSpan with [argument...] when NETSTANDARD2_1_OR_GREATER || NET is defined and otherwise fall back to WriteToTargets with new object?[]{...}. Logging level checks and message/formatProvider wiring are intact.


1730-1849: Fatal generic overloads: span-based argument packaging applied consistently

Fatal generic overloads are updated in line with the other levels; only the last argument is re-shaped into [argument...] for the span-based path, guarded by NETSTANDARD2_1_OR_GREATER || NET. No API or behavioral change beyond allocation strategy.

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

96-133: Log(level, message, arg1/arg2/arg3) compat overloads: span calls are correct

The Log overloads using GetTargetsForLevelSafe now delegate to WriteToTargetsWithSpan(..., [arg1, arg2] / [arg1, arg2, arg3]) under NETSTANDARD2_1_OR_GREATER || NET, with an object-array fallback otherwise. This mirrors the main Logger implementation and preserves behavior while enabling the span path.


264-297: Log(level, ..., string? argument) overload: widened to span path correctly

The string?-argument Log overload now uses WriteToTargetsWithSpan(level, null, formatProvider, message, [argument]) when NETSTANDARD2_1_OR_GREATER || NET is defined, and retains the new object?[]{ argument } path otherwise. The IsEnabled(level) guard and formatting semantics remain unchanged.


506-541: Log(level, ..., object? argument) overload: targetsForLevel span routing looks good

For object? argument, the method now calls WriteToTargetsWithSpan(targetsForLevel, level, null, formatProvider, message, [argument]) under NET, with a new object?[] { argument } fallback. This avoids re-looking up targets and matches the span-based helper’s contract.


714-748: Trace V1 overloads (2–3 args) correctly updated to span path

The binary-compat Trace overloads with 2 and 3 object? arguments now route through WriteToTargetsWithSpan(LogLevel.Trace, ..., [arg1, arg2/arg3]) for NET targets, preserving the existing IsTraceEnabled guard and default culture. Fallback behavior for non-NET targets is unchanged.


872-904: Trace(string? / object? argument) overloads: span packaging is consistent

The Trace overloads taking a single string? or object? argument use the same [argument] span packaging pattern under NETSTANDARD2_1_OR_GREATER || NET and still allocate new object?[]{ argument } otherwise. No change in logging semantics, only in argument transport.


1297-1335: Debug V1 overloads (2–3 args) match the updated span pattern

For Debug with 2–3 object? arguments, the updated implementations use WriteToTargetsWithSpan(LogLevel.Debug, ..., [arg1, arg2/arg3]) under NET, with the original object-array path retained for older targets. Guards and culture usage are preserved.


1459-1491: Debug(string? / object? argument) overloads: span usage mirrors Trace/Info

The single-argument Debug overloads now follow the same span-based routing as Trace/Info: [argument] for NET, new object?[]{ argument } otherwise. This keeps the overload set coherent and doesn’t change public behavior.


1888-1922: Info V1 overloads (2–3 args) span-based routing is aligned

Info overloads with 2–3 object? arguments now call WriteToTargetsWithSpan(LogLevel.Info, ..., [arg1, arg2/arg3]) when NET is available. The previous new object?[]{...} array creation is retained in the #else branch. Everything else (guards, culture) stays as before.


2050-2078: Info(string? argument) V1 overloads: span packaging looks correct

For the Info overloads that take a single string? argument (with and without formatProvider), the span-based path under NETSTANDARD2_1_OR_GREATER || NET uses [argument], falling back to new object?[]{ argument } otherwise. Behavior and overload shape are preserved.


2276-2308: Info(object? argument) V1 overloads follow the same span pattern

The Info V1 overloads with a single object? parameter now delegate to WriteToTargetsWithSpan under NET with [argument], keeping the traditional array-based path for other TFMs. The change is consistent with the rest of the file.


2475-2509: Warn V1 overloads (2–3 args) correctly migrated to span routing

Warn overloads with multiple object? arguments now use [arg1, arg2] / [arg1, arg2, arg3] for the NET span path, preserving previous logic and only changing the way arguments are transported.


2633-2665: Warn(string? argument) V1 overloads: span path consistent and symmetric

For single string? arguments, Warn now uses WriteToTargetsWithSpan with [argument] under NET and keeps the new object?[]{ argument } fallback. This is symmetrical with Trace/Debug/Info/Error/Fatal.


2863-2895: Warn(object? argument) V1 overloads use span-based path as expected

The Warn object-based overloads route via [argument] to WriteToTargetsWithSpan for NET targets and retain the previous array allocation otherwise. No other behavior changes.


3062-3096: Error V1 overloads (2–3 args) updated to span routing correctly

Error overloads with 2–3 object? arguments mirror the Warn/Info changes, with [arg1, arg2] / [arg1, arg2, arg3] feeding the span-based path. The structure and guards are unchanged.


3220-3252: Error(string? argument) V1 overloads: span-based routing matches other levels

Single-argument Error overloads for string? now use [argument] for NET while keeping the object-array path for other TFMs, aligned with Trace/Debug/Info/Warn/Fatal.


3450-3482: Error(object? argument) V1 overloads: span callsites wired correctly

The Error overloads that take an object? argument are updated to use WriteToTargetsWithSpan(LogLevel.Error, ..., [argument]) under NET, with unchanged behavior otherwise. Argument order and culture parameters look correct.


3649-3683: Fatal V1 overloads (2–3 args) correctly switch to span-based routing

Fatal multi-argument overloads now feed [arg1, arg2] / [arg1, arg2, arg3] into WriteToTargetsWithSpan in the NET span path. The prior object-array behavior is preserved in the #else branch.


3807-3839: Fatal(string? argument) V1 overloads: span packaging consistent

The Fatal overloads with a single string? argument now use [argument] under NETSTANDARD2_1_OR_GREATER || NET, matching the pattern used across other log levels, with no observable behavior change.


4037-4069: Fatal(object? argument) V1 overloads: span-based path matches Error/Warn

Single object? argument Fatal overloads now call WriteToTargetsWithSpan(LogLevel.Fatal, ..., [argument]) under NET, and fall back to the legacy array-based call otherwise. This keeps the V1 compat surface aligned with the main Logger.

tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs (1)

167-172: Broadened span-based Write override matches runtime capabilities

The PartsWriter.Write(ReadOnlySpan<char> buffer) override is now enabled under NETSTANDARD2_1_OR_GREATER || NET, which aligns with where TextWriter.Write(ReadOnlySpan<char>) exists. The body still just captures the buffer as a string, so behavior is unchanged; only test coverage across TFMs is improved.

tests/NLog.UnitTests/LogMessageFormatterTests.cs (1)

103-109: LGTM! Conditional compilation aligned with production code.

The broadened #if NETSTANDARD2_1_OR_GREATER || NET condition correctly enables Span-based test paths for all modern .NET targets. The use of explicit Span<T> constructor (not collection expressions) is compatible with all NET versions.

Also applies to: 135-140

src/NLog/Logger.cs (1)

606-668: Well-structured span-based logging implementation.

The WriteToTargetsWithSpan methods correctly use in ReadOnlySpan<object?> to avoid unnecessary copies. The implementation appropriately:

  • Handles empty spans with args.IsEmpty ? null : args.ToArray()
  • Provides optimized pre-formatting path for message templates
  • Falls back to string.Format when positional templates are detected
  • Includes proper exception handling with InternalLogger fallback

Comment on lines +191 to 193
#if NETSTANDARD2_1_OR_GREATER || NET
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(text))]
#endif
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

.NET conditional compilation symbols NET 5.0 NET6_0_OR_GREATER documentation

💡 Result:

Short answer

  • SDK-style projects define target-framework preprocessor symbols such as NET5_0 and NET6_0 and also version-range symbols like NET5_0_OR_GREATER and NET6_0_OR_GREATER. Use them with #if / #elif / #endif to conditionally compile framework-specific code. [1][2]

Example
#if NET6_0_OR_GREATER
// .NET 6+ APIs
#elif NET5_0
// .NET 5 specific code
#else
// older frameworks
#endif

References

  • Preprocessor directives (lists NET*, NET5_0, NET6_0 and _OR_GREATER symbols). [1]
  • Target frameworks in SDK-style projects (complete list of predefined framework symbols and notes on _OR_GREATER behavior). [2]

Sources
[1] Microsoft Docs — Preprocessor directives (C#).
[2] Microsoft Docs — Target frameworks in SDK-style projects / Preprocessor symbols.


🏁 Script executed:

# Check the actual file to understand the context
head -200 src/NLog/Layouts/SimpleLayout.cs | tail -20

Repository: NLog/NLog

Length of output: 937


🏁 Script executed:

# Search for usage of NET and NET5_0_OR_GREATER symbols in the codebase
rg "NET5_0_OR_GREATER|NETCOREAPP3_0_OR_GREATER|#if.*NET[^A-Z]" --max-count=30

Repository: NLog/NLog

Length of output: 21018


Use a documented preprocessor symbol instead of the non-standard NET.

The NET symbol used at line 191 is not a standard .NET preprocessor constant according to Microsoft's official documentation. Standard symbols for .NET 5.0+ are versioned, such as NET5_0_OR_GREATER and NET6_0_OR_GREATER. The codebase consistently uses these documented symbols throughout.

Change to:

-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER

This ensures the attribute is applied consistently on all .NET 5.0+ frameworks where NotNullIfNotNull is available.

📝 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
#if NETSTANDARD2_1_OR_GREATER || NET
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(text))]
#endif
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
[return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(text))]
#endif
🤖 Prompt for AI Agents
In src/NLog/Layouts/SimpleLayout.cs around lines 191 to 193, the preprocessor
symbol `NET` is non-standard; replace it with the documented versioned symbol
for .NET 5.0+ (e.g., `NET5_0_OR_GREATER`) so the attribute applies on .NET 5.0
and later; update the conditional to use `#if NETSTANDARD2_1_OR_GREATER ||
NET5_0_OR_GREATER` and ensure spacing/formatting matches surrounding code for
consistency.

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/NLog/LogLevelTypeConverter.cs (2)

52-61: Inconsistent nullable annotations for context and culture parameters.

The context parameter should be ITypeDescriptorContext? (nullable) and culture should be CultureInfo? (nullable) to match:

  1. The nullable context used in CanConvertFrom (Line 46)
  2. The .NET 10 TypeConverter base class signature

Both parameters are only passed to base methods and never dereferenced, so making them nullable is safe.

🔎 Proposed fix
-        public override object? ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+        public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)

70-80: Inconsistent nullable annotations for context, culture, and value parameters.

The parameters should be nullable to match:

  1. The nullable context used in CanConvertTo (Line 64)
  2. The .NET 10 TypeConverter base class signature: ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)

All three parameters are either only passed to base methods or safely handled with pattern matching, so making them nullable is safe.

🔎 Proposed fix
-        public override object? ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+        public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
♻️ Duplicate comments (5)
src/NLog/Internal/LogMessageTemplateFormatter.cs (1)

202-202: Critical regression: undefined preprocessor symbol NET introduced.

This line was changed from #if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER to #if NETSTANDARD2_1_OR_GREATER || NET. The symbol NET is not a standard MSBuild-defined preprocessor constant, so the condition will only evaluate to true for netstandard2.1 targets, not for modern .NET (5.0+). This means the span-based Render overload on line 203 won't be compiled for .NET 5+ targets.

Replace NET with:

  • NET5_0_OR_GREATER (to support all .NET 5+ targets)
  • Or revert to the original NET9_0_OR_GREATER (if .NET 9+ only was intended)

The same pattern appears across the codebase (~104 occurrences per the previous review) and needs consistent correction.

🔎 Proposed fix
-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
src/NLog/Logger-generated.cs (1)

274-275: Verify C# language version for NET5-7 targets or use NET8_0_OR_GREATER.

The collection expression syntax [argument] requires C# 12, but the NET symbol includes .NET 5-7 which default to C# 9-11 respectively. This will cause compilation errors unless the project explicitly sets <LangVersion>12</LangVersion> or higher.

Based on the past review comment that was marked as addressed, this issue should have been resolved. However, the code still uses NET instead of NET8_0_OR_GREATER. Please verify:

#!/bin/bash
# Check if the project file explicitly sets LangVersion to C# 12 or higher
echo "=== Checking for LangVersion in project files ==="
fd -e csproj -x cat {} \; | rg -i "LangVersion"

echo -e "\n=== Checking target frameworks in NLog project ==="
cat src/NLog/NLog.csproj | rg -i "TargetFramework"

If the project targets .NET 5-7 without explicit LangVersion>=12, consider using NET8_0_OR_GREATER instead of NET on lines 274, 293, 315, 336, 360, 383, and all similar occurrences throughout the file.

Also applies to: 293-294, 315-316, 336-337, 360-361, 383-384

src/NLog/Logger-generated.tt (1)

250-251: Template should use NET8_0_OR_GREATER for collection expressions.

This template generates code using collection expression syntax [argument] which requires C# 12. The NET symbol includes .NET 5-7 (defaulting to C# 9-11), which will cause compilation errors unless LangVersion is explicitly set to 12 or higher.

The past review comment on this template recommended using NET8_0_OR_GREATER instead of NET, but the issue persists. Fixing this template will automatically fix the generated Logger-generated.cs file.

Consider changing the conditional from:

#if NETSTANDARD2_1_OR_GREATER || NET

To:

#if NETSTANDARD2_1_OR_GREATER || NET8_0_OR_GREATER

This ensures collection expressions are only used on targets where C# 12 is the default language version.

Also applies to: 269-270, 291-292, 312-313, 336-337, 359-360

src/NLog/Logger.cs (1)

444-445: Collection expressions require C# 12 but NET includes older targets.

The collection expression syntax (e.g., [argument], [argument1, argument2]) requires C# 12, which is the default starting with .NET 8. The NET symbol includes .NET 5-7, which default to C# 9-11 and will fail to compile without an explicit <LangVersion>12</LangVersion> setting.

This is the same issue flagged in Logger-generated.tt and Logger-generated.cs. Verify the project's language version configuration:

#!/bin/bash
# Check NLog.csproj for LangVersion and TargetFrameworks
echo "=== Checking NLog.csproj configuration ==="
cat src/NLog/NLog.csproj | rg -i "(LangVersion|TargetFramework)"

echo -e "\n=== Searching for any conditional LangVersion settings ==="
rg -n "LangVersion" src/NLog/NLog.csproj

If the project targets .NET 5-7, either:

  1. Use NET8_0_OR_GREATER instead of NET in the conditionals, or
  2. Ensure <LangVersion>12</LangVersion> is set in the project file

Also applies to: 465-466, 489-490, 512-513, 538-539, 563-564

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

2043-2045: Preprocessor typo: NETNET9_0_OR_GREATER should be NET9_0_OR_GREATER or NET

Line 2043 contains a typo with double "NET" prefix. This prevents the [OverloadResolutionPriority(-1)] attribute from being applied correctly on .NET builds.

🔎 Proposed fix
-#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATER
+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
         [OverloadResolutionPriority(-1)]
 #endif
🧹 Nitpick comments (1)
src/NLog/LogFactory.cs (1)

655-655: Consider reverting to unqualified AsyncHelpers for consistency.

The fully qualified NLog.Common.AsyncHelpers is unnecessary since using NLog.Common; is declared at line 45. The rest of the file uses unqualified names for types from NLog.Common.

🔎 Suggested consistency fix
-                asyncContinuation = asyncContinuation is null ? null : NLog.Common.AsyncHelpers.PreventMultipleCalls(asyncContinuation);
+                asyncContinuation = asyncContinuation is null ? null : AsyncHelpers.PreventMultipleCalls(asyncContinuation);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ae86481 and 7f08e88.

📒 Files selected for processing (68)
  • src/NLog/Common/AsyncLogEventInfo.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Conditions/ConditionExpression.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/Config/MethodFactory.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Internal/DictionaryEntryEnumerable.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Internal/ScopeContextAsyncState.cs
  • src/NLog/Internal/SingleItemOptimizedHashSet.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/LogFactory.cs
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Logger-V1Compat.cs
  • src/NLog/Logger-generated.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Logger.cs
  • src/NLog/MessageTemplates/ValueFormatter.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/Targets/MethodCallTarget.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
🚧 Files skipped from review as they are similar to previous changes (41)
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/Config/MethodFactory.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Conditions/ConditionExpression.cs
  • src/NLog/Internal/DictionaryEntryEnumerable.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/LogEventInfo.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/Common/AsyncLogEventInfo.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-12T01:22:52.437Z
Learnt from: thatrajeevkr
Repo: NLog/NLog PR: 5999
File: src/NLog/Layouts/JSON/JsonLayout.cs:162-166
Timestamp: 2025-09-12T01:22:52.437Z
Learning: When suggesting StringBuilder optimizations for NLog layouts marked as [ThreadAgnostic], avoid shared StringBuilder fields as they create thread safety issues. Prefer simple string operations or ThreadLocal<StringBuilder> if allocation profiling shows a real performance impact.

Applied to files:

  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/Internal/StringBuilderExt.cs
🧬 Code graph analysis (12)
src/NLog/Internal/ScopeContextAsyncState.cs (4)
src/NLog/Layouts/Typed/Layout.cs (3)
  • ToString (362-365)
  • ToString (374-377)
  • ToString (624-627)
src/NLog/ScopeContext.cs (2)
  • ToString (525-528)
  • ToString (872-875)
src/NLog/Targets/TargetWithContext.cs (1)
  • ToString (727-730)
src/NLog/Layouts/JSON/JsonLayout.cs (1)
  • ToString (475-483)
src/NLog/Internal/CallSiteInformation.cs (1)
src/NLog/Internal/StackTraceUsageUtils.cs (1)
  • StackTraceUsageUtils (44-268)
src/NLog/Logger-generated.cs (1)
src/NLog/Logger.cs (2)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
src/NLog/Internal/PropertyHelper.cs (1)
src/NLog/Internal/ReflectionHelpers.cs (1)
  • IsValidPublicProperty (183-186)
src/NLog/Logger.cs (1)
src/NLog/LayoutRenderers/LayoutRenderer.cs (1)
  • IFormatProvider (218-221)
src/NLog/Logger-V1Compat.cs (1)
src/NLog/Logger.cs (7)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
  • WriteToTargets (827-831)
  • WriteToTargets (833-841)
  • WriteToTargets (843-853)
  • WriteToTargets (855-863)
  • WriteToTargets (866-874)
src/NLog/Config/PropertyTypeConverter.cs (1)
src/NLog/Internal/StringHelpers.cs (1)
  • StringHelpers (45-170)
src/NLog/LogFactory.cs (5)
src/NLog/Common/AsyncHelpers.cs (1)
  • AsyncHelpers (46-382)
src/NLog/Internal/TargetWithFilterChain.cs (2)
  • Equals (376-379)
  • Equals (381-386)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/LogLevel.cs (2)
  • Equals (363-366)
  • Equals (374-377)
src/NLog/Internal/ReflectionHelpers.cs (1)
src/NLog/Internal/ObjectReflectionCache.cs (1)
  • PropertyInfo (222-249)
src/NLog/Internal/PropertiesDictionary.cs (7)
src/NLog/Common/AsyncLogEventInfo.cs (2)
  • Equals (89-89)
  • Equals (92-92)
src/NLog/Filters/WhenRepeatedFilter.cs (2)
  • Equals (390-406)
  • Equals (408-411)
src/NLog/Internal/ObjectReflectionCache.cs (1)
  • Equals (553-556)
src/NLog/Internal/TargetWithFilterChain.cs (2)
  • Equals (376-379)
  • Equals (381-386)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/LogFactory.cs (2)
  • Equals (1113-1116)
  • Equals (1118-1121)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/Internal/TargetWithFilterChain.cs (6)
src/NLog/Filters/WhenRepeatedFilter.cs (2)
  • Equals (390-406)
  • Equals (408-411)
src/NLog/Internal/ObjectReflectionCache.cs (1)
  • Equals (553-556)
src/NLog/Internal/PropertiesDictionary.cs (3)
  • Equals (748-748)
  • Equals (750-758)
  • Equals (772-775)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/LogLevel.cs (2)
  • Equals (363-366)
  • Equals (374-377)
src/NLog/LogLevelTypeConverter.cs (3)
src/NLog/Targets/LineEndingMode.cs (2)
  • CanConvertFrom (207-210)
  • ConvertFrom (213-217)
src/NLog/Internal/PropertyHelper.cs (1)
  • Type (186-190)
src/NLog/Config/PropertyTypeConverter.cs (1)
  • CultureInfo (199-208)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: task-list-completed
🔇 Additional comments (37)
src/NLog/Internal/TargetWithFilterChain.cs (1)

376-379: LGTM! Nullable annotation correctly applied.

The nullable annotation on the Equals method parameter aligns with .NET nullable reference type best practices. The implementation correctly handles null through pattern matching, which returns false when obj is null.

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

96-96: LGTM! Correct nullable annotation for Timer callback.

The parameter type change aligns with the TimerCallback delegate signature, which expects object? state. Since null is passed as the state argument on line 57 and the parameter is unused in the method body, this is a safe and appropriate nullable annotation.

src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs (1)

178-178: LGTM! Nullable annotation correctly reflects GetEntryAssembly() behavior.

The change accurately annotates that Assembly.GetEntryAssembly() can return null in certain hosting scenarios. The existing code at lines 157, 160, and 163 already handles this case correctly using null-conditional operators.

src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs (1)

274-313: LGTM! Nullable annotation correctly applied.

The nullable annotation on the state parameter correctly aligns with .NET's Timer callback signature. The existing null-safe handling using (state as int?) ?? 0 on line 280 ensures no behavioral change.

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

750-758: LGTM! Nullable annotation correctly added to Equals override.

The signature now properly declares that obj can be null, aligning with .NET's nullable reference types and the standard object.Equals(object?) signature. The method body already handles null safely by returning false when the object doesn't match the expected types.

src/NLog/Conditions/ConditionLiteralExpression.cs (1)

81-81: LGTM! Defensive null-coalescing for .NET 10 nullability compliance.

The addition of ?? string.Empty correctly addresses the .NET 10 nullable annotation warnings for Convert.ToString() while ensuring ToString() always returns a non-null string as per its signature. Since LiteralValue is already verified as non-null at this point (line 66), this is purely defensive programming to satisfy the compiler's nullability analysis.

src/NLog/Config/PropertyTypeConverter.cs (1)

199-208: LGTM! Explicit null check improves nullable reference type analysis.

The explicit stringValue is null check on Line 201 aligns with the PR objective of addressing .NET 10 nullable warnings. While StringHelpers.IsNullOrWhiteSpace already handles null values correctly, the explicit check makes the null-handling intent clearer and helps the nullable reference type analyzer.

src/NLog/Targets/MethodCallTarget.cs (1)

270-272: LGTM! Null-check correctly handles nullable InnerException.

The null-check properly addresses the .NET 10 nullable annotation for TargetInvocationException.InnerException. The fallback to throw; ensures the original exception is rethrown with its stack trace intact when InnerException is null.

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

586-592: LGTM! Consistent null-safety pattern.

The addition of ?? GetType().ToString() aligns with the defensive null-handling pattern used consistently throughout the codebase for ToString() overrides (e.g., ScopeContext.ScopeContextPropertiesCollapsed.ToString(), TargetWithContext, and multiple Layout classes). This appropriately addresses .NET 10 nullable annotations.

src/NLog/MessageTemplates/ValueFormatter.cs (2)

266-272: LGTM! Nullability annotation aligns with .NET 10.

The parameter change from string to string? correctly addresses .NET 10 nullable annotations. StringBuilder.Append(string?) safely handles null values, and the change accommodates call sites like Line 203 where IConvertible.ToString() may return null in .NET 10.


355-363: LGTM! Nullability annotation correctly reflects collection semantics.

The parameter change from object to object? appropriately handles nullable collection items. The method implementation safely handles null through pattern matching (lines 357, 359) and delegates to methods that accept nullable values (SerializeSimpleObject checks for null at Line 189, Convert.ToString handles null).

src/NLog/Targets/LineEndingMode.cs (3)

188-191: LGTM! Correct nullable annotation for Object.Equals override.

The nullable parameter annotation aligns with standard C# patterns for Object.Equals, and the implementation correctly handles null input through pattern matching.


196-199: LGTM! Correct nullable annotation for IEquatable implementation.

The nullable parameter annotation follows the standard IEquatable<T> pattern, and the implementation correctly returns false for null input using the null-conditional operator.


213-217: LGTM! Correct nullable return type for TypeConverter override.

The nullable return type correctly reflects that base.ConvertFrom can return null, improving type safety for consumers of this converter.

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

242-245: LGTM! Nullable parameter annotations correctly align with .NET interface.

The updated signature correctly reflects the nullable reference type annotations in .NET's IEqualityComparer<T>.Equals interface. The implementation using ReferenceEquals handles null values correctly.

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

107-107: LGTM: Good null-safety improvement.

The null-coalescing operator appropriately guards against a potential null return from method.ToString(), which may be annotated as nullable in .NET 10. The fallback to methodName preserves the original behavior.

src/NLog/LogLevelTypeConverter.cs (2)

46-49: LGTM - Nullable context annotation is correct.

The nullable context parameter aligns with the .NET 10 TypeConverter base class signature.


64-67: LGTM - Nullable annotations are correct.

The nullable context and destinationType parameters align with the .NET 10 TypeConverter base class signature.

src/NLog/Internal/CallSiteInformation.cs (2)

310-321: LGTM! Excellent null-safety improvements.

The refactoring effectively addresses nullable reference type warnings:

  • Early null check (line 310) prevents passing null to LookupAssemblyFromMethod, which expects a non-nullable MethodBase
  • Extracting declaringType to a local variable (line 317) improves readability and ensures proper null checking before calling IsHiddenClassType
  • Explicit return false (line 321) clarifies the control flow

These changes align well with the PR's objective to fix nullable warnings from .NET 10 annotations.


329-329: LGTM! Minor style improvement.

Using var for the local variable declaration improves consistency with modern C# conventions.

src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs (1)

93-93: LGTM: Null-safety improvement.

The null-coalescing operator correctly ensures ResolveNLogDir always returns a non-null string, as Path.GetDirectoryName can return null per the .NET API.

src/NLog/Internal/ReflectionHelpers.cs (3)

183-186: LGTM: Correct nullable parameter annotation.

Making the PropertyInfo parameter nullable accurately reflects the method's implementation, which already handles null via the p != null check. This aligns with the usage in ObjectReflectionCache.cs where the method is called within LINQ expressions on potentially null PropertyInfo instances.


188-195: LGTM: Accurate nullable return type.

Changing the return type to object? correctly reflects that property values can be null. The parameter p remaining non-nullable is also appropriate since a valid PropertyInfo instance is required to retrieve a value.


74-74: The preprocessor directive change aligns with the codebase's standard pattern. The actual code uses #if NETSTANDARD2_1_OR_GREATER || NET, which is the established pattern used consistently across 40+ locations in the codebase (LogEventInfo.cs, Logger-generated.cs, SimpleLayout.cs, and others). The runtime check for IsDynamicCodeSupported is present and handles multi-targeting correctly. No concerns with this change.

src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs (1)

55-58: LGTM! Correct nullable annotation for Thread.Name.

The nullable return type properly reflects that Thread.CurrentThread.Name can be null. The existing usage at line 52 with StringBuilder.Append() is safe since it handles null values by treating them as empty strings.

tests/NLog.UnitTests/LogMessageFormatterTests.cs (1)

103-103: LGTM: Test coverage broadened to all NET targets.

The preprocessor condition change from NETCOREAPP3_1_OR_GREATER to NET correctly aligns test coverage with the implementation changes, enabling span-based LogEventInfo3 testing on all NET targets.

Also applies to: 135-135

src/NLog/Logger.cs (2)

606-607: LGTM: Correct use of 'in' modifier with ReadOnlySpan.

The method signature changes from params ReadOnlySpan<object?> to in ReadOnlySpan<object?> are correct. The in modifier ensures the ReadOnlySpan is passed by reference without defensive copies, which is the recommended pattern for ReadOnlySpan parameters.

Also applies to: 616-616


644-667: LGTM: Preformatted log event rendering implementation.

The RenderPreformattedLogEvent method correctly uses the in modifier with ReadOnlySpan and includes proper error handling for template formatting failures.

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

101-106: Span-based logging optimization looks good

The changes consistently route logging calls to WriteToTargetsWithSpan with collection expression syntax [arg1, arg2] for NETSTANDARD2_1_OR_GREATER || NET targets, while maintaining backward compatibility with object[] arrays for older targets. This aligns with the PR's objective to leverage modern runtime features and reduce allocations.

Also applies to: 127-132, 268-273, 511-516, 718-723, 1106-1111

src/NLog/LogFactory.cs (5)

288-296: LGTM: Event handler sender parameter correctly annotated as nullable.

The nullable annotation on the sender parameter is appropriate for event handlers and consistent with the other event handler signatures in this file (lines 1227, 1249).


1113-1116: LGTM: Standard nullable override for Equals method.

The nullable annotation on the obj parameter correctly matches the modern signature of Object.Equals and is consistent with similar patterns in other files (LogLevel.cs, LineEndingMode.cs, etc.).


1227-1247: LGTM: Event handler correctly annotated with nullable sender.

The nullable annotation is appropriate for this event handler. Note that line 1231 explicitly passes null as the sender when invoking _loggerShutdown, confirming that null is an expected value.


1249-1274: LGTM: Event handler correctly annotated with nullable sender.

The nullable annotation is appropriate and consistent with the other event handler signatures in this file.


54-56: Conditional compilation directive correctly gates IAsyncDisposable to supported frameworks.

The change from NETCOREAPP3_1_OR_GREATER to NETSTANDARD2_1_OR_GREATER || NET is correct. IAsyncDisposable is only available in .NET Standard 2.1+ and .NET Core 3.1+, so the new directive properly excludes the older frameworks in the project's target list (net35, net46, netstandard2.0) where it is unavailable.

src/NLog/Internal/PropertyHelper.cs (2)

439-439: Good defensive null-check for nullable reference type analysis.

While IsValidPublicProperty() already handles null internally, the explicit check helps .NET 10's nullable reference type analysis and makes the intent clearer.


175-184: The Try* pattern implementation is correct and consistent across all callers.

The method correctly returns true when a property is found and false when not found, following standard .NET Try* conventions. All callers throughout the codebase (LayoutParser.cs and LoggingConfigurationParser.cs) properly handle this return value, treating true as success and false as failure. The public overload at line 150 correctly delegates to the private method at line 158 and returns its result unchanged.

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

424-432: This review comment is based on an incorrect premise and should be disregarded.

The file src/NLog/Internal/StringBuilderExt.cs is being created as a new file in this commit, not modified. There was no previous preprocessor condition to change from NETCOREAPP3_0_OR_GREATER to NET.

The condition #if NETSTANDARD2_1_OR_GREATER || NET is appropriate for the project's actual target frameworks (net35, net46, netstandard2.0, netstandard2.1):

  • netstandard2.1 has LangVersion=13, which fully supports C# 12 collection expressions
  • The NET macro correctly covers .NET 5.0+ unified framework
  • The project never targeted netcoreapp3.0 or netcoreapp3.1, so there is no excluded framework to fall back to the multiple-Append path

Likely an incorrect or invalid review comment.

}

string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue();
string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Find the IStringValueRenderer interface definition
echo "=== IStringValueRenderer interface definition ==="
find . -type f -name "*.cs" | xargs grep -l "interface IStringValueRenderer" | head -5
echo ""

# Get the interface definition
find . -type f -name "*.cs" | xargs grep -A 10 "interface IStringValueRenderer"

Repository: NLog/NLog

Length of output: 16429


🏁 Script executed:

#!/bin/bash

# Find all IStringValueRenderer implementations with their GetFormattedString method
echo "=== All IStringValueRenderer implementations ==="
rg -B 2 "IStringValueRenderer" --type=cs | head -100

Repository: NLog/NLog

Length of output: 7116


🏁 Script executed:

#!/bin/bash

# Search specifically for GetFormattedString implementations
echo "=== GetFormattedString method implementations ==="
rg -n "GetFormattedString" --type=cs -A 1 | head -150

Repository: NLog/NLog

Length of output: 5909


Fix inconsistent return types across IStringValueRenderer implementations.

The interface signature defines string? as the return type, but several implementations incorrectly return string (non-nullable): LoggerNameLayoutRenderer, ShortDateLayoutRenderer, ThreadIdLayoutRenderer, GuidLayoutRenderer, EnvironmentUserLayoutRenderer, CurrentDirLayoutRenderer, DateLayoutRenderer, CallSiteFileNameLayoutRenderer, and AppSettingLayoutRenderer. Update all implementations to consistently return string? to match the interface contract.

Comment on lines +2749 to 2751
#if NETSTANDARD2_1_OR_GREATER || NET
[OverloadResolutionPriority(-1)]
#endif
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Incorrect preprocessor symbol for OverloadResolutionPriority attribute

Line 2749 uses || NET instead of || NET9_0_OR_GREATER. The OverloadResolutionPriority attribute was introduced in .NET 9.0, so using NET (which matches all .NET versions including pre-9.0) will cause compilation errors on older .NET versions.

🔎 Proposed fix
-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
         [OverloadResolutionPriority(-1)]
 #endif
📝 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
#if NETSTANDARD2_1_OR_GREATER || NET
[OverloadResolutionPriority(-1)]
#endif
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
[OverloadResolutionPriority(-1)]
#endif
🤖 Prompt for AI Agents
In src/NLog/Logger-V1Compat.cs around lines 2749 to 2751, the preprocessor check
uses "NET" causing the OverloadResolutionPriority attribute to be compiled for
older runtimes; change the condition to use NET9_0_OR_GREATER so the attribute
is only included for .NET 9.0+ (i.e., replace "|| NET" with "||
NET9_0_OR_GREATER"), keeping the NETSTANDARD2_1_OR_GREATER branch as-is.

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

♻️ Duplicate comments (5)
src/NLog/Layouts/SimpleLayout.cs (1)

191-193: Non-standard preprocessor symbol NET should be replaced with versioned symbol.

The use of NET as a preprocessor symbol is not standard according to Microsoft documentation. As noted in the previous review, the documented symbols for .NET 5.0+ are versioned (e.g., NET5_0_OR_GREATER, NET6_0_OR_GREATER). The codebase consistently uses these versioned symbols elsewhere.

Consider changing to:

-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER

This ensures the NotNullIfNotNull attribute is applied consistently across all .NET 5.0+ frameworks where it is available.

src/NLog/Internal/ObjectGraphScanner.cs (2)

201-203: Previous concern about null filtering remains unaddressed.

The past review comment on these lines identified a critical logic inconsistency: null filtering here creates behavioral changes beyond fixing nullable warnings. The concern that ScanPropertiesList at line 165 checks for null elements, but this filtering makes that check inconsistent depending on the list type (readonly/array ILists at line 209 bypass filtering) remains valid.

As noted previously, this requires either:

  • Removing null filtering to preserve original behavior, OR
  • Adjusting the logic consistently and adding test coverage

Please clarify if the runtime behavior change is intentional or if the goal is purely compile-time null-safety.


214-214: Same null-filtering concern applies here.

This line also introduces runtime null filtering, contributing to the inconsistent behavior identified in the past review. Non-IList enumerables now have nulls filtered, while readonly/array ILists (line 209) do not. This inconsistency should be resolved as part of addressing the broader concern.

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

2043-2045: Typo in preprocessor directive: NETNET9_0_OR_GREATER

Line 2043 contains NETNET9_0_OR_GREATER (double "NET"), which will never be defined. Although a previous review comment indicates this was "Addressed in commit b8cb19b", the typo still appears in the current code.

🔎 Proposed fix
-#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATER
+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
         [OverloadResolutionPriority(-1)]
 #endif

2749-2751: Incorrect preprocessor symbol for OverloadResolutionPriority attribute

Line 2749 uses || NET instead of || NET9_0_OR_GREATER. The OverloadResolutionPriority attribute was introduced in .NET 9.0, so using NET (which matches all .NET versions including pre-9.0) will cause compilation errors on .NET 6, 7, and 8.

🔎 Proposed fix
-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
         [OverloadResolutionPriority(-1)]
 #endif
🧹 Nitpick comments (3)
src/NLog/Targets/MethodCallTarget.cs (1)

270-272: Nullable fix looks good; consider preserving stack traces.

The null check correctly addresses .NET 10's nullable annotations on TargetInvocationException.InnerException. The fallback to throw; when InnerException is null properly preserves the original exception context.

However, throw ex.InnerException; on line 271 resets the stack trace of the inner exception. Consider using ExceptionDispatchInfo to preserve the original stack trace:

🔎 Suggested improvement to preserve stack traces
+using System.Runtime.ExceptionServices;

Then update the catch block:

 catch (TargetInvocationException ex)
 {
     InternalLogger.Warn("{0}: Failed to invoke method - {1}", this, ex.Message);
     if (ex.InnerException != null)
-        throw ex.InnerException;
+        ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
     throw;
 }
src/NLog/Conditions/ConditionRelationalExpression.cs (1)

219-220: LGTM! Correctly handles nullable Convert.ToString result.

The changes appropriately address .NET 10's nullable annotations by applying null-coalescing to ensure a non-null string is passed to LogLevel.FromString. The try-catch block will handle any parsing failures from an empty string input.

Optional: Consider inline null-coalescing for conciseness

The temporary variable strval could be eliminated:

-                    var strval = Convert.ToString(val, CultureInfo.InvariantCulture);
-                    val = LogLevel.FromString(strval ?? string.Empty);
+                    val = LogLevel.FromString(Convert.ToString(val, CultureInfo.InvariantCulture) ?? string.Empty);

Both approaches are valid; the current version may be clearer for debugging.

src/NLog/LogFactory.cs (1)

655-655: Remove unnecessary fully qualified name.

The fully qualified name NLog.Common.AsyncHelpers.PreventMultipleCalls is unnecessary since NLog.Common is already imported at Line 45. This is inconsistent with the rest of the codebase.

🔎 Proposed simplification
-asyncContinuation = asyncContinuation is null ? null : NLog.Common.AsyncHelpers.PreventMultipleCalls(asyncContinuation);
+asyncContinuation = asyncContinuation is null ? null : AsyncHelpers.PreventMultipleCalls(asyncContinuation);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f08e88 and 1868610.

📒 Files selected for processing (68)
  • src/NLog/Common/AsyncLogEventInfo.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Conditions/ConditionExpression.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/Config/MethodFactory.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Internal/DictionaryEntryEnumerable.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Internal/ScopeContextAsyncState.cs
  • src/NLog/Internal/SingleItemOptimizedHashSet.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/LogFactory.cs
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Logger-V1Compat.cs
  • src/NLog/Logger-generated.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Logger.cs
  • src/NLog/MessageTemplates/ValueFormatter.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/Targets/MethodCallTarget.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
🚧 Files skipped from review as they are similar to previous changes (30)
  • src/NLog/Internal/ScopeContextAsyncState.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Conditions/ConditionExpression.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Internal/SingleItemOptimizedHashSet.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/MessageTemplates/ValueFormatter.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-12T01:22:52.437Z
Learnt from: thatrajeevkr
Repo: NLog/NLog PR: 5999
File: src/NLog/Layouts/JSON/JsonLayout.cs:162-166
Timestamp: 2025-09-12T01:22:52.437Z
Learning: When suggesting StringBuilder optimizations for NLog layouts marked as [ThreadAgnostic], avoid shared StringBuilder fields as they create thread safety issues. Prefer simple string operations or ThreadLocal<StringBuilder> if allocation profiling shows a real performance impact.

Applied to files:

  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Internal/StringBuilderExt.cs
🧬 Code graph analysis (16)
src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs (2)
src/NLog/Internal/ReflectionHelpers.cs (1)
  • ReflectionHelpers (45-196)
src/NLog/Targets/MethodCallTarget.cs (2)
  • ReflectionHelpers (184-197)
  • ReflectionHelpers (206-220)
src/NLog/Config/LoggingConfiguration.cs (2)
src/NLog/Config/LoggingRule.cs (1)
  • Target (134-134)
src/NLog/Internal/ThreadSafeDictionary.cs (1)
  • TryGetValue (156-159)
src/NLog/Conditions/ConditionRelationalExpression.cs (1)
src/NLog/Config/PropertyTypeConverter.cs (2)
  • Convert (84-114)
  • CultureInfo (199-208)
src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (1)
src/NLog/Layouts/Typed/Layout.cs (3)
  • ToString (362-365)
  • ToString (374-377)
  • ToString (624-627)
src/NLog/Targets/LineEndingMode.cs (3)
src/NLog/Common/AsyncLogEventInfo.cs (2)
  • Equals (89-89)
  • Equals (92-92)
src/NLog/LogFactory.cs (2)
  • Equals (1113-1116)
  • Equals (1118-1121)
src/NLog/LogLevel.cs (2)
  • Equals (363-366)
  • Equals (374-377)
src/NLog/Logger-V1Compat.cs (1)
src/NLog/Logger.cs (7)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
  • WriteToTargets (827-831)
  • WriteToTargets (833-841)
  • WriteToTargets (843-853)
  • WriteToTargets (855-863)
  • WriteToTargets (866-874)
src/NLog/Internal/PropertyHelper.cs (1)
src/NLog/Internal/ReflectionHelpers.cs (1)
  • IsValidPublicProperty (183-186)
src/NLog/Common/AsyncLogEventInfo.cs (7)
src/NLog/Filters/WhenRepeatedFilter.cs (2)
  • Equals (390-406)
  • Equals (408-411)
src/NLog/Internal/PropertiesDictionary.cs (4)
  • Equals (748-748)
  • Equals (750-758)
  • Equals (772-775)
  • Equals (786-800)
src/NLog/Internal/TargetWithFilterChain.cs (2)
  • Equals (376-379)
  • Equals (381-386)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/LogFactory.cs (2)
  • Equals (1113-1116)
  • Equals (1118-1121)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/Config/DynamicRangeLevelFilter.cs (1)
  • Equals (140-143)
src/NLog/Logger.cs (1)
src/NLog/LayoutRenderers/LayoutRenderer.cs (1)
  • IFormatProvider (218-221)
src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (2)
src/NLog/Targets/Wrappers/LogEventDroppedEventArgs.cs (2)
  • LogEventDroppedEventArgs (41-53)
  • LogEventDroppedEventArgs (47-47)
src/NLog/Targets/Wrappers/LogEventQueueGrowEventArgs.cs (2)
  • LogEventQueueGrowEventArgs (44-66)
  • LogEventQueueGrowEventArgs (51-55)
src/NLog/Internal/CallSiteInformation.cs (1)
src/NLog/Internal/StackTraceUsageUtils.cs (1)
  • StackTraceUsageUtils (44-268)
src/NLog/Logger-generated.cs (1)
src/NLog/Logger.cs (2)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
src/NLog/LogLevelTypeConverter.cs (1)
src/NLog/Targets/LineEndingMode.cs (2)
  • CanConvertFrom (207-210)
  • ConvertFrom (213-217)
src/NLog/Internal/ExceptionMessageFormatProvider.cs (1)
src/NLog/Internal/PropertyHelper.cs (1)
  • Type (186-190)
src/NLog/Config/PropertyTypeConverter.cs (1)
src/NLog/Internal/StringHelpers.cs (1)
  • StringHelpers (45-170)
src/NLog/LogFactory.cs (6)
src/NLog/Config/ServiceRepositoryUpdateEventArgs.cs (2)
  • ServiceRepositoryUpdateEventArgs (42-57)
  • ServiceRepositoryUpdateEventArgs (48-51)
tests/NLog.UnitTests/Config/ServiceRepositoryTests.cs (1)
  • NLog (222-237)
tests/NLog.UnitTests/Layouts/LayoutTypedTests.cs (1)
  • NLog (1173-1182)
src/NLog/Common/AsyncHelpers.cs (1)
  • AsyncHelpers (46-382)
src/NLog/Internal/TargetWithFilterChain.cs (2)
  • Equals (376-379)
  • Equals (381-386)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: task-list-completed
🔇 Additional comments (60)
src/NLog/Internal/ExceptionMessageFormatProvider.cs (1)

45-45: LGTM! Nullable annotations correctly align with .NET 10 interface definitions.

The updated signatures for ICustomFormatter.Format and IFormatProvider.GetFormat correctly reflect the nullable reference type annotations introduced in .NET 10. The implementations safely handle nullable parameters:

  • Line 47: Pattern matching handles null arg correctly
  • Line 82: Null formatType returns null as expected

Also applies to: 80-80

src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (1)

272-275: LGTM! Consistent null-safety pattern.

The null-coalescing fallback to GetType().ToString() is appropriate and mirrors the same pattern used elsewhere in the codebase (e.g., Layout.cs lines 361-364). This defensive change correctly addresses potential null returns from ToString() per .NET 10's nullable annotations.

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

109-109: LGTM – Correctly addresses .NET 10 nullable annotations.

The change from object? returning default to object returning string.Empty properly satisfies the non-nullable IDictionaryEnumerator.Key contract introduced in .NET 10.

Note: There's a subtle inconsistency where Entry.Key (from the default DictionaryEntry at line 108) would be null, while the direct Key property now returns string.Empty. This is acceptable since MoveNext() always returns false for this empty enumerator, and accessing these properties outside a valid enumeration position is undefined behavior per the IDictionaryEnumerator contract.

src/NLog/Targets/ColoredConsoleAnsiPrinter.cs (1)

88-88: Verify exclusion of .NET Core 3.0/3.1 from span-based optimization is intentional.

The change from NETCOREAPP3_0_OR_GREATER to NET excludes .NET Core 3.0 and 3.1 from the optimized span-based code path, even though these versions supported TextWriter.Write(ReadOnlySpan<char>). The character-by-character fallback will now be used for these EOL targets. If this trade-off (simpler conditions vs. optimization for EOL frameworks) is intentional, the change is reasonable.

src/NLog/Config/MethodFactory.cs (3)

173-173: LGTM! Nullable array type correctly propagates through the chain.

The updated return type of ResolveDefaultMethodParameters to object?[] correctly reflects that method parameters can be null, especially for optional parameters with null defaults.


208-208: LGTM! Nullable annotations correctly reflect reflection behavior.

The return type object? is appropriate since MethodInfo.Invoke can return null. The parameter type object?[] aligns with the nullable-aware signatures used throughout the method chain.


223-229: LGTM! Nullable array correctly handles optional parameter defaults.

Both the return type object?[] and the array initialization are correct since method parameter default values can be null (e.g., optional reference-type parameters with null defaults).

src/NLog/ScopeContext.cs (1)

525-528: LGTM! Appropriate null-safety fallback for .NET 10 annotations.

The addition of ?? GetType().ToString() correctly handles the case where ToString() returns null, which is now reflected in .NET 10's nullable annotations for object.ToString(). The fallback to the type name is a reasonable and meaningful default.

src/NLog/LogLevelTypeConverter.cs (4)

46-49: LGTM! Nullable context parameter aligns with .NET 10 annotations.

The nullable annotation for the context parameter correctly matches the updated .NET 10 TypeConverter base class signature. The implementation appropriately passes the nullable context to the base method.


52-61: LGTM! Nullable return type correctly reflects possible null return from base class.

The nullable return type annotation is appropriate since base.ConvertFrom can return null. The defensive null checks (value?.GetType(), value?.ToString()) are good practice for library code.

Note: The context parameter remains non-nullable here while it's nullable in CanConvertFrom. This asymmetry likely matches the .NET 10 TypeConverter base class signatures.


64-67: LGTM! Nullable annotations for both parameters are correct.

Both the context and destinationType nullable annotations align with .NET 10 TypeConverter signatures. The implementation safely handles null values: the equality check returns false for null, and IsNumericType includes an explicit null check (lines 84-85).


70-80: LGTM! Nullable return type appropriately reflects the conversion behavior.

The nullable return type correctly indicates that the method can return null, particularly from base.ConvertTo. The implementation safely handles the type conversion logic.

src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs (1)

89-95: LGTM! Proper null validation added for getter method.

The explicit null check for GetGetMethod() prevents passing a potentially null value to CreateLateBoundMethod and provides a clear error message when a property lacks a valid getter. This aligns well with .NET 10's nullable reference type annotations and improves robustness.

src/NLog/Internal/PropertyHelper.cs (3)

175-183: LGTM! Clean early-return pattern.

The inverted null-check with early return improves code readability while maintaining identical functional behavior. This refactoring aligns well with modern nullable reference type patterns.


262-262: LGTM! Type inference improves nullable compatibility.

Using var instead of explicit MethodInfo type helps the code adapt to .NET 10's nullable annotations where GetMethod returns MethodInfo?. The existing null check on Line 263 ensures safe usage.


439-439: LGTM! Explicit null check aids nullable flow analysis.

While IsValidPublicProperty already handles null internally, the explicit null check helps .NET 10's nullable flow analysis understand that comparerPropInfo is non-null in the subsequent code path. This is a common pattern for addressing nullable reference type warnings.

src/NLog/Conditions/ConditionRelationalExpression.cs (1)

226-226: LGTM! Proper null-coalescing for string conversion.

The null-coalescing operator ensures a non-null string result when promoting to string type, correctly addressing .NET 10's nullable annotations. Using string.Empty as the fallback is appropriate and maintains type safety.

src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs (1)

93-93: LGTM: Null-safety guard aligns with .NET 10 annotations.

The null-coalescing guard ensures a non-null return value, which is consistent with the existing string.Empty fallback in the else branch (line 97).

src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs (1)

157-157: LGTM: Null-safety guard with appropriate downstream handling.

The guard ensures a non-null return, and callers at lines 114 and 140 already validate with !string.IsNullOrEmpty(processDir) before using the value.

src/NLog/Targets/FileTarget.cs (2)

1018-1018: LGTM: Correct nullable annotation for Timer callback.

The signature change aligns with .NET's TimerCallback delegate signature, which accepts object? state.


1248-1248: LGTM: Null-safety guard with appropriate exception handling.

The guard ensures a non-null value. If Path.GetDirectoryName(filePath) returns null (e.g., root path), the empty string will cause Directory.CreateDirectory to throw at line 1252, which is caught and wrapped in a NLogRuntimeException with a clear error message.

src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1)

56-56: LGTM: Null-safety guard with explicit validation.

The guard ensures a non-null value, and the overloaded method at line 62 explicitly validates string.IsNullOrEmpty(fileDirectory) at line 69, returning false for empty directories.

src/NLog/LogFactory.cs (5)

288-288: LGTM!

The nullable annotation for the sender parameter is correct and follows the standard event handler pattern.


1113-1116: LGTM!

The nullable annotation for the obj parameter is correct for overriding Object.Equals with nullable reference types enabled. The implementation using pattern matching is safe and appropriate.


1227-1227: LGTM!

The nullable annotation for the sender parameter follows the standard event handler pattern and is correct.


1249-1249: LGTM!

The nullable annotation for the sender parameter is correct and follows the standard event handler pattern.


54-56: The conditional compilation directive is correct. NET is a valid versionless conditional compilation symbol defined by the SDK for .NET 5.0 and later. The condition NETSTANDARD2_1_OR_GREATER || NET properly targets all platforms supporting IAsyncDisposable: .NET Standard 2.1+ and modern .NET 5.0+. No changes needed.

Likely an incorrect or invalid review comment.

src/NLog/LogEventBuilder.cs (1)

123-123: LGTM! Broadening span-based overload availability.

The change from NET9_0_OR_GREATER to NET correctly enables the ReadOnlySpan-based Properties overload on all .NET 5.0+ targets, not just .NET 9.0+. Since ReadOnlySpan<T> is supported in all .NET 5.0+ versions, this is safe and beneficial for performance across more target frameworks.

src/NLog/Internal/StackTraceUsageUtils.cs (2)

107-107: LGTM: Good defensive null-coalescing.

The null-coalescing operator properly handles the case where method.ToString() returns null, ensuring methodName always has a valid value by falling back to the original method name.


219-221: The breaking change is properly handled. The parameter change from MethodBase? to MethodBase is safe—the only caller in CallSiteInformation.cs at line 313 checks for null at line 310 before invoking the method, ensuring a non-null value is always passed.

src/NLog/Internal/CallSiteInformation.cs (2)

310-322: LGTM: Coordinated changes for null-safety and enhanced filtering.

The changes work well together:

  1. The null check at lines 310-311 ensures stackMethod is non-null before calling LookupAssemblyFromMethod, which now requires a non-nullable parameter.
  2. Lines 317-320 add filtering for hidden declaring types, making the stack frame filtering more comprehensive.
  3. The explicit return false at line 321 improves code clarity.

329-329: LGTM: Improved clarity with explicit variable extraction.

Extracting DeclaringType to a local variable improves readability and makes the null-conditional access more explicit, aligning well with nullable reference type patterns.

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

424-432: The preprocessor condition change and collection expression usage are compatible with the project's configuration. The project targets net35, net46, netstandard2.0, and netstandard2.1 (not NET5+), and the only target compiling this code path is netstandard2.1, which is configured with LangVersion=13 and fully supports C# 12 collection expressions. The #else fallback correctly handles other target frameworks using separate Append() calls. No compilation issue exists.

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

96-99: LGTM!

The nullable annotation on the state parameter aligns with the .NET TimerCallback delegate signature and correctly reflects that null is passed at the call site (line 57).

src/NLog/Config/LoggingConfiguration.cs (1)

118-118: LGTM!

The nullable annotation on the out Target? parameter correctly reflects the dictionary's TryGetValue behavior where the out value is null when the key is not found. Callers appropriately check the boolean return value before using the result.

src/NLog/Config/PropertyTypeConverter.cs (1)

199-207: LGTM!

The explicit stringValue is null check is technically redundant since StringHelpers.IsNullOrWhiteSpace already handles null, but it helps the compiler's nullable flow analysis understand that stringValue is non-null on subsequent lines. This is a valid pattern for nullable reference type compatibility.

src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs (1)

274-280: LGTM!

The nullable annotation correctly reflects the Timer callback signature. The existing (state as int?) ?? 0 pattern at line 280 safely handles null state values by defaulting to 0.

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

750-758: LGTM!

The nullable annotation on Equals(object? obj) aligns with the base object.Equals signature under nullable reference types. The pattern matching with is already safely handles null inputs by returning false.

src/NLog/Common/AsyncLogEventInfo.cs (1)

92-92: LGTM!

The nullable annotation correctly aligns the Equals override with the base signature under nullable reference types. The pattern matching safely handles null by returning false.

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

274-278: LGTM - Span-based logging path enabled for all .NET 5+ targets.

The change from NET9_0_OR_GREATER to NET broadens the span-based WriteToTargetsWithSpan path to all modern .NET targets. The collection expression syntax [argument] creates a stack-allocated span, avoiding heap allocations.

Note the intentional distinction: the params ReadOnlySpan<object?> overloads (lines 187-216, etc.) correctly retain NET9_0_OR_GREATER guard since params with Span is a C# 13/.NET 9 feature.


1843-1848: Consistent pattern applied across all log levels.

The same span-based optimization pattern is correctly applied across all 36 generic method overloads (6 per log level × 6 levels). The fallback to object?[] allocation for non-.NET targets is preserved.

tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs (1)

167-172: Preprocessor change is confirmed safe and intentional.

The condition changed from NETCOREAPP3_0_OR_GREATER to NET as part of a systematic PR-wide refactoring affecting Logger.cs, LogEventBuilder.cs, and test files. Verification confirms NLog does not target .NET Core 3.x (targets are net35, net46, netstandard2.0, netstandard2.1 for the main library and net462, net8.0 for tests), so the replacement is safe. All NETCOREAPP3_0_OR_GREATER references have been removed from the codebase, confirming a clean, intentional modernization.

src/NLog/Targets/LineEndingMode.cs (2)

188-199: LGTM! Nullable annotations correctly applied to Equals methods.

The nullable annotations align with .NET 10 standards. The implementations correctly handle null through pattern matching (line 190) and the null-conditional operator (line 198), consistent with similar patterns in LogLevel.cs.


213-217: LGTM! Nullable return type correctly reflects method behavior.

The object? return type appropriately indicates that base.ConvertFrom can return null when the value cannot be converted.

src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs (1)

178-188: LGTM! Nullable return type accurately reflects API behavior.

The nullable annotation is correct since Assembly.GetEntryAssembly() can return null. All call sites in GetVersion() (lines 152, 157, 160, 163) properly handle the nullable return using the null-conditional operator.

tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs (1)

389-394: LGTM! Conditional compilation broadened to align with PR objectives.

The change from NETCOREAPP3_0_OR_GREATER to NET broadens the span-based overload availability to all .NET versions, aligning with the PR's goal to widen .NET conditional compilation.

src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs (2)

173-180: LGTM! Nullable return type correctly reflects StackTrace.GetFrame behavior.

The nullable annotation is appropriate since StackTrace.GetFrame() can return null when the requested frame doesn't exist.


197-198: LGTM! Proper null handling for nullable StackFrame.

The null-conditional operator f?.ToString() correctly handles potential null frames, preventing NullReferenceException.

src/NLog/Logger.cs (5)

444-450: LGTM! Consistent broadening of .NET target and modern span syntax.

The changes correctly:

  1. Broaden the conditional compilation from NET9_0_OR_GREATER to NET, aligning with PR objectives
  2. Use collection expression syntax [argument] for cleaner span creation

465-471: LGTM! Consistent pattern applied.

Same improvements as the previous method: broadened .NET targeting and collection expression syntax for span arguments.


489-495: LGTM! Consistent modernization across all generic log methods.

All generic log method overloads consistently apply:

  • Broadened .NET conditional compilation to NET
  • Collection expression syntax for cleaner span creation: [argument1, argument2], [argument1, argument2, argument3]

Also applies to: 512-518, 538-544, 563-569


571-604: LGTM! New span-based overloads properly guarded.

The new ReadOnlySpan<object?> overloads are correctly:

  • Guarded with NETSTANDARD2_1_OR_GREATER || NET
  • Positioned to provide direct span-based logging for modern .NET

607-614: LGTM! Proper use of in modifier for ReadOnlySpan parameters.

The change from params ReadOnlySpan<object?> to in ReadOnlySpan<object?> is correct:

  • The in modifier prevents unnecessary copies of the span struct
  • Aligns with best practices for ReadOnlySpan parameters
  • Maintains compatibility with collection expression syntax at call sites

Also applies to: 616-642

src/NLog/LogEventInfo.cs (2)

136-159: LGTM! New span-based constructor properly implemented.

The new constructor correctly:

  1. Broadens conditional compilation to NET (consistent with PR objectives)
  2. Accepts ReadOnlySpan<MessageTemplateParameter> for allocation-free parameter passing
  3. Properly converts span to array and initializes _properties (lines 151-157)
  4. Follows the same pattern as the IList<MessageTemplateParameter> constructor

675-692: LGTM! Span overload consistency maintained.

The conditional compilation update from NET9_0_OR_GREATER to NET ensures the span-based NeedToPreformatMessage overload is available across all .NET versions, consistent with the broadened targeting throughout the PR.

src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (4)

469-473: LGTM: Nullable annotation aligns with Timer callback signature.

The change from object state to object? state is correct since null is explicitly passed as the state parameter when constructing the Timer (line 295), and this aligns with .NET's Timer callback signature.


523-557: LGTM: Nullable annotation matches WaitCallback signature.

The change from object state to object? state correctly reflects the WaitCallback delegate signature. The method already handles the null case properly by casting to AsyncContinuation? (line 525) and checking for null before use (line 531).


617-620: LGTM: Nullable sender follows event handler conventions.

The change from object sender to object? sender aligns with standard .NET event handler patterns where the sender parameter can be null. The implementation correctly handles this by not using the sender parameter directly.


622-625: LGTM: Nullable sender follows event handler conventions.

Consistent with OnRequestQueueDropItem, the nullable sender annotation properly reflects standard .NET event handler patterns. The sender parameter is unused in the method body, making the nullable annotation appropriate.

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

101-106: Collection expressions are properly configured for supported target frameworks.

The code uses collection expressions [arg1, arg2] guarded by #if NETSTANDARD2_1_OR_GREATER || NET. The project correctly sets LangVersion to 13 (C# 13) for netstandard2.1, which exceeds the C# 12 requirement for collection expressions. Other frameworks (net35, net46, netstandard2.0) fall back to the legacy syntax new object?[] { arg1, arg2 } as expected. The current configuration poses no issues.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
src/NLog/Internal/ObjectReflectionCache.cs (1)

263-270: Fix array sizing issue introduced by the null check.

The defensive null checks are appropriate for handling .NET 10's nullable annotations, but the implementation has a logic issue:

  1. The fastLookup array is pre-allocated to properties.Length + fastAccessIndex (line 254)
  2. The new continue on line 265 skips properties with null getters/return types
  3. This means fastAccessIndex won't reach its expected final value
  4. The returned array contains uninitialized FastPropertyLookup structs at the end
  5. These default structs have null Name and null ValueLookup delegates
  6. When the array is iterated elsewhere (e.g., lines 374-381), these invalid entries are processed, causing inefficiency and potential edge-case issues
🔎 Suggested fix: resize the array or use a dynamic collection

Option 1: Resize the array after the loop

 foreach (var prop in properties)
 {
     var getterMethod = prop.GetGetMethod();
     var propertyType = getterMethod?.ReturnType;
     if (getterMethod is null || propertyType is null)
         continue;
     ReflectionHelpers.LateBoundMethod valueLookup = ReflectionHelpers.CreateLateBoundMethod(getterMethod);
     TypeCode typeCode = Type.GetTypeCode(propertyType);
     fastLookup[fastAccessIndex++] = new FastPropertyLookup(prop.Name, typeCode, valueLookup);
 }
+if (fastAccessIndex < fastLookup.Length)
+{
+    Array.Resize(ref fastLookup, fastAccessIndex);
+}
 return fastLookup;

Option 2: Use a List for dynamic sizing

 int fastAccessIndex = includeType ? 1 : 0;
-FastPropertyLookup[] fastLookup = new FastPropertyLookup[properties.Length + fastAccessIndex];
+var fastLookup = new List<FastPropertyLookup>(properties.Length + fastAccessIndex);
 if (includeType)
 {
-    fastLookup[0] = new FastPropertyLookup("Type", TypeCode.String, (o, p) => o.GetType().ToString());
+    fastLookup.Add(new FastPropertyLookup("Type", TypeCode.String, (o, p) => o.GetType().ToString()));
 }

 foreach (var prop in properties)
 {
     var getterMethod = prop.GetGetMethod();
     var propertyType = getterMethod?.ReturnType;
     if (getterMethod is null || propertyType is null)
         continue;
     ReflectionHelpers.LateBoundMethod valueLookup = ReflectionHelpers.CreateLateBoundMethod(getterMethod);
     TypeCode typeCode = Type.GetTypeCode(propertyType);
-    fastLookup[fastAccessIndex++] = new FastPropertyLookup(prop.Name, typeCode, valueLookup);
+    fastLookup.Add(new FastPropertyLookup(prop.Name, typeCode, valueLookup));
 }
-return fastLookup;
+return fastLookup.ToArray();
src/NLog/LogLevelTypeConverter.cs (1)

52-61: Add nullable annotations to method parameters in ConvertFrom and ConvertTo.

The context and culture parameters in ConvertFrom (line 52) and ConvertTo (line 70) should be nullable to match the .NET 10 TypeConverter base class signatures.

Current signatures:

  • Line 52: ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, ...)
  • Line 70: ConvertTo(ITypeDescriptorContext context, CultureInfo culture, ...)

Should be:

  • ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, ...)
  • ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, ...)

Note: CanConvertFrom (line 46) and CanConvertTo (line 64) already have the correct nullable context parameter.

src/NLog/Logger.cs (2)

444-569: Collection expression syntax requires C# 12+ but will fail for future .NET 5+ targets.

The code uses collection expressions [argument] in the #if NETSTANDARD2_1_OR_GREATER || NET branch. Currently this works because:

  • netstandard2.1 has LangVersion explicitly set to 13
  • No .NET 5+ targets exist, so the NET symbol is never true

However, when .NET 5+ targets (e.g., net5.0, net6.0, etc.) are added in the future, they will use the default LangVersion of 9, which does not support collection expressions. This will cause compilation failures.

Add a LangVersion override for NET5_0_OR_GREATER targets:

<LangVersion Condition=" '$(TargetFramework)' != 'netstandard2.1' AND '$(TargetFramework)' != 'net35' AND '$(TargetFramework)' != 'net46' AND '$(TargetFramework)' != 'netstandard2.0' ">12</LangVersion>

Or simplify by using a condition on the target framework:

<LangVersion Condition=" $([MSBuild]::IsOSPlatform('windows')) ">$(LangVersion)</LangVersion>

Better yet, conditionalize LangVersion >= 12 for all modern targets (.NET 5+ and netstandard2.1).


571-604: Guard is overly broad and references undefined symbol; simplify to #if NETSTANDARD2_1_OR_GREATER.

The conditional guard #if NETSTANDARD2_1_OR_GREATER || NET references an undefined symbol NET (the bare identifier without a version number). The project's actual TargetFrameworks are net35;net46;netstandard2.0;netstandard2.1, and only netstandard2.1 matches this guard. Since that target is configured with LangVersion 13 in the csproj, the params ReadOnlySpan<object?> syntax (C# 13 feature) compiles successfully.

However, the guard's intent is unclear and should be simplified to #if NETSTANDARD2_1_OR_GREATER to accurately reflect the actual target framework being compiled with the required language version.

♻️ Duplicate comments (7)
src/NLog/LogLevelTypeConverter.cs (1)

70-80: Same inconsistency: context parameter should be nullable.

The context parameter should be ITypeDescriptorContext? to match CanConvertTo at line 64 and align with the .NET 10 base class.

src/NLog/Config/LoggerNameMatcher.cs (1)

173-175: Inconsistency resolved, but semantic issue remains.

The null-handling logic is now consistent across all four matchers (Equals, StartsWith, EndsWith, Contains), which resolves the inconsistency noted in the previous review. However, the semantic concern persists: when _matchingArgument is null and loggerName is not null, returning true doesn't make logical sense—a null pattern shouldn't match a non-null logger name.

Since _matchingArgument should never be null in practice (the factory method returns NoneLoggerNameMatcher for null patterns, and constructors use Substring on non-null patterns), consider using the null-forgiving operator to make this explicit and avoid the defensive check:

🔎 Alternative approach using null-forgiving operator

For EqualsLoggerNameMatcher:

 public override bool NameMatches(string loggerName)
 {
-    if (loggerName is null || _matchingArgument is null)
-        return _matchingArgument is null;
-    return loggerName.Equals(_matchingArgument, StringComparison.Ordinal);
+    if (loggerName is null) return false;
+    return loggerName.Equals(_matchingArgument!, StringComparison.Ordinal);
 }

Apply the same pattern to StartsWithLoggerNameMatcher, EndsWithLoggerNameMatcher, and ContainsLoggerNameMatcher, replacing their respective comparison methods (StartsWith, EndsWith, IndexOf).

Also applies to: 190-192, 207-209, 224-226

src/NLog/Logger-generated.cs (1)

274-1849: Generated file follows same pattern as Logger.cs.

This file is generated from Logger-generated.tt and shows the same collection expression usage as Logger.cs. The same verification requested for Logger.cs (lines 444-569) applies here: ensure C# language version 12+ and compatibility across all target frameworks where the conditional compilation evaluates to true.

Since this is a generated file, any necessary corrections should be made in the template file (Logger-generated.tt).

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

2747-2751: Align OverloadResolutionPriority guard with other overloads.

This is the only OverloadResolutionPriority site guarded by #if NETSTANDARD2_1_OR_GREATER || NET instead of || NET9_0_OR_GREATER (used elsewhere). If OverloadResolutionPriorityAttribute is only available starting in .NET 9, it’s safer and more consistent to gate this attribute on NET9_0_OR_GREATER as well, so future NET TFMs (e.g. net8.0) don’t accidentally compile it against reference assemblies that don’t define the attribute.

-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER

2040-2055: Fix typo in preprocessor symbol NETNET9_0_OR_GREATER.

#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATER contains a duplicated NET, so the NETNET9_0_OR_GREATER symbol will never be defined. As a result, [OverloadResolutionPriority(-1)] on this Info-overload is emitted for NETSTANDARD2_1_OR_GREATER but not for .NET 9+/10 builds, unlike the other overloads.

Align this with the rest, for example:

-#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATER
+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER

(or || NET if that’s now your chosen convention), and consider fixing it in any generator/source that might reintroduce the typo.

src/NLog/Logger-generated.tt (1)

246-365: Verify C# 12 requirement for collection expressions under NET guards.

The new generic overloads route into WriteToTargetsWithSpan using C# 12 collection expressions ([argument], [argument1, argument2], etc.) under #if NETSTANDARD2_1_OR_GREATER || NET. This is fine if the project explicitly sets <LangVersion> to 12 (or latest) for all target frameworks; otherwise, those sections will only compile by default on TFMs where C# 12 is the default (e.g., NET8_0_OR_GREATER).

If you don’t want to rely on an explicit LangVersion, consider narrowing the guard to something like NET8_0_OR_GREATER (or whichever minimum TFM you require for C# 12), keeping the existing non‑span/object[] path for older TFMs.

This comment applies to all similar [argument…] spans in this template.

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

202-265: NET guard on span-based Render now matches broader NET usage; just confirm target TFMs.

The span-based Render(ref TemplateEnumerator, IFormatProvider?, in ReadOnlySpan<object?>, …) is now compiled under #if NETSTANDARD2_1_OR_GREATER || NET, which aligns with the other span-based logging paths that are enabled for netstandard2.1 and any NET (net5+) targets.

This is reasonable, provided the NLog project actually includes at least one netX.0 target where NET is defined (e.g., net10.0 for the NET10 work). If you later add additional NET TFMs that don’t have the same BCL surface you rely on here, you may want to revisit and narrow the condition accordingly.

🧹 Nitpick comments (3)
src/NLog/Targets/MethodCallTarget.cs (1)

270-272: LGTM! Null-safety fix correctly addresses .NET 10 annotations.

The added null check properly handles the case where InnerException might be null, addressing the new nullable annotations in .NET 10. The fallback to throw; correctly preserves the stack trace.

Optional: Consider preserving the full exception chain

While the current approach unwraps the TargetInvocationException, you could optionally preserve the full exception context by wrapping the inner exception:

 catch (TargetInvocationException ex)
 {
     InternalLogger.Warn("{0}: Failed to invoke method - {1}", this, ex.Message);
-    if (ex.InnerException != null)
-        throw ex.InnerException;
-    throw;
+    throw ex.InnerException ?? ex;
 }

This maintains the same unwrapping behavior when an inner exception exists, while keeping the code more concise.

src/NLog/Targets/DefaultJsonSerializer.cs (1)

518-518: Optional: Redundant null-coalescing on line 518.

Since line 515 already guarantees textValue is non-null (?? string.Empty), and the cache at line 516 only stores non-null values, the null-coalescing operator on line 518 is technically redundant. However, it serves as defensive programming and has no performance impact.

🔎 Optional simplification
-            return textValue ?? string.Empty;
+            return textValue;
src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs (1)

197-198: Consider skipping null frames for consistency with other format methods.

The nullable-safe handling with f?.ToString() is correct. However, when a frame is null, the separator is still appended (line 196), which could result in consecutive separators like " => => " if middle frames are null.

For consistency with AppendFlat (line 208) and AppendDetailedFlat (line 240), which explicitly skip null frames using continue, consider adding a similar null check here.

🔎 Proposed refactor to skip null frames
 string? separator = null;
 for (int i = 0; i < stackFrameList.Count; ++i)
 {
-    builder.Append(separator);
     var f = stackFrameList[i];
+    if (f is null)
+        continue;
+    builder.Append(separator);
     builder.Append(f?.ToString());
     separator = separator ?? _separator ?? string.Empty;
 }
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1868610 and 5a66c71.

📒 Files selected for processing (68)
  • src/NLog/Common/AsyncLogEventInfo.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Conditions/ConditionExpression.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/Config/MethodFactory.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Internal/DictionaryEntryEnumerable.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Internal/ScopeContextAsyncState.cs
  • src/NLog/Internal/SingleItemOptimizedHashSet.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/LogFactory.cs
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Logger-V1Compat.cs
  • src/NLog/Logger-generated.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Logger.cs
  • src/NLog/MessageTemplates/ValueFormatter.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/Targets/MethodCallTarget.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
🚧 Files skipped from review as they are similar to previous changes (33)
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/LogFactory.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-12T01:22:52.437Z
Learnt from: thatrajeevkr
Repo: NLog/NLog PR: 5999
File: src/NLog/Layouts/JSON/JsonLayout.cs:162-166
Timestamp: 2025-09-12T01:22:52.437Z
Learning: When suggesting StringBuilder optimizations for NLog layouts marked as [ThreadAgnostic], avoid shared StringBuilder fields as they create thread safety issues. Prefer simple string operations or ThreadLocal<StringBuilder> if allocation profiling shows a real performance impact.

Applied to files:

  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/Internal/StringBuilderExt.cs
🧬 Code graph analysis (11)
src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs (4)
src/NLog/LayoutRenderers/MessageLayoutRenderer.cs (1)
  • IStringValueRenderer (152-158)
src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs (1)
  • IStringValueRenderer (178-178)
src/NLog/LayoutRenderers/ThreadIdLayoutRenderer.cs (1)
  • IStringValueRenderer (62-65)
src/NLog/Internal/IStringValueRenderer.cs (1)
  • GetFormattedString (49-49)
src/NLog/Config/LoggingConfiguration.cs (1)
src/NLog/Internal/ThreadSafeDictionary.cs (1)
  • TryGetValue (156-159)
src/NLog/Internal/ReflectionHelpers.cs (1)
src/NLog/Internal/ObjectReflectionCache.cs (1)
  • PropertyInfo (222-249)
src/NLog/Logger.cs (1)
src/NLog/LayoutRenderers/LayoutRenderer.cs (1)
  • IFormatProvider (218-221)
src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (2)
src/NLog/Targets/Wrappers/LogEventDroppedEventArgs.cs (2)
  • LogEventDroppedEventArgs (41-53)
  • LogEventDroppedEventArgs (47-47)
src/NLog/Targets/Wrappers/LogEventQueueGrowEventArgs.cs (2)
  • LogEventQueueGrowEventArgs (44-66)
  • LogEventQueueGrowEventArgs (51-55)
src/NLog/LogLevelTypeConverter.cs (1)
src/NLog/Targets/LineEndingMode.cs (2)
  • CanConvertFrom (207-210)
  • ConvertFrom (213-217)
src/NLog/Common/AsyncLogEventInfo.cs (4)
src/NLog/Internal/ObjectReflectionCache.cs (1)
  • Equals (553-556)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/LogEventInfo.cs (1)
  • AsyncLogEventInfo (576-579)
src/NLog/Logger-generated.cs (1)
src/NLog/Logger.cs (2)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
src/NLog/Logger-V1Compat.cs (1)
src/NLog/Logger.cs (7)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
  • WriteToTargets (827-831)
  • WriteToTargets (833-841)
  • WriteToTargets (843-853)
  • WriteToTargets (855-863)
  • WriteToTargets (866-874)
src/NLog/Conditions/ConditionLiteralExpression.cs (4)
src/NLog/Config/PropertyTypeConverter.cs (2)
  • Convert (84-114)
  • CultureInfo (199-208)
src/NLog/Conditions/ConditionExpression.cs (1)
  • ToString (92-92)
src/NLog/Config/LoggingConfiguration.cs (1)
  • ToString (1073-1085)
src/NLog/LayoutRenderers/LayoutRenderer.cs (1)
  • CultureInfo (232-235)
src/NLog/Internal/CallSiteInformation.cs (1)
src/NLog/Internal/StackTraceUsageUtils.cs (1)
  • StackTraceUsageUtils (44-268)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: task-list-completed
🔇 Additional comments (44)
src/NLog/Internal/AppEnvironmentWrapper.cs (1)

246-254: LGTM!

The null-coalescing operator correctly handles the edge case where Path.GetDirectoryName returns null (e.g., when entryLocation is a root path like C:\). This ensures the method fulfills its non-nullable return contract and addresses the NET10 nullable annotation warnings.

src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1)

56-56: LGTM: Correct null-safety fix.

The null-coalescing operator correctly handles the case where Path.GetDirectoryName(filePath) returns null (which can occur for root paths or paths without directory information). The downstream overload already handles empty strings appropriately (returns early at line 70), making this change both safe and semantically correct.

src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs (1)

55-60: LGTM! Nullable annotations correctly reflect the API contract.

The changes properly annotate that Thread.CurrentThread.Name can return null and align with the IStringValueRenderer interface, which expects string?. The StringBuilder.Append call at line 52 safely handles null values.

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

591-591: LGTM! Addresses NET10 nullable annotations.

The null-coalescing fallback to GetType().ToString() correctly addresses the enhanced nullable annotations in NET10, ensuring a non-null return value while maintaining backward compatibility.

src/NLog/MessageTemplates/ValueFormatter.cs (2)

266-272: LGTM! Nullable annotation aligns with .NET 10.

The parameter nullability change correctly reflects that IConvertible.ToString() (called at line 203) may return null per .NET 10 annotations. The implementation safely handles null values since StringBuilder.Append(string?) treats null as an empty string.


355-363: LGTM! Nullable annotation correctly reflects collection semantics.

The parameter nullability change appropriately reflects that collection items and dictionary values can be null. The implementation correctly handles null through:

  • Pattern matching that implicitly filters out null (lines 357, 359)
  • Fallback to SerializeSimpleObject, which explicitly handles null by outputting "NULL" (lines 189-193)
src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs (1)

86-86: LGTM! Correct null-safety guard for .NET 10 annotations.

The null-coalescing operator correctly handles the case where Path.GetDirectoryName returns null (annotated as nullable in .NET 10), ensuring the non-nullable _processDir field is always initialized with a valid string value.

src/NLog/Conditions/ConditionMethods.cs (3)

81-84: LGTM! Appropriate null handling for condition method.

The explicit null check correctly addresses .NET 10's stricter nullable annotations. Returning false when either parameter is null is semantically appropriate for a predicate method used in filter expressions, and prevents the NullReferenceException that would have occurred previously.


97-100: LGTM! Consistent null handling.

The null check follows the same defensive pattern as the Contains method. This prevents exceptions and provides sensible predicate semantics where null values evaluate to false.


113-116: LGTM! Completes consistent null handling pattern.

The null check completes the consistent defensive pattern across all three string comparison methods (Contains, StartsWith, EndsWith), effectively addressing .NET 10's nullable annotation warnings while maintaining appropriate condition method semantics.

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

360-362: LGTM! Good null-safety improvement.

The null check properly handles the potential null return from Convert.ToString as indicated by .NET 10's nullable annotations. Returning string.Empty is consistent with the catch block behavior at line 367, maintaining uniform error handling throughout the method.

src/NLog/Targets/DefaultJsonSerializer.cs (3)

444-444: LGTM! Proper null guard for .NET 10 nullable annotations.

The null-coalescing operator safely handles the case where ToString() might be annotated as potentially returning null in .NET 10, ensuring the JSON serializer always works with a non-null string.


515-515: LGTM! Proper null guard for enum conversion.

The null-coalescing operator ensures Convert.ToString() always returns a non-null string, addressing .NET 10's nullable annotations for enum-to-string conversions.


750-750: LGTM! Proper null guard for object-to-string conversion.

The null-coalescing operator ensures Convert.ToString() always returns a non-null string when serializing objects as JSON strings, properly addressing .NET 10's nullable annotations.

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

242-244: LGTM! Correct alignment with .NET 10 nullable annotations.

The updated signature correctly reflects .NET 10's nullable annotations for IEqualityComparer<T>.Equals. The implementation using ReferenceEquals(x, y) properly handles null values—returning true when both parameters are null and false when only one is null.

src/NLog/Targets/FileTarget.cs (2)

1018-1018: LGTM! Correct nullability fix for timer callback.

The parameter type change from object to object? correctly aligns with the TimerCallback delegate signature in .NET 10, which expects a nullable state parameter. This resolves the nullable annotation warning without any functional impact.


1248-1248: LGTM! Proper null-safe handling of directory path.

The null-coalescing operator ?? string.Empty correctly handles the case where Path.GetDirectoryName returns null (as per .NET 10 annotations), ensuring a non-null value is passed to Directory.CreateDirectory. This is a safe defensive change that maintains existing behavior while satisfying nullability requirements.

src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (4)

469-469: LGTM: Correct nullable annotation for Timer callback.

The parameter signature now correctly matches the TimerCallback delegate signature with nullable object? state, aligning with .NET 10 annotations.


523-523: LGTM: Correct nullable annotation with proper null handling.

The nullable object? parameter is correctly handled with safe casting and null checks before use (lines 525, 531-532, 555).


617-620: LGTM: Correct nullable annotation for event handler.

The sender parameter is now correctly annotated as nullable to match the EventHandler<T> signature in .NET 10. The non-nullable logEventDroppedEventArgs parameter is appropriate as event args should always be provided.


622-625: LGTM: Correct nullable annotation for event handler.

The sender parameter is now correctly annotated as nullable to match the EventHandler<T> signature in .NET 10. The non-nullable logEventQueueGrowEventArgs parameter is appropriate as event args should always be provided.

src/NLog/Conditions/ConditionExpression.cs (1)

56-58: No action needed—the conditional compilation change is correct.

The project does not target .NET Core 3.0 or 3.1. The TargetFrameworks in NLog.csproj are: net35;net46;netstandard2.0;netstandard2.1. The change from NETCOREAPP3_0_OR_GREATER to NETSTANDARD2_1_OR_GREATER || NET appropriately covers the actual supported frameworks without impact.

src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs (1)

173-180: LGTM! Nullable return type correctly reflects StackTrace.GetFrame behavior.

The indexer now properly returns StackFrame? since StackTrace.GetFrame() can return null according to NET10 annotations when the frame index is invalid or the stack trace was captured without all frames.

src/NLog/Conditions/ConditionLiteralExpression.cs (1)

81-81: LGTM! Null-coalescing operator correctly ensures non-nullable return.

The addition of ?? string.Empty is necessary since Convert.ToString can return null according to NET10 annotations, and the ToString() method must return a non-nullable string per its signature.

src/NLog/Internal/StackTraceUsageUtils.cs (2)

107-107: Good defensive null-coalescing fallback.

The addition of ?? methodName appropriately handles .NET 10's updated nullable annotations where ToString() may theoretically return null. The fallback ensures robustness without changing behavior in practice.


219-221: Parameter and implementation change are safe; all callers properly handle null.

The change from nullable MethodBase? to non-nullable MethodBase aligns with .NET 10 nullable annotations. Both callers in the codebase check for null before invoking: line 250 in this file explicitly checks method != null, and line 313 in CallSiteInformation.cs guards the call with a null check at lines 310-311.

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

109-109: Good fix for NET10 nullable annotation.

Changing Key from nullable returning default to non-nullable returning string.Empty appropriately addresses the nullable warning required by .NET 10's IDictionaryEnumerator.Key declaration. Since EmptyDictionaryEnumerator.MoveNext() always returns false, the Key property should never be accessed during normal enumeration, making this a safe sentinel value.

src/NLog/Config/LoggingConfiguration.cs (1)

118-118: LGTM: Correct nullable annotation for TryGetValue pattern.

The nullable out parameter aligns with the semantics of Dictionary.TryGetValue, which can return null. Usage at line 253 correctly checks the return value before accessing the target.

src/NLog/Common/AsyncLogEventInfo.cs (1)

92-92: LGTM: Correctly aligns with base Object.Equals signature.

The nullable parameter annotation matches the base Object.Equals(object?) signature required for nullable reference type compliance in .NET 5+.

src/NLog/Internal/ReflectionHelpers.cs (2)

183-188: LGTM: Correct nullable annotations for reflection helpers.

Both changes properly reflect nullable semantics:

  • IsValidPublicProperty safely handles null by checking p != null first
  • GetPropertyValue correctly returns object? since property values can be null

74-74: No action needed: .NET Core 3.1 is not a supported target framework.

The NLog library targets net35, net46, netstandard2.0, and netstandard2.1, but not .NET Core 3.1. The preprocessor directive change to NETSTANDARD2_1_OR_GREATER || NET is correct and appropriately covers all supported frameworks where RuntimeFeature.IsDynamicCodeSupported is available.

Likely an incorrect or invalid review comment.

src/NLog/Config/MethodFactory.cs (1)

173-173: LGTM: Consistent nullable propagation through method invocation.

The nullable annotations correctly propagate through the method parameter resolution pipeline:

  • ResolveDefaultMethodParameters returns object?[] since parameters can have null defaults
  • InvokeMethodInfo returns object? since MethodInfo.Invoke can return null
  • Line 173's var declaration correctly infers the nullable array type

Also applies to: 208-208, 223-229

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

424-424: This concern is not applicable to NLog's actual target frameworks.

NLog targets net35, net46, netstandard2.0, and netstandard2.1 — it does not target .NET Core 3.0 or 3.1. The original condition NETCOREAPP3_0_OR_GREATER was unnecessary. The updated condition correctly gates the collection expression code to netstandard2.1, where C# 12 and ReadOnlySpan<char> support are both available.

Likely an incorrect or invalid review comment.

src/NLog/LogLevelTypeConverter.cs (2)

46-49: LGTM! Nullable context annotation added.

The nullable annotation on the context parameter correctly aligns with .NET 10's updated TypeConverter base class.


64-67: LGTM! Nullable annotations added.

The nullable annotations on both context and destinationType parameters correctly align with .NET 10's updated TypeConverter base class.

src/NLog/Internal/CallSiteInformation.cs (3)

310-311: Good defensive null check.

This early return prevents passing a null stackMethod to LookupAssemblyFromMethod (line 313), which expects a non-nullable MethodBase parameter. This properly addresses the NET10 nullable annotation requirements.


329-329: Appropriate nullable reference handling.

Using a local variable for declaringType is idiomatic for nullable reference types, as it enables the compiler's flow analysis and avoids repeated null-conditional access.


317-321: The hidden type check is properly tested and consistent with the existing hidden assembly logic.

The code at lines 317-321 adds a check for hidden declaring types, consistent with the pattern used for hidden assemblies (lines 314-315). This functionality is validated by the existing HiddenTypeTest in CallSiteTests.cs, which verifies that types registered via AddCallSiteHiddenClassType are correctly skipped during callsite detection. The explicit return false; at line 321 improves code clarity. No regression concerns.

src/NLog/Logger.cs (1)

606-668: LGTM: Efficient span-based implementation with in modifier.

The in ReadOnlySpan<object?> parameter modifier is good practice for passing spans by readonly reference. The implementation correctly handles both deferred and preformatted message paths, optimizing allocations for small argument counts.

src/NLog/Logger-generated.cs (1)

187-216: The conditional guard #if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER in Logger-generated.cs is correct. The params ReadOnlySpan<object?> feature requires C# 13 and .NET 9+, not .NET 7+ as stated. Additionally, Logger.cs does not use conditional guards for these methods—they are defined unconditionally. The suggestion to use NET7_0_OR_GREATER would be incorrect, as the feature is unavailable in .NET 7 and .NET 8.

Likely an incorrect or invalid review comment.

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

96-107: Span-based overload routing looks consistent across Log/Trace/Debug/Info/Warn/Error/Fatal.

The new WriteToTargetsWithSpan(..., [args]) calls under #if NETSTANDARD2_1_OR_GREATER || NET correctly mirror the Logger.WriteToTargetsWithSpan signatures and preserve the old WriteToTargets(..., new object?[] { ... }) path for non‑NET targets. This pattern looks sound and consistent across all the updated overload groups referenced in this range.

Also applies to: 714-747, 1290-1311, 1888-1921, 2475-2508, 3062-3095, 3649-3682

tests/NLog.UnitTests/LogMessageFormatterTests.cs (1)

103-109: Span-based tests now align with production NET guards.

Switching the span-based test blocks to #if NETSTANDARD2_1_OR_GREATER || NET keeps them in sync with the production span paths and ensures the logEventInfo3 scenario is exercised for NET builds (e.g., net10) as well as netstandard2.1. Looks good.

Also applies to: 135-140

src/NLog/LogEventInfo.cs (2)

136-159: Span-based LogEventInfo overload and helper look correct and consistent.

The new LogEventInfo ctor that takes ReadOnlySpan<MessageTemplateParameter> correctly:

  • Chains to the existing message-template ctor,
  • Sets _formattedMessage / _messageFormatter the same way as the IList-based overload, and
  • Copies the span into an array-backed PropertiesDictionary when non-empty.

The new NeedToPreformatMessage(in ReadOnlySpan<object?>) mirrors the existing array-based logic and matches how Logger.WriteToTargetsWithSpan uses it. No functional issues spotted.

Also applies to: 675-692


161-186: NET40 exclusion on IReadOnlyList-based ctor matches framework capabilities.

Changing the guard to #if !NET35 && !NET40 for the LogEventInfo ctor that takes IReadOnlyList<KeyValuePair<object, object?>> avoids compiling this overload for .NET 4.0, which does not have IReadOnlyList<T> in its BCL. This should prevent potential compile-time issues on the NET40 target while keeping the overload available for newer TFMs.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/NLog/Logger.cs (1)

571-604: Critical: params ReadOnlySpan overloads use incorrect preprocessor guard

The guard at line 571 uses #if NETSTANDARD2_1_OR_GREATER || NET but should use NET9_0_OR_GREATER to match all other params ReadOnlySpan methods in the codebase. The params ReadOnlySpan<T> feature requires C# 13 / .NET 9.0+, but the current guard exposes these overloads on .NET 5.0-8.0 targets where the syntax is not supported, causing compilation errors:

  • Logger-Conditional.cs: Uses #if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER for all params ReadOnlySpan methods
  • Logger-generated.cs: Uses #if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER for all params ReadOnlySpan methods
  • Logger.cs lines 571-604: Currently uses #if NETSTANDARD2_1_OR_GREATER || NET

Fix: Change line 571 to #if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER

♻️ Duplicate comments (3)
src/NLog/Logger.cs (1)

444-445: Critical: Collection expression syntax incompatible with .NET 5.0-7.0 targets

Same issue as flagged in Logger-generated.cs: The collection expression syntax [argument] requires C# 12, but the #if NETSTANDARD2_1_OR_GREATER || NET condition includes .NET 5.0, 6.0, and 7.0 where the default C# version doesn't support collection expressions.

Recommended fix: Change to #if NETSTANDARD2_1_OR_GREATER || NET8_0_OR_GREATER

Also applies to: 465-466, 489-490, 512-513, 538-539, 563-564

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

2043-2043: Typo in preprocessor symbol: NETNET9_0_OR_GREATER

The typo NETNET9_0_OR_GREATER (duplicate "NET") is still present. This was flagged in a previous review and marked as addressed, but appears unresolved. The symbol should be NET9_0_OR_GREATER to properly enable the OverloadResolutionPriority attribute on .NET 9.0+ builds.

🔎 Proposed fix
-#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATER
+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
         [OverloadResolutionPriority(-1)]
 #endif

2749-2751: Incorrect preprocessor symbol for OverloadResolutionPriority attribute

This line uses || NET which will cause compilation errors on .NET 5, 6, 7, and 8 where the OverloadResolutionPriority attribute doesn't exist. The attribute was introduced in .NET 9.0, so the condition should be || NET9_0_OR_GREATER.

🔎 Proposed fix
-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
         [OverloadResolutionPriority(-1)]
 #endif
🧹 Nitpick comments (1)
src/NLog/Logger-generated.cs (1)

274-275: Design issue: Conditional uses NET but doesn't guarantee C# 12+ language version

The generic <TArgument> method overloads use #if NETSTANDARD2_1_OR_GREATER || NET with C# 12 collection expression syntax [argument]. The NET symbol encompasses .NET 5.0+, which do not default to C# 12 language version. While this currently compiles for netstandard2.1 (which has LangVersion 13 explicitly set), the condition is misleading and fragile.

For consistency with the params ReadOnlySpan overloads that use NET9_0_OR_GREATER, consider changing this conditional to NET9_0_OR_GREATER instead of NET, or explicitly set C# 12+ LangVersion for .NET 5.0-8.x targets.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5a66c71 and bb618eb.

📒 Files selected for processing (68)
  • src/NLog/Common/AsyncLogEventInfo.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Conditions/ConditionExpression.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/Config/MethodFactory.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Internal/DictionaryEntryEnumerable.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Internal/ScopeContextAsyncState.cs
  • src/NLog/Internal/SingleItemOptimizedHashSet.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/LogFactory.cs
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Logger-V1Compat.cs
  • src/NLog/Logger-generated.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Logger.cs
  • src/NLog/MessageTemplates/ValueFormatter.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/Targets/MethodCallTarget.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
🚧 Files skipped from review as they are similar to previous changes (36)
  • src/NLog/LogEventBuilder.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/MessageTemplates/ValueFormatter.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/LogFactory.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Layouts/Typed/Layout.cs
🧰 Additional context used
🧬 Code graph analysis (10)
src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs (2)
src/NLog/Internal/ReflectionHelpers.cs (1)
  • ReflectionHelpers (45-196)
src/NLog/Targets/MethodCallTarget.cs (2)
  • ReflectionHelpers (184-197)
  • ReflectionHelpers (206-220)
src/NLog/Conditions/ConditionRelationalExpression.cs (2)
src/NLog/Config/PropertyTypeConverter.cs (2)
  • Convert (84-114)
  • CultureInfo (199-208)
src/NLog/LayoutRenderers/LayoutRenderer.cs (1)
  • CultureInfo (232-235)
src/NLog/Internal/ReflectionHelpers.cs (1)
src/NLog/Internal/ObjectReflectionCache.cs (1)
  • PropertyInfo (222-249)
src/NLog/Logger-generated.cs (1)
src/NLog/Logger.cs (2)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (2)
src/NLog/Targets/Wrappers/LogEventDroppedEventArgs.cs (2)
  • LogEventDroppedEventArgs (41-53)
  • LogEventDroppedEventArgs (47-47)
src/NLog/Targets/Wrappers/LogEventQueueGrowEventArgs.cs (2)
  • LogEventQueueGrowEventArgs (44-66)
  • LogEventQueueGrowEventArgs (51-55)
src/NLog/Logger-V1Compat.cs (1)
src/NLog/Logger.cs (7)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
  • WriteToTargets (827-831)
  • WriteToTargets (833-841)
  • WriteToTargets (843-853)
  • WriteToTargets (855-863)
  • WriteToTargets (866-874)
src/NLog/Config/AssemblyExtensionLoader.cs (2)
src/NLog/Internal/PathHelpers.cs (2)
  • PathHelpers (38-98)
  • TrimDirectorySeparators (71-78)
src/NLog/Internal/AssemblyHelpers.cs (1)
  • System (45-113)
src/NLog/Conditions/ConditionLiteralExpression.cs (3)
src/NLog/Conditions/ConditionExpression.cs (1)
  • ToString (92-92)
src/NLog/Conditions/ConditionLayoutExpression.cs (1)
  • ToString (65-68)
src/NLog/LayoutRenderers/LayoutRenderer.cs (1)
  • CultureInfo (232-235)
src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs (1)
src/NLog/Internal/StackTraceUsageUtils.cs (1)
  • StackTraceUsageUtils (44-268)
src/NLog/Targets/LineEndingMode.cs (3)
src/NLog/Internal/TargetWithFilterChain.cs (2)
  • Equals (376-379)
  • Equals (381-386)
src/NLog/LogFactory.cs (2)
  • Equals (1113-1116)
  • Equals (1118-1121)
src/NLog/LogLevel.cs (2)
  • Equals (363-366)
  • Equals (374-377)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: task-list-completed
🔇 Additional comments (44)
src/NLog/Internal/TimeoutContinuation.cs (1)

96-96: LGTM! Signature correctly updated for .NET 10 nullable annotations.

The parameter type change from object to object? correctly aligns with the TimerCallback delegate signature in .NET 10, which annotates the state parameter as nullable. Since the parameter is unused in the method body and null is always passed at the call site (line 57), this annotation accurately reflects the actual usage.

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

109-109: LGTM! Correctly addresses .NET 10 nullable annotation.

The change from default to string.Empty properly satisfies the non-nullable contract for IDictionaryEnumerator.Key in .NET 10. Since MoveNext() always returns false for this empty enumerator, the property should not be accessed in typical usage patterns, making string.Empty a safe and harmless sentinel value.

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

591-591: LGTM! Null-safe fallback correctly addresses NET10 nullable warnings.

The addition of ?? GetType().ToString() provides a defensive fallback for the theoretical case where base.ToString() returns null, consistent with .NET 10's nullable annotations. This ensures the method always returns a non-null string while maintaining existing behavior.

src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (1)

469-469: LGTM! Nullable annotations correctly align with .NET delegate signatures.

All four method signature updates correctly add nullable annotations to match the updated .NET 10 delegate signatures:

  • ProcessPendingEvents and FlushEventsInQueue now match TimerCallback and WaitCallback respectively
  • OnRequestQueueDropItem and OnRequestQueueGrow now match EventHandler<T>

The method bodies already handle these parameters safely (unused, or using null-safe operators), so no additional logic changes are required.

Also applies to: 523-523, 617-617, 622-622

src/NLog/Targets/LineEndingMode.cs (3)

188-191: LGTM! Nullable annotation correctly applied.

The Equals(object?) override properly accepts a nullable parameter, and the implementation using pattern matching safely handles null inputs.


196-199: LGTM! Nullable annotation correctly applied.

The Equals(LineEndingMode?) method properly accepts a nullable parameter, and the implementation correctly uses the null-conditional operator other?._newLineCharacters for safe null handling.


213-217: LGTM! Nullable return type correctly applied.

The ConvertFrom override now correctly returns object? to reflect that the base implementation can return null, aligning with .NET 10's nullable annotations for TypeConverter.ConvertFrom.

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

360-362: LGTM! Correctly handles .NET 10 nullable annotations.

The change from explicit string to var combined with the null-check properly addresses the .NET 10 nullable reference type annotations for Convert.ToString. When the input value is null, Convert.ToString returns null (not string.Empty), so the defensive null-check maintains the method's contract of always returning a non-null string while avoiding compiler warnings.

src/NLog/Conditions/ConditionMethods.cs (3)

79-85: LGTM - Improved null safety for condition evaluation.

The explicit null checks prevent NullReferenceException and align with .NET 10's nullable annotations. Returning false for null inputs is appropriate for a predicate method used in condition expressions and consistent with how the Length method handles nulls.


95-101: LGTM - Consistent null handling.

The null checks mirror the approach in Contains and prevent exceptions during condition evaluation. The implementation correctly guards the StartsWith call.


111-117: LGTM - Completes consistent null safety pattern.

The null checks complete the consistent approach across Contains, StartsWith, and EndsWith, ensuring all three string predicate methods handle null inputs uniformly.

src/NLog/Config/MethodFactory.cs (3)

173-173: LGTM: Type inference aligns with updated nullable return type.

The change from explicit object[] to var is appropriate given that ResolveDefaultMethodParameters now returns object?[] to properly handle nullable elements.


208-208: LGTM: Correct nullable annotation for reflection invoke.

The return type change from object to object? properly reflects that MethodInfo.Invoke can return null, aligning with NET10 nullable annotations.


223-229: LGTM: Properly propagates nullable annotations for optional parameters.

The changes from object[] to object?[] correctly reflect that optional parameter default values (line 233) can be null. This ensures proper nullable reference type handling throughout the method parameter resolution chain.

src/NLog/Targets/MethodCallTarget.cs (1)

270-272: LGTM - Null-safe exception handling.

The null check ensures the original exception is preserved when InnerException is null, preventing a potential null reference throw. This correctly aligns with NET10's nullable annotations for TargetInvocationException.InnerException.

src/NLog/Common/InternalLogger.cs (1)

425-425: LGTM - Type inference with nullable handling.

The change to var correctly infers string? from Path.GetDirectoryName, and the subsequent null check on Line 426 properly handles the nullable result.

src/NLog/Common/AsyncLogEventInfo.cs (1)

92-92: LGTM - Standard nullable Equals override.

The nullable parameter annotation correctly aligns with .NET's nullable reference type annotations for object.Equals(object?), and the pattern matching implementation handles null appropriately.

src/NLog/Filters/WhenRepeatedFilter.cs (3)

151-151: LGTM - Out var usage.

The change to out var is clean and the type FilterInfo is clear from context.


381-381: LGTM - Null-safe hash code computation.

The null check correctly prevents potential NullReferenceException when stringValue is null, returning 0 as a reasonable hash code for null values. This aligns with NET10's nullable annotations for StringComparer.GetHashCode.


408-408: LGTM - Standard nullable Equals override.

The nullable parameter annotation correctly aligns with .NET's nullable reference type annotations, and the pattern matching implementation handles null appropriately.

src/NLog/Internal/ExceptionMessageFormatProvider.cs (2)

45-45: LGTM - Interface signature aligned with .NET nullable annotations.

The nullable parameter annotations correctly match the ICustomFormatter.Format interface signature from .NET, and the implementation safely handles null values for all parameters.


80-80: LGTM - Interface signature aligned with .NET nullable annotations.

The nullable parameter annotation correctly matches the IFormatProvider.GetFormat interface signature from .NET, and the implementation safely handles null via the equality comparison.

src/NLog/Targets/FileTarget.cs (2)

1018-1018: Correctly updated to match .NET 10 Timer callback signature.

The parameter nullability annotation aligns with the updated TimerCallback delegate signature in .NET 10, which now explicitly marks the state parameter as nullable.


1248-1248: LGTM! Handles new nullable annotation from .NET 10.

The null-coalescing operator appropriately handles Path.GetDirectoryName now returning string? in .NET 10. While the fallback to string.Empty might cause Directory.CreateDirectory to throw an ArgumentException in rare edge cases where the path has no directory component, this is acceptable given the PR's objective of fixing nullable warnings.

src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs (1)

89-95: LGTM! Improved null safety and error clarity.

The explicit null check for the getter method prevents passing null to CreateLateBoundMethod and provides a clearer error message when a property lacks a valid getter. This aligns well with .NET 10's stricter nullable annotations.

src/NLog/Internal/ReflectionHelpers.cs (3)

74-74: LGTM! Widened conditional to cover all modern .NET targets.

Changing from NETCOREAPP3_1_OR_GREATER to NET (which includes .NET 5.0+) is correct since all these targets support RuntimeFeature.IsDynamicCodeSupported. This simplifies the conditional logic while maintaining the same behavior.


183-186: LGTM! Nullable parameter aligns with implementation.

The nullable PropertyInfo? parameter correctly reflects that the method handles null input (line 185 checks p != null), matching .NET 10's nullable annotations.


188-195: LGTM! Nullable return type matches actual behavior.

The return type change to object? correctly reflects that both PropertyInfo.GetValue and MethodInfo.Invoke can return null values.

src/NLog/Targets/DefaultJsonSerializer.cs (1)

444-444: LGTM! Appropriate null-safety guards for NET10 annotations.

The null-coalescing operators added to ToString() and Convert.ToString() calls are appropriate defensive measures responding to NET10's improved nullable annotations. Falling back to string.Empty ensures consistent non-null string handling throughout the JSON serialization pipeline and prevents potential null reference issues in downstream methods like AppendStringEscape.

Also applies to: 515-515, 750-750

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

251-251: LGTM! Null-safety improvement for Path.GetDirectoryName.

The null-coalescing operator correctly handles the case where Path.GetDirectoryName returns null, ensuring the method always returns a non-null string as expected by the property signature. This aligns with .NET 10's nullable annotations.

src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs (1)

93-93: LGTM! Consistent null-safety improvement.

The null-coalescing operator correctly guards against a null return from Path.GetDirectoryName, matching the pattern applied throughout the codebase for .NET 10 compatibility.

src/NLog/Config/AssemblyExtensionLoader.cs (1)

317-317: LGTM! Consistent null-coalescing for Assembly.FullName.

The null-coalescing operators correctly handle the case where Assembly.FullName could be null, ensuring non-null values are stored in the alreadyRegistered HashSet. This provides consistent null-safety across assembly registration logic.

Also applies to: 328-328, 352-352

src/NLog/Internal/StackTraceUsageUtils.cs (2)

107-107: LGTM! Safe fallback for ToString() null return.

The null-coalescing operator provides a sensible fallback when method.ToString() returns null, preserving the original method name. This improves robustness against .NET 10's nullable annotations.


219-221: No action required. Both call sites of LookupAssemblyFromMethod already perform null-checking before invocation:

  • Line 313 (CallSiteInformation.cs): stackMethod is null-checked at lines 310–311 before the call
  • Line 250 (StackTraceUsageUtils.cs): method is null-checked in the condition (if (method != null &&)

All callers follow the correct pattern. The method implementation is also safe with the null-conditional operator on line 221.

src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs (2)

173-173: LGTM: Proper nullable annotation.

The indexer return type correctly reflects that StackTrace.GetFrame() may return null in NET10.


191-204: LGTM: Correct nullable handling.

The separator pattern is properly implemented—starts null, appends nothing on the first iteration, then appends the configured separator between subsequent frames. Null frames are correctly skipped.

src/NLog/Targets/ColoredConsoleSystemPrinter.cs (1)

89-89: Concern not applicable to NLog's target framework strategy.

NLog targets net35, net46, netstandard2.0, and netstandard2.1 only. It does not directly target netcoreapp3.0 or netcoreapp3.1. The change from NETCOREAPP3_0_OR_GREATER to NET has no practical impact: projects using .NET Core 3.x will consume NLog via the netstandard2.1 build, which has NETSTANDARD2_1_OR_GREATER defined and therefore will use the span-based AsSpan path. No regression occurs for any supported consumer of NLog.

Likely an incorrect or invalid review comment.

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

242-245: LGTM: Signature correctly updated to match .NET 10 interface annotations.

The signature now accepts nullable parameters T? x, T? y as required by .NET 10's IEqualityComparer<T>.Equals. The ReferenceEquals implementation is null-safe and handles all cases correctly (returns true when both are null, false when only one is null, compares references otherwise).

src/NLog/Conditions/ConditionRelationalExpression.cs (2)

217-222: LGTM! Defensive null handling for LogLevel conversion.

The introduction of the strval local variable with null-coalescing ensures LogLevel.FromString receives a non-null string, addressing NET10's nullable annotations. Using string.Empty as a fallback is appropriate.


224-228: LGTM! Defensive null handling for string conversion.

The null-coalescing operator ensures the val parameter is assigned a non-null string, addressing NET10's nullable annotations. The string.Empty fallback maintains the method's contract.

src/NLog/Conditions/ConditionLiteralExpression.cs (1)

81-81: LGTM! Ensures ToString never returns null.

The null-coalescing operator guarantees the method satisfies its non-null string return contract, addressing NET10's nullable annotations. The string.Empty fallback is appropriate for cases where Convert.ToString returns null.

src/NLog/Conditions/ConditionExpression.cs (1)

56-58: Correct the characterization: this change narrows, not broadens, the condition.

The change from NETCOREAPP3_0_OR_GREATER to NET actually narrows the scope. NET covers only .NET 5.0+, while NETCOREAPP3_0_OR_GREATER covers .NET Core 3.0 and 3.1, all of which support the NotNullIfNotNull attribute. Since NLog targets net35;net46;netstandard2.0;netstandard2.1, neither the old nor new part of the OR condition affects actual library targets—the effective condition is NETSTANDARD2_1_OR_GREATER alone. The redundant || NET part is harmless for this library but represents a narrower coverage of supported frameworks compared to the original || NETCOREAPP3_0_OR_GREATER.

Likely an incorrect or invalid review comment.

tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs (1)

167-167: LGTM! Preprocessor condition appropriately broadened.

The change from NETCOREAPP3_0_OR_GREATER to NET correctly broadens the availability of the span-based Write overload to all .NET 5.0+ targets while maintaining .NET Standard 2.1 support.

src/NLog/Logger.cs (1)

606-668: Method signature changes are correct but highlight a language version compatibility concern

The signature changes from params ReadOnlySpan<object?> to in ReadOnlySpan<object?> are appropriate:

  • Public API methods use params ReadOnlySpan, which is a C# 13 feature (.NET 9.0+)
  • Private helper methods use in ReadOnlySpan, which works on earlier .NET versions
  • The implementation correctly handles the span (checking IsEmpty, calling ToArray() when needed)

However, these private methods are within a #if NETSTANDARD2_1_OR_GREATER || NET block yet are called with collection expression syntax [argument], which requires C# 12. This creates a mismatch: the preprocessor guard targets framework compatibility, but collection expressions are a language feature that requires a specific C# compiler version.

Logger-V1Compat.cs calls these methods but does not define them—it relies on the definitions in Logger.cs.

{
var nlogAssemblyLocation = AssemblyHelpers.GetAssemblyFileLocation(typeof(LogFactory).Assembly);
var nlogAssemblyDirectory = string.IsNullOrEmpty(nlogAssemblyLocation) ? nlogAssemblyLocation : PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation));
var nlogAssemblyDirectory = string.IsNullOrEmpty(nlogAssemblyLocation) ? nlogAssemblyLocation : PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation) ?? string.Empty);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Review null-handling in ternary expression.

The ternary string.IsNullOrEmpty(nlogAssemblyLocation) ? nlogAssemblyLocation : ... returns nlogAssemblyLocation when it's null or empty, which could propagate a null value to nlogAssemblyDirectory. This appears inconsistent with the intent to ensure a non-null string.

Consider simplifying to ensure non-null:

🔎 Suggested refactor
-var nlogAssemblyDirectory = string.IsNullOrEmpty(nlogAssemblyLocation) ? nlogAssemblyLocation : PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation) ?? string.Empty);
+var nlogAssemblyDirectory = string.IsNullOrEmpty(nlogAssemblyLocation) ? string.Empty : PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation) ?? string.Empty);
📝 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
var nlogAssemblyDirectory = string.IsNullOrEmpty(nlogAssemblyLocation) ? nlogAssemblyLocation : PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation) ?? string.Empty);
var nlogAssemblyDirectory = string.IsNullOrEmpty(nlogAssemblyLocation) ? string.Empty : PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation) ?? string.Empty);
🤖 Prompt for AI Agents
In src/NLog/Config/AssemblyExtensionLoader.cs around line 398, the ternary
currently returns nlogAssemblyLocation when it is null or empty which can
propagate null; change it to return a non-null string (e.g. string.Empty) when
nlogAssemblyLocation is null or empty, otherwise compute and trim the directory:
use string.IsNullOrEmpty(nlogAssemblyLocation) ? string.Empty :
PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation)
?? string.Empty); this guarantees nlogAssemblyDirectory is never null while
preserving the trimmed directory path when available.

Comment on lines +101 to +102
#if NETSTANDARD2_1_OR_GREATER || NET
WriteToTargetsWithSpan(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, [arg1, arg2]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Collection expression syntax requires .NET 8.0 or greater

The collection expression syntax [arg1, arg2] used in WriteToTargetsWithSpan calls is guarded by #if NETSTANDARD2_1_OR_GREATER || NET, but collection expressions were introduced in C# 12, which is the default for .NET 8+. The NET symbol matches .NET 5, 6, and 7 where this syntax will cause compilation errors.

This pattern appears throughout the file in multiple overload methods across all log levels (Log, Trace, Debug, Info, Warn, Error, Fatal).

🔎 Recommended fix

Change the conditional compilation directive to use NET8_0_OR_GREATER:

-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET8_0_OR_GREATER
                 WriteToTargetsWithSpan(..., [...]);
 #else
                 WriteToTargets(..., new object?[] { ... });
 #endif

Apply this change to all occurrences throughout the file.

Also applies to: 127-128, 268-269, 291-292, 511-512, 535-536, 718-719, 742-743, 876-877, 898-899, 1106-1107, 1128-1129, 1305-1306, 1329-1330, 1463-1464, 1485-1486, 1693-1694, 1715-1716, 1892-1893, 1916-1917, 2050-2051, 2072-2073, 2280-2281, 2302-2303, 2479-2480, 2503-2504, 2637-2638, 2659-2660, 2867-2868, 2889-2890, 3066-3067, 3090-3091, 3224-3225, 3246-3247, 3454-3455, 3476-3477, 3653-3654, 3677-3678, 3811-3812, 3833-3834, 4041-4042, 4063-4064

🤖 Prompt for AI Agents
In src/NLog/Logger-V1Compat.cs around lines 101-102 (and also at the other
listed occurrences), the preprocessor symbol check uses "#if
NETSTANDARD2_1_OR_GREATER || NET" which incorrectly allows collection expression
syntax (C# 12) to be compiled on .NET versions prior to .NET 8; update the
conditional to "#if NET8_0_OR_GREATER" (replace the current directive) for every
occurrence listed so the collection expression `[arg1, arg2]` is only compiled
when C# 12/.NET 8 or greater is targeted.

string archiveFilePath = BuildArchiveFilePath(archiveFileName, int.MaxValue, DateTime.MinValue);
string archiveFileWildcard = archiveFilePath.Replace(int.MaxValue.ToString(), "*");
string archiveDirectory = Path.GetDirectoryName(archiveFilePath);
string archiveDirectory = Path.GetDirectoryName(archiveFilePath) ?? string.Empty;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the file around lines 85-95 and 245-255 to examine both patterns
echo "=== Lines 85-95 (line 89 context) ==="
sed -n '85,95p' src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs

echo -e "\n=== Lines 245-255 (line 247-249 context) ==="
sed -n '245,255p' src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs

Repository: NLog/NLog

Length of output: 1590


🏁 Script executed:

#!/bin/bash
# Find the DeleteOldFilesBeforeArchive method definition to understand what it expects
rg -A 15 'DeleteOldFilesBeforeArchive.*\(' src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs | head -50

Repository: NLog/NLog

Length of output: 1113


🏁 Script executed:

#!/bin/bash
# Check if there are any other usages of Path.GetDirectoryName in this file
rg 'Path\.GetDirectoryName' src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs -B 2 -A 2

Repository: NLog/NLog

Length of output: 988


Consider adopting the same defensive pattern as line 247-249 for consistency.

The null-coalescing operator correctly addresses the NET10 nullable annotation warning. However, line 247-249 uses a safer pattern for the same scenario: explicitly checking if (string.IsNullOrEmpty(archiveDirectory)) return 0; and returning early.

At line 89, passing an empty string to DeleteOldFilesBeforeArchive may be unintended. Since Path.GetDirectoryName can return null, consider either:

  • Adopting the null/empty check pattern from line 247-249 (return early or handle gracefully), or
  • Verifying that DeleteOldFilesBeforeArchive correctly handles empty string inputs

This would make the null-handling approach consistent across the codebase.

🤖 Prompt for AI Agents
In src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs around
line 89, Path.GetDirectoryName(archiveFilePath) is assigned with a
null-coalescing to string.Empty which can pass an empty string into
DeleteOldFilesBeforeArchive; change this to the same defensive pattern used at
lines 247-249 by checking if string.IsNullOrEmpty(archiveDirectory) and return
early (or otherwise handle the null case) before calling
DeleteOldFilesBeforeArchive, or alternatively ensure and document that
DeleteOldFilesBeforeArchive correctly handles empty-string inputs — make the
null/empty check consistent with the existing pattern.

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/NLog/Config/AssemblyExtensionLoader.cs (1)

357-357: Fix null-safety handling of Assembly.FullName on lines 357 and 362

Lines 357 and 362 use assembly.FullName directly without null protection, but line 352 adds to the HashSet with null-coalescing (?? string.Empty). This inconsistency creates a null-safety bug:

  • Line 357: assembly.FullName.StartsWith() will throw NullReferenceException if FullName is null
  • Line 362: IncludeAsHiddenAssembly(assembly.FullName) passes null without protection

Both lines must handle null consistently with line 352, either by using null-coalescing (?? string.Empty) or a null check.

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

2043-2043: Typo in preprocessor symbol: NETNET9_0_OR_GREATER

Line 2043 contains NETNET9_0_OR_GREATER which appears to be a typo. This should likely be NET9_0_OR_GREATER or NET to match the pattern used elsewhere in the file.

🔎 Proposed fix
-#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATER
+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER

2749-2751: Incorrect preprocessor symbol for OverloadResolutionPriority attribute

Line 2749 uses || NET instead of || NET9_0_OR_GREATER. The OverloadResolutionPriority attribute was introduced in .NET 9.0, so using NET (which matches all .NET versions including pre-9.0) will cause compilation errors on .NET 5, 6, 7, and 8.

🔎 Proposed fix
-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATER
         [OverloadResolutionPriority(-1)]
 #endif

101-102: Collection expression syntax requires .NET 8.0 or greater

The collection expression syntax [arg1, arg2, ...] used throughout this file is guarded by #if NETSTANDARD2_1_OR_GREATER || NET. However, collection expressions were introduced in C# 12, which is the default language version for .NET 8+. The NET symbol matches .NET 5, 6, and 7 where this syntax will cause compilation errors.

🔎 Recommended fix

Change all occurrences of the conditional compilation directive to use NET8_0_OR_GREATER:

-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET8_0_OR_GREATER
                 WriteToTargetsWithSpan(..., [...]);
 #else
                 WriteToTargets(..., new object?[] { ... });
 #endif

Apply this change to all 44 occurrences listed in the line ranges above.

Also applies to: 127-128, 268-270, 291-293, 511-512, 535-536, 718-719, 742-743, 876-878, 898-900, 1106-1107, 1128-1129, 1305-1306, 1329-1330, 1463-1465, 1485-1487, 1693-1694, 1715-1716, 1892-1893, 1916-1917, 2050-2052, 2072-2074, 2280-2281, 2302-2303, 2479-2480, 2503-2504, 2637-2639, 2659-2661, 2867-2868, 2889-2890, 3066-3067, 3090-3091, 3224-3226, 3246-3248, 3454-3455, 3476-3477, 3653-3654, 3677-3678, 3811-3813, 3833-3835, 4041-4042, 4063-4064

🧹 Nitpick comments (3)
src/NLog/Config/AssemblyExtensionLoader.cs (1)

398-398: Return string.Empty instead of potentially-null nlogAssemblyLocation

The ternary returns nlogAssemblyLocation when it's null or empty, which propagates null. While subsequent usages (lines 400, 405, 410) safely check for null/empty, this is semantically inconsistent with the PR's nullable-safety improvements.

🔎 Suggested fix
-var nlogAssemblyDirectory = string.IsNullOrEmpty(nlogAssemblyLocation) ? nlogAssemblyLocation : PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation) ?? string.Empty);
+var nlogAssemblyDirectory = string.IsNullOrEmpty(nlogAssemblyLocation) ? string.Empty : PathHelpers.TrimDirectorySeparators(System.IO.Path.GetDirectoryName(nlogAssemblyLocation) ?? string.Empty);
src/NLog/LogFactory.cs (1)

655-655: Unnecessary fully-qualified name for AsyncHelpers.

The NLog.Common namespace is already imported via using NLog.Common; at line 45. The fully-qualified call NLog.Common.AsyncHelpers.PreventMultipleCalls(...) could be simplified to just AsyncHelpers.PreventMultipleCalls(...).

🔎 Suggested simplification
-                asyncContinuation = asyncContinuation is null ? null : NLog.Common.AsyncHelpers.PreventMultipleCalls(asyncContinuation);
+                asyncContinuation = asyncContinuation is null ? null : AsyncHelpers.PreventMultipleCalls(asyncContinuation);
src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs (1)

89-95: Good defensive null-check for the getter method.

The explicit validation prevents potential NullReferenceException when GetGetMethod() returns null. Consider a slightly more precise error message since the property was found but lacks a getter:

🔎 Optional: More precise error message
             var getGetMethodInfo = propertyInfo.GetGetMethod();
             if (getGetMethodInfo is null)
             {
-                throw new ArgumentException($"Property '{Property}' not found in System.Diagnostics.Process with valid getter-method");
+                throw new ArgumentException($"Property '{Property}' in System.Diagnostics.Process does not have a public getter method");
             }
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb618eb and 739827f.

📒 Files selected for processing (68)
  • src/NLog/Common/AsyncLogEventInfo.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Conditions/ConditionExpression.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/Config/MethodFactory.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Internal/DictionaryEntryEnumerable.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Internal/ScopeContextAsyncState.cs
  • src/NLog/Internal/SingleItemOptimizedHashSet.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/LogFactory.cs
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Logger-V1Compat.cs
  • src/NLog/Logger-generated.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Logger.cs
  • src/NLog/MessageTemplates/ValueFormatter.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/Targets/MethodCallTarget.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
🚧 Files skipped from review as they are similar to previous changes (33)
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
  • src/NLog/ScopeContext.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • src/NLog/Targets/MethodCallTarget.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-12T01:22:52.437Z
Learnt from: thatrajeevkr
Repo: NLog/NLog PR: 5999
File: src/NLog/Layouts/JSON/JsonLayout.cs:162-166
Timestamp: 2025-09-12T01:22:52.437Z
Learning: When suggesting StringBuilder optimizations for NLog layouts marked as [ThreadAgnostic], avoid shared StringBuilder fields as they create thread safety issues. Prefer simple string operations or ThreadLocal<StringBuilder> if allocation profiling shows a real performance impact.

Applied to files:

  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
🧬 Code graph analysis (14)
src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (3)
src/NLog/Layouts/Typed/Layout.cs (3)
  • ToString (362-365)
  • ToString (374-377)
  • ToString (624-627)
src/NLog/Targets/TargetWithContext.cs (1)
  • ToString (727-730)
src/NLog/Layouts/XML/XmlElementBase.cs (1)
  • ToString (800-812)
src/NLog/Config/LoggingConfiguration.cs (2)
src/NLog/Config/LoggingRule.cs (1)
  • Target (134-134)
src/NLog/Internal/ThreadSafeDictionary.cs (1)
  • TryGetValue (156-159)
src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs (2)
src/NLog/Internal/ReflectionHelpers.cs (1)
  • ReflectionHelpers (45-196)
src/NLog/Targets/MethodCallTarget.cs (2)
  • ReflectionHelpers (184-197)
  • ReflectionHelpers (206-220)
src/NLog/Internal/ScopeContextAsyncState.cs (10)
src/NLog/Config/LoggerNameMatcher.cs (1)
  • ToString (114-117)
src/NLog/Internal/ObjectReflectionCache.cs (2)
  • ToString (331-331)
  • ToString (423-426)
src/NLog/Layouts/Typed/Layout.cs (3)
  • ToString (362-365)
  • ToString (374-377)
  • ToString (624-627)
src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (2)
  • ToString (250-253)
  • ToString (272-275)
src/NLog/LogEventInfo.cs (1)
  • ToString (585-588)
src/NLog/ScopeContext.cs (2)
  • ToString (525-528)
  • ToString (872-875)
src/NLog/Targets/TargetWithContext.cs (1)
  • ToString (727-730)
src/NLog/Config/LoggingConfigurationParser.cs (1)
  • ToString (1678-1681)
src/NLog/Layouts/JSON/JsonLayout.cs (1)
  • ToString (475-483)
src/NLog/LogLevel.cs (1)
  • ToString (343-346)
src/NLog/Internal/ReflectionHelpers.cs (1)
src/NLog/Internal/ObjectReflectionCache.cs (1)
  • PropertyInfo (222-249)
src/NLog/Common/AsyncLogEventInfo.cs (1)
src/NLog/LogEventInfo.cs (1)
  • AsyncLogEventInfo (576-579)
src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs (3)
src/NLog/LayoutRenderers/GuidLayoutRenderer.cs (2)
  • GetStringValue (80-83)
  • IStringValueRenderer (78-78)
src/NLog/LayoutRenderers/ThreadIdLayoutRenderer.cs (1)
  • IStringValueRenderer (62-65)
src/NLog/Internal/IStringValueRenderer.cs (1)
  • GetFormattedString (49-49)
src/NLog/Config/AssemblyExtensionLoader.cs (2)
src/NLog/Internal/PathHelpers.cs (2)
  • PathHelpers (38-98)
  • TrimDirectorySeparators (71-78)
src/NLog/Internal/AssemblyHelpers.cs (1)
  • System (45-113)
src/NLog/LogLevelTypeConverter.cs (2)
src/NLog/Targets/LineEndingMode.cs (2)
  • CanConvertFrom (207-210)
  • ConvertFrom (213-217)
src/NLog/Internal/PropertyHelper.cs (1)
  • Type (186-190)
src/NLog/Logger-V1Compat.cs (1)
src/NLog/Logger.cs (7)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
  • WriteToTargets (827-831)
  • WriteToTargets (833-841)
  • WriteToTargets (843-853)
  • WriteToTargets (855-863)
  • WriteToTargets (866-874)
src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs (1)
src/NLog/Internal/StackTraceUsageUtils.cs (1)
  • StackTraceUsageUtils (44-268)
src/NLog/Conditions/ConditionLiteralExpression.cs (10)
src/NLog/Conditions/ConditionExpression.cs (1)
  • ToString (92-92)
src/NLog/Conditions/ConditionRelationalExpression.cs (1)
  • ToString (79-82)
src/NLog/Conditions/ConditionAndExpression.cs (1)
  • ToString (66-69)
src/NLog/Conditions/ConditionExceptionExpression.cs (1)
  • ToString (42-45)
src/NLog/Conditions/ConditionLayoutExpression.cs (1)
  • ToString (65-68)
src/NLog/Conditions/ConditionLoggerNameExpression.cs (1)
  • ToString (42-45)
src/NLog/Conditions/ConditionMessageExpression.cs (1)
  • ToString (42-45)
src/NLog/Conditions/ConditionNotExpression.cs (1)
  • ToString (57-60)
src/NLog/Conditions/ConditionOrExpression.cs (1)
  • ToString (65-68)
src/NLog/Conditions/ConditionLevelExpression.cs (1)
  • ToString (42-45)
src/NLog/Internal/PropertyHelper.cs (1)
src/NLog/Internal/ReflectionHelpers.cs (1)
  • IsValidPublicProperty (183-186)
src/NLog/Layouts/Typed/Layout.cs (3)
src/NLog/Layouts/SimpleLayout.cs (1)
  • ToString (274-282)
src/NLog/Targets/TargetWithContext.cs (1)
  • ToString (727-730)
src/NLog/Layouts/XML/XmlElementBase.cs (1)
  • ToString (800-812)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: task-list-completed
🔇 Additional comments (53)
src/NLog/Internal/FormatHelper.cs (1)

65-65: LGTM! Null-safety guard correctly addresses NET10 nullable annotations.

The null-coalescing operator ensures the method always returns a non-null string, which aligns with the method's return type. This defensive approach is consistent with the null-handling pattern used elsewhere in the method (lines 54, 80) and addresses the compiler warning introduced by NET10's enhanced nullable annotations for Convert.ToString.

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

591-591: LGTM: Null-safe fallback aligns with NET10 nullable annotations.

The addition of ?? GetType().ToString() ensures the method always returns a non-null string, addressing NET10's stricter nullable reference type annotations. This pattern is consistent with similar ToString() implementations throughout the codebase (e.g., ScopeContext.cs, TargetWithContext.cs, Layout.cs).

src/NLog/Config/MethodFactory.cs (3)

173-173: LGTM!

Using var here correctly infers the object?[] type from the updated ResolveDefaultMethodParameters return type, aligning the nullable annotations throughout the call chain.


208-221: LGTM!

The object? return type correctly reflects that MethodInfo.Invoke can return null (for void methods or methods explicitly returning null). This aligns with .NET 10's updated nullable annotations.


223-245: LGTM!

The object?[] return type and matching allocation correctly reflect that ParameterInfo.DefaultValue can be null. This properly addresses nullable warnings from .NET 10 annotations while maintaining semantic correctness.

src/NLog/Targets/ColoredConsoleSystemPrinter.cs (1)

89-89: LGTM! Correctly enables span APIs for .NET 5.0+.

The change from NETCOREAPP3_0_OR_GREATER to NET is correct and fixes the preprocessor condition for modern .NET. The NET symbol properly targets .NET 5.0 and later, whereas NETCOREAPP3_0_OR_GREATER doesn't apply to .NET 5.0+ frameworks. This ensures the span-based optimization is used on all supported modern .NET targets.

src/NLog/LogLevelTypeConverter.cs (1)

46-99: LGTM! Nullable annotations correctly align with .NET 10 TypeConverter base class.

The nullable annotation updates are appropriate:

  • CanConvertFrom and CanConvertTo now have nullable context parameters matching the base class signature
  • ConvertFrom and ConvertTo now return object? to reflect that they can return null
  • Null handling throughout the implementation is safe (e.g., value?.GetType(), value?.ToString() ?? string.Empty)
  • The IsNumericType helper correctly handles nullable Type? parameter

These changes are necessary mechanical updates to address .NET 10's enhanced nullable reference type annotations.

src/NLog/Targets/FileTarget.cs (2)

1018-1018: LGTM! Correct nullable annotation for Timer callback.

The parameter change from object state to object? state correctly aligns with .NET 10's updated TimerCallback signature. Since the state parameter is unused in the method body, this is a safe and appropriate fix for the nullable warning.


1248-1248: LGTM! Appropriate null-safety guard for Path API.

The null-coalescing operator correctly handles .NET 10's updated nullable annotation for Path.GetDirectoryName, which now returns string?. Using string.Empty as a fallback is appropriate since:

  • It satisfies the non-null requirement for Directory.CreateDirectory on Line 1252
  • If the directory name is actually empty (edge case), the subsequent CreateDirectory call will throw, which is caught and properly wrapped in a NLogRuntimeException on Line 1257
src/NLog/Filters/WhenRepeatedFilter.cs (2)

151-151: LGTM: Minor syntax modernization.

The change to out var is a minor style improvement that makes the code slightly more concise.


381-381: LGTM: Proper nullable-reference handling for .NET 10.

The null-safe hash code computation (line 381) correctly guards against passing null to StringComparer.Ordinal.GetHashCode(), which now expects a non-nullable string in .NET 10. Returning 0 for null is a reasonable default.

The nullable parameter annotation on the Equals override (line 408) correctly reflects the updated .NET 10 signature.

Also applies to: 408-408

src/NLog/MessageTemplates/ValueFormatter.cs (2)

266-272: LGTM! Nullable parameter handled correctly.

The change from string to string? aligns with NET10 nullable annotations, particularly for the call site at line 203 where IConvertible.ToString() now returns string?. The method body correctly handles null values since StringBuilder.Append(string?) treats null as an empty string without throwing an exception.


355-363: LGTM! Null handling is correct.

The change from object to object? correctly reflects that dictionary values and collection items can be null, aligning with NET10 nullable annotations. Null values are properly handled through the method chain: when pattern matching fails for null items, the code eventually reaches SerializeSimpleObject (lines 189-193), which explicitly handles null by appending "NULL" to the output.

src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs (1)

80-80: No action needed—the change is valid.

The project's TargetFrameworks in src/NLog/NLog.csproj does not include netcoreapp3.0 or netcoreapp3.1. The TargetFrameworks are: net35;net46;netstandard2.0;netstandard2.1.

The change from NETCOREAPP3_0_OR_GREATER to NET is a safe simplification that correctly targets the frameworks the project actually builds for. The condition NETSTANDARD2_1_OR_GREATER || NET applies the span optimization to netstandard2.1 targets and .NET 5+ targets, with no loss of coverage.

src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs (4)

173-173: LGTM: Nullable return type correctly reflects NET10 annotations.

The indexer return type change to StackFrame? is appropriate given that StackTrace.GetFrame() can return null in NET10.


196-201: LGTM: Proper nullable handling for NET10.

The null-conditional operator ?. correctly handles potentially null StackFrame instances, and the empty string check provides appropriate defensive handling for frames without managed methods.


206-222: Excellent: Critical separator bug fixed!

This change correctly implements the separator logic that was proposed in previous reviews. The pattern now matches AppendRaw:

  • First frame: separator is null, so StringBuilder.Append(null) is a no-op (no leading separator)
  • Subsequent frames: separator contains _separator, which is properly inserted between frames

The nullable handling at Line 217 (type?.Name ?? "<no type>") is also a good improvement.


224-239: Excellent: Critical separator bug fixed!

Same as AppendFlat, this change correctly implements the separator logic proposed in previous reviews. The method now properly avoids a leading separator while correctly inserting separators between frames.

src/NLog/Config/AssemblyExtensionLoader.cs (1)

317-317: LGTM! Consistent null-safety for Assembly.FullName

The null-coalescing operators appropriately guard against null values from Assembly.FullName, which can be null according to NET10 annotations. This ensures the alreadyRegistered HashSet always contains non-null strings, preventing lookup mismatches and NullReferenceException.

Also applies to: 328-328, 352-352

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

251-251: LGTM! Null-safety improvement for Path.GetDirectoryName.

The null-coalescing operator correctly guards against Path.GetDirectoryName returning null (which can occur for root directories or paths without a directory component). This ensures EntryAssemblyLocation always returns a non-null string, addressing the NET10 nullable annotations.

src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs (1)

93-93: LGTM! Consistent null-safety pattern.

The change mirrors the null-safety improvement applied across other path-handling components in this PR, ensuring ResolveNLogDir returns a non-null string when Path.GetDirectoryName yields null.

src/NLog/Conditions/ConditionLiteralExpression.cs (1)

81-81: LGTM! Ensures non-null return from ToString().

The null-coalescing operator correctly handles the case where Convert.ToString returns null (addressed by NET10 nullable annotations). This ensures the ToString() override always returns a non-null string as required by the base class contract.

src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs (1)

56-56: LGTM! Null-safety for directory path extraction.

The null-coalescing operator guards against Path.GetDirectoryName returning null. The fallback to string.Empty is appropriate since the called overload at line 59 already handles empty strings by returning early (lines 69-70).

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

109-109: LGTM! Safer null-handling for empty enumerator.

The change from object? Key => default to object Key => string.Empty improves null-safety by guaranteeing a non-null return value. While the Key property should never be accessed in practice (MoveNext always returns false at line 112), returning string.Empty is safer than null and aligns with the tightened nullability contract.

tests/NLog.UnitTests/LogMessageFormatterTests.cs (1)

103-103: LGTM! Broadened conditional compilation for span-based paths.

The change from NETCOREAPP3_1_OR_GREATER to NET appropriately broadens support for span-based MessageTemplateParameter functionality to all modern .NET versions (NET5+), aligning with the PR's objective to address NET10 annotations and span-based APIs.

Also applies to: 135-135

src/NLog/Conditions/ConditionMethods.cs (3)

81-84: LGTM! Null-safety improvement for Contains.

The explicit null checks prevent NullReferenceException and return false when either haystack or needle is null. This is semantically appropriate for a predicate method (you cannot find a substring in a null string).


97-100: LGTM! Null-safety improvement for StartsWith.

Consistent with the Contains method, the null checks prevent exceptions and return false for null inputs, which is appropriate behavior for string predicate operations.


113-116: LGTM! Null-safety improvement for EndsWith.

The null checks complete the consistent null-handling pattern across all three string predicate methods (Contains, StartsWith, EndsWith), ensuring robust behavior when dealing with nullable string inputs.

src/NLog/Config/LoggingConfiguration.cs (1)

118-118: LGTM! Correct nullability annotation for TryGet pattern.

The change from out Target target to out Target? target accurately reflects that the out parameter can be null when the target is not found (TryGetValue returns false). This is proper nullability annotation for the TryGet pattern.

src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs (1)

155-158: LGTM!

The null-coalescing to string.Empty correctly handles Path.GetDirectoryName returning null (e.g., for root paths). Callers already guard against empty strings via !string.IsNullOrEmpty(processDir), so this is a safe nullable warning fix.

src/NLog/Conditions/ConditionExpression.cs (1)

56-58: LGTM!

Broadening the conditional compilation from NETCOREAPP3_0_OR_GREATER to NETSTANDARD2_1_OR_GREATER || NET correctly enables the NotNullIfNotNull attribute for all applicable frameworks where it's available.

src/NLog/Common/AsyncLogEventInfo.cs (1)

92-92: LGTM!

The nullable annotation object? on the Equals override is the correct signature for nullable-aware code. The pattern match obj is AsyncLogEventInfo other safely handles null input.

tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs (1)

389-394: LGTM!

The updated conditional compilation NETSTANDARD2_1_OR_GREATER || NET consistently aligns with the production code changes in this PR for span-based overloads.

src/NLog/Logger-generated.cs (1)

187-216: LGTM on the public span overloads.

The params ReadOnlySpan<object?> overloads for Trace (and similar for other log levels) remain gated under NET9_0_OR_GREATER, which correctly limits these public API surface additions to .NET 9+ where params with spans is supported.

src/NLog/Logger.cs (2)

606-616: Good use of in modifier for span parameters.

Using in ReadOnlySpan<object?> for the private WriteToTargetsWithSpan methods is appropriate—it passes the span by readonly reference, avoiding unnecessary copies on the hot path.


571-604: LGTM on the public Log overloads with ReadOnlySpan.

The span-based Log overloads are correctly gated under NETSTANDARD2_1_OR_GREATER || NET and delegate to the internal WriteToTargetsWithSpan methods appropriately.

src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs (1)

55-60: LGTM - Correctly aligns with interface nullability.

The string? return type now correctly reflects that Thread.CurrentThread.Name can be null, and matches the IStringValueRenderer.GetFormattedString interface signature which declares string?.

Note: The past review comment flagged that other implementations (ThreadIdLayoutRenderer, GuidLayoutRenderer, etc.) return non-nullable string while the interface declares string?. Those implementations are not incorrect—they simply never return null—but this file's change is the correct approach for thread name which genuinely can be null.

src/NLog/LogFactory.cs (4)

53-56: LGTM on IAsyncDisposable implementation.

The IAsyncDisposable interface is correctly gated under NETSTANDARD2_1_OR_GREATER || NET, matching where the interface is available.


376-411: Well-implemented DisposeAsync with appropriate timeout handling.

The async disposal correctly:

  1. Short-circuits if already disposing or no config loaded
  2. Awaits FlushAsync before disposal
  3. Uses a timeout task to prevent indefinite hangs during dispose
  4. Catches and logs exceptions appropriately

One minor observation: the timeout exception is wrapped via .ContinueWith(... throw ...) which creates a faulted task—this works but is slightly unconventional.


1113-1116: LGTM on nullable Equals override.

Standard nullable annotation update for Object.Equals override, correctly handled by the pattern match.


1227-1249: LGTM on nullable sender parameters.

Event handler signatures with object? sender follow .NET conventions for nullable-aware event handlers.

src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (1)

272-275: Consistent null-safety fallback for ToString().

The null-coalescing pattern aligns with similar implementations across the codebase (e.g., Layout.cs line 364, TargetWithContext.cs line 728) ensuring ToString() never returns null.

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

242-245: Correct nullable annotation update for IEqualityComparer<T>.Equals.

The parameter nullability aligns with .NET's updated interface contract. ReferenceEquals already handles null arguments correctly, so no behavioral change.

src/NLog/Internal/PropertyHelper.cs (3)

175-184: Cleaner null-check logic in obsolete method.

The refactored flow correctly inverts the condition to handle null PropertyInfo by returning false early, while assigning a valid result when found. Logic is correct and clearer.


262-263: Minor style change using var for method reflection.

No functional change; just a stylistic preference. The subsequent null-check on line 263 remains correct.


439-442: Explicit null-check before IsValidPublicProperty.

While IsValidPublicProperty now accepts PropertyInfo? and handles null internally (returns false), the explicit comparerPropInfo != null check provides clearer intent and short-circuits the call. This is a valid pattern.

src/NLog/Layouts/Typed/Layout.cs (4)

362-365: Consistent null-safety fallback in FuncMethodValue.ToString().

The null-coalescing pattern ensures a non-null return, consistent with other ToString() implementations in this PR.


380-396: Correct nullable annotation for Equals(object?).

The signature now matches .NET's updated Object.Equals(object?) contract. The implementation correctly handles null via ReferenceEquals checks and object.Equals.


401-405: Correct nullable annotation for IEquatable<T>.Equals(T?).

Aligns with .NET's updated interface contract. The implementation using object.Equals already handles null values correctly.


624-627: Consistent null-safety fallback in LayoutTypeValue.ToString().

Same pattern as other ToString() implementations, ensuring non-null returns when the inner layout's ToString() returns null.

src/NLog/Internal/ReflectionHelpers.cs (3)

183-186: Correct nullable annotation for IsValidPublicProperty.

The parameter type PropertyInfo? accurately reflects that null is handled (the method returns false when p is null). This aligns with callers like ObjectReflectionCache.GetPublicProperties which pass potentially null values.


188-195: Correct nullable return type for GetPropertyValue.

The object? return type accurately reflects that property values can be null. This is a correct annotation of existing behavior.


74-89: Scope narrowed: NETCOREAPP3_1_OR_GREATER removed in favor of NET.

This change removes coverage of .NET Core 3.1 and 4.x since NET only applies to .NET 5+, whereas NETCOREAPP3_1_OR_GREATER covered .NET Core 3.1 and all later versions. The new condition NETSTANDARD2_1_OR_GREATER || NET will only enable this fallback path for .NET Standard 2.1 and .NET 5+ (if those frameworks are targeted). Confirm this narrowing is intentional given the project's current target frameworks (net35, net46, netstandard2.0, netstandard2.1).

Comment on lines +274 to 278
#if NETSTANDARD2_1_OR_GREATER || NET
WriteToTargetsWithSpan(LogLevel.Trace, null, formatProvider, message, [argument]);
#else
WriteToTargets(LogLevel.Trace, formatProvider, message, new object?[] { argument });
#endif
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the target frameworks and language version settings in project files
echo "=== Checking target frameworks in NLog.csproj ==="
fd -t f "NLog.csproj" --exec cat {} | grep -E "(TargetFramework|LangVersion)" | head -20

echo -e "\n=== Check if there are any explicit LangVersion settings ==="
rg -n "LangVersion" --type=xml | head -10

echo -e "\n=== Check the Directory.Build.props for global settings ==="
cat Directory.Build.props 2>/dev/null || echo "No Directory.Build.props found"

echo -e "\n=== Check for any .editorconfig with LangVersion ==="
cat .editorconfig 2>/dev/null | grep -i "lang" || echo "No relevant LangVersion in .editorconfig"

Repository: NLog/NLog

Length of output: 1520


Remove the NET symbol from the conditional or replace with NET8_0_OR_GREATER.

Collection expressions require C# 12, but the default LangVersion is 9. While netstandard2.1 has LangVersion 13 set explicitly, the NET symbol (matching .NET 5+) is not guaranteed the same. The condition #if NETSTANDARD2_1_OR_GREATER || NET creates a latent issue: if .NET 5, 6, or 7 frameworks are added to TargetFrameworks later, they would compile with LangVersion 9 and fail. Either remove the NET part and rely solely on NETSTANDARD2_1_OR_GREATER, or replace with NET8_0_OR_GREATER to align with C# 12 availability.

🤖 Prompt for AI Agents
In src/NLog/Logger-generated.cs around lines 274 to 278, the preprocessor
condition uses the broad NET symbol which may compile under older C#
langversions (9) and break C#12 collection expressions; update the directive to
either remove NET and rely on NETSTANDARD2_1_OR_GREATER only, or replace NET
with NET8_0_OR_GREATER so the collection-expression branch is only compiled when
C#12 is available (i.e., change the condition to use NETSTANDARD2_1_OR_GREATER
|| NET8_0_OR_GREATER or just NETSTANDARD2_1_OR_GREATER) and keep the alternate
WriteToTargets fallback for other frameworks.

@snakefoot snakefoot force-pushed the SpanNoParams branch 2 times, most recently from 5a4a3b5 to c1660d2 Compare December 29, 2025 12:12
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/NLog/LogLevelTypeConverter.cs (1)

52-61: Make context and culture parameters nullable to match .NET 10 TypeConverter signature.

The context and culture parameters should both be nullable. The .NET 10 TypeConverter.ConvertFrom base method signature is:

public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)

Update both parameters to include the ? annotation:

public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
♻️ Duplicate comments (9)
src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs (1)

89-89: Inconsistent null-handling pattern compared to lines 247-249.

The null-coalescing operator addresses the nullable warning, but passing an empty string to DeleteOldFilesBeforeArchive may be unintended. Lines 247-249 use a more defensive approach by checking if (string.IsNullOrEmpty(archiveDirectory)) and returning early. Consider adopting the same pattern here for consistency.

src/NLog/Logger-generated.tt (1)

250-251: Duplicate: C# 12 collection expression compatibility already flagged.

A past review already raised the concern about using NET instead of NET8_0_OR_GREATER for C# 12 collection expressions. The same applies to this and all similar occurrences in this file.

src/NLog/Layouts/SimpleLayout.cs (1)

191-193: Use documented preprocessor symbol instead of NET.

As flagged in the previous review, the NET symbol is not a standard .NET preprocessor constant. The documented symbols for .NET 5.0+ are versioned, such as NET5_0_OR_GREATER and NET6_0_OR_GREATER.

Update to:

-#if NETSTANDARD2_1_OR_GREATER || NET
+#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER

This ensures the NotNullIfNotNull attribute is applied consistently on all .NET 5.0+ frameworks where it's available.

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

201-203: Logic inconsistency already identified in previous review.

The null filtering at these lines creates the same issues previously flagged:

  • Makes the firstElement is null check at line 165 unreachable
  • Represents a behavioral change rather than a pure compile-time nullable annotation fix
  • Changes the branching logic in ScanPropertiesList
  • Lacks test coverage to verify the behavioral change is safe

The previous review comment provides comprehensive analysis and remediation options.

Also applies to: 214-214

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

2043-2045: Typo in preprocessor directive (duplicate issue)

The preprocessor symbol NETNET9_0_OR_GREATER at line 2043 is a typo that was previously flagged. This should be || NET to match the pattern used elsewhere in the file.


2749-2751: Incorrect preprocessor directive for OverloadResolutionPriority (duplicate issue)

Line 2749 uses || NET which would apply the OverloadResolutionPriority attribute to .NET versions before 9.0, causing compilation errors. This was previously flagged and should be changed to || NET9_0_OR_GREATER.


101-102: Collection expression syntax requires .NET 8+ (duplicate issue)

The collection expression syntax [arg1, arg2] used throughout this file is guarded by #if NETSTANDARD2_1_OR_GREATER || NET, but collection expressions require C# 12 which is only available in .NET 8.0 and later. The NET symbol matches .NET 5, 6, and 7, which will cause compilation errors.

This issue was previously flagged and affects all the WriteToTargetsWithSpan calls using collection expressions. The conditional should be changed to #if NETSTANDARD2_1_OR_GREATER || NET8_0_OR_GREATER or just #if NET8_0_OR_GREATER.

Also applies to: 127-128, 268-272, 291-295, 511-512, 535-536, 718-719, 742-743, 876-880, 898-902, 1106-1107, 1128-1129, 1305-1306, 1329-1330, 1463-1467, 1485-1489, 1693-1694, 1715-1716, 1892-1893, 1916-1917, 2050-2054, 2072-2076, 2280-2281, 2302-2303, 2479-2480, 2503-2504, 2637-2641, 2659-2663, 2867-2868, 2889-2890, 3066-3067, 3090-3091, 3224-3228, 3246-3250, 3454-3455, 3476-3477, 3653-3654, 3677-3678, 3811-3815, 3833-3837, 4041-4042, 4063-4064

tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs (1)

167-167: Verify the NET preprocessor symbol is defined.

The preprocessor condition uses NET which may be undefined (same issue flagged in src/NLog/Internal/LogMessageTemplateFormatter.cs Line 202). If NET is not a predefined MSBuild symbol, the span-based Write(ReadOnlySpan<char>) overload will only compile for netstandard2.1 targets, not modern .NET 5+ as intended.

Based on past review findings, verify that NET is properly defined or replace it with an explicit target like NET5_0_OR_GREATER.

src/NLog/LogFactory.cs (1)

54-56: Verify the NET preprocessor symbol is defined.

The IAsyncDisposable implementation uses the NET symbol which may be undefined (same issue as LogMessageTemplateFormatter.cs). If undefined, this interface implementation would only be available on netstandard2.1, not modern .NET 5+ targets.

Consider replacing with NET5_0_OR_GREATER or verify that NET is properly defined in the project.

🧹 Nitpick comments (2)
src/NLog/Targets/MethodCallTarget.cs (1)

267-273: LGTM! Null check correctly addresses .NET 10 nullable annotations.

The null check for ex.InnerException before rethrowing is correct and necessary. The fallback to throw; when InnerException is null properly preserves the original exception. This change addresses both the .NET 10 compiler warnings and prevents a potential NullReferenceException at runtime.

Optional: Consider preserving stack traces when rethrowing inner exceptions.

While the current unwrapping pattern is standard and widely used, using ExceptionDispatchInfo would preserve the original stack trace of the inner exception rather than resetting it at the throw point:

🔎 Optional refactor to preserve stack traces

Add the using directive at the top of the file:

+using System.Runtime.ExceptionServices;

Then update the catch block:

 catch (TargetInvocationException ex)
 {
     InternalLogger.Warn("{0}: Failed to invoke method - {1}", this, ex.Message);
     if (ex.InnerException != null)
-        throw ex.InnerException;
+        ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
     throw;
 }
src/NLog/Config/LoggerNameMatcher.cs (1)

173-174: Null-handling behavior when _matchingArgument is null may be unexpected.

The inconsistency noted in the previous review has been resolved—all four matchers now use identical null-checking logic. However, the current implementation returns true when _matchingArgument is null and loggerName is not null, meaning a null matching argument would match any non-null logger name.

In practice, _matchingArgument should never be null (it's derived via Substring operations), and these checks were likely added to satisfy NET10 nullable annotations. Consider one of these approaches:

  1. If _matchingArgument being null is truly impossible, use the null-forgiving operator (_matchingArgument!) to bypass the warning
  2. If defensive null-checking is desired, return false when _matchingArgument is null to align with the semantics of NoneLoggerNameMatcher (which always returns false for null patterns)
🔎 Suggested approaches

Approach 1: Null-forgiving operator (if confident _matchingArgument is never null)

 public override bool NameMatches(string loggerName)
 {
-    if (loggerName is null || _matchingArgument is null)
-        return _matchingArgument is null;
-    return loggerName.StartsWith(_matchingArgument, StringComparison.Ordinal);
+    if (loggerName is null) return false;
+    return loggerName.StartsWith(_matchingArgument!, StringComparison.Ordinal);
 }

Approach 2: Return false when _matchingArgument is null

 public override bool NameMatches(string loggerName)
 {
-    if (loggerName is null || _matchingArgument is null)
-        return _matchingArgument is null;
+    if (loggerName is null || _matchingArgument is null)
+        return false;
     return loggerName.StartsWith(_matchingArgument, StringComparison.Ordinal);
 }

Apply the same pattern to EqualsLoggerNameMatcher, EndsWithLoggerNameMatcher, and ContainsLoggerNameMatcher.

Also applies to: 190-191, 207-208, 224-225

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 739827f and c1660d2.

📒 Files selected for processing (68)
  • src/NLog/Common/AsyncLogEventInfo.cs
  • src/NLog/Common/InternalLogger.cs
  • src/NLog/Conditions/ConditionExpression.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/Conditions/ConditionRelationalExpression.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Config/LoggerNameMatcher.cs
  • src/NLog/Config/LoggingConfiguration.cs
  • src/NLog/Config/MethodFactory.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Internal/AppEnvironmentWrapper.cs
  • src/NLog/Internal/AssemblyHelpers.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Internal/DictionaryEntryEnumerable.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Internal/LogMessageTemplateFormatter.cs
  • src/NLog/Internal/MultiFileWatcher.cs
  • src/NLog/Internal/ObjectGraphScanner.cs
  • src/NLog/Internal/ObjectReflectionCache.cs
  • src/NLog/Internal/PropertiesDictionary.cs
  • src/NLog/Internal/PropertyHelper.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Internal/ScopeContextAsyncState.cs
  • src/NLog/Internal/SingleItemOptimizedHashSet.cs
  • src/NLog/Internal/StackTraceUsageUtils.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TargetWithFilterChain.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/Layouts/Typed/Layout.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/LogEventBuilder.cs
  • src/NLog/LogEventInfo.cs
  • src/NLog/LogFactory.cs
  • src/NLog/LogLevelTypeConverter.cs
  • src/NLog/Logger-V1Compat.cs
  • src/NLog/Logger-generated.cs
  • src/NLog/Logger-generated.tt
  • src/NLog/Logger.cs
  • src/NLog/MessageTemplates/ValueFormatter.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Targets/AsyncTaskTarget.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/Targets/MethodCallTarget.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs
  • src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs
  • tests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cs
  • tests/NLog.UnitTests/LogMessageFormatterTests.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
🚧 Files skipped from review as they are similar to previous changes (29)
  • src/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cs
  • src/NLog/ScopeContext.cs
  • src/NLog/Filters/WhenRepeatedFilter.cs
  • src/NLog/Conditions/ConditionLiteralExpression.cs
  • src/NLog/Internal/ExceptionMessageFormatProvider.cs
  • src/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cs
  • src/NLog/Internal/XmlHelper.cs
  • src/NLog/Logger-generated.cs
  • src/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cs
  • src/NLog/Config/AssemblyExtensionLoader.cs
  • src/NLog/Targets/DefaultJsonSerializer.cs
  • src/NLog/Targets/ColoredConsoleAnsiPrinter.cs
  • src/NLog/Internal/CallSiteInformation.cs
  • src/NLog/Internal/DictionaryEntryEnumerable.cs
  • src/NLog/Targets/TargetWithContext.cs
  • src/NLog/Config/PropertyTypeConverter.cs
  • src/NLog/Internal/ReflectionHelpers.cs
  • src/NLog/Conditions/ConditionMethods.cs
  • src/NLog/Internal/FormatHelper.cs
  • src/NLog/Targets/ColoredConsoleSystemPrinter.cs
  • src/NLog/Targets/LineEndingMode.cs
  • src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs
  • src/NLog/Logger.cs
  • src/NLog/Internal/StringBuilderExt.cs
  • src/NLog/Internal/TimeoutContinuation.cs
  • src/NLog/Targets/FileTarget.cs
  • src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs
  • src/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cs
  • tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-09-12T01:22:52.437Z
Learnt from: thatrajeevkr
Repo: NLog/NLog PR: 5999
File: src/NLog/Layouts/JSON/JsonLayout.cs:162-166
Timestamp: 2025-09-12T01:22:52.437Z
Learning: When suggesting StringBuilder optimizations for NLog layouts marked as [ThreadAgnostic], avoid shared StringBuilder fields as they create thread safety issues. Prefer simple string operations or ThreadLocal<StringBuilder> if allocation profiling shows a real performance impact.

Applied to files:

  • src/NLog/Layouts/SimpleLayout.cs
  • src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs
  • src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs
🧬 Code graph analysis (15)
src/NLog/Internal/ObjectReflectionCache.cs (1)
src/NLog/Internal/ReflectionHelpers.cs (3)
  • ReflectionHelpers (45-196)
  • LateBoundMethod (72-92)
  • LateBoundMethod (94-123)
src/NLog/Config/LoggingConfiguration.cs (1)
src/NLog/Internal/ThreadSafeDictionary.cs (1)
  • TryGetValue (156-159)
src/NLog/Internal/ScopeContextAsyncState.cs (4)
src/NLog/Layouts/Typed/Layout.cs (3)
  • ToString (362-365)
  • ToString (374-377)
  • ToString (624-627)
src/NLog/ScopeContext.cs (2)
  • ToString (525-528)
  • ToString (872-875)
src/NLog/Targets/TargetWithContext.cs (1)
  • ToString (727-730)
src/NLog/Layouts/JSON/JsonLayout.cs (1)
  • ToString (475-483)
src/NLog/Internal/AssemblyHelpers.cs (1)
src/NLog/Internal/StackTraceUsageUtils.cs (1)
  • Assembly (219-240)
src/NLog/LogFactory.cs (6)
src/NLog/Config/ServiceRepositoryUpdateEventArgs.cs (2)
  • ServiceRepositoryUpdateEventArgs (42-57)
  • ServiceRepositoryUpdateEventArgs (48-51)
src/NLog/Common/AsyncHelpers.cs (1)
  • AsyncHelpers (46-382)
src/NLog/Internal/ObjectReflectionCache.cs (1)
  • Equals (559-562)
src/NLog/Internal/PropertiesDictionary.cs (4)
  • Equals (748-748)
  • Equals (750-758)
  • Equals (772-775)
  • Equals (786-800)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/LogLevel.cs (2)
  • Equals (363-366)
  • Equals (374-377)
src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs (5)
src/NLog/Layouts/SimpleLayout.cs (1)
  • IStringValueRenderer (483-486)
src/NLog/LayoutRenderers/Wrappers/WhenEmptyLayoutRendererWrapper.cs (1)
  • IStringValueRenderer (101-104)
src/NLog/LayoutRenderers/Wrappers/CachedLayoutRendererWrapper.cs (1)
  • IStringValueRenderer (178-178)
src/NLog/LayoutRenderers/ThreadIdLayoutRenderer.cs (1)
  • IStringValueRenderer (62-65)
src/NLog/Internal/IStringValueRenderer.cs (1)
  • GetFormattedString (49-49)
src/NLog/LogLevelTypeConverter.cs (3)
src/NLog/Targets/LineEndingMode.cs (2)
  • CanConvertFrom (207-210)
  • ConvertFrom (213-217)
src/NLog/Internal/PropertyHelper.cs (1)
  • Type (186-190)
src/NLog/Config/PropertyTypeConverter.cs (1)
  • CultureInfo (199-208)
src/NLog/Internal/SingleItemOptimizedHashSet.cs (2)
src/NLog/Layouts/Typed/Layout.cs (4)
  • T (146-149)
  • T (151-173)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/Logger.cs (2)
  • T (709-712)
  • T (722-733)
src/NLog/MessageTemplates/ValueFormatter.cs (3)
src/NLog/Internal/ExceptionMessageFormatProvider.cs (1)
  • IFormatProvider (80-83)
src/NLog/LayoutRenderers/LayoutRenderer.cs (1)
  • IFormatProvider (218-221)
src/NLog/Internal/SingleItemOptimizedHashSet.cs (1)
  • SingleItemOptimizedHashSet (98-119)
src/NLog/Internal/TargetWithFilterChain.cs (5)
src/NLog/Filters/WhenRepeatedFilter.cs (2)
  • Equals (390-406)
  • Equals (408-411)
src/NLog/Internal/PropertiesDictionary.cs (3)
  • Equals (748-748)
  • Equals (750-758)
  • Equals (772-775)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/LogLevel.cs (2)
  • Equals (363-366)
  • Equals (374-377)
src/NLog/Logger-V1Compat.cs (2)
src/NLog/Logger.cs (7)
  • WriteToTargetsWithSpan (607-614)
  • WriteToTargetsWithSpan (616-642)
  • WriteToTargets (827-831)
  • WriteToTargets (833-841)
  • WriteToTargets (843-853)
  • WriteToTargets (855-863)
  • WriteToTargets (866-874)
src/NLog/Logger-generated.cs (10)
  • Trace (111-117)
  • Trace (125-131)
  • Trace (137-144)
  • Trace (165-171)
  • Trace (223-229)
  • Info (695-701)
  • Info (709-715)
  • Info (721-728)
  • Info (749-755)
  • Info (807-813)
src/NLog/Internal/PropertyHelper.cs (1)
src/NLog/Internal/ReflectionHelpers.cs (1)
  • IsValidPublicProperty (183-186)
src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (2)
src/NLog/Targets/Wrappers/LogEventDroppedEventArgs.cs (2)
  • LogEventDroppedEventArgs (41-53)
  • LogEventDroppedEventArgs (47-47)
src/NLog/Targets/Wrappers/LogEventQueueGrowEventArgs.cs (2)
  • LogEventQueueGrowEventArgs (44-66)
  • LogEventQueueGrowEventArgs (51-55)
src/NLog/Internal/PropertiesDictionary.cs (5)
src/NLog/Common/AsyncLogEventInfo.cs (2)
  • Equals (89-89)
  • Equals (92-92)
src/NLog/Filters/WhenRepeatedFilter.cs (2)
  • Equals (390-406)
  • Equals (408-411)
src/NLog/Internal/TargetWithFilterChain.cs (2)
  • Equals (376-379)
  • Equals (381-386)
src/NLog/Layouts/Typed/Layout.cs (2)
  • Equals (380-396)
  • Equals (401-405)
src/NLog/Targets/LineEndingMode.cs (2)
  • Equals (188-191)
  • Equals (196-199)
src/NLog/Layouts/Typed/Layout.cs (4)
src/NLog/Layouts/SimpleLayout.cs (1)
  • ToString (274-282)
src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (2)
  • ToString (250-253)
  • ToString (272-275)
src/NLog/Targets/TargetWithContext.cs (1)
  • ToString (727-730)
src/NLog/Layouts/JSON/JsonLayout.cs (1)
  • ToString (475-483)
🔇 Additional comments (51)
src/NLog/Targets/Wrappers/BufferingTargetWrapper.cs (1)

274-274: LGTM! Nullable annotation aligns with NET10 Timer callback signature.

The update correctly reflects that Timer.TimerCallback accepts a nullable state parameter in NET10. The existing implementation at line 280 already handles null safely with the null-coalescing operator.

src/NLog/Common/InternalLogger.cs (1)

425-425: LGTM! Appropriate nullable annotation handling.

The change from explicit string to var correctly addresses .NET 10's nullable annotation for Path.GetDirectoryName, which now returns string?. The existing null check on line 426 properly guards against null values before calling Directory.CreateDirectory.

src/NLog/Config/LoggingConfiguration.cs (1)

118-118: LGTM! Correct nullable annotation for TryGet pattern.

The nullable annotation on the out Target? parameter correctly reflects the TryGetValue pattern semantics, where the out parameter is only valid when the method returns true. This aligns with .NET 10's enhanced nullable reference type annotations for Dictionary<TKey, TValue>.TryGetValue.

src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs (2)

55-58: LGTM! Correct nullability for Thread.Name.

The return type change from string to string? correctly reflects that Thread.CurrentThread.Name returns a nullable string, as threads may not have a name assigned.


60-60: LGTM! Interface contract now correctly implemented.

The return type change from string to string? correctly implements the IStringValueRenderer.GetFormattedString interface contract, which defines the return type as nullable. This resolves the inconsistency previously flagged in code reviews.

src/NLog/Targets/AsyncTaskTarget.cs (1)

83-83: LGTM! Nullable annotations correctly address NET10 compiler warnings.

The nullable annotations on the delegate field and method parameters are correct and consistent:

  • _taskCompletion delegate type properly matches the updated TaskCompletion method signature
  • continuation parameter is safely handled with null checks at lines 758-762
  • state parameter is unused and correctly nullable (Timer created with null state at line 174)

These internal changes align with NET10's stricter nullable reference type annotations without affecting the public API surface.

Also applies to: 707-707, 786-786

src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (4)

469-469: LGTM: Nullability annotation aligns with TimerCallback signature.

The change from object state to object? state correctly matches the .NET 10 TimerCallback delegate signature, which expects a nullable state parameter.


523-523: LGTM: Nullability annotation aligns with WaitCallback signature.

The change from object state to object? state correctly matches the .NET 10 WaitCallback delegate signature. The method safely handles null values via the as operator on Line 525.


617-617: LGTM: Nullability annotation aligns with EventHandler signature.

The change from object sender to object? sender correctly matches the .NET 10 EventHandler<LogEventDroppedEventArgs> delegate signature.


622-622: LGTM: Nullability annotation aligns with EventHandler signature.

The change from object sender to object? sender correctly matches the .NET 10 EventHandler<LogEventQueueGrowEventArgs> delegate signature.

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

591-591: LGTM! Good null-safety fallback.

The addition of ?? GetType().ToString() ensures a non-null return value when base.ToString() returns null, addressing .NET 10's nullable annotations. This pattern is consistent with similar ToString() implementations throughout the PR (e.g., in ScopeContext.cs, TargetWithContext.cs).

src/NLog/Config/MethodFactory.cs (2)

173-173: LGTM! Nullable annotations correctly reflect method invocation behavior.

The updates to InvokeMethodInfo and its usage properly align with .NET 10 nullable annotations. The return type object? correctly represents that MethodInfo.Invoke can return null, and the parameter type object?[] allows nullable elements to propagate through the invocation chain.

Also applies to: 208-221


223-245: LGTM! Nullable array correctly represents optional parameter defaults.

The change from object[] to object?[] for ResolveDefaultMethodParameters properly handles nullable default values from optional parameters. This aligns with the fact that ParameterInfo.DefaultValue can be null and ensures type safety throughout the parameter resolution chain.

src/NLog/Logger-generated.tt (1)

269-270: Implementation pattern is consistent and correct.

The span-based argument packaging using collection expressions ([argument], [argument1, argument2], [argument1, argument2, argument3]) is applied consistently across all generic overloads. The separation between:

  • NET9_0_OR_GREATER for params ReadOnlySpan<object?> overloads (C# 13 feature at lines 163, 185)
  • NET for collection expression usage in these generic methods

...is logically sound, though the C# 12 language version concern from the past review still applies.

Also applies to: 291-292, 312-313, 336-337, 359-360

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

263-274: LGTM! Critical issue from past review is now resolved.

The fix correctly addresses the array size mismatch issue flagged in the previous review. By adding a dummy FastPropertyLookup entry instead of using continue, the array is now fully populated and won't contain trailing default entries that could cause NullReferenceException.

The defensive null-check handles the edge case where .NET 10 nullable annotations indicate GetGetMethod() or ReturnType might return null, even though the comment notes this should be rare due to the IsValidPublicProperty filtering.

src/NLog/Internal/PropertyHelper.cs (3)

175-183: LGTM! Correct Try pattern implementation.

The null-check logic correctly implements the standard Try pattern, returning false with a null result when the property is not found, and true with the PropertyInfo when found. This change aligns with the PR objective to fix nullable warnings from .NET 10 annotations.


262-262: LGTM! Minor style improvement.

Using var instead of explicit MethodInfo type is a minor change that maintains the same type inference while potentially improving nullable flow analysis. No functional impact.


439-439: LGTM! Explicit null-check improves nullable flow analysis.

Adding the explicit null-check for comparerPropInfo before calling IsValidPublicProperty() is good defensive programming. While the extension method handles null internally, the explicit check helps the compiler's nullable flow analysis understand that comparerPropInfo is non-null inside the if block, ensuring safe access to GetPropertyValue on line 441.

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

246-254: LGTM!

The null-coalescing guard on Path.GetDirectoryName is correct. This method can return null when given a root path (e.g., "C:\" or "/"), so the ?? string.Empty ensures a non-null return value consistent with the method's contract and the else branch fallback.

src/NLog/LayoutRenderers/NLogDirLayoutRenderer.cs (1)

87-99: LGTM!

The null-coalescing guard is appropriate here. Path.GetDirectoryName returns null for root paths, and the ?? string.Empty fallback aligns with the else branch, ensuring consistent non-null return values.

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

45-53: LGTM!

The nullable parameter annotation Assembly? assembly correctly reflects the reality that callers (e.g., Assembly.GetEntryAssembly()) may pass null. The implementation properly handles this with null-conditional access on line 50 for non-NETFRAMEWORK and an explicit null check on line 52 for NETFRAMEWORK.

src/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cs (1)

84-87: LGTM!

The null-coalescing guard ensures _processDir is never null, which is correct since Path.GetDirectoryName can return null for root paths. This maintains the readonly field's non-null guarantee.

src/NLog/LayoutRenderers/StackTraceLayoutRenderer.cs (4)

173-180: LGTM!

The indexer return type change to StackFrame? correctly reflects that StackTrace.GetFrame() can return null, improving type safety for callers.


191-203: LGTM!

The AppendRaw method now correctly handles nullable frames by converting to string early and checking for null/empty before appending. The separator logic correctly uses a nullable local that starts as null, ensuring no leading separator on the first frame.


206-221: LGTM!

The separator handling is now correct. The separator variable starts as null, so the first valid frame appends nothing before it. After processing the first frame, separator is assigned _separator ?? string.Empty for subsequent iterations. The type?.Name ?? "<no type>" fallback also properly handles null DeclaringType.


224-238: LGTM!

Same correct separator pattern as AppendFlat. The nullable separator ensures no leading separator on the first frame, and the assignment on line 237 sets it for subsequent iterations.

src/NLog/Internal/StackTraceUsageUtils.cs (2)

105-108: LGTM!

The null-coalescing fallback method.ToString() ?? methodName is a defensive guard. While MethodBase.ToString() typically doesn't return null, this ensures robustness and satisfies nullable analysis.


219-221: All callers of LookupAssemblyFromMethod properly null-check before invocation.

  • Line 250 (StackTraceUsageUtils.cs): if (method != null && LookupAssemblyFromMethod(method) != null) — checks before call
  • Line 313 (CallSiteInformation.cs): stackMethod is guarded by an early return at lines 310-311 (if (stackMethod is null) return true;) before the call

The non-nullable contract is satisfied.

src/NLog/Common/AsyncLogEventInfo.cs (1)

92-92: LGTM! Nullable annotation aligns with NET10 standards.

The parameter nullability update is consistent with the project-wide nullable reference types adoption and maintains the existing equality logic.

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

376-379: LGTM! Nullable annotation consistent with project standards.

The nullability update for the Equals override follows the same pattern as other equality overrides in the codebase.

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

750-758: LGTM! Nullable annotation follows project standards.

The nullability update for the Equals override is consistent with the broader nullable reference types adoption across the codebase.

src/NLog/Layouts/Typed/Layout.cs (3)

364-364: LGTM! Defensive ToString implementation.

The null-coalescing fallback to GetType().ToString() prevents potential null returns from _layoutMethod.ToString(), aligning with similar defensive patterns elsewhere in the codebase.


380-405: LGTM! Nullable annotations consistent with project standards.

The nullable parameter annotations for both Equals(object? obj) and Equals(T? other) align with the project-wide nullable reference types adoption.


626-626: LGTM! Defensive ToString implementation.

The null-coalescing fallback ensures a non-null return value, consistent with the pattern at Line 364 and similar code in ValueTypeLayoutInfo.cs.

src/NLog/LogFactory.cs (4)

288-296: LGTM! Nullable event handler signature.

The nullable sender parameter is the standard pattern for event handlers and aligns with the nullable reference types adoption.


655-655: LGTM! Properly qualified AsyncHelpers reference.

The fully qualified NLog.Common.AsyncHelpers.PreventMultipleCalls correctly references the helper method.


1113-1116: LGTM! Nullable annotation follows project standards.

The nullable parameter for the Equals override is consistent with the project-wide nullable reference types updates.


1227-1247: LGTM! Nullable event handler signatures.

The nullable sender parameters for both shutdown event handlers follow the standard event handler pattern and align with nullable reference types.

Also applies to: 1249-1274

src/NLog/Conditions/ConditionRelationalExpression.cs (2)

219-222: LGTM! Defensive null handling for string conversion.

The null-coalescing operator handles potential null returns from Convert.ToString when promoting to LogLevel, ensuring a non-null value for LogLevel.FromString.


226-228: LGTM! Defensive null handling for string promotion.

The null-coalescing to string.Empty prevents potential null returns from Convert.ToString, aligning with NET10's nullable annotations on the Convert API.

src/NLog/LogEventBuilder.cs (1)

123-123: LGTM - Consistent conditional compilation widening.

The change from NET9_0_OR_GREATER to NET appropriately broadens the availability of the span-based Properties overload to all NET targets while maintaining NETSTANDARD2_1_OR_GREATER support. This aligns with the PR's objective to handle NET10 nullable annotations.

tests/NLog.UnitTests/LogMessageFormatterTests.cs (1)

103-109: LGTM - Consistent test coverage widening.

The preprocessor changes from NETCOREAPP3_0_OR_GREATER to NET at both the creation (line 103) and usage (line 135) of logEventInfo3 correctly widen test coverage to all NET targets. This ensures the span-based constructor path is tested consistently with the production code changes.

Also applies to: 135-140

src/NLog/Conditions/ConditionExpression.cs (1)

56-58: LGTM - Appropriate nullable annotation widening.

The change from NETCOREAPP3_0_OR_GREATER to NET correctly widens the availability of the NotNullIfNotNull attribute for the implicit operator. This helps the compiler track null-state flow across all NET targets, directly supporting the PR's objective to fix nullable warnings from NET10 annotations.

src/NLog/LogEventInfo.cs (2)

136-159: LGTM - Well-structured span-based constructor addition.

The new constructor overload accepting ReadOnlySpan<MessageTemplateParameter> is correctly implemented:

  • Properly guarded with NETSTANDARD2_1_OR_GREATER || NET to match span availability
  • Follows the existing constructor pattern at lines 129-134
  • Correctly copies span elements to array and initializes the properties dictionary
  • Supports pre-formatted message scenarios with span-based parameters

This addition aligns with the PR's objective to support NET10 annotations and span-based APIs.


675-692: LGTM - Consistent span-based overload widening.

The change from NETCOREAPP3_1_OR_GREATER to NET for the NeedToPreformatMessage span-based overload correctly widens availability to all NET targets. This is consistent with:

  • The new span-based constructor at line 136
  • The array-based overload at line 656
  • Other span-based changes throughout the PR

The logic remains unchanged and correctly evaluates whether message preformatting is needed based on parameter safety.

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

266-266: LGTM! Nullable parameter annotations correctly address NET10 compiler warnings.

Both parameter nullability changes appropriately reflect NET10's updated nullable annotations:

  • Line 266: string? parameter accommodates IConvertible.ToString() returning nullable in NET10 (called from line 203). The existing implementation at line 270 safely handles null via StringBuilder.Append.

  • Line 355: object? parameter correctly reflects that collection elements can be null. The implementation already handles null through pattern matching and the null check at line 189 (which serializes null as "NULL").

Also applies to: 355-355

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

113-113: Null-safety fix is correct and aligns with codebase pattern.

The null-coalescing operator correctly addresses NET10's nullable annotations for Path.GetDirectoryName. This pattern is already widely used throughout the codebase (FileTarget.cs, LegacyArchiveFileNameHandler.cs, BaseDirLayoutRenderer.cs, etc.).

The change does alter error handling for edge cases: instead of throwing ArgumentNullException for invalid paths (like root paths with no directory component), it now defaults to the current working directory. However, the subsequent Directory.Exists() check at line 116 provides a safeguard—if the resulting directory is invalid, a warning is logged and the watch returns early. This is an acceptable trade-off for NET10 migration, though note that other similar code in the codebase (FileTarget.cs, RollingArchiveFileHandler.cs) uses explicit string.IsNullOrEmpty() checks rather than defaulting to empty string.

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

242-245: LGTM! Nullable annotation correctly applied.

The update to accept nullable parameters (T? x, T? y) correctly aligns with .NET 10's nullable reference type annotations for IEqualityComparer<T>.Equals. The implementation using ReferenceEquals(x, y) safely handles null values.

src/NLog/LogLevelTypeConverter.cs (3)

46-49: LGTM: Nullable context annotation added correctly.

The nullable annotation on the context parameter aligns with .NET 10's TypeConverter base class signature.


64-67: LGTM: Nullable annotations added correctly.

Both the context and destinationType nullable annotations align with .NET 10's TypeConverter base class signature.


70-80: Verify context parameter nullability matches .NET 10 TypeConverter.

The context parameter in ConvertTo is non-nullable, while CanConvertTo (line 64) accepts a nullable context. Please confirm that .NET 10's TypeConverter.ConvertTo base method signature indeed has a non-nullable context parameter, maintaining consistency with the ConvertFrom pattern.

@sonarqubecloud
Copy link

@snakefoot snakefoot merged commit d88f1d3 into NLog:dev Dec 29, 2025
5 of 6 checks passed
@snakefoot snakefoot added this to the 6.1.0 milestone Jan 31, 2026
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.

1 participant