This commit is contained in:
2026-06-16 09:55:05 +02:00
parent aba8b4df46
commit f99e17e838
4 changed files with 1387 additions and 1349 deletions
+96 -96
View File
@@ -1,103 +1,103 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>.netstandard2.1; net8.0; net4.8;</TargetFrameworks> <TargetFrameworks>.netstandard2.1; net8.0; net4.8;</TargetFrameworks>
<ApplicationIcon>icon.ico</ApplicationIcon> <ApplicationIcon>icon.ico</ApplicationIcon>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Authors>EonaCat (Jeroen Saey)</Authors> <Authors>EonaCat (Jeroen Saey)</Authors>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Company>EonaCat (Jeroen Saey)</Company> <Company>EonaCat (Jeroen Saey)</Company>
<PackageIcon>icon.png</PackageIcon> <PackageIcon>icon.png</PackageIcon>
<PackageProjectUrl>https://www.nuget.org/packages/EonaCat.LogStack/</PackageProjectUrl> <PackageProjectUrl>https://www.nuget.org/packages/EonaCat.LogStack/</PackageProjectUrl>
<Description>flow-based logging library for .NET, designed for zero-allocation logging paths and superior memory efficiency. <Description>flow-based logging library for .NET, designed for zero-allocation logging paths and superior memory efficiency.
It features a rich fluent API for routing log events to dozens of destinations from console and file to Slack, Discord, Redis, Elasticsearch, and beyond.</Description> It features a rich fluent API for routing log events to dozens of destinations from console and file to Slack, Discord, Redis, Elasticsearch, and beyond.</Description>
<PackageReleaseNotes>Public release version</PackageReleaseNotes> <PackageReleaseNotes>Public release version</PackageReleaseNotes>
<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.0.9</FileVersion> <FileVersion>0.1.0</FileVersion>
<PackageReadmeFile>README.md</PackageReadmeFile> <PackageReadmeFile>README.md</PackageReadmeFile>
<GenerateDocumentationFile>True</GenerateDocumentationFile> <GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
<Title>EonaCat.LogStack</Title> <Title>EonaCat.LogStack</Title>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<EVRevisionFormat>0.0.9+{chash:10}.{c:ymd}</EVRevisionFormat> <EVRevisionFormat>0.1.0+{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>
<EVRemoveTagV>true</EVRemoveTagV> <EVRemoveTagV>true</EVRemoveTagV>
<EVVcs>git</EVVcs> <EVVcs>git</EVVcs>
<EVCheckAllAttributes>true</EVCheckAllAttributes> <EVCheckAllAttributes>true</EVCheckAllAttributes>
<EVShowRevision>true</EVShowRevision> <EVShowRevision>true</EVShowRevision>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<Version>0.0.9</Version> <Version>0.1.0</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>
<UserSecretsId>09d73ec1-ee68-42bb-b2bd-b0e31b635098</UserSecretsId> <UserSecretsId>09d73ec1-ee68-42bb-b2bd-b0e31b635098</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<Target Name="EVPack" BeforeTargets="Pack"> <Target Name="EVPack" BeforeTargets="Pack">
<Message Text="EVPack: Forcing NuGet Version = $(GeneratedVersion)" Importance="High" /> <Message Text="EVPack: Forcing NuGet Version = $(GeneratedVersion)" Importance="High" />
<PropertyGroup> <PropertyGroup>
<Version>$(GeneratedVersion)</Version> <Version>$(GeneratedVersion)</Version>
</PropertyGroup> </PropertyGroup>
</Target> </Target>
<ItemGroup> <ItemGroup>
<Compile Remove="Examples\**" /> <Compile Remove="Examples\**" />
<EmbeddedResource Remove="Examples\**" /> <EmbeddedResource Remove="Examples\**" />
<None Remove="Examples\**" /> <None Remove="Examples\**" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Remove="icon.png" /> <None Remove="icon.png" />
<None Include="..\LICENSE"> <None Include="..\LICENSE">
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath>\</PackagePath> <PackagePath>\</PackagePath>
</None> </None>
<None Include="..\README.md"> <None Include="..\README.md">
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath>\</PackagePath> <PackagePath>\</PackagePath>
</None> </None>
<None Include="icon.png"> <None Include="icon.png">
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath> <PackagePath>
</PackagePath> </PackagePath>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="EonaCat.Json" Version="2.2.3" /> <PackageReference Include="EonaCat.Json" Version="2.2.3" />
<PackageReference Include="EonaCat.Versioning" Version="1.5.8"> <PackageReference Include="EonaCat.Versioning" Version="1.5.8">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="EonaCat.Versioning.Helpers" Version="1.5.1" /> <PackageReference Include="EonaCat.Versioning.Helpers" Version="1.5.1" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" /> <PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" /> <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.9" /> <PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.9" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.9" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.9" />
<PackageReference Include="System.Net.Http" Version="4.3.4" /> <PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Threading.Channels" Version="10.0.9" /> <PackageReference Include="System.Threading.Channels" Version="10.0.9" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Update="LICENSE.md"> <None Update="LICENSE.md">
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath>\</PackagePath> <PackagePath>\</PackagePath>
</None> </None>
<None Update="README.md"> <None Update="README.md">
<Pack>True</Pack> <Pack>True</Pack>
<PackagePath>\</PackagePath> <PackagePath>\</PackagePath>
</None> </None>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Folder Include="EonaCatLoggerCore\Examples\" /> <Folder Include="EonaCatLoggerCore\Examples\" />
</ItemGroup> </ItemGroup>
</Project> </Project>
+109 -79
View File
@@ -30,7 +30,7 @@ namespace EonaCat.LogStack
private readonly LogLevel _minimumLevel; private readonly LogLevel _minimumLevel;
private readonly TimestampMode _timestampMode; private readonly TimestampMode _timestampMode;
private volatile bool _isDisposed; private int _disposed;
private long _totalLoggedCount; private long _totalLoggedCount;
private long _totalDroppedCount; private long _totalDroppedCount;
private long _totalExceptionsCount; private long _totalExceptionsCount;
@@ -73,7 +73,10 @@ namespace EonaCat.LogStack
{ {
_category = category ?? throw new ArgumentNullException(nameof(category)); _category = category ?? throw new ArgumentNullException(nameof(category));
_minimumLevel = minimumLevel; _minimumLevel = minimumLevel;
_timestampMode = timestampMode; _timestampMode = timestampMode;
// Enable async pipeline by default
UseAsyncPipeline();
} }
/// <summary> /// <summary>
@@ -83,11 +86,11 @@ namespace EonaCat.LogStack
/// <param name="capacity">Bounded channel capacity (0 = unbounded).</param> /// <param name="capacity">Bounded channel capacity (0 = unbounded).</param>
public EonaCatLogStack UseAsyncPipeline(int capacity = 65536) public EonaCatLogStack UseAsyncPipeline(int capacity = 65536)
{ {
if (_asyncChannel != null) if (_asyncChannel != null)
{ {
return this; return this;
} }
_asyncCts = new CancellationTokenSource(); _asyncCts = new CancellationTokenSource();
_asyncChannel = capacity > 0 _asyncChannel = capacity > 0
? Channel.CreateBounded<LogEvent>(new BoundedChannelOptions(capacity) ? Channel.CreateBounded<LogEvent>(new BoundedChannelOptions(capacity)
@@ -155,6 +158,12 @@ namespace EonaCat.LogStack
public EonaCatLogStack RemoveFlow(string name) public EonaCatLogStack RemoveFlow(string name)
{ {
lock (_flows) { _flows.RemoveAll(f => f.Name == name); } lock (_flows) { _flows.RemoveAll(f => f.Name == name); }
lock (_concurrentFlows)
{
var keep = _concurrentFlows.Where(f => f.Name != name).ToArray();
while (_concurrentFlows.TryTake(out _)) { }
foreach (var f in keep) _concurrentFlows.Add(f);
}
return this; return this;
} }
@@ -182,10 +191,32 @@ namespace EonaCat.LogStack
return this; return this;
} }
private void RaiseOnLog(LogMessage message)
{
var handlers = OnLog;
if (handlers == null)
{
return;
}
foreach (EventHandler<LogMessage> handler in handlers.GetInvocationList())
{
try
{
handler(this, message);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log(string message, LogLevel level = LogLevel.Information) public void Log(string message, LogLevel level = LogLevel.Information)
{ {
if (_isDisposed || level < EffectiveMinLevel()) if (Volatile.Read(ref _disposed) == 1 || level < EffectiveMinLevel())
{ {
return; return;
} }
@@ -207,11 +238,11 @@ namespace EonaCat.LogStack
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogTemplate(LogLevel level, string template, params object?[] args) public void LogTemplate(LogLevel level, string template, params object?[] args)
{ {
if (_isDisposed || level < EffectiveMinLevel()) if (Volatile.Read(ref _disposed) == 1 || level < EffectiveMinLevel())
{ {
return; return;
} }
var parsed = MessageTemplate.FromCache(template); var parsed = MessageTemplate.FromCache(template);
var builder = new LogEventBuilder() var builder = new LogEventBuilder()
.WithLevel(level) .WithLevel(level)
@@ -230,11 +261,11 @@ namespace EonaCat.LogStack
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void LogTemplate(LogLevel level, Exception exception, string template, params object?[] args) public void LogTemplate(LogLevel level, Exception exception, string template, params object?[] args)
{ {
if (_isDisposed || level < EffectiveMinLevel()) if (Volatile.Read(ref _disposed) == 1 || level < EffectiveMinLevel())
{ {
return; return;
} }
var parsed = MessageTemplate.FromCache(template); var parsed = MessageTemplate.FromCache(template);
var builder = new LogEventBuilder() var builder = new LogEventBuilder()
.WithLevel(level) .WithLevel(level)
@@ -251,17 +282,17 @@ namespace EonaCat.LogStack
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log(LogLevel level, Exception exception, string message) public void Log(LogLevel level, Exception exception, string message)
{ {
if (_isDisposed || level < EffectiveMinLevel()) if (Volatile.Read(ref _disposed) == 1 || level < EffectiveMinLevel())
{ {
return; return;
} }
TrackLevel(level); TrackLevel(level);
if (exception != null) if (exception != null)
{ {
Interlocked.Increment(ref _totalExceptionsCount); Interlocked.Increment(ref _totalExceptionsCount);
} }
var builder = new LogEventBuilder() var builder = new LogEventBuilder()
.WithLevel(level) .WithLevel(level)
.WithCategory(_category) .WithCategory(_category)
@@ -269,15 +300,15 @@ namespace EonaCat.LogStack
.WithException(exception) .WithException(exception)
.WithTimestamp(GetTimestamp()); .WithTimestamp(GetTimestamp());
ProcessLogEvent(ref builder); ProcessLogEvent(ref builder);
OnLog?.Invoke(this, 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
}); });
} }
@@ -310,7 +341,7 @@ namespace EonaCat.LogStack
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Log(LogLevel level, string message, params (string Key, object Value)[] properties) public void Log(LogLevel level, string message, params (string Key, object Value)[] properties)
{ {
if (_isDisposed || level < EffectiveMinLevel()) if (Volatile.Read(ref _disposed) == 1 || level < EffectiveMinLevel())
{ {
return; return;
} }
@@ -396,7 +427,10 @@ namespace EonaCat.LogStack
} }
// Apply modifiers // Apply modifiers
foreach (var mod in _modifiers) ActionRef<LogEventBuilder>[] modifierSnapshot;
lock (_modifiersLock) { modifierSnapshot = _modifiers.ToArray(); }
foreach (var mod in modifierSnapshot)
{ {
try try
{ {
@@ -406,36 +440,11 @@ namespace EonaCat.LogStack
} }
var logEvent = builder.Build(); var logEvent = builder.Build();
Interlocked.Increment(ref _totalLoggedCount); Interlocked.Increment(ref _totalLoggedCount);
// Async channel pipeline
if (_asyncChannel != null)
{
if (!_asyncChannel.Writer.TryWrite(logEvent))
{
Interlocked.Increment(ref _totalDroppedCount);
}
return; if (!_asyncChannel!.Writer.TryWrite(logEvent))
} {
Interlocked.Increment(ref _totalDroppedCount);
// Synchronous blast to flows
DispatchToFlows(logEvent);
}
private void DispatchToFlows(LogEvent logEvent)
{
foreach (var flow in _concurrentFlows)
{
try
{
var result = flow.BlastAsync(logEvent).GetAwaiter().GetResult();
if (result == WriteResult.Dropped)
{
Interlocked.Increment(ref _totalDroppedCount);
}
}
catch { }
} }
} }
@@ -452,10 +461,10 @@ namespace EonaCat.LogStack
try try
{ {
var result = await flow.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false); var result = await flow.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
if (result == WriteResult.Dropped) if (result == WriteResult.Dropped)
{ {
Interlocked.Increment(ref _totalDroppedCount); Interlocked.Increment(ref _totalDroppedCount);
} }
} }
catch { } catch { }
} }
@@ -488,15 +497,28 @@ namespace EonaCat.LogStack
.Where(d => d != null) .Where(d => d != null)
.ToList(); .ToList();
int flowCount;
int boosterCount;
lock (_flows)
{
flowCount = _flows.Count;
}
lock (_boosters)
{
boosterCount = _boosters.Count;
}
return new LoggerDiagnostics return new LoggerDiagnostics
{ {
Category = _category, Category = _category,
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 = _flows.Count, FlowCount = flowCount,
BoosterCount = _boosters.Count, BoosterCount = boosterCount,
Flows = flowDiagnostics Flows = flowDiagnostics
}; };
} }
@@ -535,19 +557,27 @@ namespace EonaCat.LogStack
public async ValueTask DisposeAsync() public async ValueTask DisposeAsync()
{ {
if (_isDisposed) if (Interlocked.Exchange(ref _disposed, 1) == 1)
{ {
return; return;
} }
_isDisposed = true;
// Drain and stop the async channel pipeline if active // Drain and stop the async channel pipeline if active
if (_asyncChannel != null) if (_asyncChannel != null)
{ {
_asyncChannel.Writer.TryComplete(); _asyncChannel.Writer.TryComplete();
_asyncCts?.Cancel();
try { if (_asyncConsumer != null) { await _asyncConsumer.ConfigureAwait(false); } } catch { } if (_asyncConsumer != null)
{
try
{
await _asyncConsumer.ConfigureAwait(false);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex);
}
}
} }
await FlushAsync().ConfigureAwait(false); await FlushAsync().ConfigureAwait(false);
File diff suppressed because it is too large Load Diff
@@ -16,6 +16,11 @@ namespace EonaCat.LogStack.Test.Web
{ {
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
await using var logger = LogBuilder.CreateDefault("MyApp");
logger.Information("Application started");
logger.Warning("Low memory warning");
logger.Error(new Exception("DIT IS MIJN TEST!"), "Unexpected error occurred");
// Configure the client // Configure the client
var centralOptions = new LogCentralOptions var centralOptions = new LogCentralOptions
@@ -46,6 +51,9 @@ namespace EonaCat.LogStack.Test.Web
var logBuilder = new LogBuilder(); var logBuilder = new LogBuilder();
logBuilder.WithTimestampMode(TimestampMode.Local); logBuilder.WithTimestampMode(TimestampMode.Local);
logBuilder.WriteToConsole(); logBuilder.WriteToConsole();
logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.Csv);
logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.StructuredJson);
logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.Text);
logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.Json); logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.Json);
logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.Xml); logBuilder.WriteToFile("./logs", outputFormat: FileOutputFormat.Xml);
logBuilder.WriteToTcp("127.0.0.1", 514); logBuilder.WriteToTcp("127.0.0.1", 514);