Updated README.md
Added more telemetry tooling
This commit is contained in:
@@ -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<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<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
|
||||
{
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
+1
-1
@@ -585,7 +585,7 @@ progress {
|
||||
color: #6c757d;
|
||||
}
|
||||
.blockquote-footer::before {
|
||||
content: "— ";
|
||||
content: "- ";
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
.blockquote-footer::before {
|
||||
content: "— ";
|
||||
content: "- ";
|
||||
}
|
||||
|
||||
.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