This commit is contained in:
2026-09-10 08:52:59 +02:00
committed by Jeroen Saey
parent 5c435fb2f5
commit 17d7426b44
3 changed files with 156 additions and 59 deletions
+3 -3
View File
@@ -14,7 +14,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
<Copyright>EonaCat (Jeroen Saey)</Copyright> <Copyright>EonaCat (Jeroen Saey)</Copyright>
<PackageTags>EonaCat;Logger;EonaCatLogStack;Log;Writer;Flows;LogStack;Memory;Speed;Jeroen;Saey</PackageTags> <PackageTags>EonaCat;Logger;EonaCatLogStack;Log;Writer;Flows;LogStack;Memory;Speed;Jeroen;Saey</PackageTags>
<PackageIconUrl /> <PackageIconUrl />
<FileVersion>0.2.1</FileVersion> <FileVersion>0.2.2</FileVersion>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<GenerateDocumentationFile>True</GenerateDocumentationFile> <GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
@@ -25,7 +25,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<EVRevisionFormat>0.2.1+{chash:10}.{c:ymd}</EVRevisionFormat> <EVRevisionFormat>0.2.2+{chash:10}.{c:ymd}</EVRevisionFormat>
<EVDefault>true</EVDefault> <EVDefault>true</EVDefault>
<EVInfo>true</EVInfo> <EVInfo>true</EVInfo>
<EVTagMatch>v[0-9]*</EVTagMatch> <EVTagMatch>v[0-9]*</EVTagMatch>
@@ -36,7 +36,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<Version>0.2.1</Version> <Version>0.2.2</Version>
<PackageId>EonaCat.LogStack</PackageId> <PackageId>EonaCat.LogStack</PackageId>
<Product>EonaCat.LogStack</Product> <Product>EonaCat.LogStack</Product>
<RepositoryUrl>https://git.saey.me/EonaCat/EonaCat.LogStack</RepositoryUrl> <RepositoryUrl>https://git.saey.me/EonaCat/EonaCat.LogStack</RepositoryUrl>
+106 -48
View File
@@ -177,6 +177,13 @@ namespace EonaCat.LogStack
private LogLevel EffectiveMinLevel() => private LogLevel EffectiveMinLevel() =>
_dynamicLevel != null ? _dynamicLevel.CurrentLevel : _minimumLevel; _dynamicLevel != null ? _dynamicLevel.CurrentLevel : _minimumLevel;
/// <summary>
/// Checks whether an event at the specified level would be accepted.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool IsEnabled(LogLevel level) =>
Volatile.Read(ref _disposed) == 0 && level >= EffectiveMinLevel();
/// <summary> /// <summary>
/// Adds a flow (output destination) to this logger /// Adds a flow (output destination) to this logger
/// </summary> /// </summary>
@@ -216,10 +223,10 @@ namespace EonaCat.LogStack
{ {
var keep = _concurrentFlows.Where(f => f.Name != name).ToArray(); var keep = _concurrentFlows.Where(f => f.Name != name).ToArray();
while (_concurrentFlows.TryTake(out _)) { } while (_concurrentFlows.TryTake(out _)) { }
foreach (var f in keep) foreach (var f in keep)
{ {
_concurrentFlows.Add(f); _concurrentFlows.Add(f);
} }
} }
return this; return this;
} }
@@ -248,26 +255,26 @@ namespace EonaCat.LogStack
return this; return this;
} }
private void RaiseOnLog(LogMessage message) private void RaiseOnLog(LogMessage message)
{ {
var handlers = OnLog; var handlers = OnLog;
if (handlers == null) if (handlers == null)
{ {
return; return;
} }
foreach (EventHandler<LogMessage> handler in handlers.GetInvocationList()) foreach (EventHandler<LogMessage> handler in handlers.GetInvocationList())
{ {
try try
{ {
handler(this, message); handler(this, message);
} }
catch (Exception ex) catch (Exception ex)
{ {
System.Diagnostics.Debug.WriteLine(ex); System.Diagnostics.Debug.WriteLine(ex);
} }
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -357,15 +364,15 @@ namespace EonaCat.LogStack
.WithException(exception) .WithException(exception)
.WithTimestamp(GetTimestamp()); .WithTimestamp(GetTimestamp());
ProcessLogEvent(ref builder); ProcessLogEvent(ref builder);
RaiseOnLog(new LogMessage RaiseOnLog(new LogMessage
{ {
Level = level, Level = level,
Exception = exception, Exception = exception,
Message = message, Message = message,
Category = _category, Category = _category,
Origin = null Origin = null
}); });
} }
@@ -418,14 +425,65 @@ namespace EonaCat.LogStack
ProcessLogEvent(ref builder); ProcessLogEvent(ref builder);
} }
/// <summary>
/// Logs an exception together with structured properties.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log(LogLevel level, Exception exception, string message, params (string Key, object Value)[] properties)
{
if (Volatile.Read(ref _disposed) == 1 || level < EffectiveMinLevel())
{
return;
}
TrackLevel(level);
Interlocked.Increment(ref _totalExceptionsCount);
var builder = new LogEventBuilder()
.WithLevel(level)
.WithCategory(_category)
.WithMessage(message)
.WithException(exception)
.WithTimestamp(GetTimestamp());
foreach (var (key, value) in properties)
{
builder.WithProperty(key, value);
}
ProcessLogEvent(ref builder);
}
private void Write(LogLevel level, string template, params object[] args) private void Write(LogLevel level, string template, params object[] args)
{ {
Log(level, string.Format(template, args)); Log(level, FormatTemplate(template, args));
} }
private void Write(LogLevel level, Exception ex, string template, params object[] args) private void Write(LogLevel level, Exception ex, string template, params object[] args)
{ {
Log(level, ex, string.Format(template, args)); Log(level, ex, FormatTemplate(template, args));
}
/// <summary>
/// Formats a message template, supporting both Serilog-style named holes (e.g. "{UserId}")
/// and classic string.Format-style positional holes (e.g. "{0}"). Never throws on malformed
/// templates; falls back to the raw template text instead.
/// </summary>
private static string FormatTemplate(string template, object[] args)
{
if (string.IsNullOrEmpty(template))
{
return template;
}
try
{
return MessageTemplate.FromCache(template).Render(args, out _);
}
catch
{
return template;
}
} }
public void Trace(string template, params object[] args) => Write(LogLevel.Trace, template, args); public void Trace(string template, params object[] args) => Write(LogLevel.Trace, template, args);
@@ -608,17 +666,17 @@ namespace EonaCat.LogStack
.Where(d => d != null) .Where(d => d != null)
.ToList(); .ToList();
int flowCount; int flowCount;
int boosterCount; int boosterCount;
lock (_flows) lock (_flows)
{ {
flowCount = _flows.Count; flowCount = _flows.Count;
} }
lock (_boosters) lock (_boosters)
{ {
boosterCount = _boosters.Count; boosterCount = _boosters.Count;
} }
return new LoggerDiagnostics return new LoggerDiagnostics
@@ -627,8 +685,8 @@ namespace EonaCat.LogStack
MinimumLevel = _minimumLevel, MinimumLevel = _minimumLevel,
TotalLogged = Interlocked.Read(ref _totalLoggedCount), TotalLogged = Interlocked.Read(ref _totalLoggedCount),
TotalDropped = Interlocked.Read(ref _totalDroppedCount), TotalDropped = Interlocked.Read(ref _totalDroppedCount),
TotalExceptions = Interlocked.Read(ref _totalExceptionsCount), TotalExceptions = Interlocked.Read(ref _totalExceptionsCount),
FlowCount = flowCount, FlowCount = flowCount,
BoosterCount = boosterCount, BoosterCount = boosterCount,
Flows = flowDiagnostics Flows = flowDiagnostics
}; };
@@ -1,5 +1,7 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System; using System;
using System.Collections.Generic;
using System.Linq;
namespace EonaCat.LogStack.Logging; namespace EonaCat.LogStack.Logging;
@@ -40,7 +42,7 @@ internal sealed class CategoryLogger : Microsoft.Extensions.Logging.ILogger
=> _scopes?.Push(state) ?? NullScope.Instance; => _scopes?.Push(state) ?? NullScope.Instance;
public bool IsEnabled(LogLevel logLevel) public bool IsEnabled(LogLevel logLevel)
=> logLevel != LogLevel.None; => logLevel != LogLevel.None && _EonaCatLogStack.IsEnabled(logLevel.FromLogLevel());
public void Log<TState>( public void Log<TState>(
LogLevel logLevel, LogLevel logLevel,
@@ -55,18 +57,55 @@ internal sealed class CategoryLogger : Microsoft.Extensions.Logging.ILogger
} }
var message = formatter(state, exception); var message = formatter(state, exception);
var properties = new List<(string Key, object Value)>(4)
_scopes?.ForEachScope<object?>((scope, _) =>
{ {
message = $"{message} | Scope={scope}"; ("Category", _category),
}, null); ("EventId", eventId.Id)
};
if (exception != null) if (!string.IsNullOrEmpty(eventId.Name))
{ {
message = $"{message} | Exception={exception}"; properties.Add(("EventName", eventId.Name));
} }
_EonaCatLogStack.Log($"[{logLevel}] [{_category}] {message}"); if (state is IEnumerable<KeyValuePair<string, object?>> structuredState)
{
foreach (var pair in structuredState)
{
if (!string.IsNullOrEmpty(pair.Key) && pair.Value != null)
{
properties.Add((pair.Key, pair.Value));
}
}
}
_scopes?.ForEachScope<List<(string Key, object Value)>>((scope, stateProperties) =>
{
if (scope is IEnumerable<KeyValuePair<string, object?>> structuredScope)
{
foreach (var pair in structuredScope)
{
if (!string.IsNullOrEmpty(pair.Key) && pair.Value != null)
{
stateProperties.Add((pair.Key, pair.Value));
}
}
}
else if (scope != null)
{
stateProperties.Add(("Scope", scope));
}
}, properties);
var level = logLevel.FromLogLevel();
if (exception == null)
{
_EonaCatLogStack.Log(level, message, properties.ToArray());
}
else
{
_EonaCatLogStack.Log(level, exception, message, properties.ToArray());
}
} }
} }