diff --git a/src/ZLogger.MessagePack/MessagePackZLoggerFormatter.cs b/src/ZLogger.MessagePack/MessagePackZLoggerFormatter.cs index 21dd859f..a2d4534e 100644 --- a/src/ZLogger.MessagePack/MessagePackZLoggerFormatter.cs +++ b/src/ZLogger.MessagePack/MessagePackZLoggerFormatter.cs @@ -89,9 +89,9 @@ public void FormatLogEntry(IBufferWriter writer, TEntry entry) whe if ((IncludeProperties & IncludeProperties.ScopeKeyValues) != 0) { propCount--; - if (entry.ScopeState != null) + if (entry.LogInfo.ScopeState != null) { - var scopeProperties = entry.ScopeState.Properties; + var scopeProperties = entry.LogInfo.ScopeState.Properties; for (var i = 0; i < scopeProperties.Length; i++) { if (scopeProperties[i].Key != "{OriginalFormat}") @@ -153,12 +153,11 @@ public void FormatLogEntry(IBufferWriter writer, TEntry entry) whe // Scope if ((flag & IncludeProperties.ScopeKeyValues) != 0) { - if (entry.ScopeState != null) + if (entry.LogInfo.ScopeState is { Properties: var scopeProperties }) { - var scopeProperties = entry.ScopeState.Properties; - for (var i = 0; i < scopeProperties.Length; i++) + foreach (var t in scopeProperties) { - var (key, value) = scopeProperties[i]; + var (key, value) = t; // If `BeginScope(format, arg1, arg2)` style is used, the first argument `format` string is passed with this name if (key == "{OriginalFormat}") continue; diff --git a/src/ZLogger/Formatters/SystemTextJsonZLoggerFormatter.cs b/src/ZLogger/Formatters/SystemTextJsonZLoggerFormatter.cs index 350775da..d384a8f5 100644 --- a/src/ZLogger/Formatters/SystemTextJsonZLoggerFormatter.cs +++ b/src/ZLogger/Formatters/SystemTextJsonZLoggerFormatter.cs @@ -133,7 +133,7 @@ public void FormatLogEntry(IBufferWriter writer, TEntry entry) whe // Scope if ((IncludeProperties & IncludeProperties.ScopeKeyValues) != 0) { - if (entry.ScopeState is { IsEmpty: false } scopeState) + if (entry.LogInfo.ScopeState is { IsEmpty: false } scopeState) { var properties = scopeState.Properties; for (var i = 0; i < properties.Length; i++) diff --git a/src/ZLogger/LogInfo.cs b/src/ZLogger/LogInfo.cs index a8f5eb03..591fe01d 100644 --- a/src/ZLogger/LogInfo.cs +++ b/src/ZLogger/LogInfo.cs @@ -4,22 +4,14 @@ namespace ZLogger; -public readonly struct LogInfo +public readonly struct LogInfo(LogCategory category, Timestamp timestamp, LogLevel logLevel, EventId eventId, Exception? exception, LogScopeState? scopeState) { - public readonly LogCategory Category; - public readonly Timestamp Timestamp; - public readonly LogLevel LogLevel; - public readonly EventId EventId; - public readonly Exception? Exception; - - public LogInfo(LogCategory category, Timestamp timestamp, LogLevel logLevel, EventId eventId, Exception? exception) - { - Category = category; - Timestamp = timestamp; - EventId = eventId; - LogLevel = logLevel; - Exception = exception; - } + public readonly LogCategory Category = category; + public readonly Timestamp Timestamp = timestamp; + public readonly LogLevel LogLevel = logLevel; + public readonly EventId EventId = eventId; + public readonly Exception? Exception = exception; + public readonly LogScopeState? ScopeState = scopeState; } public readonly struct LogCategory diff --git a/src/ZLogger/LogScopeState.cs b/src/ZLogger/LogScopeState.cs index 545ed196..0a4ef03c 100644 --- a/src/ZLogger/LogScopeState.cs +++ b/src/ZLogger/LogScopeState.cs @@ -11,9 +11,20 @@ public sealed class LogScopeState public bool IsEmpty => properties.Count <= 0; - public ReadOnlySpan> Properties => CollectionsMarshal.AsSpan(properties); + public ReadOnlySpan> Properties + { + get + { + ValidateVersion(); + return CollectionsMarshal.AsSpan(properties); + } + } readonly List> properties = new(); + + // pool safety token + short version; + short snapshotVersion; internal static LogScopeState Create(IExternalScopeProvider scopeProvider) { @@ -28,6 +39,7 @@ internal static LogScopeState Create(IExternalScopeProvider scopeProvider) internal void Return() { Clear(); + unchecked { version++; } cache.Enqueue(this); } @@ -55,6 +67,16 @@ void Snapshot(IExternalScopeProvider scopeProvider) break; } }, properties); + + snapshotVersion = version; + } + + void ValidateVersion() + { + if (version != snapshotVersion) + { + throw new InvalidOperationException("ZLogger scope snapshot version is unmatched. The reason is that ZLogger does not support continued outside ownership of LogInfo."); + } } } } diff --git a/src/ZLogger/ZLoggerEntry.cs b/src/ZLogger/ZLoggerEntry.cs index e7815301..37dd27f1 100644 --- a/src/ZLogger/ZLoggerEntry.cs +++ b/src/ZLogger/ZLoggerEntry.cs @@ -1,5 +1,4 @@ using System.Buffers; -using System.Text; using System.Text.Json; using ZLogger.Internal; @@ -9,7 +8,6 @@ namespace ZLogger public interface IZLoggerEntry : IZLoggerFormattable { LogInfo LogInfo { get; } - LogScopeState? ScopeState { get; set; } void FormatUtf8(IBufferWriter writer, IZLoggerFormatter formatter); void Return(); } @@ -19,8 +17,6 @@ public sealed class ZLoggerEntry : IZLoggerEntry, IObjectPoolNode> cache = new(); - public LogScopeState? ScopeState { get; set; } - ZLoggerEntry? next; ref ZLoggerEntry? IObjectPoolNode>.NextNode => ref next; @@ -80,11 +76,11 @@ public void Return() { ((IReferenceCountable)state).Release(); } - state = default!; + + logInfo.ScopeState?.Return(); logInfo = default!; - ScopeState?.Return(); - ScopeState = default; + cache.TryPush(this); } } diff --git a/src/ZLogger/ZLoggerLogger.cs b/src/ZLogger/ZLoggerLogger.cs index cf803eae..b7226733 100644 --- a/src/ZLogger/ZLoggerLogger.cs +++ b/src/ZLogger/ZLoggerLogger.cs @@ -20,18 +20,16 @@ public ZLoggerLogger(string categoryName, IAsyncLogProcessor logProcessor, ZLogg public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) { - var info = new LogInfo(category, new Timestamp(timeProvider), logLevel, eventId, exception); - var scopeState = scopeProvider != null ? LogScopeState.Create(scopeProvider) : null; + + var info = new LogInfo(category, new Timestamp(timeProvider), logLevel, eventId, exception, scopeState); var entry = state is IZLoggerEntryCreatable ? ((IZLoggerEntryCreatable)state).CreateEntry(info) // constrained call avoiding boxing for value types : new StringFormatterLogState(state, exception, formatter).CreateEntry(info); // standard `log` - entry.ScopeState = scopeState; - if (state is IReferenceCountable) { ((IReferenceCountable)state).Retain();