Fix nullable warnings from NET10 annotations#6067
Conversation
WalkthroughBroad 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
InnerExceptionis null, aligning well with the PR's nullable annotation objectives.Consider using
ExceptionDispatchInfoto preserve the full stack trace when rethrowing the inner exception. The current approach loses some context about where theTargetInvocationExceptionwas 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
NETsymbol instead ofNET9_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 correctAllowing
IsValidPublicProperty(this PropertyInfo? p)and returningobject?fromGetPropertyValuealigns with reflection semantics and avoids nullability warnings; behavior is unchanged for non-nullPropertyInfovalues.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 correctlyThe updated V1-compat
Log/Trace/Debug/Info/Warn/Error/Fataloverloads that now delegate toWriteToTargetsWithSpan(..., [arg1, arg2, arg3])under#if NETSTANDARD2_1_OR_GREATER || NETpreserve 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:WriteToTargetsWithSpanandRenderPreformattedLogEventmirror array-based behaviorThe span-based pipeline:
- Uses
LogEventInfo.NeedToPreformatMessage(in ReadOnlySpan<object?>)to decide between deferred vs upfront formatting.- Falls back to
LogEventInfo.Create(..., args.ToArray())andLogMessageStringFormatter.Default.MessageFormatterin 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
messageTemplateParametersinto theLogEventInfoctor directly and skip the subsequentCreatePropertiesInternalcall, but that’s a minor micro-optimization and not required.
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (68)
src/NLog/Common/AsyncLogEventInfo.cssrc/NLog/Common/InternalLogger.cssrc/NLog/Conditions/ConditionExpression.cssrc/NLog/Conditions/ConditionLiteralExpression.cssrc/NLog/Conditions/ConditionMethods.cssrc/NLog/Conditions/ConditionRelationalExpression.cssrc/NLog/Config/AssemblyExtensionLoader.cssrc/NLog/Config/LoggerNameMatcher.cssrc/NLog/Config/LoggingConfiguration.cssrc/NLog/Config/MethodFactory.cssrc/NLog/Config/PropertyTypeConverter.cssrc/NLog/Filters/WhenRepeatedFilter.cssrc/NLog/Internal/AppEnvironmentWrapper.cssrc/NLog/Internal/AssemblyHelpers.cssrc/NLog/Internal/CallSiteInformation.cssrc/NLog/Internal/DictionaryEntryEnumerable.cssrc/NLog/Internal/ExceptionMessageFormatProvider.cssrc/NLog/Internal/FormatHelper.cssrc/NLog/Internal/LogMessageTemplateFormatter.cssrc/NLog/Internal/MultiFileWatcher.cssrc/NLog/Internal/ObjectGraphScanner.cssrc/NLog/Internal/ObjectReflectionCache.cssrc/NLog/Internal/PropertiesDictionary.cssrc/NLog/Internal/PropertyHelper.cssrc/NLog/Internal/ReflectionHelpers.cssrc/NLog/Internal/ScopeContextAsyncState.cssrc/NLog/Internal/SingleItemOptimizedHashSet.cssrc/NLog/Internal/StackTraceUsageUtils.cssrc/NLog/Internal/StringBuilderExt.cssrc/NLog/Internal/TargetWithFilterChain.cssrc/NLog/Internal/TimeoutContinuation.cssrc/NLog/Internal/XmlHelper.cssrc/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cssrc/NLog/LayoutRenderers/BaseDirLayoutRenderer.cssrc/NLog/LayoutRenderers/NLogDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cssrc/NLog/LayoutRenderers/StackTraceLayoutRenderer.cssrc/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cssrc/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cssrc/NLog/Layouts/SimpleLayout.cssrc/NLog/Layouts/Typed/Layout.cssrc/NLog/Layouts/Typed/ValueTypeLayoutInfo.cssrc/NLog/LogEventBuilder.cssrc/NLog/LogEventInfo.cssrc/NLog/LogFactory.cssrc/NLog/LogLevelTypeConverter.cssrc/NLog/Logger-V1Compat.cssrc/NLog/Logger-generated.cssrc/NLog/Logger-generated.ttsrc/NLog/Logger.cssrc/NLog/MessageTemplates/ValueFormatter.cssrc/NLog/ScopeContext.cssrc/NLog/Targets/AsyncTaskTarget.cssrc/NLog/Targets/ColoredConsoleAnsiPrinter.cssrc/NLog/Targets/ColoredConsoleSystemPrinter.cssrc/NLog/Targets/DefaultJsonSerializer.cssrc/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cssrc/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cssrc/NLog/Targets/FileTarget.cssrc/NLog/Targets/LineEndingMode.cssrc/NLog/Targets/MethodCallTarget.cssrc/NLog/Targets/TargetWithContext.cssrc/NLog/Targets/Wrappers/AsyncTargetWrapper.cssrc/NLog/Targets/Wrappers/BufferingTargetWrapper.cstests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cstests/NLog.UnitTests/LogMessageFormatterTests.cstests/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.cssrc/NLog/Internal/StringBuilderExt.cssrc/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
stringto inferredvarcorrectly aligns the variable declaration with the actual return type ofPath.GetDirectoryName(), which returnsstring?. 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
TimerCallbackdelegate signature in modern .NET, which expectsobject? 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
objecttoobject?correctly reflects that thestateparameter 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
getGetMethodInfoensures thatCreateLateBoundMethodreceives a valid non-nullMethodInfo, 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
varhere correctly infers theobject?[]type from the updatedResolveDefaultMethodParametersreturn type. The downstream usages inResolveMethodParametersoverloads are already compatible.
208-229: Nullable annotations are correctly applied.The changes appropriately reflect nullable semantics:
InvokeMethodInforeturningobject?aligns withMethodInfo.Invoke's nullable return type in .NETResolveDefaultMethodParametersreturningobject?[]correctly models thatParameterInfo.DefaultValuecan be null for optional parameters- The local array on line 229 matches the return type
These align with the existing delegate signatures in
MethodDetailswhich already useobject?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. Whilebase.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.ToStringreturns null, which is possible according to .NET's nullable reference type annotations. Returningstring.Emptyis 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:
ProcessPendingEventsreceivesnullfrom the Timer constructor and never uses thestateparameterFlushEventsInQueuesafely handles nullable state withascast and null-conditional operatorsOnRequestQueueDropItemandOnRequestQueueGrowfollow standard event handler patterns wheresendercan be nullableThese 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
nullis 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 withAppendFlatandAppendDetailedFlat, 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.GetDirectoryNamereturns null (e.g., for root directories or filenames without directory components in .NET 5+). Falling back tostring.Emptyresolves to the current directory viaPath.GetFullPath, which is reasonable default behavior. Edge cases are handled gracefully by theDirectory.Existscheck 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_GREATERtoNETmodernizes the conditional compilation to use the unified .NET platform symbol (5.0+). Since .NET Core 3.0+ is already covered byNETSTANDARD2_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
loggerNameand_matchingArgumentimproves null safety. While_matchingArgumentshould 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 nullcheck ensures the compiler's nullable flow analysis correctly recognizesstringValueas non-null for theCultureInfoconstructor 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 benullwhen the method returnsfalse. 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 potentialNullReferenceExceptions while satisfying nullable reference type analysis.src/NLog/Layouts/SimpleLayout.cs (1)
191-191: The project targetsnet35,net46,netstandard2.0, andnetstandard2.1— not .NET Core 3.x. This change fromNETCOREAPP3_0_OR_GREATERtoNEThas no practical impact since the project doesn't support those frameworks. TheNotNullIfNotNullattribute remains applied fornetstandard2.1targets via theNETSTANDARD2_1_OR_GREATERcondition, 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?toobjectand returningstring.Emptycorrectly implements theIDictionaryEnumerator.Keyinterface signature, which definesKeyas non-nullable. Returningstring.Emptyinstead of null prevents nullable reference warnings while maintaining the contract.Minor note:
Entry.Key(fromdefault(DictionaryEntry)) would be null, but theKeyproperty now returnsstring.Empty. SinceMoveNext()always returnsfalse, this inconsistency is harmless in practice.src/NLog/Targets/DefaultJsonSerializer.cs (4)
437-437: LGTM: Clearer null-safety check ordering.Checking
value is nullbeforeobjTypeCode == TypeCode.Emptyimproves 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 callingAppendStringEscape().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_GREATERtoNETSTANDARD2_1_OR_GREATER || NETis verified. TheNETsymbol 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 separateAppendcalls. This pattern is consistent throughout the codebase (30+ occurrences) and aligns with Microsoft's deprecation ofNETCOREAPP*symbols in favor of the unifiedNETsymbol 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
Equalsoverride correctly reflects thatobject.Equalscan 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 methodDeleteOldFilesBeforeArchivealready 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.GetDirectoryNamereturns 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.Equalsaccepts 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 usingReferenceEqualscorrectly 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.Equalscan 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 bothNETFRAMEWORKand non-NETFRAMEWORKpaths. The non-NETFRAMEWORKpath already used null-conditional operator (assembly?.Location), and the explicit check at lines 52-53 provides equivalent safety for theNETFRAMEWORKpath.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
stringBufferis null andstringValueis 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 otherEqualsoverrides in the codebase (e.g.,LogLevel.cs,AsyncLogEventInfo.cs).src/NLog/Config/AssemblyExtensionLoader.cs (4)
315-318: LGTM!Defensive null-coalescing for
Assembly.FullNamewhich can be null for dynamic or in-memory assemblies. Prevents null values in theHashSet<string>.
328-328: LGTM!Consistent null-coalescing for
Assembly.FullNamewhen adding to the already-registered set.
352-352: LGTM!Same null-coalescing pattern applied consistently in
RegisterAppDomainAssemblies.
398-398: LGTM!Null-coalescing for
Path.GetDirectoryNameresult aligns with the pattern used inPathHelpers.TrimDirectorySeparatorswhich also handles null paths with?? string.Empty.src/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cs (1)
89-90: LGTM!Null-coalescing for
Path.GetDirectoryNameensures a non-null directory string is passed toDeleteOldFilesBeforeArchive. This is consistent with similar null-safety adjustments across the codebase.src/NLog/Internal/ExceptionMessageFormatProvider.cs (2)
45-56: LGTM!Nullable annotations for
ICustomFormatter.Formatparameters align with the interface contract in .NET. The implementation already handles nullargsafely by formatting it as"{0}".
80-83: LGTM!Nullable annotation for
IFormatProvider.GetFormatparameter matches the .NET interface contract. The implementation returnsnullfor unsupported format types, which is correct behavior.src/NLog/MessageTemplates/ValueFormatter.cs (2)
266-272: LGTM!Nullable annotation for
stringValueparameter reflects that this method can receive null values.StringBuilder.Append(string?)handles null safely by appending nothing.
355-363: LGTM!Nullable annotation for
itemparameter. The method logic handles null correctly - whenitemis null, it won't matchIConvertibleorIEnumerable, andSerializeSimpleObjectwill output "NULL" for null values (line 189-192).src/NLog/Targets/FileTarget.cs (2)
1018-1018: LGTM!Nullable annotation for timer callback's
stateparameter aligns withTimerCallbackdelegate signature which accepts nullable state.
1248-1248: LGTM!Null-coalescing for
Path.GetDirectoryNameresult ensures a non-null directory name is used in the subsequentDirectory.CreateDirectorycall. This prevents potentialArgumentNullException.src/NLog/Targets/LineEndingMode.cs (3)
188-191: LGTM!Nullable annotation for
Equals(object? obj)is consistent with otherEqualsoverrides in the codebase (e.g.,LogLevel.csline 362-365,AsyncLogEventInfo.csline 91).
196-199: LGTM!Nullable annotation for
Equals(LineEndingMode? other)with proper null-conditional accessother?._newLineCharacters. This matches the pattern inLogLevel.Equals(LogLevel? other)which usesother?._ordinal.
213-217: LGTM!Nullable return type for
ConvertFromis correct sincebase.ConvertFromcan return null. The implementation properly handles the nullablenamevariable.src/NLog/Internal/StackTraceUsageUtils.cs (2)
107-108: LGTM!Null-coalescing fallback for
method.ToString()is a defensive measure. WhileToString()rarely returns null, this ensures the method never returns an unexpected null value.
219-221: LGTM!The parameter signature change from
MethodBase?toMethodBaseis 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?.AssemblyandModule?.Assemblysince these can still be null even with a non-nullMethodBase.src/NLog/Internal/CallSiteInformation.cs (2)
308-322: LGTM! Null-safety improvements are correct.The refactoring properly handles null inputs:
- Early return when
stackMethodis null prevents downstream null reference issues- Explicit null check for
declaringTypebefore callingIsHiddenClassTypeeliminates potential NPEs- The explicit
return falseat the end improves code clarity
327-332: LGTM! Minor refactor for consistency.Inlining the
declaringTypevariable declaration aligns with the pattern used in the updatedSkipStackFrameWhenHiddenmethod 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
TypeConverterbase class contract in modern .NET frameworks. The internal logic remains unchanged, and theIsNumericTypehelper 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_GREATERtoNETappropriately broadens the target to include all modern .NET versions (.NET 5+) while maintainingNETSTANDARD2_1_OR_GREATERfor compatibility. This aligns with the availability ofIAsyncDisposableacross 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.PreventMultipleCallsinstead of the unqualifiedAsyncHelpersimproves code clarity and prevents potential namespace conflicts.
1113-1116: LGTM! Nullable annotation matches base class.The
object?parameter correctly aligns with theSystem.Object.Equals(object?)signature for nullable reference type compliance.
1227-1247: LGTM! Event handler signatures updated for null-safety.Both
OnLoggerShutdownandOnStopLoggingcorrectly accept nullableobject?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.GetDirectoryNamereturns null (e.g., root directory, invalid path), ensuring_processDiris always initialized to a non-null value. This aligns with similar null-safety patterns in related layout renderers likeBaseDirLayoutRendererandNLogDirLayoutRenderer.src/NLog/LayoutRenderers/BaseDirLayoutRenderer.cs (1)
155-158: LGTM! Consistent null-safety pattern.The null-coalescing operator matches the pattern used in
ProcessDirLayoutRendererand ensuresGetProcessDiralways 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
ToStringmethod 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 otherToStringimplementations.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.ToStringreturning null is handled gracefully with an empty string fallback. This guarantees theToStringoverride 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
LogLevelandstringalways produce non-null values, preventing null reference issues during condition evaluation. The explicitstring?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
ResolveNLogDiralways returns a non-null string, preventing potential null reference issues whenPath.GetDirectoryNamereturns null.src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (1)
272-275: LGTM! Consistent null-safety pattern.The null-coalescing fallback ensures
ToStringnever returns null, aligning with similar patterns inLayout.csand 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 thatAsSpanis available on all target frameworks covered by theNETsymbol.src/NLog/Internal/PropertyHelper.cs (2)
175-183: LGTM! Correct Try-pattern implementation.The logic reversal properly implements the Try-pattern by returning
falsewhen the property is not found (null) andtruewhen found, with the result properly assigned in both cases.
439-439: LGTM! Defensive null check.The explicit null check before calling
IsValidPublicPropertyimproves null-safety and clarity, even thoughIsValidPublicPropertyhandles null internally (as shown inReflectionHelpers.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
LogEventInfopath forNETSTANDARD2_1_OR_GREATER || NET. The main project targetsnetstandard2.1but does not have an explicit .NET 5+ target (likenet5.0,net6.0,net8.0), while the test project runs onnet8.0. Confirm whether usingnetstandard2.1for 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. TheNETsymbol is automatically defined by the .NET SDK for .NET 5.0 and later versions, all of which support theNotNullIfNotNullattribute. Combined withNETSTANDARD2_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 enablesReadOnlySpan<T>support across all intended targets.The
NETsymbol (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 || NETa 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_GREATERtoNETsafely 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 || NETcorrectly ensuresstring.AsSpanis only used where it's available:
NETSTANDARD2_1_OR_GREATER: covers netstandard2.1 where AsSpan was introducedNET: covers .NET 5.0+ where AsSpan is availableFrameworks without AsSpan support (net35, net46, netstandard2.0) fall through to the
#elseblock 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_GREATERtoNETSTANDARD2_1_OR_GREATER || NETaligns 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 callerGetVersion()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_GREATERtoNETenables 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
objparameter correctly reflects that Equals can receive null values.
401-405: LGTM! Consistent nullable annotation for generic Equals.The nullable annotation on the typed
Equalsmethod 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.Namecan be null. This aligns with theIStringValueRenderer.GetFormattedStringinterface signature which returnsstring?.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_GREATERtoNET, 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 appropriatelyUsing
#if NETSTANDARD2_1_OR_GREATER || NETaround theRuntimeFeature.IsDynamicCodeSupportedcheck 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 underNETMoving the
ReadOnlySpan<MessageTemplateParameter>ctor andNeedToPreformatMessage(in ReadOnlySpan<object?>)under#if NETSTANDARD2_1_OR_GREATER || NETmakes 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/NET40The updated
#if !NET35 && !NET40around theIReadOnlyList<KeyValuePair<object, object?>>ctor is appropriate, sinceIReadOnlyList<>isn’t available on those legacy frameworks and this avoids compile-time issues there.src/NLog/Logger.cs (2)
439-519: GenericLog<T...>overloads now using span pipeline correctlyRouting the generic single/dual/triple-argument
Log<T...>overloads throughWriteToTargetsWithSpan(..., [argument[..]])under#if NETSTANDARD2_1_OR_GREATER || NETkeeps behavior identical while enabling the lower-allocation span path on modern TFMs, with the existing array-based path retained for older targets.
571-604: NewLogoverloads withparams ReadOnlySpan<object?>integrate cleanlyThe new span-based
Logoverloads:
- Reuse
GetTargetsForLevelSafefor enablement checks.- Delegate directly to the internal
WriteToTargetsWithSpanpath.- 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 |
There was a problem hiding this comment.
🧩 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 -20Repository: NLog/NLog
Length of output: 1615
🏁 Script executed:
# Check for custom DefineConstants or NET symbol definitions
rg -n 'DefineConstants|<NET>' --type xml | head -30Repository: 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 -20Repository: 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 -50Repository: NLog/NLog
Length of output: 2639
🏁 Script executed:
# Search for TargetFramework definitions
rg -n 'TargetFramework' src/NLog/NLog.csproj -A 2Repository: 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.csprojRepository: 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 -200Repository: 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 propsRepository: 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 -10Repository: 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 -20Repository: 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 buildRepository: 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.csRepository: 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 -40Repository: 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 -100Repository: 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 -60Repository: 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 -20Repository: 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 csRepository: 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 -50Repository: 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 -iRepository: 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 csRepository: 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 -lRepository: 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 -lRepository: 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 -80Repository: 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 -20Repository: 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 -40Repository: 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 -iRepository: 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 -5Repository: 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 -5Repository: 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 -5Repository: 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 -20Repository: 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.csprojRepository: 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 -20Repository: 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 -cRepository: 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 -10Repository: 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.1support 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.
6cc0dc3 to
b8cb19b
Compare
There was a problem hiding this comment.
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:
Background execution after timeout: Line 403 uses
Task.WhenAnyto 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
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
CancellationTokenSourceto properly cancelDisposeInternal()on timeout- Moving
GC.SuppressFinalize(this)outside the try block or adding it to a finally block to ensure it executes even on exceptionsrc/NLog/Internal/ExceptionMessageFormatProvider.cs (1)
45-56: Fix IFormatProvider.GetFormat signature to match .NET BCL definition.The
ICustomFormatter.Formatmethod at line 45 correctly matches the .NET 10 BCL signature. However,IFormatProvider.GetFormatat 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
formatTypeparameter to match the required BCL interface contract.
♻️ Duplicate comments (3)
src/NLog/Internal/LogMessageTemplateFormatter.cs (1)
202-202: TheNETpreprocessor 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-basedRenderoverload will not be compiled for modern .NET 5+ targets as intended. The correct symbols areNET5_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
NETincludes .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 -5Also 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 symbolNETNET9_0_OR_GREATERThis directive:
#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATER [OverloadResolutionPriority(-1)] #endifcontains a typo in the symbol name (
NETNET9_0_OR_GREATER). As written, the attribute is never compiled, unlike other neighboring overloads that use eitherNET9_0_OR_GREATERorNET.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_GREATERif 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 withReflectionHelpers.IsValidPublicPropertyshown 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== truecomparison.The null guards improve safety. However, line 100's
== truecomparison is redundant sinceStartsWithreturnsbool, notbool?. Also verify that returningfalsefor 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== truecomparison.The null guards improve safety and maintain consistency with the other methods. However, line 116's
== truecomparison is redundant sinceEndsWithreturnsbool, notbool?. Also verify that returningfalsefor 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
Entryproperty returns a defaultDictionaryEntry, which has a nullKey, while theKeyproperty now returnsstring.Empty. This creates an inconsistency whereEntry.KeyandKeyrepresent 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
NullReferenceExceptionwhenInnerExceptionis null, and falls back to rethrowing the originalTargetInvocationException.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 usingExceptionDispatchInfo:+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.TrimDirectorySeparatorsalready handles null input (it returnsstring.Emptyfor null). The current logic also allowsnlogAssemblyDirectoryto be null whennlogAssemblyLocationis 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
nlogAssemblyDirectoryis 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,
TrimDirectorySeparatorsalready handles null input safely.
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (68)
src/NLog/Common/AsyncLogEventInfo.cssrc/NLog/Common/InternalLogger.cssrc/NLog/Conditions/ConditionExpression.cssrc/NLog/Conditions/ConditionLiteralExpression.cssrc/NLog/Conditions/ConditionMethods.cssrc/NLog/Conditions/ConditionRelationalExpression.cssrc/NLog/Config/AssemblyExtensionLoader.cssrc/NLog/Config/LoggerNameMatcher.cssrc/NLog/Config/LoggingConfiguration.cssrc/NLog/Config/MethodFactory.cssrc/NLog/Config/PropertyTypeConverter.cssrc/NLog/Filters/WhenRepeatedFilter.cssrc/NLog/Internal/AppEnvironmentWrapper.cssrc/NLog/Internal/AssemblyHelpers.cssrc/NLog/Internal/CallSiteInformation.cssrc/NLog/Internal/DictionaryEntryEnumerable.cssrc/NLog/Internal/ExceptionMessageFormatProvider.cssrc/NLog/Internal/FormatHelper.cssrc/NLog/Internal/LogMessageTemplateFormatter.cssrc/NLog/Internal/MultiFileWatcher.cssrc/NLog/Internal/ObjectGraphScanner.cssrc/NLog/Internal/ObjectReflectionCache.cssrc/NLog/Internal/PropertiesDictionary.cssrc/NLog/Internal/PropertyHelper.cssrc/NLog/Internal/ReflectionHelpers.cssrc/NLog/Internal/ScopeContextAsyncState.cssrc/NLog/Internal/SingleItemOptimizedHashSet.cssrc/NLog/Internal/StackTraceUsageUtils.cssrc/NLog/Internal/StringBuilderExt.cssrc/NLog/Internal/TargetWithFilterChain.cssrc/NLog/Internal/TimeoutContinuation.cssrc/NLog/Internal/XmlHelper.cssrc/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cssrc/NLog/LayoutRenderers/BaseDirLayoutRenderer.cssrc/NLog/LayoutRenderers/NLogDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cssrc/NLog/LayoutRenderers/StackTraceLayoutRenderer.cssrc/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cssrc/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cssrc/NLog/Layouts/SimpleLayout.cssrc/NLog/Layouts/Typed/Layout.cssrc/NLog/Layouts/Typed/ValueTypeLayoutInfo.cssrc/NLog/LogEventBuilder.cssrc/NLog/LogEventInfo.cssrc/NLog/LogFactory.cssrc/NLog/LogLevelTypeConverter.cssrc/NLog/Logger-V1Compat.cssrc/NLog/Logger-generated.cssrc/NLog/Logger-generated.ttsrc/NLog/Logger.cssrc/NLog/MessageTemplates/ValueFormatter.cssrc/NLog/ScopeContext.cssrc/NLog/Targets/AsyncTaskTarget.cssrc/NLog/Targets/ColoredConsoleAnsiPrinter.cssrc/NLog/Targets/ColoredConsoleSystemPrinter.cssrc/NLog/Targets/DefaultJsonSerializer.cssrc/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cssrc/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cssrc/NLog/Targets/FileTarget.cssrc/NLog/Targets/LineEndingMode.cssrc/NLog/Targets/MethodCallTarget.cssrc/NLog/Targets/TargetWithContext.cssrc/NLog/Targets/Wrappers/AsyncTargetWrapper.cssrc/NLog/Targets/Wrappers/BufferingTargetWrapper.cstests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cstests/NLog.UnitTests/LogMessageFormatterTests.cstests/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
stateparameter is appropriate sincenullis 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?)flushTimeoutas state, and the existing null-safe handling at line 280 using?? 0ensures 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.ToStringandTargetWithContext.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
varhere correctly adapts to the nullable return type ofResolveDefaultMethodParameters. The inferred typeobject?[]properly flows through to downstreamResolveMethodParameterscalls which already accept nullable element arrays.
208-221: LGTM!The return type change to
object?correctly reflects thatMethodInfo.Invokecan return null (for void methods or methods that explicitly return null). This aligns with the delegate signatures inMethodDetailswhich already specifyobject?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
varinference at line 173src/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
objis null. This pattern aligns with similar changes inLogLevel.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 inLogLevel.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 inLogLevelTypeConverter.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
falsefor null inputs aligns with the intended semantics—particularly edge cases like whethernullshould be considered to containnull.src/NLog/Internal/XmlHelper.cs (1)
360-362: LGTM! Proper null-safety guard added.The null check correctly handles the case where
Convert.ToStringreturns null, which aligns with .NET nullable reference type annotations. The early return withstring.Emptyis 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.Equalssignature 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
nullis 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
NullReferenceExceptionon subsequent property accesses and aligns with the PR's null safety objectives. Returningtrue(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
declaringTypebefore callingIsHiddenClassTypeprevents violating the method's non-nullable contract (line 52). The logic correctly handles the edge case whereDeclaringTypeis null by not skipping the frame (since we cannot determine if it's hidden). The explicitreturn falseon line 321 makes the control flow clearer.
329-329: LGTM! Proper null-safe handling.The null-conditional operator
stackMethod?.DeclaringTypecombined with the null check ensures safe execution even ifstackMethodis null. While the call site at line 253 ensuresstackMethodis 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.Emptyaddition correctly handles the nullable return fromPath.GetDirectoryNameand 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 subsequentDirectory.Existscheck on line 116 validates the directory, making the approach safe even whenGetDirectoryNamereturns 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>.Equalsnow accepts nullable parameters, which aligns with .NET nullable reference type conventions. TheReferenceEqualsimplementation 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.Appendcorrectly handles null strings by treating them as empty, and the calling context at Line 203 can pass a nullable result fromIConvertible.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, andSerializeSimpleObjecthandles 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 setsLangVersion=13(C# 13), so collection expressions are fully supported. All other targets (net35, net46, netstandard2.0) fall through to the#elseblock, which uses individualAppend()calls without collection expressions. TheNETsymbol 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
NotNullIfNotNullattribute is available in .NET Standard 2.1 and later, so the conditional compilation#if NETSTANDARD2_1_OR_GREATER || NETcorrectly applies the attribute only where the framework provides it. This pattern is already used consistently inSimpleLayout.csand 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_GREATERtoNETexpands 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 consistentAll the updated
Log/Trace/Debug/Info/Warn/Error/Fataloverloads that now callWriteToTargetsWithSpan(..., [args])under#if NETSTANDARD2_1_OR_GREATER || NET(withWriteToTargets(..., new object?[]{...})in the#else) preserve the prior exception/formatProvider/culture and level gating semantics while aligning with the core span pipeline inLogger. 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 allNETtargetsSwitching the guard to
#if NETSTANDARD2_1_OR_GREATER || NETmeans theAsSpan/Write(ReadOnlySpan<char>)path is used on all modern .NET / .NET Core TFMs, withSubstringkept as the fallback. That’s a good perf win and should be safe given those APIs are available whereverNETis 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 writerAdding
Write(ReadOnlySpan<char> buffer)toPartsWriterunder#if NETSTANDARD2_1_OR_GREATER || NETnicely tracks the production change to useTextWriter.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 onStringWriter.src/NLog/Logger.cs (2)
571-604: Newparams ReadOnlySpan<object?>Log overloads – check language version and binding impactIntroducing
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 || NETis a good way to gain span-basedparamsperf benefits, but it assumes the code is compiled with a C# version that supportsparamson non-array collections (notablyReadOnlySpan<T>).(developers.redhat.com)Also, with both
params object?[]andparams 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 ofLogger.Logare OK with the new binding.
440-471: Span-based internal pipeline matches existing behavior and looks correctThe updated generic
Log<TArgument[1..3]>overloads and the internalWriteToTargetsWithSpan/RenderPreformattedLogEventpipeline:
- Share the same
GetTargetsForLevelSafe/GetTargetsForLevelgating as the object[] path.- Use
NeedToPreformatMessage(args)andAutoMessageTemplateFormatterconsistently, only allocating arrays viaargs.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 || NETlooks sound and backwards compatible aside from the expectedparams-overload binding differences noted earlier.Also applies to: 484-518, 533-569, 606-642, 644-668
| if (loggerName is null || _matchingArgument is null) | ||
| return _matchingArgument is null; |
There was a problem hiding this comment.
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.
| var item = list[i]; | ||
| if (item != null) | ||
| elements.Add(item); |
There was a problem hiding this comment.
❓ 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 -20Repository: 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 10Repository: 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 2Repository: 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 -30Repository: 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 -10Repository: 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 -80Repository: 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/nullRepository: 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:
-
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. -
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.
-
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.
| #if NETSTANDARD2_1_OR_GREATER || NET | ||
| WriteToTargetsWithSpan(LogLevel.<#=level#>, null, formatProvider, message, [argument]); |
There was a problem hiding this comment.
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_GREATERThis 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
b8cb19b to
ae86481
Compare
There was a problem hiding this comment.
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 fromNETtoNET9_0_OR_GREATERforparams ReadOnlySpanoverloads.The
params ReadOnlySpan<object?>syntax is a C# 13 feature available only in .NET 9+. Logger.cs (line 571) guards these overloads withNETSTANDARD2_1_OR_GREATER || NET, but the generated Logger-generated.cs (line 187) and Logger-generated.tt (line 163) correctly useNETSTANDARD2_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 symbolNETNET9_0_OR_GREATER
#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATERcontains a duplicatedNETand 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. UsingNETinstead ofNET8_0_OR_GREATERmay 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 -20Also 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 theNETconditional. 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 usesnew object?[...]. While both work due to array covariance, usingnew 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.PreventMultipleCallscan be simplified toAsyncHelpers.PreventMultipleCallssinceNLog.Commonis 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
📒 Files selected for processing (68)
src/NLog/Common/AsyncLogEventInfo.cssrc/NLog/Common/InternalLogger.cssrc/NLog/Conditions/ConditionExpression.cssrc/NLog/Conditions/ConditionLiteralExpression.cssrc/NLog/Conditions/ConditionMethods.cssrc/NLog/Conditions/ConditionRelationalExpression.cssrc/NLog/Config/AssemblyExtensionLoader.cssrc/NLog/Config/LoggerNameMatcher.cssrc/NLog/Config/LoggingConfiguration.cssrc/NLog/Config/MethodFactory.cssrc/NLog/Config/PropertyTypeConverter.cssrc/NLog/Filters/WhenRepeatedFilter.cssrc/NLog/Internal/AppEnvironmentWrapper.cssrc/NLog/Internal/AssemblyHelpers.cssrc/NLog/Internal/CallSiteInformation.cssrc/NLog/Internal/DictionaryEntryEnumerable.cssrc/NLog/Internal/ExceptionMessageFormatProvider.cssrc/NLog/Internal/FormatHelper.cssrc/NLog/Internal/LogMessageTemplateFormatter.cssrc/NLog/Internal/MultiFileWatcher.cssrc/NLog/Internal/ObjectGraphScanner.cssrc/NLog/Internal/ObjectReflectionCache.cssrc/NLog/Internal/PropertiesDictionary.cssrc/NLog/Internal/PropertyHelper.cssrc/NLog/Internal/ReflectionHelpers.cssrc/NLog/Internal/ScopeContextAsyncState.cssrc/NLog/Internal/SingleItemOptimizedHashSet.cssrc/NLog/Internal/StackTraceUsageUtils.cssrc/NLog/Internal/StringBuilderExt.cssrc/NLog/Internal/TargetWithFilterChain.cssrc/NLog/Internal/TimeoutContinuation.cssrc/NLog/Internal/XmlHelper.cssrc/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cssrc/NLog/LayoutRenderers/BaseDirLayoutRenderer.cssrc/NLog/LayoutRenderers/NLogDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cssrc/NLog/LayoutRenderers/StackTraceLayoutRenderer.cssrc/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cssrc/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cssrc/NLog/Layouts/SimpleLayout.cssrc/NLog/Layouts/Typed/Layout.cssrc/NLog/Layouts/Typed/ValueTypeLayoutInfo.cssrc/NLog/LogEventBuilder.cssrc/NLog/LogEventInfo.cssrc/NLog/LogFactory.cssrc/NLog/LogLevelTypeConverter.cssrc/NLog/Logger-V1Compat.cssrc/NLog/Logger-generated.cssrc/NLog/Logger-generated.ttsrc/NLog/Logger.cssrc/NLog/MessageTemplates/ValueFormatter.cssrc/NLog/ScopeContext.cssrc/NLog/Targets/AsyncTaskTarget.cssrc/NLog/Targets/ColoredConsoleAnsiPrinter.cssrc/NLog/Targets/ColoredConsoleSystemPrinter.cssrc/NLog/Targets/DefaultJsonSerializer.cssrc/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cssrc/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cssrc/NLog/Targets/FileTarget.cssrc/NLog/Targets/LineEndingMode.cssrc/NLog/Targets/MethodCallTarget.cssrc/NLog/Targets/TargetWithContext.cssrc/NLog/Targets/Wrappers/AsyncTargetWrapper.cssrc/NLog/Targets/Wrappers/BufferingTargetWrapper.cstests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cstests/NLog.UnitTests/LogMessageFormatterTests.cstests/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
stateparameter is appropriate since the Timer is constructed withnullas 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.ConvertFromcan 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.GetDirectoryNamereturnsnull(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
NETsymbol is defined for .NET 5 and later, which all supportStringBuilder.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 thatStackTrace.GetFrame()can returnnullin 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. Whenfis 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.EmptyforKeysatisfies the non-nullable annotation from .NET 10. However, this creates a subtle inconsistency:Entry.Key(from the defaultDictionaryEntrystruct) remainsnull, while theKeyproperty now returnsstring.Empty.This is acceptable because:
MoveNext()always returnsfalse, so these properties should never be accessed in correct enumeration patterns.- Allocating a new
DictionaryEntryjust 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
stackMethodat the start ensures safe access to its members- Defensive check for
declaringTypebefore callingIsHiddenClassType- Explicit
return falsestatement improves code clarityThese 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 wherestackMethodmight 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 inScopeContext.csline 527.src/NLog/Targets/Wrappers/AsyncTargetWrapper.cs (4)
469-469: LGTM! Nullable annotation aligns with framework delegate signature.The nullable annotation on the
stateparameter correctly matches theTimerCallbackdelegate signature in .NET, which expectsobject? 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
stateparameter correctly matches theWaitCallbackdelegate signature. The method safely handles null values using theasoperator (line 525) and null checks (line 531).
617-620: LGTM! Nullable annotation aligns with framework event handler signature.The nullable annotation on the
senderparameter correctly matches theEventHandler<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
senderparameter correctly matches theEventHandler<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_GREATERtoNETcorrectly broadens the availability of theProperties(ReadOnlySpan<(string, object?)>)overload to all .NET 5.0+ targets, since this method doesn't use theparamsmodifier with ReadOnlySpan. This is intentionally different from line 253, which correctly retainsNET9_0_OR_GREATERbecause theparamsmodifier 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
objparameter is the correct signature for overridingObject.Equalsand 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
otherparameter is appropriate forIEquatable<T>whereTmight be nullable. The implementation correctly handles null values usingobject.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), ensuringToString()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
stringtostring?appropriately reflects that the method can receive null values (e.g., fromIConvertible.ToString()at line 203). The implementation safely handles null sinceStringBuilder.Append(string?)treats null as an empty string.
355-363: LGTM! Nullable annotation correctly added.The signature change from
objecttoobject?appropriately reflects that collection items (dictionary keys, values, or list elements) can be null. The implementation correctly handles null through the cascading checks—ultimately reachingSerializeSimpleObjectwhich 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_GREATERtoNETappropriately broadens support to all modern .NET versions (5+) while maintaining .NET Standard 2.1+ compatibility. TheNotNullIfNotNullattribute 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
falsewhen either operand is null).
97-100: LGTM! Null safety improved.Consistent with the
Containsmethod, 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.FromStringreceives a non-null argument, preventing potential exceptions whenConvert.ToStringreturns 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 whenConvert.ToStringreturns 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 sincebase.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 toPath.GetFullPathandDirectory.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
assemblyparameter 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 passAssembly.GetEntryAssembly()which can legitimately be null, and three callers passtypeof(...).Assemblywhich 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 theAsSpanpath is not applicable—the project targets onlynet35,net46,netstandard2.0, andnetstandard2.1, not any .NET Core versions. The change fromNETCOREAPP3_0_OR_GREATERtoNEThas no impact on supported frameworks, as neither directive activates in the current build matrix. Only theNETSTANDARD2_1_OR_GREATERpart 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
DeleteOldFilesBeforeArchivemethod in the parent class explicitly handles the empty directory case by returningfalse(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
!NET40exclusion is appropriate sinceIReadOnlyList<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_GREATERtoNETbroadens the availability of theReadOnlySpan<object?>overload ofNeedToPreformatMessageto all .NET 5.0+ targets, which is consistent with the change on line 136. SinceReadOnlySpan<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
NETpreprocessor symbol is defined only for .NET 5.0+ (the unified platform) and does not include older .NET Framework versions (net35, net46). The patternNETSTANDARD2_1_OR_GREATER || NETcorrectly enables these code paths only on platforms that supportReadOnlySpan<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!NET40exclusion at line 161 is also correct, asIReadOnlyList<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
varhere correctly accommodates the new nullable return type (object?[]) fromResolveDefaultMethodParameterswhile maintaining readability.
208-221: LGTM: Correct nullable return type.Changing the return type from
objecttoobject?accurately reflects thatMethodInfo.Invokecan 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[]toobject?[]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 inMethodDetails.src/NLog/LogFactory.cs (6)
288-288: LGTM: Correct nullable annotation for event handler.The nullable
senderparameter aligns with the standardEventHandler<T>pattern where the sender can be null.
376-411: LGTM: Async disposal implementation with timeout.The
DisposeAsyncimplementation properly flushes before disposing and uses a timeout to prevent indefinite blocking. Note that if the timeout expires (line 402), the backgroundDisposeInternaltask 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
objparameter matches the baseObject.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
senderparameter is appropriate for the event handler pattern and matches the usage on line 1231 wherenullis explicitly passed as the sender.
1249-1274: LGTM: Correct nullable annotation for event handler.The nullable
senderparameter aligns with the event handler pattern and is consistent with theOnLoggerShutdownmethod signature.
54-56: TheNETpreprocessor 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 likeNET5_0_OR_GREATER,NET6_0_OR_GREATER). The symbol is also not explicitly defined inNLog.csproj. Since NET is undefined, the condition#if NETSTANDARD2_1_OR_GREATER || NETeffectively only matches when compiling for .NET Standard 2.1, not for other target frameworks (net35, net46, netstandard2.0).Define the
NETsymbol 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 consistentTrace generic overloads now delegate to
WriteToTargetsWithSpanunderNETSTANDARD2_1_OR_GREATER || NETwith[argument]/[arg1, arg2, arg3]and fall back tonew object?[] { ... }otherwise. The guards and culture handling are preserved; routing is consistent and safe.
562-681: Debug span-based overload routing matches Trace patternDebug generic overloads mirror the Trace changes: same
IsDebugEnabledguards, same culture arguments, and[argument...]span packaging behindNETSTANDARD2_1_OR_GREATER || NET, with object-array fallback otherwise. No functional regressions spotted.
854-973: Info span-based overloads correctly expanded to NETInfo generic overloads are updated to use span-based routing under
NETSTANDARD2_1_OR_GREATER || NETand 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 levelsWarn generic overloads follow the same pattern as Trace/Debug/Info: the only change is how arguments are packaged (
[arg...]vsnew object?[]{...}). Control flow and observable behavior are preserved.
1438-1557: Error generic overloads: span vs array paths correctly conditionedError generic overloads now use
WriteToTargetsWithSpanwith[argument...]whenNETSTANDARD2_1_OR_GREATER || NETis defined and otherwise fall back toWriteToTargetswithnew object?[]{...}. Logging level checks and message/formatProvider wiring are intact.
1730-1849: Fatal generic overloads: span-based argument packaging applied consistentlyFatal 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 byNETSTANDARD2_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 correctThe
Logoverloads usingGetTargetsForLevelSafenow delegate toWriteToTargetsWithSpan(..., [arg1, arg2] / [arg1, arg2, arg3])underNETSTANDARD2_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 correctlyThe
string?-argumentLogoverload now usesWriteToTargetsWithSpan(level, null, formatProvider, message, [argument])whenNETSTANDARD2_1_OR_GREATER || NETis defined, and retains thenew object?[]{ argument }path otherwise. TheIsEnabled(level)guard and formatting semantics remain unchanged.
506-541: Log(level, ..., object? argument) overload: targetsForLevel span routing looks goodFor
object?argument, the method now callsWriteToTargetsWithSpan(targetsForLevel, level, null, formatProvider, message, [argument])under NET, with anew 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 pathThe binary-compat
Traceoverloads with 2 and 3object?arguments now route throughWriteToTargetsWithSpan(LogLevel.Trace, ..., [arg1, arg2/arg3])for NET targets, preserving the existingIsTraceEnabledguard and default culture. Fallback behavior for non-NET targets is unchanged.
872-904: Trace(string? / object? argument) overloads: span packaging is consistentThe
Traceoverloads taking a singlestring?orobject?argument use the same[argument]span packaging pattern underNETSTANDARD2_1_OR_GREATER || NETand still allocatenew object?[]{ argument }otherwise. No change in logging semantics, only in argument transport.
1297-1335: Debug V1 overloads (2–3 args) match the updated span patternFor
Debugwith 2–3object?arguments, the updated implementations useWriteToTargetsWithSpan(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/InfoThe single-argument
Debugoverloads 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
Infooverloads with 2–3object?arguments now callWriteToTargetsWithSpan(LogLevel.Info, ..., [arg1, arg2/arg3])when NET is available. The previousnew object?[]{...}array creation is retained in the#elsebranch. Everything else (guards, culture) stays as before.
2050-2078: Info(string? argument) V1 overloads: span packaging looks correctFor the
Infooverloads that take a singlestring?argument (with and withoutformatProvider), the span-based path underNETSTANDARD2_1_OR_GREATER || NETuses[argument], falling back tonew object?[]{ argument }otherwise. Behavior and overload shape are preserved.
2276-2308: Info(object? argument) V1 overloads follow the same span patternThe
InfoV1 overloads with a singleobject?parameter now delegate toWriteToTargetsWithSpanunder 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
Warnoverloads with multipleobject?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 symmetricFor single
string?arguments,Warnnow usesWriteToTargetsWithSpanwith[argument]under NET and keeps thenew object?[]{ argument }fallback. This is symmetrical with Trace/Debug/Info/Error/Fatal.
2863-2895: Warn(object? argument) V1 overloads use span-based path as expectedThe
Warnobject-based overloads route via[argument]toWriteToTargetsWithSpanfor 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
Erroroverloads with 2–3object?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 levelsSingle-argument
Erroroverloads forstring?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 correctlyThe
Erroroverloads that take anobject?argument are updated to useWriteToTargetsWithSpan(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 routingFatal multi-argument overloads now feed
[arg1, arg2]/[arg1, arg2, arg3]intoWriteToTargetsWithSpanin the NET span path. The prior object-array behavior is preserved in the#elsebranch.
3807-3839: Fatal(string? argument) V1 overloads: span packaging consistentThe
Fataloverloads with a singlestring?argument now use[argument]underNETSTANDARD2_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/WarnSingle
object?argument Fatal overloads now callWriteToTargetsWithSpan(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 capabilitiesThe
PartsWriter.Write(ReadOnlySpan<char> buffer)override is now enabled underNETSTANDARD2_1_OR_GREATER || NET, which aligns with whereTextWriter.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 || NETcondition correctly enables Span-based test paths for all modern .NET targets. The use of explicitSpan<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
WriteToTargetsWithSpanmethods correctly usein 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.Formatwhen positional templates are detected- Includes proper exception handling with
InternalLoggerfallback
| #if NETSTANDARD2_1_OR_GREATER || NET | ||
| [return: System.Diagnostics.CodeAnalysis.NotNullIfNotNull(nameof(text))] | ||
| #endif |
There was a problem hiding this comment.
🧩 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 -20Repository: 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=30Repository: 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_GREATERThis 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.
| #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.
ae86481 to
7f08e88
Compare
There was a problem hiding this comment.
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 forcontextandcultureparameters.The
contextparameter should beITypeDescriptorContext?(nullable) andcultureshould beCultureInfo?(nullable) to match:
- The nullable context used in
CanConvertFrom(Line 46)- The .NET 10
TypeConverterbase class signatureBoth 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 forcontext,culture, andvalueparameters.The parameters should be nullable to match:
- The nullable context used in
CanConvertTo(Line 64)- The .NET 10
TypeConverterbase 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 symbolNETintroduced.This line was changed from
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATERto#if NETSTANDARD2_1_OR_GREATER || NET. The symbolNETis not a standard MSBuild-defined preprocessor constant, so the condition will only evaluate to true fornetstandard2.1targets, not for modern .NET (5.0+). This means the span-basedRenderoverload on line 203 won't be compiled for .NET 5+ targets.Replace
NETwith:
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_GREATERsrc/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 theNETsymbol 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
NETinstead ofNET8_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 usingNET8_0_OR_GREATERinstead ofNETon 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. TheNETsymbol includes .NET 5-7 (defaulting to C# 9-11), which will cause compilation errors unlessLangVersionis explicitly set to 12 or higher.The past review comment on this template recommended using
NET8_0_OR_GREATERinstead ofNET, but the issue persists. Fixing this template will automatically fix the generatedLogger-generated.csfile.Consider changing the conditional from:
#if NETSTANDARD2_1_OR_GREATER || NETTo:
#if NETSTANDARD2_1_OR_GREATER || NET8_0_OR_GREATERThis 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. TheNETsymbol 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.ttandLogger-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.csprojIf the project targets .NET 5-7, either:
- Use
NET8_0_OR_GREATERinstead ofNETin the conditionals, or- Ensure
<LangVersion>12</LangVersion>is set in the project fileAlso 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_GREATERshould beNET9_0_OR_GREATERorNETLine 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.AsyncHelpersis unnecessary sinceusing NLog.Common;is declared at line 45. The rest of the file uses unqualified names for types fromNLog.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
📒 Files selected for processing (68)
src/NLog/Common/AsyncLogEventInfo.cssrc/NLog/Common/InternalLogger.cssrc/NLog/Conditions/ConditionExpression.cssrc/NLog/Conditions/ConditionLiteralExpression.cssrc/NLog/Conditions/ConditionMethods.cssrc/NLog/Conditions/ConditionRelationalExpression.cssrc/NLog/Config/AssemblyExtensionLoader.cssrc/NLog/Config/LoggerNameMatcher.cssrc/NLog/Config/LoggingConfiguration.cssrc/NLog/Config/MethodFactory.cssrc/NLog/Config/PropertyTypeConverter.cssrc/NLog/Filters/WhenRepeatedFilter.cssrc/NLog/Internal/AppEnvironmentWrapper.cssrc/NLog/Internal/AssemblyHelpers.cssrc/NLog/Internal/CallSiteInformation.cssrc/NLog/Internal/DictionaryEntryEnumerable.cssrc/NLog/Internal/ExceptionMessageFormatProvider.cssrc/NLog/Internal/FormatHelper.cssrc/NLog/Internal/LogMessageTemplateFormatter.cssrc/NLog/Internal/MultiFileWatcher.cssrc/NLog/Internal/ObjectGraphScanner.cssrc/NLog/Internal/ObjectReflectionCache.cssrc/NLog/Internal/PropertiesDictionary.cssrc/NLog/Internal/PropertyHelper.cssrc/NLog/Internal/ReflectionHelpers.cssrc/NLog/Internal/ScopeContextAsyncState.cssrc/NLog/Internal/SingleItemOptimizedHashSet.cssrc/NLog/Internal/StackTraceUsageUtils.cssrc/NLog/Internal/StringBuilderExt.cssrc/NLog/Internal/TargetWithFilterChain.cssrc/NLog/Internal/TimeoutContinuation.cssrc/NLog/Internal/XmlHelper.cssrc/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cssrc/NLog/LayoutRenderers/BaseDirLayoutRenderer.cssrc/NLog/LayoutRenderers/NLogDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cssrc/NLog/LayoutRenderers/StackTraceLayoutRenderer.cssrc/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cssrc/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cssrc/NLog/Layouts/SimpleLayout.cssrc/NLog/Layouts/Typed/Layout.cssrc/NLog/Layouts/Typed/ValueTypeLayoutInfo.cssrc/NLog/LogEventBuilder.cssrc/NLog/LogEventInfo.cssrc/NLog/LogFactory.cssrc/NLog/LogLevelTypeConverter.cssrc/NLog/Logger-V1Compat.cssrc/NLog/Logger-generated.cssrc/NLog/Logger-generated.ttsrc/NLog/Logger.cssrc/NLog/MessageTemplates/ValueFormatter.cssrc/NLog/ScopeContext.cssrc/NLog/Targets/AsyncTaskTarget.cssrc/NLog/Targets/ColoredConsoleAnsiPrinter.cssrc/NLog/Targets/ColoredConsoleSystemPrinter.cssrc/NLog/Targets/DefaultJsonSerializer.cssrc/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cssrc/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cssrc/NLog/Targets/FileTarget.cssrc/NLog/Targets/LineEndingMode.cssrc/NLog/Targets/MethodCallTarget.cssrc/NLog/Targets/TargetWithContext.cssrc/NLog/Targets/Wrappers/AsyncTargetWrapper.cssrc/NLog/Targets/Wrappers/BufferingTargetWrapper.cstests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cstests/NLog.UnitTests/LogMessageFormatterTests.cstests/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.cssrc/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
Equalsmethod parameter aligns with .NET nullable reference type best practices. The implementation correctly handles null through pattern matching, which returns false whenobjis null.src/NLog/Internal/TimeoutContinuation.cs (1)
96-96: LGTM! Correct nullable annotation for Timer callback.The parameter type change aligns with the
TimerCallbackdelegate signature, which expectsobject? state. Sincenullis 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 reflectsGetEntryAssembly()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
stateparameter correctly aligns with .NET's Timer callback signature. The existing null-safe handling using(state as int?) ?? 0on 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
objcan be null, aligning with .NET's nullable reference types and the standardobject.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.Emptycorrectly addresses the .NET 10 nullable annotation warnings forConvert.ToString()while ensuringToString()always returns a non-null string as per its signature. SinceLiteralValueis 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 nullcheck on Line 201 aligns with the PR objective of addressing .NET 10 nullable warnings. WhileStringHelpers.IsNullOrWhiteSpacealready 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 tothrow;ensures the original exception is rethrown with its stack trace intact whenInnerExceptionis 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 forToString()overrides (e.g.,ScopeContext.ScopeContextPropertiesCollapsed.ToString(),TargetWithContext, and multipleLayoutclasses). 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
stringtostring?correctly addresses .NET 10 nullable annotations.StringBuilder.Append(string?)safely handles null values, and the change accommodates call sites like Line 203 whereIConvertible.ToString()may return null in .NET 10.
355-363: LGTM! Nullability annotation correctly reflects collection semantics.The parameter change from
objecttoobject?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 (SerializeSimpleObjectchecks for null at Line 189,Convert.ToStringhandles 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.ConvertFromcan 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>.Equalsinterface. The implementation usingReferenceEqualshandles 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 tomethodNamepreserves 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
TypeConverterbase class signature.
64-67: LGTM - Nullable annotations are correct.The nullable context and destinationType parameters align with the .NET 10
TypeConverterbase 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-nullableMethodBase- Extracting
declaringTypeto a local variable (line 317) improves readability and ensures proper null checking before callingIsHiddenClassType- Explicit
return false(line 321) clarifies the control flowThese changes align well with the PR's objective to fix nullable warnings from .NET 10 annotations.
329-329: LGTM! Minor style improvement.Using
varfor 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
ResolveNLogDiralways returns a non-null string, asPath.GetDirectoryNamecan return null per the .NET API.src/NLog/Internal/ReflectionHelpers.cs (3)
183-186: LGTM: Correct nullable parameter annotation.Making the
PropertyInfoparameter nullable accurately reflects the method's implementation, which already handles null via thep != nullcheck. This aligns with the usage inObjectReflectionCache.cswhere 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 parameterpremaining 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 forIsDynamicCodeSupportedis 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.Namecan be null. The existing usage at line 52 withStringBuilder.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_GREATERtoNETcorrectly 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?>toin ReadOnlySpan<object?>are correct. Theinmodifier ensures theReadOnlySpanis 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
RenderPreformattedLogEventmethod correctly uses theinmodifier withReadOnlySpanand includes proper error handling for template formatting failures.src/NLog/Logger-V1Compat.cs (1)
101-106: Span-based logging optimization looks goodThe changes consistently route logging calls to
WriteToTargetsWithSpanwith collection expression syntax[arg1, arg2]forNETSTANDARD2_1_OR_GREATER || NETtargets, while maintaining backward compatibility withobject[]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
senderparameter 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
objparameter correctly matches the modern signature ofObject.Equalsand 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
nullas 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_GREATERtoNETSTANDARD2_1_OR_GREATER || NETis 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
truewhen a property is found andfalsewhen not found, following standard .NET Try* conventions. All callers throughout the codebase (LayoutParser.cs and LoggingConfigurationParser.cs) properly handle this return value, treatingtrueas success andfalseas 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.csis being created as a new file in this commit, not modified. There was no previous preprocessor condition to change fromNETCOREAPP3_0_OR_GREATERtoNET.The condition
#if NETSTANDARD2_1_OR_GREATER || NETis appropriate for the project's actual target frameworks (net35,net46,netstandard2.0,netstandard2.1):
netstandard2.1hasLangVersion=13, which fully supports C# 12 collection expressions- The
NETmacro correctly covers .NET 5.0+ unified framework- The project never targeted
netcoreapp3.0ornetcoreapp3.1, so there is no excluded framework to fall back to the multiple-Append pathLikely an incorrect or invalid review comment.
| } | ||
|
|
||
| string IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue(); | ||
| string? IStringValueRenderer.GetFormattedString(LogEventInfo logEvent) => GetStringValue(); |
There was a problem hiding this comment.
🧩 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 -100Repository: 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 -150Repository: 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.
| #if NETSTANDARD2_1_OR_GREATER || NET | ||
| [OverloadResolutionPriority(-1)] | ||
| #endif |
There was a problem hiding this comment.
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.
| #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.
7f08e88 to
1868610
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (5)
src/NLog/Layouts/SimpleLayout.cs (1)
191-193: Non-standard preprocessor symbolNETshould be replaced with versioned symbol.The use of
NETas 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_GREATERThis ensures the
NotNullIfNotNullattribute 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
ScanPropertiesListat 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_GREATERLine 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 forOverloadResolutionPriorityattributeLine 2749 uses
|| NETinstead of|| NET9_0_OR_GREATER. TheOverloadResolutionPriorityattribute was introduced in .NET 9.0, so usingNET(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 tothrow;whenInnerExceptionis null properly preserves the original exception context.However,
throw ex.InnerException;on line 271 resets the stack trace of the inner exception. Consider usingExceptionDispatchInfoto 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
strvalcould 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.PreventMultipleCallsis unnecessary sinceNLog.Commonis 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
📒 Files selected for processing (68)
src/NLog/Common/AsyncLogEventInfo.cssrc/NLog/Common/InternalLogger.cssrc/NLog/Conditions/ConditionExpression.cssrc/NLog/Conditions/ConditionLiteralExpression.cssrc/NLog/Conditions/ConditionMethods.cssrc/NLog/Conditions/ConditionRelationalExpression.cssrc/NLog/Config/AssemblyExtensionLoader.cssrc/NLog/Config/LoggerNameMatcher.cssrc/NLog/Config/LoggingConfiguration.cssrc/NLog/Config/MethodFactory.cssrc/NLog/Config/PropertyTypeConverter.cssrc/NLog/Filters/WhenRepeatedFilter.cssrc/NLog/Internal/AppEnvironmentWrapper.cssrc/NLog/Internal/AssemblyHelpers.cssrc/NLog/Internal/CallSiteInformation.cssrc/NLog/Internal/DictionaryEntryEnumerable.cssrc/NLog/Internal/ExceptionMessageFormatProvider.cssrc/NLog/Internal/FormatHelper.cssrc/NLog/Internal/LogMessageTemplateFormatter.cssrc/NLog/Internal/MultiFileWatcher.cssrc/NLog/Internal/ObjectGraphScanner.cssrc/NLog/Internal/ObjectReflectionCache.cssrc/NLog/Internal/PropertiesDictionary.cssrc/NLog/Internal/PropertyHelper.cssrc/NLog/Internal/ReflectionHelpers.cssrc/NLog/Internal/ScopeContextAsyncState.cssrc/NLog/Internal/SingleItemOptimizedHashSet.cssrc/NLog/Internal/StackTraceUsageUtils.cssrc/NLog/Internal/StringBuilderExt.cssrc/NLog/Internal/TargetWithFilterChain.cssrc/NLog/Internal/TimeoutContinuation.cssrc/NLog/Internal/XmlHelper.cssrc/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cssrc/NLog/LayoutRenderers/BaseDirLayoutRenderer.cssrc/NLog/LayoutRenderers/NLogDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cssrc/NLog/LayoutRenderers/StackTraceLayoutRenderer.cssrc/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cssrc/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cssrc/NLog/Layouts/SimpleLayout.cssrc/NLog/Layouts/Typed/Layout.cssrc/NLog/Layouts/Typed/ValueTypeLayoutInfo.cssrc/NLog/LogEventBuilder.cssrc/NLog/LogEventInfo.cssrc/NLog/LogFactory.cssrc/NLog/LogLevelTypeConverter.cssrc/NLog/Logger-V1Compat.cssrc/NLog/Logger-generated.cssrc/NLog/Logger-generated.ttsrc/NLog/Logger.cssrc/NLog/MessageTemplates/ValueFormatter.cssrc/NLog/ScopeContext.cssrc/NLog/Targets/AsyncTaskTarget.cssrc/NLog/Targets/ColoredConsoleAnsiPrinter.cssrc/NLog/Targets/ColoredConsoleSystemPrinter.cssrc/NLog/Targets/DefaultJsonSerializer.cssrc/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cssrc/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cssrc/NLog/Targets/FileTarget.cssrc/NLog/Targets/LineEndingMode.cssrc/NLog/Targets/MethodCallTarget.cssrc/NLog/Targets/TargetWithContext.cssrc/NLog/Targets/Wrappers/AsyncTargetWrapper.cssrc/NLog/Targets/Wrappers/BufferingTargetWrapper.cstests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cstests/NLog.UnitTests/LogMessageFormatterTests.cstests/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.cssrc/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.FormatandIFormatProvider.GetFormatcorrectly reflect the nullable reference type annotations introduced in .NET 10. The implementations safely handle nullable parameters:
- Line 47: Pattern matching handles null
argcorrectly- Line 82: Null
formatTypereturns null as expectedAlso 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.cslines 361-364). This defensive change correctly addresses potential null returns fromToString()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?returningdefaulttoobjectreturningstring.Emptyproperly satisfies the non-nullableIDictionaryEnumerator.Keycontract introduced in .NET 10.Note: There's a subtle inconsistency where
Entry.Key(from the defaultDictionaryEntryat line 108) would be null, while the directKeyproperty now returnsstring.Empty. This is acceptable sinceMoveNext()always returns false for this empty enumerator, and accessing these properties outside a valid enumeration position is undefined behavior per theIDictionaryEnumeratorcontract.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_GREATERtoNETexcludes .NET Core 3.0 and 3.1 from the optimized span-based code path, even though these versions supportedTextWriter.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
ResolveDefaultMethodParameterstoobject?[]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 sinceMethodInfo.Invokecan return null. The parameter typeobject?[]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 whereToString()returns null, which is now reflected in .NET 10's nullable annotations forobject.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
contextparameter 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.ConvertFromcan return null. The defensive null checks (value?.GetType(),value?.ToString()) are good practice for library code.Note: The
contextparameter remains non-nullable here while it's nullable inCanConvertFrom. This asymmetry likely matches the .NET 10 TypeConverter base class signatures.
64-67: LGTM! Nullable annotations for both parameters are correct.Both the
contextanddestinationTypenullable annotations align with .NET 10 TypeConverter signatures. The implementation safely handles null values: the equality check returns false for null, andIsNumericTypeincludes 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 toCreateLateBoundMethodand 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
varinstead of explicitMethodInfotype helps the code adapt to .NET 10's nullable annotations whereGetMethodreturnsMethodInfo?. The existing null check on Line 263 ensures safe usage.
439-439: LGTM! Explicit null check aids nullable flow analysis.While
IsValidPublicPropertyalready handles null internally, the explicit null check helps .NET 10's nullable flow analysis understand thatcomparerPropInfois 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.Emptyas 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.Emptyfallback 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
TimerCallbackdelegate signature, which acceptsobject? 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 causeDirectory.CreateDirectoryto throw at line 1252, which is caught and wrapped in aNLogRuntimeExceptionwith 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, returningfalsefor empty directories.src/NLog/LogFactory.cs (5)
288-288: LGTM!The nullable annotation for the
senderparameter is correct and follows the standard event handler pattern.
1113-1116: LGTM!The nullable annotation for the
objparameter is correct for overridingObject.Equalswith nullable reference types enabled. The implementation using pattern matching is safe and appropriate.
1227-1227: LGTM!The nullable annotation for the
senderparameter follows the standard event handler pattern and is correct.
1249-1249: LGTM!The nullable annotation for the
senderparameter is correct and follows the standard event handler pattern.
54-56: The conditional compilation directive is correct.NETis a valid versionless conditional compilation symbol defined by the SDK for .NET 5.0 and later. The conditionNETSTANDARD2_1_OR_GREATER || NETproperly targets all platforms supportingIAsyncDisposable: .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_GREATERtoNETcorrectly enables theReadOnlySpan-basedPropertiesoverload on all .NET 5.0+ targets, not just .NET 9.0+. SinceReadOnlySpan<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, ensuringmethodNamealways has a valid value by falling back to the original method name.
219-221: The breaking change is properly handled. The parameter change fromMethodBase?toMethodBaseis safe—the only caller inCallSiteInformation.csat 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:
- The null check at lines 310-311 ensures
stackMethodis non-null before callingLookupAssemblyFromMethod, which now requires a non-nullable parameter.- Lines 317-320 add filtering for hidden declaring types, making the stack frame filtering more comprehensive.
- The explicit
return falseat line 321 improves code clarity.
329-329: LGTM: Improved clarity with explicit variable extraction.Extracting
DeclaringTypeto 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 targetsnet35,net46,netstandard2.0, andnetstandard2.1(not NET5+), and the only target compiling this code path isnetstandard2.1, which is configured withLangVersion=13and fully supports C# 12 collection expressions. The#elsefallback correctly handles other target frameworks using separateAppend()calls. No compilation issue exists.src/NLog/Internal/TimeoutContinuation.cs (1)
96-99: LGTM!The nullable annotation on the
stateparameter aligns with the .NETTimerCallbackdelegate signature and correctly reflects thatnullis 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'sTryGetValuebehavior where the out value isnullwhen 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 nullcheck is technically redundant sinceStringHelpers.IsNullOrWhiteSpacealready handles null, but it helps the compiler's nullable flow analysis understand thatstringValueis 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?) ?? 0pattern 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 baseobject.Equalssignature under nullable reference types. The pattern matching withisalready safely handles null inputs by returning false.src/NLog/Common/AsyncLogEventInfo.cs (1)
92-92: LGTM!The nullable annotation correctly aligns the
Equalsoverride 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_GREATERtoNETbroadens the span-basedWriteToTargetsWithSpanpath 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 retainNET9_0_OR_GREATERguard sinceparamswithSpanis 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_GREATERtoNETas 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 thatbase.ConvertFromcan 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 inGetVersion()(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_GREATERtoNETbroadens 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:
- Broaden the conditional compilation from
NET9_0_OR_GREATERtoNET, aligning with PR objectives- 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 ofinmodifier for ReadOnlySpan parameters.The change from
params ReadOnlySpan<object?>toin ReadOnlySpan<object?>is correct:
- The
inmodifier 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:
- Broadens conditional compilation to
NET(consistent with PR objectives)- Accepts
ReadOnlySpan<MessageTemplateParameter>for allocation-free parameter passing- Properly converts span to array and initializes
_properties(lines 151-157)- Follows the same pattern as the
IList<MessageTemplateParameter>constructor
675-692: LGTM! Span overload consistency maintained.The conditional compilation update from
NET9_0_OR_GREATERtoNETensures the span-basedNeedToPreformatMessageoverload 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 statetoobject? stateis correct sincenullis 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 statetoobject? statecorrectly reflects theWaitCallbackdelegate signature. The method already handles the null case properly by casting toAsyncContinuation?(line 525) and checking for null before use (line 531).
617-620: LGTM: Nullable sender follows event handler conventions.The change from
object sendertoobject? senderaligns 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 setsLangVersionto13(C# 13) fornetstandard2.1, which exceeds the C# 12 requirement for collection expressions. Other frameworks (net35, net46, netstandard2.0) fall back to the legacy syntaxnew object?[] { arg1, arg2 }as expected. The current configuration poses no issues.
1868610 to
5a66c71
Compare
There was a problem hiding this comment.
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:
- The
fastLookuparray is pre-allocated toproperties.Length + fastAccessIndex(line 254)- The new
continueon line 265 skips properties with null getters/return types- This means
fastAccessIndexwon't reach its expected final value- The returned array contains uninitialized
FastPropertyLookupstructs at the end- These default structs have null
Nameand nullValueLookupdelegates- 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
contextandcultureparameters inConvertFrom(line 52) andConvertTo(line 70) should be nullable to match the .NET 10TypeConverterbase 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) andCanConvertTo(line 64) already have the correct nullablecontextparameter.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 || NETbranch. Currently this works because:
netstandard2.1hasLangVersionexplicitly set to 13- No .NET 5+ targets exist, so the
NETsymbol is never trueHowever, when .NET 5+ targets (e.g.,
net5.0,net6.0, etc.) are added in the future, they will use the defaultLangVersionof 9, which does not support collection expressions. This will cause compilation failures.Add a
LangVersionoverride forNET5_0_OR_GREATERtargets:<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 >= 12for all modern targets (.NET 5+andnetstandard2.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 || NETreferences an undefined symbolNET(the bare identifier without a version number). The project's actual TargetFrameworks arenet35;net46;netstandard2.0;netstandard2.1, and onlynetstandard2.1matches this guard. Since that target is configured withLangVersion 13in the csproj, theparams ReadOnlySpan<object?>syntax (C# 13 feature) compiles successfully.However, the guard's intent is unclear and should be simplified to
#if NETSTANDARD2_1_OR_GREATERto 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:contextparameter should be nullable.The
contextparameter should beITypeDescriptorContext?to matchCanConvertToat 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
_matchingArgumentis null andloggerNameis not null, returningtruedoesn't make logical sense—a null pattern shouldn't match a non-null logger name.Since
_matchingArgumentshould never be null in practice (the factory method returnsNoneLoggerNameMatcherfor null patterns, and constructors useSubstringon 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, andContainsLoggerNameMatcher, 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
OverloadResolutionPrioritysite guarded by#if NETSTANDARD2_1_OR_GREATER || NETinstead of|| NET9_0_OR_GREATER(used elsewhere). IfOverloadResolutionPriorityAttributeis only available starting in .NET 9, it’s safer and more consistent to gate this attribute onNET9_0_OR_GREATERas 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 symbolNETNET9_0_OR_GREATER.
#if NETSTANDARD2_1_OR_GREATER || NETNET9_0_OR_GREATERcontains a duplicatedNET, so theNETNET9_0_OR_GREATERsymbol will never be defined. As a result,[OverloadResolutionPriority(-1)]on this Info-overload is emitted forNETSTANDARD2_1_OR_GREATERbut 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
|| NETif 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 underNETguards.The new generic overloads route into
WriteToTargetsWithSpanusing 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 (orlatest) 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-basedRendernow 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.0target whereNETis 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
InnerExceptionmight be null, addressing the new nullable annotations in .NET 10. The fallback tothrow;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
textValueis 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) andAppendDetailedFlat(line 240), which explicitly skip null frames usingcontinue, 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
📒 Files selected for processing (68)
src/NLog/Common/AsyncLogEventInfo.cssrc/NLog/Common/InternalLogger.cssrc/NLog/Conditions/ConditionExpression.cssrc/NLog/Conditions/ConditionLiteralExpression.cssrc/NLog/Conditions/ConditionMethods.cssrc/NLog/Conditions/ConditionRelationalExpression.cssrc/NLog/Config/AssemblyExtensionLoader.cssrc/NLog/Config/LoggerNameMatcher.cssrc/NLog/Config/LoggingConfiguration.cssrc/NLog/Config/MethodFactory.cssrc/NLog/Config/PropertyTypeConverter.cssrc/NLog/Filters/WhenRepeatedFilter.cssrc/NLog/Internal/AppEnvironmentWrapper.cssrc/NLog/Internal/AssemblyHelpers.cssrc/NLog/Internal/CallSiteInformation.cssrc/NLog/Internal/DictionaryEntryEnumerable.cssrc/NLog/Internal/ExceptionMessageFormatProvider.cssrc/NLog/Internal/FormatHelper.cssrc/NLog/Internal/LogMessageTemplateFormatter.cssrc/NLog/Internal/MultiFileWatcher.cssrc/NLog/Internal/ObjectGraphScanner.cssrc/NLog/Internal/ObjectReflectionCache.cssrc/NLog/Internal/PropertiesDictionary.cssrc/NLog/Internal/PropertyHelper.cssrc/NLog/Internal/ReflectionHelpers.cssrc/NLog/Internal/ScopeContextAsyncState.cssrc/NLog/Internal/SingleItemOptimizedHashSet.cssrc/NLog/Internal/StackTraceUsageUtils.cssrc/NLog/Internal/StringBuilderExt.cssrc/NLog/Internal/TargetWithFilterChain.cssrc/NLog/Internal/TimeoutContinuation.cssrc/NLog/Internal/XmlHelper.cssrc/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cssrc/NLog/LayoutRenderers/BaseDirLayoutRenderer.cssrc/NLog/LayoutRenderers/NLogDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cssrc/NLog/LayoutRenderers/StackTraceLayoutRenderer.cssrc/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cssrc/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cssrc/NLog/Layouts/SimpleLayout.cssrc/NLog/Layouts/Typed/Layout.cssrc/NLog/Layouts/Typed/ValueTypeLayoutInfo.cssrc/NLog/LogEventBuilder.cssrc/NLog/LogEventInfo.cssrc/NLog/LogFactory.cssrc/NLog/LogLevelTypeConverter.cssrc/NLog/Logger-V1Compat.cssrc/NLog/Logger-generated.cssrc/NLog/Logger-generated.ttsrc/NLog/Logger.cssrc/NLog/MessageTemplates/ValueFormatter.cssrc/NLog/ScopeContext.cssrc/NLog/Targets/AsyncTaskTarget.cssrc/NLog/Targets/ColoredConsoleAnsiPrinter.cssrc/NLog/Targets/ColoredConsoleSystemPrinter.cssrc/NLog/Targets/DefaultJsonSerializer.cssrc/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cssrc/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cssrc/NLog/Targets/FileTarget.cssrc/NLog/Targets/LineEndingMode.cssrc/NLog/Targets/MethodCallTarget.cssrc/NLog/Targets/TargetWithContext.cssrc/NLog/Targets/Wrappers/AsyncTargetWrapper.cssrc/NLog/Targets/Wrappers/BufferingTargetWrapper.cstests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cstests/NLog.UnitTests/LogMessageFormatterTests.cstests/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.cssrc/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.GetDirectoryNamereturnsnull(e.g., whenentryLocationis a root path likeC:\). 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.Namecan return null and align with theIStringValueRendererinterface, which expectsstring?. TheStringBuilder.Appendcall 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 returnnullper .NET 10 annotations. The implementation safely handles null values sinceStringBuilder.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.GetDirectoryNamereturns null (annotated as nullable in .NET 10), ensuring the non-nullable_processDirfield 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
falsewhen either parameter is null is semantically appropriate for a predicate method used in filter expressions, and prevents theNullReferenceExceptionthat would have occurred previously.
97-100: LGTM! Consistent null handling.The null check follows the same defensive pattern as the
Containsmethod. This prevents exceptions and provides sensible predicate semantics where null values evaluate tofalse.
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.ToStringas indicated by .NET 10's nullable annotations. Returningstring.Emptyis 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 usingReferenceEquals(x, y)properly handles null values—returningtruewhen both parameters are null andfalsewhen only one is null.src/NLog/Targets/FileTarget.cs (2)
1018-1018: LGTM! Correct nullability fix for timer callback.The parameter type change from
objecttoobject?correctly aligns with theTimerCallbackdelegate 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.Emptycorrectly handles the case wherePath.GetDirectoryNamereturns null (as per .NET 10 annotations), ensuring a non-null value is passed toDirectory.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
TimerCallbackdelegate signature with nullableobject?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
senderparameter is now correctly annotated as nullable to match theEventHandler<T>signature in .NET 10. The non-nullablelogEventDroppedEventArgsparameter is appropriate as event args should always be provided.
622-625: LGTM: Correct nullable annotation for event handler.The
senderparameter is now correctly annotated as nullable to match theEventHandler<T>signature in .NET 10. The non-nullablelogEventQueueGrowEventArgsparameter 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
TargetFrameworksin NLog.csproj are:net35;net46;netstandard2.0;netstandard2.1. The change fromNETCOREAPP3_0_OR_GREATERtoNETSTANDARD2_1_OR_GREATER || NETappropriately 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?sinceStackTrace.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.Emptyis necessary sinceConvert.ToStringcan return null according to NET10 annotations, and theToString()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
?? methodNameappropriately handles .NET 10's updated nullable annotations whereToString()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-nullableMethodBasealigns with .NET 10 nullable annotations. Both callers in the codebase check for null before invoking: line 250 in this file explicitly checksmethod != 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
Keyfrom nullable returningdefaultto non-nullable returningstring.Emptyappropriately addresses the nullable warning required by .NET 10'sIDictionaryEnumerator.Keydeclaration. SinceEmptyDictionaryEnumerator.MoveNext()always returns false, theKeyproperty 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:
IsValidPublicPropertysafely handles null by checkingp != nullfirstGetPropertyValuecorrectly returnsobject?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 || NETis correct and appropriately covers all supported frameworks whereRuntimeFeature.IsDynamicCodeSupportedis 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:
ResolveDefaultMethodParametersreturnsobject?[]since parameters can have null defaultsInvokeMethodInforeturnsobject?sinceMethodInfo.Invokecan return null- Line 173's
vardeclaration correctly infers the nullable array typeAlso 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_GREATERwas unnecessary. The updated condition correctly gates the collection expression code to netstandard2.1, where C# 12 andReadOnlySpan<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
contextparameter correctly aligns with .NET 10's updatedTypeConverterbase class.
64-67: LGTM! Nullable annotations added.The nullable annotations on both
contextanddestinationTypeparameters correctly align with .NET 10's updatedTypeConverterbase class.src/NLog/Internal/CallSiteInformation.cs (3)
310-311: Good defensive null check.This early return prevents passing a null
stackMethodtoLookupAssemblyFromMethod(line 313), which expects a non-nullableMethodBaseparameter. This properly addresses the NET10 nullable annotation requirements.
329-329: Appropriate nullable reference handling.Using a local variable for
declaringTypeis 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
HiddenTypeTestin CallSiteTests.cs, which verifies that types registered viaAddCallSiteHiddenClassTypeare correctly skipped during callsite detection. The explicitreturn false;at line 321 improves code clarity. No regression concerns.src/NLog/Logger.cs (1)
606-668: LGTM: Efficient span-based implementation withinmodifier.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_GREATERin Logger-generated.cs is correct. Theparams 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 useNET7_0_OR_GREATERwould 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 || NETcorrectly mirror theLogger.WriteToTargetsWithSpansignatures and preserve the oldWriteToTargets(..., 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 productionNETguards.Switching the span-based test blocks to
#if NETSTANDARD2_1_OR_GREATER || NETkeeps them in sync with the production span paths and ensures thelogEventInfo3scenario 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
LogEventInfoctor that takesReadOnlySpan<MessageTemplateParameter>correctly:
- Chains to the existing message-template ctor,
- Sets
_formattedMessage/_messageFormatterthe same way as the IList-based overload, and- Copies the span into an array-backed
PropertiesDictionarywhen non-empty.The new
NeedToPreformatMessage(in ReadOnlySpan<object?>)mirrors the existing array-based logic and matches howLogger.WriteToTargetsWithSpanuses 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 && !NET40for theLogEventInfoctor that takesIReadOnlyList<KeyValuePair<object, object?>>avoids compiling this overload for .NET 4.0, which does not haveIReadOnlyList<T>in its BCL. This should prevent potential compile-time issues on the NET40 target while keeping the overload available for newer TFMs.
5a66c71 to
bb618eb
Compare
There was a problem hiding this comment.
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 ReadOnlySpanoverloads use incorrect preprocessor guardThe guard at line 571 uses
#if NETSTANDARD2_1_OR_GREATER || NETbut should useNET9_0_OR_GREATERto match all otherparams ReadOnlySpanmethods in the codebase. Theparams 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_GREATERfor allparams ReadOnlySpanmethods- Logger-generated.cs: Uses
#if NETSTANDARD2_1_OR_GREATER || NET9_0_OR_GREATERfor allparams ReadOnlySpanmethods- Logger.cs lines 571-604: Currently uses
#if NETSTANDARD2_1_OR_GREATER || NETFix: 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 targetsSame issue as flagged in
Logger-generated.cs: The collection expression syntax[argument]requires C# 12, but the#if NETSTANDARD2_1_OR_GREATER || NETcondition 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_GREATERAlso 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_GREATERThe 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 beNET9_0_OR_GREATERto properly enable theOverloadResolutionPriorityattribute 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 forOverloadResolutionPriorityattributeThis line uses
|| NETwhich will cause compilation errors on .NET 5, 6, 7, and 8 where theOverloadResolutionPriorityattribute 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 usesNETbut doesn't guarantee C# 12+ language versionThe generic
<TArgument>method overloads use#if NETSTANDARD2_1_OR_GREATER || NETwith C# 12 collection expression syntax[argument]. TheNETsymbol 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 toNET9_0_OR_GREATERinstead ofNET, or explicitly set C# 12+ LangVersion for .NET 5.0-8.x targets.
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (68)
src/NLog/Common/AsyncLogEventInfo.cssrc/NLog/Common/InternalLogger.cssrc/NLog/Conditions/ConditionExpression.cssrc/NLog/Conditions/ConditionLiteralExpression.cssrc/NLog/Conditions/ConditionMethods.cssrc/NLog/Conditions/ConditionRelationalExpression.cssrc/NLog/Config/AssemblyExtensionLoader.cssrc/NLog/Config/LoggerNameMatcher.cssrc/NLog/Config/LoggingConfiguration.cssrc/NLog/Config/MethodFactory.cssrc/NLog/Config/PropertyTypeConverter.cssrc/NLog/Filters/WhenRepeatedFilter.cssrc/NLog/Internal/AppEnvironmentWrapper.cssrc/NLog/Internal/AssemblyHelpers.cssrc/NLog/Internal/CallSiteInformation.cssrc/NLog/Internal/DictionaryEntryEnumerable.cssrc/NLog/Internal/ExceptionMessageFormatProvider.cssrc/NLog/Internal/FormatHelper.cssrc/NLog/Internal/LogMessageTemplateFormatter.cssrc/NLog/Internal/MultiFileWatcher.cssrc/NLog/Internal/ObjectGraphScanner.cssrc/NLog/Internal/ObjectReflectionCache.cssrc/NLog/Internal/PropertiesDictionary.cssrc/NLog/Internal/PropertyHelper.cssrc/NLog/Internal/ReflectionHelpers.cssrc/NLog/Internal/ScopeContextAsyncState.cssrc/NLog/Internal/SingleItemOptimizedHashSet.cssrc/NLog/Internal/StackTraceUsageUtils.cssrc/NLog/Internal/StringBuilderExt.cssrc/NLog/Internal/TargetWithFilterChain.cssrc/NLog/Internal/TimeoutContinuation.cssrc/NLog/Internal/XmlHelper.cssrc/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cssrc/NLog/LayoutRenderers/BaseDirLayoutRenderer.cssrc/NLog/LayoutRenderers/NLogDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cssrc/NLog/LayoutRenderers/StackTraceLayoutRenderer.cssrc/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cssrc/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cssrc/NLog/Layouts/SimpleLayout.cssrc/NLog/Layouts/Typed/Layout.cssrc/NLog/Layouts/Typed/ValueTypeLayoutInfo.cssrc/NLog/LogEventBuilder.cssrc/NLog/LogEventInfo.cssrc/NLog/LogFactory.cssrc/NLog/LogLevelTypeConverter.cssrc/NLog/Logger-V1Compat.cssrc/NLog/Logger-generated.cssrc/NLog/Logger-generated.ttsrc/NLog/Logger.cssrc/NLog/MessageTemplates/ValueFormatter.cssrc/NLog/ScopeContext.cssrc/NLog/Targets/AsyncTaskTarget.cssrc/NLog/Targets/ColoredConsoleAnsiPrinter.cssrc/NLog/Targets/ColoredConsoleSystemPrinter.cssrc/NLog/Targets/DefaultJsonSerializer.cssrc/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cssrc/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cssrc/NLog/Targets/FileTarget.cssrc/NLog/Targets/LineEndingMode.cssrc/NLog/Targets/MethodCallTarget.cssrc/NLog/Targets/TargetWithContext.cssrc/NLog/Targets/Wrappers/AsyncTargetWrapper.cssrc/NLog/Targets/Wrappers/BufferingTargetWrapper.cstests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cstests/NLog.UnitTests/LogMessageFormatterTests.cstests/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
objecttoobject?correctly aligns with theTimerCallbackdelegate signature in .NET 10, which annotates the state parameter as nullable. Since the parameter is unused in the method body andnullis 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
defaulttostring.Emptyproperly satisfies the non-nullable contract forIDictionaryEnumerator.Keyin .NET 10. SinceMoveNext()always returnsfalsefor this empty enumerator, the property should not be accessed in typical usage patterns, makingstring.Emptya 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 wherebase.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:
ProcessPendingEventsandFlushEventsInQueuenow matchTimerCallbackandWaitCallbackrespectivelyOnRequestQueueDropItemandOnRequestQueueGrownow matchEventHandler<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 operatorother?._newLineCharactersfor safe null handling.
213-217: LGTM! Nullable return type correctly applied.The
ConvertFromoverride now correctly returnsobject?to reflect that the base implementation can return null, aligning with .NET 10's nullable annotations forTypeConverter.ConvertFrom.src/NLog/Internal/XmlHelper.cs (1)
360-362: LGTM! Correctly handles .NET 10 nullable annotations.The change from explicit
stringtovarcombined with the null-check properly addresses the .NET 10 nullable reference type annotations forConvert.ToString. When the input value is null,Convert.ToStringreturns null (notstring.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
NullReferenceExceptionand align with .NET 10's nullable annotations. Returningfalsefor null inputs is appropriate for a predicate method used in condition expressions and consistent with how theLengthmethod handles nulls.
95-101: LGTM - Consistent null handling.The null checks mirror the approach in
Containsand prevent exceptions during condition evaluation. The implementation correctly guards theStartsWithcall.
111-117: LGTM - Completes consistent null safety pattern.The null checks complete the consistent approach across
Contains,StartsWith, andEndsWith, 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[]tovaris appropriate given thatResolveDefaultMethodParametersnow returnsobject?[]to properly handle nullable elements.
208-208: LGTM: Correct nullable annotation for reflection invoke.The return type change from
objecttoobject?properly reflects thatMethodInfo.Invokecan return null, aligning with NET10 nullable annotations.
223-229: LGTM: Properly propagates nullable annotations for optional parameters.The changes from
object[]toobject?[]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
InnerExceptionis null, preventing a potential null reference throw. This correctly aligns with NET10's nullable annotations forTargetInvocationException.InnerException.src/NLog/Common/InternalLogger.cs (1)
425-425: LGTM - Type inference with nullable handling.The change to
varcorrectly infersstring?fromPath.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 varis clean and the typeFilterInfois clear from context.
381-381: LGTM - Null-safe hash code computation.The null check correctly prevents potential
NullReferenceExceptionwhenstringValueis null, returning 0 as a reasonable hash code for null values. This aligns with NET10's nullable annotations forStringComparer.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.Formatinterface 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.GetFormatinterface 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
TimerCallbackdelegate 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.GetDirectoryNamenow returningstring?in .NET 10. While the fallback tostring.Emptymight causeDirectory.CreateDirectoryto throw anArgumentExceptionin 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
CreateLateBoundMethodand 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_GREATERtoNET(which includes .NET 5.0+) is correct since all these targets supportRuntimeFeature.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 checksp != 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 bothPropertyInfo.GetValueandMethodInfo.Invokecan 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()andConvert.ToString()calls are appropriate defensive measures responding to NET10's improved nullable annotations. Falling back tostring.Emptyensures consistent non-null string handling throughout the JSON serialization pipeline and prevents potential null reference issues in downstream methods likeAppendStringEscape.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.GetDirectoryNamereturns 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.FullNamecould be null, ensuring non-null values are stored in thealreadyRegisteredHashSet. 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 ofLookupAssemblyFromMethodalready perform null-checking before invocation:
- Line 313 (CallSiteInformation.cs):
stackMethodis null-checked at lines 310–311 before the call- Line 250 (StackTraceUsageUtils.cs):
methodis 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, andnetstandard2.1only. It does not directly targetnetcoreapp3.0ornetcoreapp3.1. The change fromNETCOREAPP3_0_OR_GREATERtoNEThas no practical impact: projects using .NET Core 3.x will consume NLog via thenetstandard2.1build, which hasNETSTANDARD2_1_OR_GREATERdefined and therefore will use the span-basedAsSpanpath. 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? yas required by .NET 10'sIEqualityComparer<T>.Equals. TheReferenceEqualsimplementation 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
strvallocal variable with null-coalescing ensuresLogLevel.FromStringreceives a non-null string, addressing NET10's nullable annotations. Usingstring.Emptyas a fallback is appropriate.
224-228: LGTM! Defensive null handling for string conversion.The null-coalescing operator ensures the
valparameter is assigned a non-null string, addressing NET10's nullable annotations. Thestring.Emptyfallback 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.Emptyfallback is appropriate for cases whereConvert.ToStringreturns 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_GREATERtoNETactually narrows the scope.NETcovers only .NET 5.0+, whileNETCOREAPP3_0_OR_GREATERcovers .NET Core 3.0 and 3.1, all of which support theNotNullIfNotNullattribute. Since NLog targetsnet35;net46;netstandard2.0;netstandard2.1, neither the old nor new part of the OR condition affects actual library targets—the effective condition isNETSTANDARD2_1_OR_GREATERalone. The redundant|| NETpart 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_GREATERtoNETcorrectly broadens the availability of the span-basedWriteoverload 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 concernThe signature changes from
params ReadOnlySpan<object?>toin 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, callingToArray()when needed)However, these private methods are within a
#if NETSTANDARD2_1_OR_GREATER || NETblock 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); |
There was a problem hiding this comment.
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.
| 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.
| #if NETSTANDARD2_1_OR_GREATER || NET | ||
| WriteToTargetsWithSpan(targetsForLevel, level, null, Factory.DefaultCultureInfo, message, [arg1, arg2]); |
There was a problem hiding this comment.
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?[] { ... });
#endifApply 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; |
There was a problem hiding this comment.
🧩 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.csRepository: 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 -50Repository: 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 2Repository: 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
DeleteOldFilesBeforeArchivecorrectly 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.
bb618eb to
739827f
Compare
There was a problem hiding this comment.
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 362Lines 357 and 362 use
assembly.FullNamedirectly 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 protectionBoth 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_GREATERLine 2043 contains
NETNET9_0_OR_GREATERwhich appears to be a typo. This should likely beNET9_0_OR_GREATERorNETto 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 forOverloadResolutionPriorityattributeLine 2749 uses
|| NETinstead of|| NET9_0_OR_GREATER. TheOverloadResolutionPriorityattribute was introduced in .NET 9.0, so usingNET(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 greaterThe 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+. TheNETsymbol 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?[] { ... }); #endifApply 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: Returnstring.Emptyinstead of potentially-nullnlogAssemblyLocationThe ternary returns
nlogAssemblyLocationwhen 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.Commonnamespace is already imported viausing NLog.Common;at line 45. The fully-qualified callNLog.Common.AsyncHelpers.PreventMultipleCalls(...)could be simplified to justAsyncHelpers.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
NullReferenceExceptionwhenGetGetMethod()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
📒 Files selected for processing (68)
src/NLog/Common/AsyncLogEventInfo.cssrc/NLog/Common/InternalLogger.cssrc/NLog/Conditions/ConditionExpression.cssrc/NLog/Conditions/ConditionLiteralExpression.cssrc/NLog/Conditions/ConditionMethods.cssrc/NLog/Conditions/ConditionRelationalExpression.cssrc/NLog/Config/AssemblyExtensionLoader.cssrc/NLog/Config/LoggerNameMatcher.cssrc/NLog/Config/LoggingConfiguration.cssrc/NLog/Config/MethodFactory.cssrc/NLog/Config/PropertyTypeConverter.cssrc/NLog/Filters/WhenRepeatedFilter.cssrc/NLog/Internal/AppEnvironmentWrapper.cssrc/NLog/Internal/AssemblyHelpers.cssrc/NLog/Internal/CallSiteInformation.cssrc/NLog/Internal/DictionaryEntryEnumerable.cssrc/NLog/Internal/ExceptionMessageFormatProvider.cssrc/NLog/Internal/FormatHelper.cssrc/NLog/Internal/LogMessageTemplateFormatter.cssrc/NLog/Internal/MultiFileWatcher.cssrc/NLog/Internal/ObjectGraphScanner.cssrc/NLog/Internal/ObjectReflectionCache.cssrc/NLog/Internal/PropertiesDictionary.cssrc/NLog/Internal/PropertyHelper.cssrc/NLog/Internal/ReflectionHelpers.cssrc/NLog/Internal/ScopeContextAsyncState.cssrc/NLog/Internal/SingleItemOptimizedHashSet.cssrc/NLog/Internal/StackTraceUsageUtils.cssrc/NLog/Internal/StringBuilderExt.cssrc/NLog/Internal/TargetWithFilterChain.cssrc/NLog/Internal/TimeoutContinuation.cssrc/NLog/Internal/XmlHelper.cssrc/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cssrc/NLog/LayoutRenderers/BaseDirLayoutRenderer.cssrc/NLog/LayoutRenderers/NLogDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cssrc/NLog/LayoutRenderers/StackTraceLayoutRenderer.cssrc/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cssrc/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cssrc/NLog/Layouts/SimpleLayout.cssrc/NLog/Layouts/Typed/Layout.cssrc/NLog/Layouts/Typed/ValueTypeLayoutInfo.cssrc/NLog/LogEventBuilder.cssrc/NLog/LogEventInfo.cssrc/NLog/LogFactory.cssrc/NLog/LogLevelTypeConverter.cssrc/NLog/Logger-V1Compat.cssrc/NLog/Logger-generated.cssrc/NLog/Logger-generated.ttsrc/NLog/Logger.cssrc/NLog/MessageTemplates/ValueFormatter.cssrc/NLog/ScopeContext.cssrc/NLog/Targets/AsyncTaskTarget.cssrc/NLog/Targets/ColoredConsoleAnsiPrinter.cssrc/NLog/Targets/ColoredConsoleSystemPrinter.cssrc/NLog/Targets/DefaultJsonSerializer.cssrc/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cssrc/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cssrc/NLog/Targets/FileTarget.cssrc/NLog/Targets/LineEndingMode.cssrc/NLog/Targets/MethodCallTarget.cssrc/NLog/Targets/TargetWithContext.cssrc/NLog/Targets/Wrappers/AsyncTargetWrapper.cssrc/NLog/Targets/Wrappers/BufferingTargetWrapper.cstests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cstests/NLog.UnitTests/LogMessageFormatterTests.cstests/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.cssrc/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
varhere correctly infers theobject?[]type from the updatedResolveDefaultMethodParametersreturn type, aligning the nullable annotations throughout the call chain.
208-221: LGTM!The
object?return type correctly reflects thatMethodInfo.Invokecan 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 thatParameterInfo.DefaultValuecan 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_GREATERtoNETis correct and fixes the preprocessor condition for modern .NET. TheNETsymbol properly targets .NET 5.0 and later, whereasNETCOREAPP3_0_OR_GREATERdoesn'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:
CanConvertFromandCanConvertTonow have nullable context parameters matching the base class signatureConvertFromandConvertTonow returnobject?to reflect that they can return null- Null handling throughout the implementation is safe (e.g.,
value?.GetType(),value?.ToString() ?? string.Empty)- The
IsNumericTypehelper correctly handles nullableType?parameterThese 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 statetoobject? statecorrectly aligns with .NET 10's updatedTimerCallbacksignature. Since thestateparameter 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 returnsstring?. Usingstring.Emptyas a fallback is appropriate since:
- It satisfies the non-null requirement for
Directory.CreateDirectoryon Line 1252- If the directory name is actually empty (edge case), the subsequent
CreateDirectorycall will throw, which is caught and properly wrapped in aNLogRuntimeExceptionon Line 1257src/NLog/Filters/WhenRepeatedFilter.cs (2)
151-151: LGTM: Minor syntax modernization.The change to
out varis 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
Equalsoverride (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
stringtostring?aligns with NET10 nullable annotations, particularly for the call site at line 203 whereIConvertible.ToString()now returnsstring?. The method body correctly handles null values sinceStringBuilder.Append(string?)treats null as an empty string without throwing an exception.
355-363: LGTM! Null handling is correct.The change from
objecttoobject?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 reachesSerializeSimpleObject(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
TargetFrameworksinsrc/NLog/NLog.csprojdoes not includenetcoreapp3.0ornetcoreapp3.1. The TargetFrameworks are:net35;net46;netstandard2.0;netstandard2.1.The change from
NETCOREAPP3_0_OR_GREATERtoNETis a safe simplification that correctly targets the frameworks the project actually builds for. The conditionNETSTANDARD2_1_OR_GREATER || NETapplies 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 thatStackTrace.GetFrame()can return null in NET10.
196-201: LGTM: Proper nullable handling for NET10.The null-conditional operator
?.correctly handles potentially nullStackFrameinstances, 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:
separatoris null, soStringBuilder.Append(null)is a no-op (no leading separator)- Subsequent frames:
separatorcontains_separator, which is properly inserted between framesThe 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.FullNameThe null-coalescing operators appropriately guard against null values from
Assembly.FullName, which can be null according to NET10 annotations. This ensures thealreadyRegisteredHashSet 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 => defaulttoobject Key => string.Emptyimproves 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_GREATERtoNETappropriately 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 targettoout Target? targetaccurately 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.Emptycorrectly handlesPath.GetDirectoryNamereturningnull(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_GREATERtoNETSTANDARD2_1_OR_GREATER || NETcorrectly enables theNotNullIfNotNullattribute for all applicable frameworks where it's available.src/NLog/Common/AsyncLogEventInfo.cs (1)
92-92: LGTM!The nullable annotation
object?on theEqualsoverride is the correct signature for nullable-aware code. The pattern matchobj is AsyncLogEventInfo othersafely handles null input.tests/NLog.UnitTests/Targets/ColoredConsoleTargetTests.cs (1)
389-394: LGTM!The updated conditional compilation
NETSTANDARD2_1_OR_GREATER || NETconsistently 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 forTrace(and similar for other log levels) remain gated underNET9_0_OR_GREATER, which correctly limits these public API surface additions to .NET 9+ whereparamswith spans is supported.src/NLog/Logger.cs (2)
606-616: Good use ofinmodifier for span parameters.Using
in ReadOnlySpan<object?>for the privateWriteToTargetsWithSpanmethods 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
Logoverloads are correctly gated underNETSTANDARD2_1_OR_GREATER || NETand delegate to the internalWriteToTargetsWithSpanmethods appropriately.src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs (1)
55-60: LGTM - Correctly aligns with interface nullability.The
string?return type now correctly reflects thatThread.CurrentThread.Namecan be null, and matches theIStringValueRenderer.GetFormattedStringinterface signature which declaresstring?.Note: The past review comment flagged that other implementations (ThreadIdLayoutRenderer, GuidLayoutRenderer, etc.) return non-nullable
stringwhile the interface declaresstring?. 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
IAsyncDisposableinterface is correctly gated underNETSTANDARD2_1_OR_GREATER || NET, matching where the interface is available.
376-411: Well-implemented DisposeAsync with appropriate timeout handling.The async disposal correctly:
- Short-circuits if already disposing or no config loaded
- Awaits
FlushAsyncbefore disposal- Uses a timeout task to prevent indefinite hangs during dispose
- 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.Equalsoverride, correctly handled by the pattern match.
1227-1249: LGTM on nullable sender parameters.Event handler signatures with
object? senderfollow .NET conventions for nullable-aware event handlers.src/NLog/Layouts/Typed/ValueTypeLayoutInfo.cs (1)
272-275: Consistent null-safety fallback forToString().The null-coalescing pattern aligns with similar implementations across the codebase (e.g.,
Layout.csline 364,TargetWithContext.csline 728) ensuringToString()never returns null.src/NLog/Internal/SingleItemOptimizedHashSet.cs (1)
242-245: Correct nullable annotation update forIEqualityComparer<T>.Equals.The parameter nullability aligns with .NET's updated interface contract.
ReferenceEqualsalready 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
PropertyInfoby returning false early, while assigning a valid result when found. Logic is correct and clearer.
262-263: Minor style change usingvarfor method reflection.No functional change; just a stylistic preference. The subsequent null-check on line 263 remains correct.
439-442: Explicit null-check beforeIsValidPublicProperty.While
IsValidPublicPropertynow acceptsPropertyInfo?and handles null internally (returns false), the explicitcomparerPropInfo != nullcheck 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 inFuncMethodValue.ToString().The null-coalescing pattern ensures a non-null return, consistent with other
ToString()implementations in this PR.
380-396: Correct nullable annotation forEquals(object?).The signature now matches .NET's updated
Object.Equals(object?)contract. The implementation correctly handles null viaReferenceEqualschecks andobject.Equals.
401-405: Correct nullable annotation forIEquatable<T>.Equals(T?).Aligns with .NET's updated interface contract. The implementation using
object.Equalsalready handles null values correctly.
624-627: Consistent null-safety fallback inLayoutTypeValue.ToString().Same pattern as other
ToString()implementations, ensuring non-null returns when the inner layout'sToString()returns null.src/NLog/Internal/ReflectionHelpers.cs (3)
183-186: Correct nullable annotation forIsValidPublicProperty.The parameter type
PropertyInfo?accurately reflects that null is handled (the method returns false whenpis null). This aligns with callers likeObjectReflectionCache.GetPublicPropertieswhich pass potentially null values.
188-195: Correct nullable return type forGetPropertyValue.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_GREATERremoved in favor ofNET.This change removes coverage of .NET Core 3.1 and 4.x since
NETonly applies to .NET 5+, whereasNETCOREAPP3_1_OR_GREATERcovered .NET Core 3.1 and all later versions. The new conditionNETSTANDARD2_1_OR_GREATER || NETwill 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).
| #if NETSTANDARD2_1_OR_GREATER || NET | ||
| WriteToTargetsWithSpan(LogLevel.Trace, null, formatProvider, message, [argument]); | ||
| #else | ||
| WriteToTargets(LogLevel.Trace, formatProvider, message, new object?[] { argument }); | ||
| #endif |
There was a problem hiding this comment.
🧩 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.
5a4a3b5 to
c1660d2
Compare
There was a problem hiding this comment.
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: Makecontextandcultureparameters nullable to match .NET 10 TypeConverter signature.The
contextandcultureparameters should both be nullable. The .NET 10TypeConverter.ConvertFrombase 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
DeleteOldFilesBeforeArchivemay be unintended. Lines 247-249 use a more defensive approach by checkingif (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
NETinstead ofNET8_0_OR_GREATERfor 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 ofNET.As flagged in the previous review, the
NETsymbol is not a standard .NET preprocessor constant. The documented symbols for .NET 5.0+ are versioned, such asNET5_0_OR_GREATERandNET6_0_OR_GREATER.Update to:
-#if NETSTANDARD2_1_OR_GREATER || NET +#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATERThis ensures the
NotNullIfNotNullattribute 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 nullcheck 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_GREATERat line 2043 is a typo that was previously flagged. This should be|| NETto match the pattern used elsewhere in the file.
2749-2751: Incorrect preprocessor directive for OverloadResolutionPriority (duplicate issue)Line 2749 uses
|| NETwhich would apply theOverloadResolutionPriorityattribute 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. TheNETsymbol 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_GREATERor 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 theNETpreprocessor symbol is defined.The preprocessor condition uses
NETwhich may be undefined (same issue flagged in src/NLog/Internal/LogMessageTemplateFormatter.cs Line 202). IfNETis not a predefined MSBuild symbol, the span-basedWrite(ReadOnlySpan<char>)overload will only compile fornetstandard2.1targets, not modern .NET 5+ as intended.Based on past review findings, verify that
NETis properly defined or replace it with an explicit target likeNET5_0_OR_GREATER.src/NLog/LogFactory.cs (1)
54-56: Verify theNETpreprocessor symbol is defined.The IAsyncDisposable implementation uses the
NETsymbol which may be undefined (same issue as LogMessageTemplateFormatter.cs). If undefined, this interface implementation would only be available onnetstandard2.1, not modern .NET 5+ targets.Consider replacing with
NET5_0_OR_GREATERor verify thatNETis 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.InnerExceptionbefore rethrowing is correct and necessary. The fallback tothrow;whenInnerExceptionis null properly preserves the original exception. This change addresses both the .NET 10 compiler warnings and prevents a potentialNullReferenceExceptionat runtime.Optional: Consider preserving stack traces when rethrowing inner exceptions.
While the current unwrapping pattern is standard and widely used, using
ExceptionDispatchInfowould 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
truewhen_matchingArgumentis null andloggerNameis not null, meaning a null matching argument would match any non-null logger name.In practice,
_matchingArgumentshould never be null (it's derived viaSubstringoperations), and these checks were likely added to satisfy NET10 nullable annotations. Consider one of these approaches:
- If
_matchingArgumentbeing null is truly impossible, use the null-forgiving operator (_matchingArgument!) to bypass the warning- If defensive null-checking is desired, return
falsewhen_matchingArgumentis null to align with the semantics ofNoneLoggerNameMatcher(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, andContainsLoggerNameMatcher.Also applies to: 190-191, 207-208, 224-225
📜 Review details
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (68)
src/NLog/Common/AsyncLogEventInfo.cssrc/NLog/Common/InternalLogger.cssrc/NLog/Conditions/ConditionExpression.cssrc/NLog/Conditions/ConditionLiteralExpression.cssrc/NLog/Conditions/ConditionMethods.cssrc/NLog/Conditions/ConditionRelationalExpression.cssrc/NLog/Config/AssemblyExtensionLoader.cssrc/NLog/Config/LoggerNameMatcher.cssrc/NLog/Config/LoggingConfiguration.cssrc/NLog/Config/MethodFactory.cssrc/NLog/Config/PropertyTypeConverter.cssrc/NLog/Filters/WhenRepeatedFilter.cssrc/NLog/Internal/AppEnvironmentWrapper.cssrc/NLog/Internal/AssemblyHelpers.cssrc/NLog/Internal/CallSiteInformation.cssrc/NLog/Internal/DictionaryEntryEnumerable.cssrc/NLog/Internal/ExceptionMessageFormatProvider.cssrc/NLog/Internal/FormatHelper.cssrc/NLog/Internal/LogMessageTemplateFormatter.cssrc/NLog/Internal/MultiFileWatcher.cssrc/NLog/Internal/ObjectGraphScanner.cssrc/NLog/Internal/ObjectReflectionCache.cssrc/NLog/Internal/PropertiesDictionary.cssrc/NLog/Internal/PropertyHelper.cssrc/NLog/Internal/ReflectionHelpers.cssrc/NLog/Internal/ScopeContextAsyncState.cssrc/NLog/Internal/SingleItemOptimizedHashSet.cssrc/NLog/Internal/StackTraceUsageUtils.cssrc/NLog/Internal/StringBuilderExt.cssrc/NLog/Internal/TargetWithFilterChain.cssrc/NLog/Internal/TimeoutContinuation.cssrc/NLog/Internal/XmlHelper.cssrc/NLog/LayoutRenderers/AssemblyVersionLayoutRenderer.cssrc/NLog/LayoutRenderers/BaseDirLayoutRenderer.cssrc/NLog/LayoutRenderers/NLogDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessDirLayoutRenderer.cssrc/NLog/LayoutRenderers/ProcessInfoLayoutRenderer.cssrc/NLog/LayoutRenderers/StackTraceLayoutRenderer.cssrc/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cssrc/NLog/LayoutRenderers/Wrappers/WrapLineLayoutRendererWrapper.cssrc/NLog/Layouts/SimpleLayout.cssrc/NLog/Layouts/Typed/Layout.cssrc/NLog/Layouts/Typed/ValueTypeLayoutInfo.cssrc/NLog/LogEventBuilder.cssrc/NLog/LogEventInfo.cssrc/NLog/LogFactory.cssrc/NLog/LogLevelTypeConverter.cssrc/NLog/Logger-V1Compat.cssrc/NLog/Logger-generated.cssrc/NLog/Logger-generated.ttsrc/NLog/Logger.cssrc/NLog/MessageTemplates/ValueFormatter.cssrc/NLog/ScopeContext.cssrc/NLog/Targets/AsyncTaskTarget.cssrc/NLog/Targets/ColoredConsoleAnsiPrinter.cssrc/NLog/Targets/ColoredConsoleSystemPrinter.cssrc/NLog/Targets/DefaultJsonSerializer.cssrc/NLog/Targets/FileArchiveHandlers/BaseFileArchiveHandler.cssrc/NLog/Targets/FileArchiveHandlers/LegacyArchiveFileNameHandler.cssrc/NLog/Targets/FileTarget.cssrc/NLog/Targets/LineEndingMode.cssrc/NLog/Targets/MethodCallTarget.cssrc/NLog/Targets/TargetWithContext.cssrc/NLog/Targets/Wrappers/AsyncTargetWrapper.cssrc/NLog/Targets/Wrappers/BufferingTargetWrapper.cstests/NLog.RegEx.Tests/ColoredConsoleTargetTests.cstests/NLog.UnitTests/LogMessageFormatterTests.cstests/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.cssrc/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cssrc/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.TimerCallbackaccepts 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
stringtovarcorrectly addresses .NET 10's nullable annotation forPath.GetDirectoryName, which now returnsstring?. The existing null check on line 426 properly guards against null values before callingDirectory.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 forDictionary<TKey, TValue>.TryGetValue.src/NLog/LayoutRenderers/ThreadNameLayoutRenderer.cs (2)
55-58: LGTM! Correct nullability for Thread.Name.The return type change from
stringtostring?correctly reflects thatThread.CurrentThread.Namereturns a nullable string, as threads may not have a name assigned.
60-60: LGTM! Interface contract now correctly implemented.The return type change from
stringtostring?correctly implements theIStringValueRenderer.GetFormattedStringinterface 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:
_taskCompletiondelegate type properly matches the updatedTaskCompletionmethod signaturecontinuationparameter is safely handled with null checks at lines 758-762stateparameter 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 statetoobject? statecorrectly matches the .NET 10TimerCallbackdelegate signature, which expects a nullable state parameter.
523-523: LGTM: Nullability annotation aligns with WaitCallback signature.The change from
object statetoobject? statecorrectly matches the .NET 10WaitCallbackdelegate signature. The method safely handles null values via theasoperator on Line 525.
617-617: LGTM: Nullability annotation aligns with EventHandler signature.The change from
object sendertoobject? sendercorrectly matches the .NET 10EventHandler<LogEventDroppedEventArgs>delegate signature.
622-622: LGTM: Nullability annotation aligns with EventHandler signature.The change from
object sendertoobject? sendercorrectly matches the .NET 10EventHandler<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 whenbase.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
InvokeMethodInfoand its usage properly align with .NET 10 nullable annotations. The return typeobject?correctly represents thatMethodInfo.Invokecan return null, and the parameter typeobject?[]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[]toobject?[]forResolveDefaultMethodParametersproperly handles nullable default values from optional parameters. This aligns with the fact thatParameterInfo.DefaultValuecan 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_GREATERforparams ReadOnlySpan<object?>overloads (C# 13 feature at lines 163, 185)NETfor 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
FastPropertyLookupentry instead of usingcontinue, the array is now fully populated and won't contain trailing default entries that could causeNullReferenceException.The defensive null-check handles the edge case where .NET 10 nullable annotations indicate
GetGetMethod()orReturnTypemight return null, even though the comment notes this should be rare due to theIsValidPublicPropertyfiltering.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
varinstead of explicitMethodInfotype 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
comparerPropInfobefore callingIsValidPublicProperty()is good defensive programming. While the extension method handles null internally, the explicit check helps the compiler's nullable flow analysis understand thatcomparerPropInfois non-null inside the if block, ensuring safe access toGetPropertyValueon line 441.src/NLog/Internal/AppEnvironmentWrapper.cs (1)
246-254: LGTM!The null-coalescing guard on
Path.GetDirectoryNameis correct. This method can returnnullwhen given a root path (e.g.,"C:\"or"/"), so the?? string.Emptyensures 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.GetDirectoryNamereturnsnullfor root paths, and the?? string.Emptyfallback 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? assemblycorrectly 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
_processDiris never null, which is correct sincePath.GetDirectoryNamecan 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 thatStackTrace.GetFrame()can return null, improving type safety for callers.
191-203: LGTM!The
AppendRawmethod 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 asnull, ensuring no leading separator on the first frame.
206-221: LGTM!The separator handling is now correct. The
separatorvariable starts asnull, so the first valid frame appends nothing before it. After processing the first frame,separatoris assigned_separator ?? string.Emptyfor subsequent iterations. Thetype?.Name ?? "<no type>"fallback also properly handles nullDeclaringType.
224-238: LGTM!Same correct separator pattern as
AppendFlat. The nullableseparatorensures 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() ?? methodNameis a defensive guard. WhileMethodBase.ToString()typically doesn't return null, this ensures robustness and satisfies nullable analysis.
219-221: All callers ofLookupAssemblyFromMethodproperly null-check before invocation.
- Line 250 (StackTraceUsageUtils.cs):
if (method != null && LookupAssemblyFromMethod(method) != null)— checks before call- Line 313 (CallSiteInformation.cs):
stackMethodis guarded by an early return at lines 310-311 (if (stackMethod is null) return true;) before the callThe 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)andEquals(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
senderparameter 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.PreventMultipleCallscorrectly 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
senderparameters 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.ToStringwhen promoting to LogLevel, ensuring a non-null value forLogLevel.FromString.
226-228: LGTM! Defensive null handling for string promotion.The null-coalescing to
string.Emptyprevents potential null returns fromConvert.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_GREATERtoNETappropriately broadens the availability of the span-basedPropertiesoverload 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_GREATERtoNETat both the creation (line 103) and usage (line 135) oflogEventInfo3correctly 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_GREATERtoNETcorrectly widens the availability of theNotNullIfNotNullattribute 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 || NETto 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_GREATERtoNETfor theNeedToPreformatMessagespan-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 accommodatesIConvertible.ToString()returning nullable in NET10 (called from line 203). The existing implementation at line 270 safely handles null viaStringBuilder.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
ArgumentNullExceptionfor invalid paths (like root paths with no directory component), it now defaults to the current working directory. However, the subsequentDirectory.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 explicitstring.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 forIEqualityComparer<T>.Equals. The implementation usingReferenceEquals(x, y)safely handles null values.src/NLog/LogLevelTypeConverter.cs (3)
46-49: LGTM: Nullable context annotation added correctly.The nullable annotation on the
contextparameter aligns with .NET 10's TypeConverter base class signature.
64-67: LGTM: Nullable annotations added correctly.Both the
contextanddestinationTypenullable annotations align with .NET 10's TypeConverter base class signature.
70-80: Verifycontextparameter nullability matches .NET 10 TypeConverter.The
contextparameter inConvertTois non-nullable, whileCanConvertTo(line 64) accepts a nullable context. Please confirm that .NET 10'sTypeConverter.ConvertTobase method signature indeed has a non-nullable context parameter, maintaining consistency with theConvertFrompattern.
|



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