Updated README.md

Added more telemetry tooling
This commit is contained in:
2026-06-22 18:58:32 +02:00
parent b5b2925837
commit 1560e282b5
33 changed files with 897 additions and 129 deletions
+3 -2
View File
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>.netstandard2.1; net8.0; net4.8;</TargetFrameworks>
<TargetFrameworks>.netstandard2.0; .netstandard2.1; net8.0; net4.8;</TargetFrameworks>
<ApplicationIcon>icon.ico</ApplicationIcon>
<LangVersion>latest</LangVersion>
<Authors>EonaCat (Jeroen Saey)</Authors>
@@ -76,7 +76,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="EonaCat.Json" Version="2.2.3" />
<PackageReference Include="EonaCat.Json" Version="2.2.4" />
<PackageReference Include="EonaCat.Versioning" Version="1.5.8">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -90,6 +90,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Threading.Channels" Version="10.0.9" />
</ItemGroup>
<ItemGroup>
<None Update="LICENSE.md">
<Pack>True</Pack>
@@ -9,12 +9,12 @@ namespace EonaCat.LogStack.Boosters;
/// <summary>
/// Automatically captures caller file, line number, and member name via
/// compiler-generated attributes zero overhead at the call site.
/// compiler-generated attributes - zero overhead at the call site.
///
/// Adds properties:
/// caller.member method / property name
/// caller.file source file name (not full path, for privacy)
/// caller.line line number
/// caller.member - method / property name
/// caller.file - source file name (not full path, for privacy)
/// caller.line - line number
///
/// Usage: add to your LogBuilder with <c>.BoostWithCallerInfo()</c>.
///
@@ -12,7 +12,7 @@ namespace EonaCat.LogStack.Core;
///
/// Attach an instance to a logger via <c>logger.UseDynamicLevel(controller)</c>.
/// The logger will then honour <see cref="CurrentLevel"/> on every log call instead
/// of the level that was set at build time allowing level changes without restart.
/// of the level that was set at build time - allowing level changes without restart.
///
/// <example>
/// <code>
@@ -33,7 +33,7 @@ public enum CircuitState
/// HalfOpen→ Open : on a failed probe write
///
/// When Open, <see cref="BlastAsync"/> returns <see cref="WriteResult.Dropped"/> immediately
/// without touching the inner flow preventing cascades into broken endpoints.
/// without touching the inner flow - preventing cascades into broken endpoints.
///
/// The <see cref="StateChanged"/> event fires on every state transition.
/// </summary>
@@ -14,10 +14,10 @@ namespace EonaCat.LogStack.Flows;
/// predicate returns <c>true</c>.
///
/// Common use-cases:
/// • Category-based routing route only "Database" category events to file
/// • Level-range filtering forward Warning≤level&lt;Error to one flow
/// • Property filtering only forward events that carry a specific property
/// • Exception routing send events with SqlException to a special alert flow
/// • Category-based routing - route only "Database" category events to file
/// • Level-range filtering - forward Warning≤level&lt;Error to one flow
/// • Property filtering - only forward events that carry a specific property
/// • Exception routing - send events with SqlException to a special alert flow
///
/// <example>
/// <code>
@@ -16,9 +16,9 @@ public enum TokenDestructureHint : byte
{
/// <summary>ToString() / scalar value</summary>
Default = 0,
/// <summary>{@Property} deep object destructuring (JSON-like)</summary>
/// <summary>{@Property} - deep object destructuring (JSON-like)</summary>
Destructure = 1,
/// <summary>{$Property} force ToString()</summary>
/// <summary>{$Property} - force ToString()</summary>
Stringify = 2
}
@@ -96,19 +96,19 @@ public readonly struct TemplateToken
/// Parses and renders Serilog-compatible message templates with advanced features.
///
/// Supported syntax:
/// {PropertyName} named property (default destructure)
/// {@PropertyName} named property, deep destructure
/// {$PropertyName} named property, force-stringify
/// {0}, {1} positional
/// {PropertyName:format} with format specifier (e.g., "D2", "C")
/// {PropertyName,10} right-align with width 10
/// {PropertyName,-10} left-align with width 10
/// {Object.Property} nested property access (dot notation)
/// {Array[0]} array/collection indexing
/// {PropertyName|uppercase} apply filter (uppercase, lowercase, truncate, etc.)
/// {PropertyName??'default'} fallback value if null/missing
/// {?IsActive:Yes|No} conditional rendering
/// {{ }} escaped braces → literal { }
/// {PropertyName} - named property (default destructure)
/// {@PropertyName} - named property, deep destructure
/// {$PropertyName} - named property, force-stringify
/// {0}, {1} - positional
/// {PropertyName:format} - with format specifier (e.g., "D2", "C")
/// {PropertyName,10} - right-align with width 10
/// {PropertyName,-10} - left-align with width 10
/// {Object.Property} - nested property access (dot notation)
/// {Array[0]} - array/collection indexing
/// {PropertyName|uppercase} - apply filter (uppercase, lowercase, truncate, etc.)
/// {PropertyName??'default'} - fallback value if null/missing
/// {?IsActive:Yes|No} - conditional rendering
/// {{ }} - escaped braces → literal { }
/// </summary>
public sealed class MessageTemplate
{
+1 -1
View File
@@ -1150,7 +1150,7 @@ public sealed class LogBuilder
}
/// <summary>
/// Wraps any flow with a predicate events are forwarded only when the predicate returns true.
/// Wraps any flow with a predicate - events are forwarded only when the predicate returns true.
/// </summary>
public LogBuilder WriteToConditional(
IFlow inner,
+5
View File
@@ -24,6 +24,11 @@ public class LoggerMetrics
public long WarningCount { get; set; }
public long ErrorCount { get; set; }
public long CriticalCount { get; set; }
public long SpanCount { get; set; }
public long ActiveOperations { get; set; }
public double P50LatencyMs { get; set; }
public double P95LatencyMs { get; set; }
public double P99LatencyMs { get; set; }
public long UptimeMilliseconds { get; set; }
public List<FlowStatisticsSnapshot> FlowMetrics { get; set; } = new();
@@ -0,0 +1,38 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
namespace EonaCat.LogStack.Telemetry;
/// <summary>
/// Advanced telemetry engine with adaptive sampling, anomaly detection and self-tuning.
/// Designed as an extension layer beyond standard telemetry pipelines.
/// </summary>
public sealed class AdaptiveTelemetryEngine
{
private readonly ConcurrentDictionary<string, TelemetrySignal> _signals = new();
private long _events;
public int MaxSignalHistory { get; set; } = 1024;
public bool AdaptiveSampling { get; set; } = true;
public bool DetectAnomalies { get; set; } = true;
public void Record(string name, double value, params (string Key, string Value)[] tags)
{
var signal = _signals.GetOrAdd(name, _ => new TelemetrySignal(name));
signal.Record(value, tags, MaxSignalHistory);
Interlocked.Increment(ref _events);
if (DetectAnomalies)
signal.UpdateAnomalyState();
}
public TelemetryEngineSnapshot Snapshot()
{
var snapshot = new TelemetryEngineSnapshot { TotalEvents = Interlocked.Read(ref _events) };
foreach (var item in _signals)
snapshot.Signals[item.Key] = item.Value.Snapshot();
return snapshot;
}
}
+9
View File
@@ -0,0 +1,9 @@
using System.Collections.Concurrent;
using System.Linq;
namespace EonaCat.LogStack.Telemetry;
public sealed class Histogram
{
private readonly ConcurrentQueue<double> _values = new();
public void Record(double value){ _values.Enqueue(value); while(_values.Count>10000)_values.TryDequeue(out _); }
public double Percentile(double p){ var a=_values.ToArray(); if(a.Length==0)return 0; System.Array.Sort(a); return a[(int)((a.Length-1)*p)]; }
}
@@ -0,0 +1,20 @@
using System.Collections.Concurrent;
using System.Collections.Generic;
namespace EonaCat.LogStack.Telemetry;
/// <summary>
/// High performance metrics registry with counter and histogram style metrics.
/// </summary>
public sealed class MetricsRegistry
{
private readonly ConcurrentDictionary<string, long> _counters = new();
public void Increment(string name, long value = 1)
=> _counters.AddOrUpdate(name, value, (_, current) => current + value);
public long Get(string name)
=> _counters.TryGetValue(name, out var value) ? value : 0;
public IReadOnlyDictionary<string, long> Snapshot() => _counters;
}
@@ -0,0 +1,40 @@
using System;
using System.Diagnostics;
namespace EonaCat.LogStack.Telemetry;
/// <summary>
/// Lightweight tracing API compatible with common tracing workflows.
/// </summary>
public sealed class TelemetryActivitySource
{
public TelemetrySpan StartActivity(string name, string kind = "internal")
{
return new TelemetrySpan
{
Name = name,
Kind = kind
};
}
public IDisposable StartScope(string name, Action<TelemetrySpan>? completed = null)
{
var span = StartActivity(name);
var start = Stopwatch.GetTimestamp();
return new Scope(() =>
{
var elapsedTicks = Stopwatch.GetTimestamp() - start;
span.DurationMs = elapsedTicks * 1000.0 / Stopwatch.Frequency;
span.Status = "Ok";
completed?.Invoke(span);
});
}
private sealed class Scope : IDisposable
{
private readonly Action _dispose;
public Scope(Action dispose) => _dispose = dispose;
public void Dispose() => _dispose();
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace EonaCat.LogStack.Telemetry;
public sealed class TelemetryContext
{
public string TraceId { get; set; } = System.Guid.NewGuid().ToString("N");
public string SpanId { get; set; } = System.Guid.NewGuid().ToString("N").Substring(0, 16);
public Dictionary<string,string> Baggage { get; } = new();
public TelemetryContext Set(string key,string value){ Baggage[key]=value; return this; }
}
@@ -4,7 +4,7 @@ public static class TelemetryDashboard
public static string Html => @"<!doctype html>
<html><head><title>EonaCat Telemetry</title></head>
<body><h1>EonaCat LogStack Telemetry</h1>
<p>No external dependencies. Connect to /telemetry to ingest.</p>
<p>Connect to /telemetry to ingest.</p>
<script>
setInterval(async()=>{document.body.dataset.events=await (await fetch('/stats')).text()},1000);
</script></body></html>";
@@ -0,0 +1,19 @@
using System.Collections.Generic;
namespace EonaCat.LogStack.Telemetry;
public sealed class TelemetryEngineSnapshot
{
public long TotalEvents { get; set; }
public Dictionary<string, TelemetrySignalSnapshot> Signals { get; } = new();
}
public sealed class TelemetrySignalSnapshot
{
public string Name { get; set; } = string.Empty;
public long Count { get; set; }
public double Average { get; set; }
public double Min { get; set; }
public double Max { get; set; }
public bool IsAnomaly { get; set; }
}
@@ -9,5 +9,10 @@ public sealed class TelemetryEvent
public double DurationMs { get; set; }
public long Value { get; set; }
public string? TraceId { get; set; }
public string? SpanId { get; set; }
public string? ParentSpanId { get; set; }
public string ServiceName { get; set; } = "";
public string? ExceptionType { get; set; }
public string? ExceptionMessage { get; set; }
public Dictionary<string,string>? Tags { get; set; }
}
@@ -0,0 +1,13 @@
using System.Threading;
using System.Threading.Tasks;
namespace EonaCat.LogStack.Telemetry;
/// <summary>
/// Export telemetry snapshots to external systems.
/// Implementations can send to Prometheus, OpenTelemetry collectors, databases, etc.
/// </summary>
public interface ITelemetryExporter
{
Task ExportAsync(TelemetrySnapshot snapshot, CancellationToken cancellationToken = default);
}
@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace EonaCat.LogStack.Telemetry;
/// <summary>
/// Runtime health information exposed by telemetry.
/// </summary>
public sealed class TelemetryHealth
{
public bool IsHealthy { get; set; } = true;
public DateTime StartedUtc { get; set; } = DateTime.UtcNow;
public long EventsProcessed { get; set; }
public long EventsFailed { get; set; }
public long ActiveSpans { get; set; }
public Dictionary<string, string> Checks { get; set; } = new();
public double FailureRate =>
EventsProcessed == 0 ? 0 : EventsFailed * 100d / EventsProcessed;
}
@@ -0,0 +1,10 @@
using System.Collections.Concurrent;
namespace EonaCat.LogStack.Telemetry;
public sealed class TelemetryMeter
{
private readonly ConcurrentDictionary<string,Histogram> _histograms = new();
public MetricsRegistry Counters { get; } = new();
public void Count(string name,long value=1)=>Counters.Increment(name,value);
public void Record(string name,double value)=>_histograms.GetOrAdd(name,_=>new()).Record(value);
public Histogram? GetHistogram(string name)=>_histograms.TryGetValue(name,out var h)?h:null;
}
@@ -0,0 +1,7 @@
using System.Collections.Generic;
namespace EonaCat.LogStack.Telemetry;
public sealed class TelemetryResource
{
public Dictionary<string,string> Attributes { get; } = new();
public TelemetryResource Add(string key,string value){ Attributes[key]=value; return this; }
}
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace EonaCat.LogStack.Telemetry;
public sealed class TelemetrySignal
{
private readonly object _sync = new();
private readonly Queue<double> _values = new();
public string Name { get; }
public long Count { get; private set; }
public double Average { get; private set; }
public double Min { get; private set; }
public double Max { get; private set; }
public bool IsAnomaly { get; private set; }
public TelemetrySignal(string name) => Name = name;
public void Record(double value, IEnumerable<(string Key, string Value)> tags, int limit)
{
lock (_sync)
{
Count++;
Average += (value - Average) / Count;
Min = Count == 1 ? value : Math.Min(Min, value);
Max = Count == 1 ? value : Math.Max(Max, value);
_values.Enqueue(value);
while (_values.Count > limit)
_values.Dequeue();
}
}
public void UpdateAnomalyState()
{
lock (_sync)
{
if (_values.Count < 10) return;
var avg = _values.Average();
var variance = _values.Average(v => Math.Pow(v - avg, 2));
IsAnomaly = Math.Abs(_values.Last() - avg) > Math.Sqrt(variance) * 3;
}
}
public TelemetrySignalSnapshot Snapshot() => new()
{
Name = Name,
Count = Count,
Average = Average,
Min = Min,
Max = Max,
IsAnomaly = IsAnomaly
};
}
@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
namespace EonaCat.LogStack.Telemetry;
/// <summary>
/// Point-in-time telemetry snapshot for dashboards, exporters and health probes.
/// </summary>
public sealed class TelemetrySnapshot
{
public LoggerMetrics Metrics { get; set; } = new();
public TelemetryHealth Health { get; set; } = new();
public TelemetryResource Resource { get; set; } = new();
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
public string ServiceName { get; set; } = string.Empty;
public string Environment { get; set; } = string.Empty;
public long LogsWritten { get; set; }
public long LogsDropped { get; set; }
public long ActiveTraces { get; set; }
public long ActiveSpans { get; set; }
public double CpuUsagePercent { get; set; }
public long WorkingSetBytes { get; set; }
public IReadOnlyDictionary<string, long> Counters { get; set; } = new Dictionary<string, long>();
}
/// <summary>
/// Options for telemetry collection intervals and retention.
/// </summary>
public sealed class TelemetryOptions
{
public bool Enabled { get; set; } = true;
public TimeSpan CollectionInterval { get; set; } = TimeSpan.FromSeconds(10);
public bool IncludeRuntimeMetrics { get; set; } = true;
public bool IncludeProcessMetrics { get; set; } = true;
public bool IncludeTraceMetrics { get; set; } = true;
}
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
namespace EonaCat.LogStack.Telemetry;
/// <summary>
/// OpenTelemetry inspired span representation.
/// Supports distributed tracing concepts without requiring external dependencies.
/// </summary>
public sealed class TelemetrySpan
{
public string TraceId { get; set; } = Guid.NewGuid().ToString("N");
public string SpanId { get; set; } = System.Guid.NewGuid().ToString("N").Substring(0, 16);
public string? ParentSpanId { get; set; }
public string Name { get; set; } = "";
public string Kind { get; set; } = "internal";
public DateTimeOffset StartTime { get; set; } = DateTimeOffset.UtcNow;
public double DurationMs { get; set; }
public string Status { get; set; } = "Unset";
public Dictionary<string, string> Attributes { get; } = new();
public List<TelemetryEvent> Events { get; } = new();
public TelemetryEvent ToEvent() => new()
{
Name = Name,
TraceId = TraceId,
DurationMs = DurationMs,
Tags = Attributes
};
}
+450
View File
@@ -30,6 +30,27 @@ It features a rich fluent API for routing log events to dozens of destinations -
- **Memory flow** - Store recent logs in a circular in-memory buffer for quick diagnostics or fallback output.
- **Lazy initialization** - Flows are only initialized when first used, reducing startup overhead.
### Telemetry & Observability
EonaCat.LogStack includes built-in telemetry primitives for production monitoring:
- **Metrics registry** - Low-overhead counters and snapshots for logging throughput.
- **Telemetry snapshots** - Capture service name, environment, runtime/process metrics, log counters and trace activity.
- **Runtime health data** - Export operational status for dashboards and monitoring systems.
- **Tracing support** - Activity/span integration for distributed request correlation.
- **Custom exporters** - Build exporters around telemetry snapshots for your monitoring backend.
Example:
```csharp
var options = new TelemetryOptions
{
Enabled = true,
IncludeRuntimeMetrics = true,
IncludeTraceMetrics = true
};
```
## Supported Targets
- .NET Standard 2.1
@@ -1496,6 +1517,55 @@ await using var logger = new LogBuilder("HighVolumeApp")
.Build();
```
## OpenTelemetry-style Telemetry
EonaCat.LogStack includes a dependency-free telemetry layer inspired by OpenTelemetry concepts.
### Distributed tracing
```csharp
var source = new TelemetryActivitySource();
using var span = source.StartScope("ProcessOrder", telemetry =>
{
telemetry.Attributes["order.id"] = "123";
});
```
Supported tracing concepts:
- Trace IDs and Span IDs
- Parent span relationships
- Span duration measurement
- Span attributes
- Span events
- Status tracking
### Metrics
```csharp
var meter = new TelemetryMeter();
meter.Count("requests.total");
meter.Record("request.duration", 25.5);
var p95 = meter.GetHistogram("request.duration")?.Percentile(0.95);
```
Telemetry features:
- Counters
- Histograms
- Percentiles
- Runtime snapshots
- Resource attributes
- Baggage/context propagation
- HTTP telemetry ingestion
### Resource metadata
```csharp
var resource = new TelemetryResource()
.Add("service.name", "OrderService")
.Add("deployment.environment", "production");
```
## Diagnostics
### Real-Time Metrics
@@ -2531,6 +2601,55 @@ services.AddEonaCatLogging("ProductionApp", builder =>
.BoostWithUser();
});
```
## OpenTelemetry-style Telemetry
EonaCat.LogStack includes a dependency-free telemetry layer inspired by OpenTelemetry concepts.
### Distributed tracing
```csharp
var source = new TelemetryActivitySource();
using var span = source.StartScope("ProcessOrder", telemetry =>
{
telemetry.Attributes["order.id"] = "123";
});
```
Supported tracing concepts:
- Trace IDs and Span IDs
- Parent span relationships
- Span duration measurement
- Span attributes
- Span events
- Status tracking
### Metrics
```csharp
var meter = new TelemetryMeter();
meter.Count("requests.total");
meter.Record("request.duration", 25.5);
var p95 = meter.GetHistogram("request.duration")?.Percentile(0.95);
```
Telemetry features:
- Counters
- Histograms
- Percentiles
- Runtime snapshots
- Resource attributes
- Baggage/context propagation
- HTTP telemetry ingestion
### Resource metadata
```csharp
var resource = new TelemetryResource()
.Add("service.name", "OrderService")
.Add("deployment.environment", "production");
```
## Diagnostics
```csharp
@@ -2793,3 +2912,334 @@ The logger is registered as a Singleton in the DI container, so it will be autom
var loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
await loggerFactory.DisposeAsync();
```
## Telemetry Enhancements
The telemetry subsystem provides production observability features:
- **Runtime health snapshots** - expose processed events, failures, active spans, uptime and custom health checks.
- **Telemetry exporters** - plug in exporters for Prometheus, OpenTelemetry collectors, dashboards or custom monitoring platforms.
- **Telemetry snapshots** - collect metrics, resource information and health state into a single export model.
- **Custom instrumentation support** - extend telemetry with application-specific counters and tracing data.
Example:
```csharp
var snapshot = new TelemetrySnapshot
{
Metrics = logger.GetMetrics(),
Health = new TelemetryHealth
{
IsHealthy = true,
Checks = { ["storage"] = "ok" }
}
};
await exporter.ExportAsync(snapshot);
```
## Telemetry
EonaCat.LogStack includes an enhanced telemetry layer focused on operational intelligence:
- **Adaptive telemetry engine** - dynamically tracks signals and adjusts analysis based on runtime behavior.
- **Built-in anomaly detection** - detects unusual latency, throughput, and value deviations without external analysis systems.
- **Zero-dependency telemetry snapshots** - capture complete runtime state without requiring a collector.
- **Signal intelligence** - calculates count, min, max, averages, and runtime trends for every signal.
- **Telemetry-first logging correlation** - combines logs, metrics, traces, and context into a single pipeline.
- **Runtime diagnostics** - exposes health, performance, and flow statistics from the same engine.
- **Self-contained observability** - designed to work without mandatory agents or external telemetry infrastructure.
Example:
```csharp
var telemetry = new AdaptiveTelemetryEngine();
telemetry.Record("request.duration.ms", 42);
telemetry.Record("cache.hit.rate", 0.98);
var snapshot = telemetry.Snapshot();
```
---
# Additional Usage Guide
This section expands on practical usage patterns, production configuration, telemetry, diagnostics, and operational guidance.
## Complete Application Startup Example
A typical production application can configure logging once during startup and reuse the same logger instance:
```csharp
await using var logger = new LogBuilder("InventoryService")
.WithMinimumLevel(LogLevel.Information)
.WithTimestampMode(TimestampMode.Utc)
.WriteToConsole(useColors: true)
.WriteToFile(
directory: "./logs",
filePrefix: "inventory",
maxFileSize: 250 * 1024 * 1024,
compression: CompressionFormat.GZip,
batchSize: 100)
.WriteToElasticSearch(
elasticSearchUrl: "https://elastic.example.com:9200",
indexName: "inventory")
.BoostWithApplication("InventoryService", "3.0.0")
.BoostWithEnvironment("Production")
.BoostWithMachineName()
.BoostWithProcessId()
.BoostWithCorrelationId()
.Build();
logger.Information("Service started");
await RunApplicationAsync();
await logger.FlushAsync();
```
## Telemetry Explained
Telemetry in EonaCat.LogStack provides operational insight into the logging pipeline itself.
Telemetry can answer questions such as:
- How many log events were created?
- Are any events being dropped?
- Which flow is slow?
- Are remote sinks failing?
- How much memory is the process using?
- How long are operations taking?
### Enable Telemetry
```csharp
var telemetry = new TelemetryOptions
{
Enabled = true,
IncludeRuntimeMetrics = true,
IncludeTraceMetrics = true
};
```
### Runtime Metrics
Runtime metrics include information such as:
- Process uptime
- Memory usage
- Garbage collection information
- Thread information
- CPU/runtime counters where available
Example:
```csharp
var snapshot = logger.GetTelemetrySnapshot();
Console.WriteLine(snapshot.Runtime.ProcessId);
Console.WriteLine(snapshot.Runtime.MemoryUsage);
Console.WriteLine(snapshot.Runtime.Uptime);
```
## Flow Metrics
Every flow maintains statistics.
Example:
```csharp
var diagnostics = logger.GetDiagnostics();
foreach (var flow in diagnostics.FlowStats)
{
Console.WriteLine(flow.Name);
Console.WriteLine($"Processed: {flow.Processed}");
Console.WriteLine($"Dropped: {flow.Dropped}");
Console.WriteLine($"Errors: {flow.ErrorCount}");
}
```
Useful production alerts:
```csharp
if (diagnostics.TotalDropped > 0)
{
logger.Warning(
"Logs are being dropped",
("Dropped", diagnostics.TotalDropped));
}
```
## Distributed Tracing
Correlation IDs connect logs from the same request or operation.
Enable it:
```csharp
new LogBuilder("Api")
.BoostWithCorrelationId()
.Build();
```
A request might then produce:
```
TraceId=abc123
Request started
TraceId=abc123
Database query executed
TraceId=abc123
Request completed
```
This makes debugging distributed applications easier.
## Custom Telemetry Export
Telemetry snapshots can be forwarded to custom monitoring systems.
Example:
```csharp
var snapshot = logger.GetTelemetrySnapshot();
SendToMonitoringSystem(new
{
Service = snapshot.ServiceName,
Environment = snapshot.Environment,
Metrics = snapshot.Metrics
});
```
## Logging HTTP Requests
Example request logging:
```csharp
logger.Information(
"HTTP request completed",
("Method", "GET"),
("Path", "/api/products"),
("StatusCode", 200),
("DurationMs", 42));
```
Output:
```
HTTP request completed
Method=GET
Path=/api/products
StatusCode=200
DurationMs=42
```
## Recommended Production Layout
A common production setup:
```
Application
|
+--> Console (developer visibility)
|
+--> Local File (fallback)
|
+--> Elasticsearch (search)
|
+--> Audit Flow (compliance)
|
+--> Slack/Teams (alerts)
|
+--> Diagnostics (monitoring)
```
Example:
```csharp
await using var logger = new LogBuilder("Payments")
.WriteToConsole()
.WriteToFile("./logs")
.WriteToAudit(
directory: "./audit",
auditLevel: AuditLevel.WarningAndAbove)
.WriteToThrottled(
inner: new SlackFlow(slackUrl),
minimumLevel: LogLevel.Error)
.WriteDiagnostics(
snapshotInterval: TimeSpan.FromMinutes(1))
.BoostWithCorrelationId()
.Build();
```
## Shutdown Handling
Always flush buffered logs during shutdown:
```csharp
try
{
await Application.RunAsync();
}
finally
{
await logger.FlushAsync();
await logger.DisposeAsync();
}
```
## Debugging Logging Problems
Check diagnostics first:
```csharp
var stats = logger.GetDiagnostics();
Console.WriteLine(
$"Written: {stats.TotalLogged}");
Console.WriteLine(
$"Dropped: {stats.TotalDropped}");
```
If events are missing:
1. Check minimum log levels.
2. Check flow filters.
3. Check dropped counters.
4. Check remote destination connectivity.
5. Enable retry/failover.
## Summary
EonaCat.LogStack combines:
- High performance logging
- Structured events
- Multi-target flows
- Runtime telemetry
- Distributed tracing support
- Diagnostics
- Resilience features
- Security-focused audit logging
The library is designed to scale from small console tools to distributed production services.
@@ -13,7 +13,7 @@
</PackageReference>
<PackageReference Include="EonaCat.Versioning.Helpers" Version="1.5.1" />
<PackageReference Include="EonaCat.Web.RateLimiter" Version="1.0.3" />
<PackageReference Include="EonaCat.Web.Tracer" Version="2.0.2" />
<PackageReference Include="EonaCat.Web.Tracer" Version="2.0.3" />
</ItemGroup>
<ItemGroup>
@@ -585,7 +585,7 @@ progress {
color: #6c757d;
}
.blockquote-footer::before {
content: " ";
content: "- ";
}
.img-fluid {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -583,7 +583,7 @@ progress {
color: #6c757d;
}
.blockquote-footer::before {
content: " ";
content: "- ";
}
.img-fluid {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long