Repository navigation
Nilog v1.0.4
Added
-
Typed overloads extended to 16 arguments — the source generator (
Nilog.SourceGenerators)
now emits zero-arrayWrite*/Nilogger.Logoverloads for 6–16 arguments (raised from 6–8
in v1.0.3), lifting the typed ceiling from 8 to 16. A nine-argument call such as
WriteInformation("{A}…{I}", 1…9)now binds toWriteInformation<T0…T8>instead of falling
back toparams object[]— so the disabled path allocates 0 bytes and the enabled path
carries no array. Measured: 9-arg disabled 0.45 ns / 0 B vs Microsoft 211 ns / 368 B
(≈ 469× faster); 9-arg enabled 156 ns / 368 B vs Microsoft 246 ns / 368 B (37%
faster — boxing is unavoidable on the enabled path; the struct itself adds nothing). -
Typed multi-pair scope overloads — three new
WriteScopeoverloads that eliminate the
dictionary allocation for the most common scope shapes:WriteScope<T1,T2>(key1, val1, key2, val2)— backed by a stack-allocatedTwoScopestructWriteScope<T1,T2,T3>(key1, val1, key2, val2, key3, val3)— backed byThreeScopeWriteScope<T1,T2,T3,T4>(k1,v1, k2,v2, k3,v3, k4,v4)— backed byFourScope
All three surface a scope compatible with the standard MEL
ILoggerProviderpipeline. The
underlying readonly structs require no array copy — values are boxed only once each, the
unavoidable minimum forILoggerFactoryinterop. -
Compact exception report (
moreDetailsEnabled: false) —WriteErrorExceptionand
WriteCriticalExceptionwithmoreDetailsEnabled: falsenow render a compact single-line
summary ([Title] Type: Message (Source=…, HResult=…)) that allocates < 300 bytes per
call, down from ≈ 992 bytes in v1.0.3. The verbose multi-line report (moreDetailsEnabled: true) is unchanged. Guarded by a new allocation gate test. -
ExceptionBasicReport_AllocatesBelow300Bytesallocation gate — a new test in
AllocationGateTestsasserts that a singleWriteErrorException(ex, …, moreDetailsEnabled: false)call allocates fewer than 300 bytes (after JIT warmup), using aCaptureLoggerinner
class that does not allocate duringLog. Runs in CI Release to catch any regression. -
DisabledPath_NineTypedArgs_AllocatesZeroBytestest — added toAllocationGateTeststo
assert that a 9-typed-arg disabled call allocates exactly0L, covering the newly typed range. -
Typed scope unit tests —
TypedTwoPairScope_HasExpectedEntries,
TypedThreePairScope_HasExpectedEntries, andTypedFourPairScope_HasExpectedEntriesadded to
ScopeTests, verifying key/value ordering, counts, and correct enumeration. -
Benchmark additions and improvements:
TwoArgBenchmarks— newEnabled (int+int)category proves the 2-arg enabled path is 34%
faster than Microsoft when types match (46 ns vs 70 ns); the int+decimal delta (62 ns) is
explained by decimal boxing being 24 B vs 16 B for int — the code path is identical.HighArityExtendedBenchmarks— updated benchmark descriptions from "params" to "typed" to
reflect the v1.0.4 source-generator change; confirms 9-arg disabled = 0.45 ns / 0 B.TemplateCacheBenchmarks— benchmarks the per-thread single-slot cache hit (WarmCache)
and the fullConcurrentDictionarymiss path (ColdParse).TypedScopeBenchmarks— compares single-pair, typed 2-pair vs dict 2-pair, and typed 3-pair.ValueVsReferenceArgBenchmarks— compares int, string, and mixed argument boxing cost.- Debugger guard in
Nilog.Benchmark/Program.cs— aborts with a clear message if a
managed debugger is attached, preventing benchmarks from running under the debugger and
producing misleadingly slow numbers.
Changed
-
Nilog.Demoupdated:- Section 3b comment corrected from "Nine or more values — the familiar params path" to "Nine
values — still typed and zero-allocation (source-generated, 6–16 args)", reflecting that the
source generator now covers 1–16 args and a 9-arg call binds toWriteInformation<T0…T8>. - Section 8 (scopes) updated to showcase the new typed
WriteScope<T1,T2/T3/T4>overloads
with 2-pair, 3-pair, and 4-pair examples alongside theIReadOnlyDictionaryfallback.
- Section 3b comment corrected from "Nine or more values — the familiar params path" to "Nine
-
Nilog.Functionupdated — the per-request 3-entry dictionary scope inOrdersFunction
replaced withWriteScope("OrderId", orderId, "CustomerId", request.CustomerId, "Currency", request.Currency)(typedWriteScope<T1,T2,T3>), eliminating the dictionary allocation for
the correlation scope that wraps every checkout invocation. -
Nilog.Demo,Nilog.Function, andNilog.Benchmarkupdated to reflect v1.0.4 changes.
Performance
Measured with BenchmarkDotNet (ShortRun: 3 warmup + 3 measurement, Server GC), .NET 10.0,
Intel Core i7-13850HX:
| Path | v1.0.3 | v1.0.4 | Δ |
|---|---|---|---|
9-arg disabled — WriteDebug("{A}…{I}", 1…9) |
params, 211 ns / 368 B (≈ Microsoft) | 0.45 ns / 0 B | ≈ 469× faster, zero alloc |
9-arg enabled — WriteInformation("{A}…{I}", 1…9) |
params, 246 ns / 368 B (≈ Microsoft) | 156 ns / 368 B | 37% faster (boxing is unavoidable on the enabled path; no array overhead added) |
| 5-arg enabled | 77.70 ns / 160 B | 77.70 ns / 160 B | unchanged — confirmed < 140 ns target ✅ |
| 2-arg enabled (int+int) | n/a | 46.27 ns / 96 B | 34% faster than Microsoft (70 ns / 136 B) |
Compact exception report (basic, moreDetailsEnabled: false) |
≈ 992 B | < 300 B | > 3× less allocation per report |
The 0–8-arg paths are unchanged from v1.0.3.
Full Changelog: v1.0.3...v1.0.4