Updated README.md
Added more telemetry tooling
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFrameworks>.netstandard2.1; net8.0; net4.8;</TargetFrameworks>
|
<TargetFrameworks>.netstandard2.0; .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>
|
||||||
@@ -76,7 +76,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
|
|||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<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">
|
<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>
|
||||||
@@ -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.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>
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ namespace EonaCat.LogStack.Boosters;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Automatically captures caller file, line number, and member name via
|
/// 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:
|
/// Adds properties:
|
||||||
/// caller.member — method / property name
|
/// caller.member - method / property name
|
||||||
/// caller.file — source file name (not full path, for privacy)
|
/// caller.file - source file name (not full path, for privacy)
|
||||||
/// caller.line — line number
|
/// caller.line - line number
|
||||||
///
|
///
|
||||||
/// Usage: add to your LogBuilder with <c>.BoostWithCallerInfo()</c>.
|
/// 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>.
|
/// 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
|
/// 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>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public enum CircuitState
|
|||||||
/// HalfOpen→ Open : on a failed probe write
|
/// HalfOpen→ Open : on a failed probe write
|
||||||
///
|
///
|
||||||
/// When Open, <see cref="BlastAsync"/> returns <see cref="WriteResult.Dropped"/> immediately
|
/// 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.
|
/// The <see cref="StateChanged"/> event fires on every state transition.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ namespace EonaCat.LogStack.Flows;
|
|||||||
/// predicate returns <c>true</c>.
|
/// predicate returns <c>true</c>.
|
||||||
///
|
///
|
||||||
/// Common use-cases:
|
/// Common use-cases:
|
||||||
/// • Category-based routing — route only "Database" category events to file
|
/// • Category-based routing - route only "Database" category events to file
|
||||||
/// • Level-range filtering — forward Warning≤level<Error to one flow
|
/// • Level-range filtering - forward Warning≤level<Error to one flow
|
||||||
/// • Property filtering — only forward events that carry a specific property
|
/// • Property filtering - only forward events that carry a specific property
|
||||||
/// • Exception routing — send events with SqlException to a special alert flow
|
/// • Exception routing - send events with SqlException to a special alert flow
|
||||||
///
|
///
|
||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ public enum TokenDestructureHint : byte
|
|||||||
{
|
{
|
||||||
/// <summary>ToString() / scalar value</summary>
|
/// <summary>ToString() / scalar value</summary>
|
||||||
Default = 0,
|
Default = 0,
|
||||||
/// <summary>{@Property} — deep object destructuring (JSON-like)</summary>
|
/// <summary>{@Property} - deep object destructuring (JSON-like)</summary>
|
||||||
Destructure = 1,
|
Destructure = 1,
|
||||||
/// <summary>{$Property} — force ToString()</summary>
|
/// <summary>{$Property} - force ToString()</summary>
|
||||||
Stringify = 2
|
Stringify = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,19 +96,19 @@ public readonly struct TemplateToken
|
|||||||
/// Parses and renders Serilog-compatible message templates with advanced features.
|
/// Parses and renders Serilog-compatible message templates with advanced features.
|
||||||
///
|
///
|
||||||
/// Supported syntax:
|
/// Supported syntax:
|
||||||
/// {PropertyName} — named property (default destructure)
|
/// {PropertyName} - named property (default destructure)
|
||||||
/// {@PropertyName} — named property, deep destructure
|
/// {@PropertyName} - named property, deep destructure
|
||||||
/// {$PropertyName} — named property, force-stringify
|
/// {$PropertyName} - named property, force-stringify
|
||||||
/// {0}, {1} — positional
|
/// {0}, {1} - positional
|
||||||
/// {PropertyName:format} — with format specifier (e.g., "D2", "C")
|
/// {PropertyName:format} - with format specifier (e.g., "D2", "C")
|
||||||
/// {PropertyName,10} — right-align with width 10
|
/// {PropertyName,10} - right-align with width 10
|
||||||
/// {PropertyName,-10} — left-align with width 10
|
/// {PropertyName,-10} - left-align with width 10
|
||||||
/// {Object.Property} — nested property access (dot notation)
|
/// {Object.Property} - nested property access (dot notation)
|
||||||
/// {Array[0]} — array/collection indexing
|
/// {Array[0]} - array/collection indexing
|
||||||
/// {PropertyName|uppercase} — apply filter (uppercase, lowercase, truncate, etc.)
|
/// {PropertyName|uppercase} - apply filter (uppercase, lowercase, truncate, etc.)
|
||||||
/// {PropertyName??'default'} — fallback value if null/missing
|
/// {PropertyName??'default'} - fallback value if null/missing
|
||||||
/// {?IsActive:Yes|No} — conditional rendering
|
/// {?IsActive:Yes|No} - conditional rendering
|
||||||
/// {{ }} — escaped braces → literal { }
|
/// {{ }} - escaped braces → literal { }
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class MessageTemplate
|
public sealed class MessageTemplate
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1150,7 +1150,7 @@ public sealed class LogBuilder
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public LogBuilder WriteToConditional(
|
public LogBuilder WriteToConditional(
|
||||||
IFlow inner,
|
IFlow inner,
|
||||||
|
|||||||
@@ -1,90 +1,95 @@
|
|||||||
using EonaCat.LogStack.Analytics;
|
using EonaCat.LogStack.Analytics;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace EonaCat.LogStack;
|
namespace EonaCat.LogStack;
|
||||||
|
|
||||||
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
// This file is part of the EonaCat project(s) which is released under the Apache License.
|
||||||
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
// See the LICENSE file or go to https://EonaCat.com/License for full license details.
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Comprehensive metrics about the logger
|
/// Comprehensive metrics about the logger
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class LoggerMetrics
|
public class LoggerMetrics
|
||||||
{
|
{
|
||||||
public long TotalLogged { get; set; }
|
public long TotalLogged { get; set; }
|
||||||
public long TotalDropped { get; set; }
|
public long TotalDropped { get; set; }
|
||||||
public long TotalExceptions { get; set; }
|
public long TotalExceptions { get; set; }
|
||||||
public long TotalBytes { get; set; }
|
public long TotalBytes { get; set; }
|
||||||
public double WritesPerSecond { get; set; }
|
public double WritesPerSecond { get; set; }
|
||||||
public long TraceCount { get; set; }
|
public long TraceCount { get; set; }
|
||||||
public long DebugCount { get; set; }
|
public long DebugCount { get; set; }
|
||||||
public long InformationCount { get; set; }
|
public long InformationCount { get; set; }
|
||||||
public long WarningCount { get; set; }
|
public long WarningCount { get; set; }
|
||||||
public long ErrorCount { get; set; }
|
public long ErrorCount { get; set; }
|
||||||
public long CriticalCount { get; set; }
|
public long CriticalCount { get; set; }
|
||||||
public long UptimeMilliseconds { get; set; }
|
public long SpanCount { get; set; }
|
||||||
public List<FlowStatisticsSnapshot> FlowMetrics { get; set; } = new();
|
public long ActiveOperations { get; set; }
|
||||||
|
public double P50LatencyMs { get; set; }
|
||||||
/// <summary>
|
public double P95LatencyMs { get; set; }
|
||||||
/// Gets the success rate (logged / (logged + dropped))
|
public double P99LatencyMs { get; set; }
|
||||||
/// </summary>
|
public long UptimeMilliseconds { get; set; }
|
||||||
public double SuccessRate
|
public List<FlowStatisticsSnapshot> FlowMetrics { get; set; } = new();
|
||||||
{
|
|
||||||
get
|
/// <summary>
|
||||||
{
|
/// Gets the success rate (logged / (logged + dropped))
|
||||||
var total = TotalLogged + TotalDropped;
|
/// </summary>
|
||||||
return total > 0 ? (TotalLogged * 100.0) / total : 100;
|
public double SuccessRate
|
||||||
}
|
{
|
||||||
}
|
get
|
||||||
|
{
|
||||||
/// <summary>
|
var total = TotalLogged + TotalDropped;
|
||||||
/// Gets average bytes per event
|
return total > 0 ? (TotalLogged * 100.0) / total : 100;
|
||||||
/// </summary>
|
}
|
||||||
public double AverageBytesPerEvent
|
}
|
||||||
{
|
|
||||||
get => TotalLogged > 0 ? TotalBytes / (double)TotalLogged : 0;
|
/// <summary>
|
||||||
}
|
/// Gets average bytes per event
|
||||||
|
/// </summary>
|
||||||
/// <summary>
|
public double AverageBytesPerEvent
|
||||||
/// Returns formatted report
|
{
|
||||||
/// </summary>
|
get => TotalLogged > 0 ? TotalBytes / (double)TotalLogged : 0;
|
||||||
public override string ToString()
|
}
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
/// <summary>
|
||||||
sb.AppendLine("===== LOGGER METRICS =====");
|
/// Returns formatted report
|
||||||
sb.AppendLine($"Total Logged: {TotalLogged:N0}");
|
/// </summary>
|
||||||
sb.AppendLine($"Total Dropped: {TotalDropped:N0}");
|
public override string ToString()
|
||||||
sb.AppendLine($"Success Rate: {SuccessRate:F2}%");
|
{
|
||||||
sb.AppendLine($"Total Exceptions: {TotalExceptions:N0}");
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine($"Total Bytes: {TotalBytes:N0}");
|
sb.AppendLine("===== LOGGER METRICS =====");
|
||||||
sb.AppendLine($"Average Bytes/Event: {AverageBytesPerEvent:F2}");
|
sb.AppendLine($"Total Logged: {TotalLogged:N0}");
|
||||||
sb.AppendLine($"Writes/Second: {WritesPerSecond:F2}");
|
sb.AppendLine($"Total Dropped: {TotalDropped:N0}");
|
||||||
sb.AppendLine();
|
sb.AppendLine($"Success Rate: {SuccessRate:F2}%");
|
||||||
|
sb.AppendLine($"Total Exceptions: {TotalExceptions:N0}");
|
||||||
sb.AppendLine("Events by Level:");
|
sb.AppendLine($"Total Bytes: {TotalBytes:N0}");
|
||||||
sb.AppendLine($" Trace: {TraceCount:N0}");
|
sb.AppendLine($"Average Bytes/Event: {AverageBytesPerEvent:F2}");
|
||||||
sb.AppendLine($" Debug: {DebugCount:N0}");
|
sb.AppendLine($"Writes/Second: {WritesPerSecond:F2}");
|
||||||
sb.AppendLine($" Information: {InformationCount:N0}");
|
sb.AppendLine();
|
||||||
sb.AppendLine($" Warning: {WarningCount:N0}");
|
|
||||||
sb.AppendLine($" Error: {ErrorCount:N0}");
|
sb.AppendLine("Events by Level:");
|
||||||
sb.AppendLine($" Critical: {CriticalCount:N0}");
|
sb.AppendLine($" Trace: {TraceCount:N0}");
|
||||||
sb.AppendLine();
|
sb.AppendLine($" Debug: {DebugCount:N0}");
|
||||||
|
sb.AppendLine($" Information: {InformationCount:N0}");
|
||||||
sb.AppendLine($"Uptime: {System.TimeSpan.FromMilliseconds(UptimeMilliseconds):hh\\:mm\\:ss}");
|
sb.AppendLine($" Warning: {WarningCount:N0}");
|
||||||
|
sb.AppendLine($" Error: {ErrorCount:N0}");
|
||||||
if (FlowMetrics.Count > 0)
|
sb.AppendLine($" Critical: {CriticalCount:N0}");
|
||||||
{
|
sb.AppendLine();
|
||||||
sb.AppendLine();
|
|
||||||
sb.AppendLine("Flow Statistics:");
|
sb.AppendLine($"Uptime: {System.TimeSpan.FromMilliseconds(UptimeMilliseconds):hh\\:mm\\:ss}");
|
||||||
foreach (var flow in FlowMetrics)
|
|
||||||
{
|
if (FlowMetrics.Count > 0)
|
||||||
sb.AppendLine($" {flow.FlowName} ({flow.FlowType}): {flow.EventsProcessed:N0} events, {flow.SuccessRate:F2}% success");
|
{
|
||||||
}
|
sb.AppendLine();
|
||||||
}
|
sb.AppendLine("Flow Statistics:");
|
||||||
|
foreach (var flow in FlowMetrics)
|
||||||
sb.AppendLine("==========================");
|
{
|
||||||
return sb.ToString();
|
sb.AppendLine($" {flow.FlowName} ({flow.FlowType}): {flow.EventsProcessed:N0} events, {flow.SuccessRate:F2}% success");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sb.AppendLine("==========================");
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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>
|
public static string Html => @"<!doctype html>
|
||||||
<html><head><title>EonaCat Telemetry</title></head>
|
<html><head><title>EonaCat Telemetry</title></head>
|
||||||
<body><h1>EonaCat LogStack Telemetry</h1>
|
<body><h1>EonaCat LogStack Telemetry</h1>
|
||||||
<p>No external dependencies. Connect to /telemetry to ingest.</p>
|
<p>Connect to /telemetry to ingest.</p>
|
||||||
<script>
|
<script>
|
||||||
setInterval(async()=>{document.body.dataset.events=await (await fetch('/stats')).text()},1000);
|
setInterval(async()=>{document.body.dataset.events=await (await fetch('/stats')).text()},1000);
|
||||||
</script></body></html>";
|
</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 double DurationMs { get; set; }
|
||||||
public long Value { get; set; }
|
public long Value { get; set; }
|
||||||
public string? TraceId { 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; }
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -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.
|
- **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.
|
- **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
|
## Supported Targets
|
||||||
|
|
||||||
- .NET Standard 2.1
|
- .NET Standard 2.1
|
||||||
@@ -1496,6 +1517,55 @@ await using var logger = new LogBuilder("HighVolumeApp")
|
|||||||
.Build();
|
.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
|
## Diagnostics
|
||||||
|
|
||||||
### Real-Time Metrics
|
### Real-Time Metrics
|
||||||
@@ -2531,6 +2601,55 @@ services.AddEonaCatLogging("ProductionApp", builder =>
|
|||||||
.BoostWithUser();
|
.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
|
## Diagnostics
|
||||||
|
|
||||||
```csharp
|
```csharp
|
||||||
@@ -2792,4 +2911,335 @@ The logger is registered as a Singleton in the DI container, so it will be autom
|
|||||||
```csharp
|
```csharp
|
||||||
var loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
|
var loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
|
||||||
await loggerFactory.DisposeAsync();
|
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>
|
||||||
<PackageReference Include="EonaCat.Versioning.Helpers" Version="1.5.1" />
|
<PackageReference Include="EonaCat.Versioning.Helpers" Version="1.5.1" />
|
||||||
<PackageReference Include="EonaCat.Web.RateLimiter" Version="1.0.3" />
|
<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>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
+1
-1
@@ -585,7 +585,7 @@ progress {
|
|||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
}
|
}
|
||||||
.blockquote-footer::before {
|
.blockquote-footer::before {
|
||||||
content: "— ";
|
content: "- ";
|
||||||
}
|
}
|
||||||
|
|
||||||
.img-fluid {
|
.img-fluid {
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -583,7 +583,7 @@ progress {
|
|||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
}
|
}
|
||||||
.blockquote-footer::before {
|
.blockquote-footer::before {
|
||||||
content: "— ";
|
content: "- ";
|
||||||
}
|
}
|
||||||
|
|
||||||
.img-fluid {
|
.img-fluid {
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user