diff --git a/EonaCat.LogStack/DependencyInjection/LoggerDIOptions.cs b/EonaCat.LogStack/DependencyInjection/LoggerDIOptions.cs
new file mode 100644
index 0000000..2422b19
--- /dev/null
+++ b/EonaCat.LogStack/DependencyInjection/LoggerDIOptions.cs
@@ -0,0 +1,45 @@
+using System;
+
+namespace EonaCat.LogStack.DependencyInjection
+{
+ ///
+ /// Advanced configuration options for DI registration
+ ///
+ public sealed class LoggerDIOptions
+ {
+ ///
+ /// Gets or sets whether named loggers should be eagerly initialized
+ ///
+ public bool EagerlyInitializeNamedLoggers { get; set; } = false;
+
+ ///
+ /// Gets or sets whether to register ILogger decorator
+ ///
+ public bool RegisterLoggerDecorator { get; set; } = true;
+
+ ///
+ /// Gets or sets the default logger name for constructor injection
+ ///
+ public string DefaultLoggerName { get; set; } = "Default";
+
+ ///
+ /// Gets or sets whether to enable telemetry integration
+ ///
+ public bool EnableTelemetry { get; set; } = true;
+
+ ///
+ /// Gets or sets whether to enable distributed tracing
+ ///
+ public bool EnableTracing { get; set; } = false;
+
+ ///
+ /// Gets or sets whether to enable performance monitoring
+ ///
+ public bool EnablePerformanceMonitoring { get; set; } = false;
+
+ ///
+ /// Gets or sets whether to enable health monitoring
+ ///
+ public bool EnableHealthMonitoring { get; set; } = true;
+ }
+}
diff --git a/EonaCat.LogStack/EonaCat.LogStack.csproj b/EonaCat.LogStack/EonaCat.LogStack.csproj
index 7d5acb3..7486ce5 100644
--- a/EonaCat.LogStack/EonaCat.LogStack.csproj
+++ b/EonaCat.LogStack/EonaCat.LogStack.csproj
@@ -14,7 +14,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
EonaCat (Jeroen Saey)
EonaCat;Logger;EonaCatLogStack;Log;Writer;Flows;LogStack;Memory;Speed;Jeroen;Saey
- 0.1.2
+ 0.1.3
README.md
True
LICENSE
@@ -25,7 +25,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
- 0.1.2+{chash:10}.{c:ymd}
+ 0.1.3+{chash:10}.{c:ymd}
true
true
v[0-9]*
@@ -36,7 +36,7 @@ It features a rich fluent API for routing log events to dozens of destinations f
- 0.1.2
+ 0.1.3
EonaCat.LogStack
EonaCat.LogStack
https://git.saey.me/EonaCat/EonaCat.LogStack
@@ -101,4 +101,10 @@ It features a rich fluent API for routing log events to dozens of destinations f
\
+
+
+
+
+
+
\ No newline at end of file
diff --git a/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/CorrelatedEventFlow.cs b/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/CorrelatedEventFlow.cs
new file mode 100644
index 0000000..505e730
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/CorrelatedEventFlow.cs
@@ -0,0 +1,358 @@
+using EonaCat.LogStack.Core;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EonaCat.LogStack.Flows
+{
+ // 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.
+
+ ///
+ /// CorrelatedEventFlow tracks and correlates related events across flows and time windows.
+ /// Enables request tracing, transaction tracking, and detection of related log patterns.
+ ///
+ public sealed class CorrelatedEventFlow : FlowBase
+ {
+ private readonly int _maxCorrelationGroups;
+ private readonly TimeSpan _correlationWindow;
+ private readonly ConcurrentDictionary _correlationGroups = new();
+ private readonly object _cleanupLock = new();
+
+ ///
+ /// Creates a new CorrelatedEventFlow
+ ///
+ /// Maximum number of correlation groups to track (default 10000)
+ /// Time window for correlating events (default 5 minutes)
+ /// Minimum log level to process
+ public CorrelatedEventFlow(
+ int maxCorrelationGroups = 10000,
+ TimeSpan? correlationWindow = null,
+ LogLevel minimumLevel = LogLevel.Trace)
+ : base("CorrelatedEvent", minimumLevel)
+ {
+ _maxCorrelationGroups = Math.Max(1000, maxCorrelationGroups);
+ _correlationWindow = correlationWindow ?? TimeSpan.FromMinutes(5);
+ }
+
+ public override async Task BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
+ {
+ if (!IsEnabled || !IsLogLevelEnabled(logEvent))
+ return WriteResult.Success;
+
+ try
+ {
+ var correlationId = ExtractCorrelationId(logEvent);
+ if (string.IsNullOrEmpty(correlationId))
+ return WriteResult.Success;
+
+ // Get or create correlation group
+ var group = _correlationGroups.AddOrUpdate(
+ correlationId,
+ new CorrelationGroup(correlationId),
+ (key, existing) => existing
+ );
+
+ group.AddEvent(logEvent);
+
+ // Cleanup if needed
+ if (_correlationGroups.Count > _maxCorrelationGroups)
+ {
+ CleanupExpiredGroups();
+ }
+
+ return await Task.FromResult(WriteResult.Success);
+ }
+ catch (Exception ex)
+ {
+ return WriteResult.Failed;
+ }
+ }
+
+ public override async Task BlastBatchAsync(ReadOnlyMemory logEvents, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ foreach (var logEvent in logEvents.Span)
+ {
+ if (IsLogLevelEnabled(logEvent))
+ {
+ var correlationId = ExtractCorrelationId(logEvent);
+ if (!string.IsNullOrEmpty(correlationId))
+ {
+ var group = _correlationGroups.AddOrUpdate(
+ correlationId,
+ new CorrelationGroup(correlationId),
+ (key, existing) => existing
+ );
+
+ group.AddEvent(logEvent);
+ }
+ }
+ }
+
+ if (_correlationGroups.Count > _maxCorrelationGroups)
+ {
+ CleanupExpiredGroups();
+ }
+
+ return await Task.FromResult(WriteResult.Success);
+ }
+ catch (Exception ex)
+ {
+ return WriteResult.Failed;
+ }
+ }
+
+ public override async Task FlushAsync(CancellationToken cancellationToken = default)
+ {
+ CleanupExpiredGroups();
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// Get all events for a correlation ID
+ ///
+ public IEnumerable GetCorrelatedEvents(string correlationId)
+ {
+ if (_correlationGroups.TryGetValue(correlationId, out var group))
+ return group.Events.ToList();
+
+ return new List();
+ }
+
+ ///
+ /// Get correlation statistics
+ ///
+ public CorrelationStatistics GetStatistics()
+ {
+ var stats = new CorrelationStatistics
+ {
+ TotalCorrelationIds = _correlationGroups.Count,
+ TotalTrackedEvents = _correlationGroups.Values.Sum(g => g.Events.Count),
+ AverageEventsPerCorrelation = _correlationGroups.Count > 0
+ ? _correlationGroups.Values.Average(g => g.Events.Count)
+ : 0
+ };
+
+ // Calculate event distribution
+ if (_correlationGroups.Count > 0)
+ {
+ var eventCounts = _correlationGroups.Values.Select(g => g.Events.Count).ToList();
+ stats.MinEventsPerCorrelation = eventCounts.Min();
+ stats.MaxEventsPerCorrelation = eventCounts.Max();
+ stats.MedianEventsPerCorrelation = GetMedian(eventCounts);
+ }
+
+ return stats;
+ }
+
+ ///
+ /// Find correlation groups with errors
+ ///
+ public IEnumerable GetErrorCorrelations()
+ {
+ return _correlationGroups.Values
+ .Where(g => g.Events.Any(e => e.Level >= LogLevel.Error))
+ .ToList();
+ }
+
+ ///
+ /// Find slow correlation groups (based on duration property)
+ ///
+ public IEnumerable GetSlowCorrelations(TimeSpan minimumDuration)
+ {
+ return _correlationGroups.Values
+ .Where(g => g.Time().TotalMilliseconds >= minimumDuration.TotalMilliseconds)
+ .ToList();
+ }
+
+ ///
+ /// Get correlation groups by category pattern
+ ///
+ public IEnumerable QueryByPattern(string pattern, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
+ {
+ return _correlationGroups.Values
+ .Where(g => g.Events.Any(e =>
+ {
+ var msg = e.Message.ToString();
+ return msg.Contains(pattern, comparison);
+ }))
+ .ToList();
+ }
+
+ ///
+ /// Clear all correlation tracking
+ ///
+ public void Clear()
+ {
+ _correlationGroups.Clear();
+ }
+
+ // Private helpers
+
+ private string ExtractCorrelationId(LogEvent logEvent)
+ {
+ // Try to extract from common sources
+ if (logEvent.Properties != null)
+ {
+ if (logEvent.Properties.TryGetValue("CorrelationId", out var corrId) && corrId is string str)
+ return str;
+ if (logEvent.Properties.TryGetValue("RequestId", out var reqId) && reqId is string str2)
+ return str2;
+ if (logEvent.Properties.TryGetValue("TraceId", out var traceId) && traceId is string str3)
+ return str3;
+ }
+
+ // Try thread-based correlation
+ return null;
+ }
+
+ private void CleanupExpiredGroups()
+ {
+ lock (_cleanupLock)
+ {
+ var now = DateTime.UtcNow;
+ var expiredIds = _correlationGroups
+ .Where(kvp => now - kvp.Value.LastEventTime > _correlationWindow)
+ .Select(kvp => kvp.Key)
+ .ToList();
+
+ foreach (var id in expiredIds)
+ {
+ _correlationGroups.TryRemove(id, out _);
+ }
+ }
+ }
+
+ private double GetMedian(List values)
+ {
+ if (values.Count == 0) return 0;
+ var sorted = values.OrderBy(x => x).ToList();
+ if (sorted.Count % 2 == 0)
+ return (sorted[sorted.Count / 2 - 1] + sorted[sorted.Count / 2]) / 2.0;
+ return sorted[sorted.Count / 2];
+ }
+
+ public override async ValueTask DisposeAsync()
+ {
+ Clear();
+ await base.DisposeAsync();
+ }
+ }
+
+ ///
+ /// Represents a group of correlated log events
+ ///
+ public class CorrelationGroup
+ {
+ private readonly List _events = new();
+ private readonly object _eventLock = new();
+
+ public string CorrelationId { get; }
+ public IReadOnlyList Events
+ {
+ get
+ {
+ lock (_eventLock)
+ return _events.AsReadOnly();
+ }
+ }
+ public DateTime FirstEventTime { get; private set; }
+ public DateTime LastEventTime { get; private set; }
+
+ public CorrelationGroup(string correlationId)
+ {
+ CorrelationId = correlationId ?? throw new ArgumentNullException(nameof(correlationId));
+ FirstEventTime = DateTime.UtcNow;
+ LastEventTime = DateTime.UtcNow;
+ }
+
+ public void AddEvent(LogEvent logEvent)
+ {
+ lock (_eventLock)
+ {
+ _events.Add(logEvent);
+ LastEventTime = DateTime.UtcNow;
+ }
+ }
+
+ ///
+ /// Get the total time span of this correlation group
+ ///
+ public TimeSpan Time()
+ {
+ lock (_eventLock)
+ {
+ if (_events.Count == 0) return TimeSpan.Zero;
+ return new TimeSpan(_events[_events.Count - 1].Timestamp - _events[0].Timestamp);
+ }
+ }
+
+ ///
+ /// Get events at specific log level
+ ///
+ public IEnumerable GetEventsByLevel(LogLevel level)
+ {
+ lock (_eventLock)
+ return _events.Where(e => e.Level == level).ToList();
+ }
+
+ ///
+ /// Check if this group contains any exceptions
+ ///
+ public bool HasExceptions
+ {
+ get
+ {
+ lock (_eventLock)
+ return _events.Any(e => e.Exception != null);
+ }
+ }
+
+ ///
+ /// Get event count by category
+ ///
+ public Dictionary GetEventsByCategory()
+ {
+ lock (_eventLock)
+ {
+ return _events
+ .GroupBy(e => e.Category ?? "Unknown")
+ .ToDictionary(g => g.Key, g => g.Count());
+ }
+ }
+
+ public override string ToString()
+ {
+ lock (_eventLock)
+ {
+ var errorCount = _events.Count(e => e.Level >= LogLevel.Error);
+ var duration = Time();
+ return $"CorrelationGroup [ID: {CorrelationId}, Events: {_events.Count}, Errors: {errorCount}, Duration: {duration.TotalMilliseconds:F0}ms]";
+ }
+ }
+ }
+
+ ///
+ /// Statistics about correlation tracking
+ ///
+ public class CorrelationStatistics
+ {
+ public int TotalCorrelationIds { get; set; }
+ public int TotalTrackedEvents { get; set; }
+ public double AverageEventsPerCorrelation { get; set; }
+ public int MinEventsPerCorrelation { get; set; }
+ public int MaxEventsPerCorrelation { get; set; }
+ public double MedianEventsPerCorrelation { get; set; }
+
+ public override string ToString()
+ {
+ return $"CorrelationStats: IDs={TotalCorrelationIds}, Events={TotalTrackedEvents}, " +
+ $"Avg={AverageEventsPerCorrelation:F1}, Min={MinEventsPerCorrelation}, Max={MaxEventsPerCorrelation}";
+ }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/LocalStorageQueryFlow.cs b/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/LocalStorageQueryFlow.cs
new file mode 100644
index 0000000..2031036
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/LocalStorageQueryFlow.cs
@@ -0,0 +1,262 @@
+using EonaCat.LogStack.Core;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Runtime.CompilerServices;
+
+namespace EonaCat.LogStack.Flows
+{
+ // 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.
+
+ ///
+ /// LocalStorageQueryFlow stores recent log events in memory and provides LINQ-like querying capabilities.
+ /// Useful for diagnostics, testing, and real-time log analysis without external storage.
+ /// Uses a circular buffer with configurable maximum size to prevent unbounded memory growth.
+ ///
+ public sealed class LocalStorageQueryFlow : FlowBase
+ {
+ private readonly int _maxCapacity;
+ private readonly ConcurrentQueue _logBuffer = new();
+ private int _currentCount = 0;
+
+ ///
+ /// Gets all stored log events
+ ///
+ public IReadOnlyList AllEvents => _logBuffer.ToList();
+
+ ///
+ /// Gets the number of events currently stored
+ ///
+ public int StoredEventCount => _currentCount;
+
+ ///
+ /// Creates a new LocalStorageQueryFlow with specified capacity
+ ///
+ /// Maximum number of log events to keep in memory (default 10000)
+ /// Minimum log level to store
+ public LocalStorageQueryFlow(
+ int maxCapacity = 10000,
+ LogLevel minimumLevel = LogLevel.Trace)
+ : base("LocalStorageQuery", minimumLevel)
+ {
+ if (maxCapacity <= 0)
+ throw new ArgumentException("Capacity must be greater than 0", nameof(maxCapacity));
+
+ _maxCapacity = maxCapacity;
+ }
+
+ public override async Task BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
+ {
+ if (!IsEnabled || !IsLogLevelEnabled(logEvent))
+ return WriteResult.Success;
+
+ try
+ {
+ _logBuffer.Enqueue(logEvent);
+ int newCount = Interlocked.Increment(ref _currentCount);
+
+ // Maintain circular buffer by dropping oldest items when over capacity
+ while (newCount > _maxCapacity)
+ {
+ if (_logBuffer.TryDequeue(out _))
+ {
+ newCount = Interlocked.Decrement(ref _currentCount);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return await Task.FromResult(WriteResult.Success);
+ }
+ catch (Exception ex)
+ {
+ return WriteResult.Failed;
+ }
+ }
+
+ public override async Task BlastBatchAsync(ReadOnlyMemory logEvents, CancellationToken cancellationToken = default)
+ {
+ try
+ {
+ foreach (var logEvent in logEvents.Span)
+ {
+ if (IsLogLevelEnabled(logEvent))
+ {
+ _logBuffer.Enqueue(logEvent);
+ int newCount = Interlocked.Increment(ref _currentCount);
+
+ // Maintain circular buffer
+ while (newCount > _maxCapacity)
+ {
+ if (_logBuffer.TryDequeue(out _))
+ {
+ newCount = Interlocked.Decrement(ref _currentCount);
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+
+ return await Task.FromResult(WriteResult.Success);
+ }
+ catch (Exception ex)
+ {
+ return WriteResult.Failed;
+ }
+ }
+
+ public override async Task FlushAsync(CancellationToken cancellationToken = default)
+ {
+ // No async I/O needed for in-memory storage
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// Query stored events by log level
+ ///
+ public IEnumerable QueryByLevel(LogLevel level)
+ {
+ return _logBuffer.Where(e => e.Level == level);
+ }
+
+ ///
+ /// Query stored events by category
+ ///
+ public IEnumerable QueryByCategory(string category)
+ {
+ return _logBuffer.Where(e => e.Category?.Equals(category, StringComparison.OrdinalIgnoreCase) ?? false);
+ }
+
+ ///
+ /// Query stored events by message pattern
+ ///
+ public IEnumerable QueryByMessagePattern(string pattern, StringComparison comparison = StringComparison.OrdinalIgnoreCase)
+ {
+ return _logBuffer.Where(e =>
+ {
+ var msg = e.Message.ToString();
+ return msg.Contains(pattern, comparison);
+ });
+ }
+
+ ///
+ /// Query stored events by time range
+ ///
+ public IEnumerable QueryByTimeRange(DateTime from, DateTime to)
+ {
+ var fromTicks = from.Ticks;
+ var toTicks = to.Ticks;
+ return _logBuffer.Where(e => e.Timestamp >= fromTicks && e.Timestamp <= toTicks);
+ }
+
+ ///
+ /// Query stored events that contain exceptions
+ ///
+ public IEnumerable QueryExceptionEvents()
+ {
+ return _logBuffer.Where(e => e.Exception != null);
+ }
+
+ ///
+ /// Query stored events by duration (for performance logs)
+ ///
+ public IEnumerable QueryByMinimumDuration(TimeSpan duration)
+ {
+ return _logBuffer.Where(e =>
+ e.Properties != null &&
+ e.Properties.TryGetValue("duration", out var durValue) &&
+ durValue is TimeSpan ts &&
+ ts >= duration);
+ }
+
+ ///
+ /// Get statistics about stored events
+ ///
+ public LogStorageStatistics GetStatistics()
+ {
+ var events = _logBuffer.ToList();
+ if (events.Count == 0)
+ return new LogStorageStatistics();
+
+ var stats = new LogStorageStatistics
+ {
+ TotalEvents = events.Count,
+ EventsByLevel = events
+ .GroupBy(e => e.Level)
+ .ToDictionary(g => g.Key, g => g.Count()),
+ EventsByCategory = events
+ .GroupBy(e => e.Category ?? "Unknown")
+ .ToDictionary(g => g.Key, g => g.Count()),
+ OldestEventTime = new DateTime(events.Min(e => e.Timestamp)),
+ NewestEventTime = new DateTime(events.Max(e => e.Timestamp)),
+ ExceptionCount = events.Count(e => e.Exception != null),
+ AverageMessageLength = (int)events.Average(e => e.Message.Length)
+ };
+
+ return stats;
+ }
+
+ ///
+ /// Clear all stored events
+ ///
+ public void Clear()
+ {
+ while (_logBuffer.TryDequeue(out _)) { }
+ _currentCount = 0;
+ }
+
+ ///
+ /// Get the most recent N events
+ ///
+ public IEnumerable GetLatest(int count)
+ {
+ return _logBuffer.Reverse().Take(count);
+ }
+
+ ///
+ /// Search with custom predicate
+ ///
+ public IEnumerable Search(Func predicate)
+ {
+ return _logBuffer.Where(predicate);
+ }
+
+ public override async ValueTask DisposeAsync()
+ {
+ Clear();
+ await base.DisposeAsync();
+ }
+ }
+
+ ///
+ /// Statistics about logs stored in LocalStorageQueryFlow
+ ///
+ public class LogStorageStatistics
+ {
+ public int TotalEvents { get; set; }
+ public Dictionary EventsByLevel { get; set; } = new();
+ public Dictionary EventsByCategory { get; set; } = new();
+ public DateTime OldestEventTime { get; set; }
+ public DateTime NewestEventTime { get; set; }
+ public int ExceptionCount { get; set; }
+ public int AverageMessageLength { get; set; }
+
+ public override string ToString()
+ {
+ var levelBreakdown = string.Join(", ", EventsByLevel.Select(kvp => $"{kvp.Key}: {kvp.Value}"));
+ var categoryBreakdown = string.Join(", ", EventsByCategory.Take(3).Select(kvp => $"{kvp.Key}: {kvp.Value}"));
+
+ return $"LogStorageStats: Total={TotalEvents}, Exceptions={ExceptionCount}, " +
+ $"Levels=[{levelBreakdown}], Categories=[{categoryBreakdown}]";
+ }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/LogFilterPresetFlow.cs b/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/LogFilterPresetFlow.cs
new file mode 100644
index 0000000..7d65b76
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/LogFilterPresetFlow.cs
@@ -0,0 +1,357 @@
+using EonaCat.LogStack.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Channels;
+using System.Threading.Tasks;
+
+namespace EonaCat.LogStack.Flows
+{
+ // 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.
+
+ ///
+ /// LogFilterPresetFlow provides commonly-used filter combinations as reusable presets.
+ /// Reduces boilerplate for common scenarios like error tracking, performance monitoring, etc.
+ ///
+ public sealed class LogFilterPresetFlow : FlowBase
+ {
+ private readonly Channel _channel;
+ private readonly IFlow _targetFlow;
+ private readonly LogFilterPreset _preset;
+ private Task _processingTask;
+
+ ///
+ /// Creates a LogFilterPresetFlow with a specific preset
+ ///
+ /// The flow to send filtered events to
+ /// The filter preset to apply
+ /// Minimum log level
+ public LogFilterPresetFlow(
+ IFlow targetFlow,
+ LogFilterPreset preset,
+ LogLevel minimumLevel = LogLevel.Trace)
+ : base($"FilterPreset_{preset.Name}", minimumLevel)
+ {
+ _targetFlow = targetFlow ?? throw new ArgumentNullException(nameof(targetFlow));
+ _preset = preset ?? throw new ArgumentNullException(nameof(preset));
+ _channel = Channel.CreateUnbounded(new UnboundedChannelOptions
+ {
+ SingleWriter = false,
+ SingleReader = true
+ });
+
+ StartProcessing();
+ }
+
+ private void StartProcessing()
+ {
+ _processingTask = ProcessChannelAsync();
+ }
+
+ private async Task ProcessChannelAsync()
+ {
+ try
+ {
+ await foreach (var logEvent in _channel.Reader.ReadAllAsync())
+ {
+ var filtered = _preset.Filter(logEvent);
+ if (filtered)
+ {
+ await _targetFlow.BlastAsync(logEvent);
+ }
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ // Expected when channel is closed
+ }
+ }
+
+ public override async Task BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
+ {
+ if (!IsEnabled || !IsLogLevelEnabled(logEvent))
+ return WriteResult.Success;
+
+ try
+ {
+ await _channel.Writer.WriteAsync(logEvent, cancellationToken);
+ return WriteResult.Success;
+ }
+ catch (Exception ex)
+ {
+ return WriteResult.Failed;
+ }
+ }
+
+ public override async Task BlastBatchAsync(ReadOnlyMemory logEvents, CancellationToken cancellationToken = default)
+ {
+ var events = logEvents.ToArray(); // Copy to array to avoid span issues across await
+ try
+ {
+ foreach (var logEvent in events)
+ {
+ if (IsLogLevelEnabled(logEvent))
+ {
+ await _channel.Writer.WriteAsync(logEvent, cancellationToken);
+ }
+ }
+ return WriteResult.Success;
+ }
+ catch (Exception ex)
+ {
+ return WriteResult.Failed;
+ }
+ }
+
+ public override async Task FlushAsync(CancellationToken cancellationToken = default)
+ {
+ await _targetFlow.FlushAsync(cancellationToken);
+ }
+
+ public override async ValueTask DisposeAsync()
+ {
+ _channel.Writer.TryComplete();
+ if (_processingTask != null)
+ await _processingTask;
+ await _targetFlow.DisposeAsync();
+ await base.DisposeAsync();
+ }
+ }
+
+ ///
+ /// Base class for filter presets
+ ///
+ public abstract class LogFilterPreset
+ {
+ public string Name { get; set; }
+
+ protected LogFilterPreset(string name)
+ {
+ Name = name ?? "Custom";
+ }
+
+ public abstract bool Filter(LogEvent logEvent);
+ }
+
+ ///
+ /// Preset: Only errors and exceptions
+ ///
+ public class ErrorOnlyPreset : LogFilterPreset
+ {
+ public ErrorOnlyPreset() : base("ErrorOnly") { }
+
+ public override bool Filter(LogEvent logEvent)
+ {
+ return logEvent.Level >= LogLevel.Error || logEvent.Exception != null;
+ }
+ }
+
+ ///
+ /// Preset: Errors with full stack traces
+ ///
+ public class ErrorWithStackTracePreset : LogFilterPreset
+ {
+ public ErrorWithStackTracePreset() : base("ErrorWithStackTrace") { }
+
+ public override bool Filter(LogEvent logEvent)
+ {
+ return (logEvent.Level >= LogLevel.Error || logEvent.Exception != null) &&
+ logEvent.Exception?.StackTrace != null;
+ }
+ }
+
+ ///
+ /// Preset: Performance-related logs (duration, latency, timeout)
+ ///
+ public class PerformanceLogsPreset : LogFilterPreset
+ {
+ private readonly string[] _keywords = { "duration", "latency", "timeout", "slow", "performance", "elapsed", "ms" };
+
+ public PerformanceLogsPreset() : base("PerformanceLogs") { }
+
+ public override bool Filter(LogEvent logEvent)
+ {
+ var message = logEvent.Message.ToString();
+ return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase)) ||
+ (logEvent.Properties != null && logEvent.Properties.Keys.Any(k => k.Contains("duration", StringComparison.OrdinalIgnoreCase)));
+ }
+ }
+
+ ///
+ /// Preset: Authentication and security-related logs
+ ///
+ public class SecurityLogsPreset : LogFilterPreset
+ {
+ private readonly string[] _keywords = { "auth", "security", "permission", "token", "password", "encrypt", "decrypt", "unauthorized", "forbidden" };
+
+ public SecurityLogsPreset() : base("SecurityLogs") { }
+
+ public override bool Filter(LogEvent logEvent)
+ {
+ var message = logEvent.Message.ToString();
+ return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ ///
+ /// Preset: Health check and monitoring logs
+ ///
+ public class HealthCheckPreset : LogFilterPreset
+ {
+ private readonly string[] _keywords = { "health", "check", "monitor", "alive", "heartbeat", "ping", "status", "ready" };
+
+ public HealthCheckPreset() : base("HealthCheck") { }
+
+ public override bool Filter(LogEvent logEvent)
+ {
+ var message = logEvent.Message.ToString();
+ var category = logEvent.Category ?? string.Empty;
+ return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
+ category.Contains(kw, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ ///
+ /// Preset: Data access and database-related logs
+ ///
+ public class DatabaseLogsPreset : LogFilterPreset
+ {
+ private readonly string[] _keywords = { "database", "sql", "query", "transaction", "connection", "db:", "entity", "datacontext" };
+
+ public DatabaseLogsPreset() : base("DatabaseLogs") { }
+
+ public override bool Filter(LogEvent logEvent)
+ {
+ var message = logEvent.Message.ToString();
+ var category = logEvent.Category ?? string.Empty;
+ return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
+ category.Contains(kw, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ ///
+ /// Preset: Business logic and domain events
+ ///
+ public class DomainEventsPreset : LogFilterPreset
+ {
+ private readonly string[] _keywords = { "event", "domain", "aggregate", "business", "order", "payment", "transaction" };
+
+ public DomainEventsPreset() : base("DomainEvents") { }
+
+ public override bool Filter(LogEvent logEvent)
+ {
+ var message = logEvent.Message.ToString();
+ var category = logEvent.Category ?? string.Empty;
+ return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
+ category.Contains(kw, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ ///
+ /// Preset: External service calls and integrations
+ ///
+ public class ExternalServicePreset : LogFilterPreset
+ {
+ private readonly string[] _keywords = { "http", "api", "external", "service", "remote", "call", "webhook", "webhook", "third-party" };
+
+ public ExternalServicePreset() : base("ExternalService") { }
+
+ public override bool Filter(LogEvent logEvent)
+ {
+ var message = logEvent.Message.ToString();
+ var category = logEvent.Category ?? string.Empty;
+ return _keywords.Any(kw => message.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
+ category.Contains(kw, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ ///
+ /// Preset: Critical warnings and errors (anything that might need attention)
+ ///
+ public class CriticalAlertsPreset : LogFilterPreset
+ {
+ public CriticalAlertsPreset() : base("CriticalAlerts") { }
+
+ public override bool Filter(LogEvent logEvent)
+ {
+ return logEvent.Level >= LogLevel.Warning ||
+ logEvent.Exception != null ||
+ (logEvent.Properties?.ContainsKey("Alert") ?? false);
+ }
+ }
+
+ ///
+ /// Preset: Development and debugging logs
+ ///
+ public class DebugLogsPreset : LogFilterPreset
+ {
+ private readonly string[] _keywords = { "debug", "trace", "verbose", "diagnostic", "test", "dev" };
+
+ public DebugLogsPreset() : base("DebugLogs") { }
+
+ public override bool Filter(LogEvent logEvent)
+ {
+ var msg = logEvent.Message.ToString();
+ var cat = logEvent.Category ?? string.Empty;
+ return logEvent.Level <= LogLevel.Debug ||
+ _keywords.Any(kw => msg.Contains(kw, StringComparison.OrdinalIgnoreCase) ||
+ cat.Contains(kw, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ ///
+ /// Preset: Custom filter based on predicate
+ ///
+ public class CustomPreset : LogFilterPreset
+ {
+ private readonly Func _predicate;
+
+ public CustomPreset(string name, Func predicate)
+ : base(name)
+ {
+ _predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));
+ }
+
+ public override bool Filter(LogEvent logEvent)
+ {
+ return _predicate(logEvent);
+ }
+ }
+
+ ///
+ /// Preset Registry for managing and creating presets
+ ///
+ public static class LogFilterPresetRegistry
+ {
+ private static readonly Dictionary _presets = new()
+ {
+ ["ErrorOnly"] = new ErrorOnlyPreset(),
+ ["ErrorWithStackTrace"] = new ErrorWithStackTracePreset(),
+ ["Performance"] = new PerformanceLogsPreset(),
+ ["Security"] = new SecurityLogsPreset(),
+ ["HealthCheck"] = new HealthCheckPreset(),
+ ["Database"] = new DatabaseLogsPreset(),
+ ["DomainEvents"] = new DomainEventsPreset(),
+ ["ExternalService"] = new ExternalServicePreset(),
+ ["CriticalAlerts"] = new CriticalAlertsPreset(),
+ ["Debug"] = new DebugLogsPreset()
+ };
+
+ public static LogFilterPreset? Get(string presetName)
+ {
+ return _presets.TryGetValue(presetName, out var preset) ? preset : null;
+ }
+
+ public static IEnumerable GetAvailablePresets()
+ {
+ return _presets.Keys;
+ }
+
+ public static void Register(string name, LogFilterPreset preset)
+ {
+ _presets[name] = preset;
+ }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/PerformanceAnomalyDetectorFlow.cs b/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/PerformanceAnomalyDetectorFlow.cs
new file mode 100644
index 0000000..e59804b
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/EonaCatLoggerCore/Flows/PerformanceAnomalyDetectorFlow.cs
@@ -0,0 +1,384 @@
+using EonaCat.LogStack.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EonaCat.LogStack.Flows
+{
+ // 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.
+
+ ///
+ /// PerformanceAnomalyDetectorFlow detects anomalies in performance metrics and generates alerts.
+ /// Uses statistical analysis to identify deviations from baseline performance.
+ ///
+ public sealed class PerformanceAnomalyDetectorFlow : FlowBase
+ {
+ private readonly int _windowSize;
+ private readonly double _standardDeviationThreshold;
+ private readonly IFlow _alertTarget;
+ private readonly Dictionary _trackers = new();
+ private readonly object _trackersLock = new();
+
+ ///
+ /// Creates a new PerformanceAnomalyDetectorFlow
+ ///
+ /// Flow to send anomaly alerts to
+ /// Size of the sliding window for statistical analysis (default 100)
+ /// Alert threshold in standard deviations (default 2.0)
+ /// Minimum log level to process
+ public PerformanceAnomalyDetectorFlow(
+ IFlow? alertTarget = null,
+ int windowSize = 100,
+ double standardDeviationThreshold = 2.0,
+ LogLevel minimumLevel = LogLevel.Information)
+ : base("PerformanceAnomalyDetector", minimumLevel)
+ {
+ _windowSize = Math.Max(10, windowSize);
+ _standardDeviationThreshold = Math.Max(1.0, standardDeviationThreshold);
+ _alertTarget = alertTarget;
+ }
+
+ public override async Task BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
+ {
+ if (!IsEnabled || !IsLogLevelEnabled(logEvent))
+ return WriteResult.Success;
+
+ try
+ {
+ var metrics = ExtractPerformanceMetrics(logEvent);
+ if (metrics.Count == 0)
+ return WriteResult.Success;
+
+ lock (_trackersLock)
+ {
+ foreach (var metric in metrics)
+ {
+ var key = metric.Key;
+ if (!_trackers.TryGetValue(key, out var tracker))
+ {
+ tracker = new PerformanceMetricTracker(key, _windowSize, _standardDeviationThreshold);
+ _trackers[key] = tracker;
+ }
+
+ var anomaly = tracker.AddMeasurement(metric.Value, logEvent);
+ if (anomaly != null && _alertTarget != null)
+ {
+ // Send anomaly alert
+ var alertEvent = new LogEvent
+ {
+ Timestamp = DateTime.UtcNow.Ticks,
+ Level = LogLevel.Warning,
+ Category = "PerformanceAnomaly",
+ Message = anomaly.Description.AsMemory(),
+ Properties = new Dictionary
+ {
+ ["MetricName"] = anomaly.MetricName,
+ ["CurrentValue"] = anomaly.CurrentValue,
+ ["AverageValue"] = anomaly.AverageValue,
+ ["Deviation"] = anomaly.DeviationMultiplier,
+ ["IsSlowdown"] = anomaly.IsSlowdown
+ }
+ };
+
+ _ = _alertTarget.BlastAsync(alertEvent);
+ }
+ }
+ }
+
+ return await Task.FromResult(WriteResult.Success);
+ }
+ catch (Exception ex)
+ {
+ return WriteResult.Failed;
+ }
+ }
+
+ public override async Task BlastBatchAsync(ReadOnlyMemory logEvents, CancellationToken cancellationToken = default)
+ {
+ foreach (var logEvent in logEvents.ToArray())
+ {
+ var result = await BlastAsync(logEvent, cancellationToken);
+ if (result != WriteResult.Success)
+ return result;
+ }
+ return WriteResult.Success;
+ }
+
+ public override async Task FlushAsync(CancellationToken cancellationToken = default)
+ {
+ if (_alertTarget != null)
+ await _alertTarget.FlushAsync(cancellationToken);
+ await Task.CompletedTask;
+ }
+
+ ///
+ /// Get anomaly detection statistics
+ ///
+ public AnomalyDetectionStatistics GetStatistics()
+ {
+ lock (_trackersLock)
+ {
+ var stats = new AnomalyDetectionStatistics
+ {
+ MonitoredMetrics = _trackers.Count,
+ TotalMeasurements = _trackers.Values.Sum(t => t.MeasurementCount),
+ TotalAnomaliesDetected = _trackers.Values.Sum(t => t.AnomalyCount),
+ MetricStatistics = _trackers.ToDictionary(
+ kvp => kvp.Key,
+ kvp => new MetricStatistic
+ {
+ AverageValue = kvp.Value.Average,
+ StandardDeviation = kvp.Value.StandardDeviation,
+ MinValue = kvp.Value.MinValue,
+ MaxValue = kvp.Value.MaxValue,
+ AnomalyCount = kvp.Value.AnomalyCount
+ })
+ };
+
+ return stats;
+ }
+ }
+
+ ///
+ /// Get recent anomalies
+ ///
+ public IEnumerable GetRecentAnomalies(int count = 10)
+ {
+ lock (_trackersLock)
+ {
+ return _trackers.Values
+ .SelectMany(t => t.RecentAnomalies)
+ .OrderByDescending(a => a.DetectedAt)
+ .Take(count)
+ .ToList();
+ }
+ }
+
+ ///
+ /// Get anomalies for a specific metric
+ ///
+ public IEnumerable GetAnomaliesForMetric(string metricName)
+ {
+ lock (_trackersLock)
+ {
+ if (_trackers.TryGetValue(metricName, out var tracker))
+ return tracker.RecentAnomalies.ToList();
+ return new List();
+ }
+ }
+
+ ///
+ /// Reset anomaly tracking for a metric
+ ///
+ public void ResetMetric(string metricName)
+ {
+ lock (_trackersLock)
+ {
+ _trackers.Remove(metricName);
+ }
+ }
+
+ ///
+ /// Clear all tracking data
+ ///
+ public void Clear()
+ {
+ lock (_trackersLock)
+ {
+ _trackers.Clear();
+ }
+ }
+
+ // Private helpers
+
+ private Dictionary ExtractPerformanceMetrics(LogEvent logEvent)
+ {
+ var metrics = new Dictionary();
+
+ if (logEvent.Properties == null)
+ return metrics;
+
+ // Look for common performance metric property names
+ var metricNames = new[] { "duration", "latency", "elapsed", "time_ms", "response_time", "memory", "cpu" };
+
+ foreach (var propKey in logEvent.Properties.Keys)
+ {
+ var keyStr = propKey.ToString();
+ if (metricNames.Any(mn => keyStr.Contains(mn, StringComparison.OrdinalIgnoreCase)))
+ {
+ if (logEvent.Properties[propKey] is double dVal)
+ {
+ metrics[keyStr] = dVal;
+ }
+ else if (logEvent.Properties[propKey] is int iVal)
+ {
+ metrics[keyStr] = iVal;
+ }
+ else if (logEvent.Properties[propKey] is long lVal)
+ {
+ metrics[keyStr] = lVal;
+ }
+ else if (double.TryParse(logEvent.Properties[propKey]?.ToString(), out var parsed))
+ {
+ metrics[keyStr] = parsed;
+ }
+ }
+ }
+
+ return metrics;
+ }
+
+ public override async ValueTask DisposeAsync()
+ {
+ Clear();
+ if (_alertTarget != null)
+ await _alertTarget.DisposeAsync();
+ await base.DisposeAsync();
+ }
+ }
+
+ ///
+ /// Tracks performance metrics and detects anomalies
+ ///
+ public class PerformanceMetricTracker
+ {
+ private readonly string _metricName;
+ private readonly int _windowSize;
+ private readonly double _threshold;
+ private readonly List _measurements = new();
+ private readonly List _recentAnomalies = new();
+ private double _sum = 0;
+
+ public string MetricName => _metricName;
+ public int MeasurementCount => _measurements.Count;
+ public int AnomalyCount { get; private set; } = 0;
+ public IReadOnlyList RecentAnomalies => _recentAnomalies.AsReadOnly();
+
+ public double Average => _measurements.Count == 0 ? 0 : _sum / _measurements.Count;
+ public double StandardDeviation => CalculateStandardDeviation();
+ public double MinValue => _measurements.Count == 0 ? 0 : _measurements.Min();
+ public double MaxValue => _measurements.Count == 0 ? 0 : _measurements.Max();
+
+ public PerformanceMetricTracker(string metricName, int windowSize, double threshold)
+ {
+ _metricName = metricName;
+ _windowSize = windowSize;
+ _threshold = threshold;
+ }
+
+ public PerformanceAnomaly? AddMeasurement(double value, LogEvent sourceEvent)
+ {
+ // Add measurement
+ _measurements.Add(value);
+ _sum += value;
+
+ // Keep only recent measurements
+ if (_measurements.Count > _windowSize)
+ {
+ _sum -= _measurements[0];
+ _measurements.RemoveAt(0);
+ }
+
+ // Check for anomaly
+ if (_measurements.Count >= 10) // Need enough data for statistical analysis
+ {
+ var avg = Average;
+ var stdDev = StandardDeviation;
+
+ if (stdDev > 0)
+ {
+ var zScore = Math.Abs((value - avg) / stdDev);
+ if (zScore > _threshold)
+ {
+ var isSlowdown = value > avg;
+ var anomaly = new PerformanceAnomaly
+ {
+ MetricName = _metricName,
+ CurrentValue = value,
+ AverageValue = avg,
+ StandardDeviation = stdDev,
+ DeviationMultiplier = zScore,
+ IsSlowdown = isSlowdown,
+ DetectedAt = DateTime.UtcNow,
+ Description = $"Performance anomaly detected in '{_metricName}': " +
+ $"value {value:F2} is {(isSlowdown ? "higher" : "lower")} " +
+ $"than average {avg:F2} by {zScore:F2} standard deviations"
+ };
+
+ _recentAnomalies.Add(anomaly);
+ if (_recentAnomalies.Count > 100)
+ _recentAnomalies.RemoveAt(0);
+
+ AnomalyCount++;
+ return anomaly;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private double CalculateStandardDeviation()
+ {
+ if (_measurements.Count < 2)
+ return 0;
+
+ var avg = Average;
+ var sumSquaredDiff = _measurements.Sum(x => Math.Pow(x - avg, 2));
+ return Math.Sqrt(sumSquaredDiff / _measurements.Count);
+ }
+ }
+
+ ///
+ /// Represents a detected performance anomaly
+ ///
+ public class PerformanceAnomaly
+ {
+ public string MetricName { get; set; }
+ public double CurrentValue { get; set; }
+ public double AverageValue { get; set; }
+ public double StandardDeviation { get; set; }
+ public double DeviationMultiplier { get; set; }
+ public bool IsSlowdown { get; set; }
+ public DateTime DetectedAt { get; set; }
+ public string Description { get; set; }
+
+ public override string ToString()
+ {
+ return $"Anomaly[{MetricName}]: {CurrentValue:F2} (avg: {AverageValue:F2}, " +
+ $"deviation: {DeviationMultiplier:F2}σ) - {(IsSlowdown ? "SLOWDOWN" : "SPEEDUP")}";
+ }
+ }
+
+ ///
+ /// Statistics about anomaly detection
+ ///
+ public class AnomalyDetectionStatistics
+ {
+ public int MonitoredMetrics { get; set; }
+ public int TotalMeasurements { get; set; }
+ public int TotalAnomaliesDetected { get; set; }
+ public Dictionary MetricStatistics { get; set; } = new();
+
+ public override string ToString()
+ {
+ return $"AnomalyStats: Metrics={MonitoredMetrics}, Measurements={TotalMeasurements}, " +
+ $"Anomalies={TotalAnomaliesDetected}";
+ }
+ }
+
+ ///
+ /// Statistics for a single metric
+ ///
+ public class MetricStatistic
+ {
+ public double AverageValue { get; set; }
+ public double StandardDeviation { get; set; }
+ public double MinValue { get; set; }
+ public double MaxValue { get; set; }
+ public int AnomalyCount { get; set; }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCat.LogStack/Extensions/CoolFeaturesExtensions.cs b/EonaCat.LogStack/EonaCat.LogStack/Extensions/CoolFeaturesExtensions.cs
new file mode 100644
index 0000000..c2c3c01
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/Extensions/CoolFeaturesExtensions.cs
@@ -0,0 +1,119 @@
+using EonaCat.LogStack.Core;
+using EonaCat.LogStack.Flows;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace EonaCat.LogStack.Extensions
+{
+ // 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.
+
+ ///
+ /// Extension methods for the new cool logging features
+ ///
+ public static class CoolFeaturesExtensions
+ {
+ ///
+ /// Add a filter preset flow to route specific log types
+ ///
+ public static LogFilterPresetFlow AddFilteredFlow(
+ this IFlow targetFlow,
+ string presetName)
+ {
+ var preset = LogFilterPresetRegistry.Get(presetName)
+ ?? throw new ArgumentException($"Preset '{presetName}' not found", nameof(presetName));
+ return new LogFilterPresetFlow(targetFlow, preset);
+ }
+
+ ///
+ /// Add a custom filter preset flow
+ ///
+ public static LogFilterPresetFlow AddCustomFilterFlow(
+ this IFlow targetFlow,
+ string name,
+ Func predicate)
+ {
+ var preset = new CustomPreset(name, predicate);
+ return new LogFilterPresetFlow(targetFlow, preset);
+ }
+
+ ///
+ /// Generate a heatmap visualization of stored logs
+ ///
+ public static string GenerateVisualization(
+ this LocalStorageQueryFlow queryFlow,
+ VisualizationType type = VisualizationType.Timeline,
+ int? width = null,
+ int? height = null)
+ {
+ var events = queryFlow.AllEvents;
+
+ return type switch
+ {
+ VisualizationType.Timeline => LogHeatmapAnalyzer.GenerateTimelineHeatmap(
+ events, width: width ?? 60, height: height ?? 10),
+ VisualizationType.LevelDistribution => LogHeatmapAnalyzer.GenerateLevelDistributionHeatmap(
+ events, width: width ?? 50),
+ VisualizationType.CategoryDistribution => LogHeatmapAnalyzer.GenerateCategoryHeatmap(
+ events, width: width ?? 50, topN: height ?? 10),
+ VisualizationType.ActivityMatrix => LogHeatmapAnalyzer.GenerateActivityMatrixHeatmap(events),
+ VisualizationType.Compact => LogHeatmapAnalyzer.GenerateCompactHeatmap(
+ events, buckets: width ?? 40),
+ _ => "Unknown visualization type"
+ };
+ }
+
+ ///
+ /// Print correlation group summary
+ ///
+ public static string GetCorrelationSummary(this CorrelationGroup group)
+ {
+ return $"CorrelationGroup [ID: {group.CorrelationId}, Events: {group.Events.Count}, Duration: {group.Time().TotalMilliseconds:F0}ms]";
+ }
+
+ ///
+ /// Print performance anomaly details
+ ///
+ public static string GetAnomalySummary(this PerformanceAnomaly anomaly)
+ {
+ return anomaly.ToString();
+ }
+
+ ///
+ /// Get available filter presets
+ ///
+ public static IEnumerable GetAvailableFilterPresets()
+ {
+ return LogFilterPresetRegistry.GetAvailablePresets();
+ }
+
+ ///
+ /// Print log storage statistics
+ ///
+ public static string GetStorageStatsSummary(this LogStorageStatistics stats)
+ {
+ return stats.ToString();
+ }
+
+ ///
+ /// Print anomaly detection statistics
+ ///
+ public static string GetAnomalyStatsSummary(this AnomalyDetectionStatistics stats)
+ {
+ return stats.ToString();
+ }
+ }
+
+ ///
+ /// Types of visualizations available
+ ///
+ public enum VisualizationType
+ {
+ Timeline,
+ LevelDistribution,
+ CategoryDistribution,
+ ActivityMatrix,
+ Compact
+ }
+}
diff --git a/EonaCat.LogStack/EonaCat.LogStack/LogHeatmapAnalyzer.cs b/EonaCat.LogStack/EonaCat.LogStack/LogHeatmapAnalyzer.cs
new file mode 100644
index 0000000..444e46a
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/LogHeatmapAnalyzer.cs
@@ -0,0 +1,285 @@
+using EonaCat.LogStack.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace EonaCat.LogStack
+{
+ // 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.
+
+ ///
+ /// LogHeatmapAnalyzer generates ASCII heatmaps to visualize logging frequency patterns over time.
+ /// Useful for identifying traffic patterns, spikes, and patterns in log generation.
+ ///
+ public sealed class LogHeatmapAnalyzer
+ {
+ private const char HotCharacter = '█'; // Full block
+ private const char WarmCharacter = '▓'; // Dark shade
+ private const char CoolCharacter = '▒'; // Medium shade
+ private const char ColdCharacter = '░'; // Light shade
+ private const char EmptyCharacter = '·'; // Dot for empty
+
+ ///
+ /// Generate a heatmap showing logging frequency over time
+ ///
+ /// Log events to analyze
+ /// Time bucket size in minutes (e.g., 5 = 5-minute buckets)
+ /// Width of the heatmap (default 60)
+ /// Height of the heatmap (default 10)
+ /// ASCII art heatmap string
+ public static string GenerateTimelineHeatmap(
+ IEnumerable events,
+ int timeResolutionMinutes = 5,
+ int width = 60,
+ int height = 10)
+ {
+ if (timeResolutionMinutes <= 0)
+ throw new ArgumentException("Time resolution must be positive", nameof(timeResolutionMinutes));
+
+ var eventList = events?.ToList() ?? new List();
+ if (eventList.Count == 0)
+ return "No events to visualize";
+
+ var timeBuckets = CreateTimeBuckets(eventList, timeResolutionMinutes);
+ if (timeBuckets.Count == 0)
+ return "No valid time data";
+
+ var maxCount = timeBuckets.Values.Max();
+ if (maxCount == 0)
+ return "All buckets empty";
+
+ return RenderHeatmap(timeBuckets, width, height, maxCount);
+ }
+
+ ///
+ /// Generate a heatmap by log level distribution
+ ///
+ public static string GenerateLevelDistributionHeatmap(IEnumerable events, int width = 50)
+ {
+ var eventList = events?.ToList() ?? new List();
+ if (eventList.Count == 0)
+ return "No events to visualize";
+
+ var levels = new[] { LogLevel.Trace, LogLevel.Debug, LogLevel.Information, LogLevel.Warning, LogLevel.Error, LogLevel.Critical };
+ var levelCounts = levels.ToDictionary(l => l, l => eventList.Count(e => e.Level == l));
+
+ var sb = new StringBuilder();
+ sb.AppendLine("Log Level Distribution:");
+ sb.AppendLine(new string('=', width));
+
+ var maxCount = levelCounts.Values.Max();
+ if (maxCount == 0)
+ return "No events";
+
+ foreach (var level in levels)
+ {
+ var count = levelCounts[level];
+ if (count == 0 && level != LogLevel.Information) continue;
+
+ var percentage = (double)count / eventList.Count * 100;
+ var barLength = (int)((double)count / maxCount * (width - 20));
+ var bar = new string(GetHeatCharacter(percentage), Math.Max(1, barLength));
+
+ sb.AppendLine($"{level,-12} | {bar} {count,5} ({percentage,5:F1}%)");
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Generate a heatmap for category distribution
+ ///
+ public static string GenerateCategoryHeatmap(IEnumerable events, int width = 50, int topN = 10)
+ {
+ var eventList = events?.ToList() ?? new List();
+ if (eventList.Count == 0)
+ return "No events to visualize";
+
+ var categoryCounts = eventList
+ .GroupBy(e => e.Category ?? "Unknown")
+ .OrderByDescending(g => g.Count())
+ .Take(topN)
+ .ToDictionary(g => g.Key, g => g.Count());
+
+ var sb = new StringBuilder();
+ sb.AppendLine($"Top {topN} Categories:");
+ sb.AppendLine(new string('=', width));
+
+ var maxCount = categoryCounts.Values.Max();
+ if (maxCount == 0)
+ return "No categories";
+
+ foreach (var kvp in categoryCounts)
+ {
+ var category = kvp.Key.Length > 15 ? kvp.Key.Substring(0, 12) + "..." : kvp.Key;
+ var count = kvp.Value;
+ var percentage = (double)count / eventList.Count * 100;
+ var barLength = (int)((double)count / maxCount * (width - 25));
+ var bar = new string(GetHeatCharacter(percentage), Math.Max(1, barLength));
+
+ sb.AppendLine($"{category,-15} | {bar} {count,5} ({percentage,5:F1}%)");
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Generate a compact inline heatmap for quick visualization
+ ///
+ public static string GenerateCompactHeatmap(IEnumerable events, int buckets = 40)
+ {
+ var eventList = events?.ToList() ?? new List();
+ if (eventList.Count == 0)
+ return string.Empty;
+
+ var timeBuckets = CreateTimeBuckets(eventList, (int)Math.Ceiling((double)GetTimeSpanMinutes(eventList) / buckets));
+ var maxCount = timeBuckets.Values.Max();
+ if (maxCount == 0)
+ return string.Empty;
+
+ var sb = new StringBuilder();
+ foreach (var bucket in timeBuckets.OrderBy(kvp => kvp.Key))
+ {
+ sb.Append(GetHeatCharacter((double)bucket.Value / maxCount));
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Generate a 2D heatmap showing activity by hour and day
+ ///
+ public static string GenerateActivityMatrixHeatmap(IEnumerable events)
+ {
+ var eventList = events?.ToList() ?? new List();
+ if (eventList.Count == 0)
+ return "No events";
+
+ // Create a 24x7 matrix (hours x days)
+ var hourlyMatrix = new int[24, 7];
+ var minDate = new DateTime(eventList.Min(e => e.Timestamp));
+ var maxDate = new DateTime(eventList.Max(e => e.Timestamp));
+ var daySpan = (int)Math.Ceiling((maxDate - minDate).TotalDays);
+
+ foreach (var evt in eventList)
+ {
+ var timestamp = new DateTime(evt.Timestamp);
+ var hour = timestamp.Hour;
+ var dayOfWeek = (int)timestamp.DayOfWeek;
+ hourlyMatrix[hour, dayOfWeek]++;
+ }
+
+ var sb = new StringBuilder();
+ sb.AppendLine("Activity by Hour (rows) and Day of Week (cols):");
+ sb.AppendLine(" Sun Mon Tue Wed Thu Fri Sat");
+ sb.AppendLine(new string('-', 37));
+
+ var maxValue = 0;
+ for (int h = 0; h < 24; h++)
+ for (int d = 0; d < 7; d++)
+ maxValue = Math.Max(maxValue, hourlyMatrix[h, d]);
+
+ if (maxValue == 0)
+ return "No hourly data";
+
+ for (int h = 0; h < 24; h++)
+ {
+ sb.Append($"{h:D2}h: ");
+ for (int d = 0; d < 7; d++)
+ {
+ var value = hourlyMatrix[h, d];
+ var intensity = value == 0 ? 0 : (double)value / maxValue;
+ sb.Append(GetHeatCharacter(intensity));
+ sb.Append(" ");
+ }
+ sb.AppendLine();
+ }
+
+ return sb.ToString();
+ }
+
+ // Private helpers
+
+ private static Dictionary CreateTimeBuckets(List events, int bucketMinutes)
+ {
+ var buckets = new Dictionary();
+
+ foreach (var evt in events)
+ {
+ var timestamp = new DateTime(evt.Timestamp);
+ var bucketTime = timestamp.AddSeconds(-timestamp.Second)
+ .AddMilliseconds(-timestamp.Millisecond)
+ .AddMinutes(-(timestamp.Minute % bucketMinutes));
+
+ if (buckets.ContainsKey(bucketTime))
+ buckets[bucketTime]++;
+ else
+ buckets[bucketTime] = 1;
+ }
+
+ return buckets;
+ }
+
+ private static string RenderHeatmap(Dictionary timeBuckets, int width, int height, int maxCount)
+ {
+ var sortedBuckets = timeBuckets.OrderBy(kvp => kvp.Key).ToList();
+ var step = Math.Max(1, sortedBuckets.Count / width);
+
+ var sb = new StringBuilder();
+ sb.AppendLine($"Timeline Heatmap ({sortedBuckets.Count} time periods):");
+ sb.AppendLine(new string('=', width + 4));
+
+ // Render vertical bars
+ for (int row = height; row > 0; row--)
+ {
+ var threshold = (double)maxCount / height * row;
+ sb.Append("| ");
+
+ for (int col = 0; col < width; col++)
+ {
+ var idx = col * step;
+ if (idx < sortedBuckets.Count)
+ {
+ var count = sortedBuckets[idx].Value;
+ var intensity = (double)count / maxCount;
+ var displayThreshold = (double)(row - 1) / height;
+
+ sb.Append(intensity > displayThreshold ? HotCharacter : ' ');
+ }
+ else
+ {
+ sb.Append(' ');
+ }
+ }
+
+ sb.AppendLine(" |");
+ }
+
+ sb.AppendLine(new string('=', width + 4));
+ if (sortedBuckets.Count > 0)
+ sb.AppendLine($"From: {sortedBuckets.First().Key:yyyy-MM-dd HH:mm}, To: {sortedBuckets.Last().Key:yyyy-MM-dd HH:mm}");
+
+ return sb.ToString();
+ }
+
+ private static char GetHeatCharacter(double intensity)
+ {
+ if (intensity < 0.01) return EmptyCharacter;
+ if (intensity < 0.35) return ColdCharacter;
+ if (intensity < 0.65) return CoolCharacter;
+ if (intensity < 0.85) return WarmCharacter;
+ return HotCharacter;
+ }
+
+ private static int GetTimeSpanMinutes(List events)
+ {
+ if (events.Count == 0) return 1;
+ var maxTicks = events.Max(e => e.Timestamp);
+ var minTicks = events.Min(e => e.Timestamp);
+ var span = new TimeSpan(maxTicks - minTicks);
+ return Math.Max(1, (int)span.TotalMinutes);
+ }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCatLogger.cs b/EonaCat.LogStack/EonaCatLogger.cs
index 9e25c1b..011a5c9 100644
--- a/EonaCat.LogStack/EonaCatLogger.cs
+++ b/EonaCat.LogStack/EonaCatLogger.cs
@@ -2,6 +2,11 @@
using EonaCat.LogStack.Boosters;
using EonaCat.LogStack.Core;
using EonaCat.LogStack.Flows;
+using EonaCat.LogStack.Routing;
+using EonaCat.LogStack.Sampling;
+using EonaCat.LogStack.Anomalies;
+using EonaCat.LogStack.DeadLettering;
+using EonaCat.LogStack.Diagnostics;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -62,6 +67,13 @@ namespace EonaCat.LogStack
// Dynamic level controller
private volatile DynamicLevelController? _dynamicLevel;
+ // Superior logging features
+ private readonly IntelligentRouter? _intelligentRouter;
+ private readonly AdaptiveSamplingEngine? _adaptiveSampler;
+ private readonly AnomalyDetector? _anomalyDetector;
+ private readonly ContextSnapshotCollector? _contextSnapshots;
+ private DeadLetterQueue? _deadLetterQueue;
+
public event EventHandler OnLog;
///
@@ -69,13 +81,23 @@ namespace EonaCat.LogStack
///
public EonaCatLogStack(string category = "Application",
LogLevel minimumLevel = LogLevel.Trace,
- TimestampMode timestampMode = TimestampMode.Utc)
+ TimestampMode timestampMode = TimestampMode.Utc,
+ bool enableIntelligentRouting = false,
+ bool enableAdaptiveSampling = false,
+ bool enableAnomalyDetection = false,
+ bool enableContextSnapshots = false)
{
_category = category ?? throw new ArgumentNullException(nameof(category));
_minimumLevel = minimumLevel;
- _timestampMode = timestampMode;
-
- // Enable async pipeline by default
+ _timestampMode = timestampMode;
+
+ // Initialize superior features if requested
+ _intelligentRouter = enableIntelligentRouting ? new IntelligentRouter() : null;
+ _adaptiveSampler = enableAdaptiveSampling ? new AdaptiveSamplingEngine() : null;
+ _anomalyDetector = enableAnomalyDetection ? new AnomalyDetector() : null;
+ _contextSnapshots = enableContextSnapshots ? new ContextSnapshotCollector() : null;
+
+ // Enable async pipeline by default
UseAsyncPipeline();
}
@@ -119,6 +141,38 @@ namespace EonaCat.LogStack
return this;
}
+ ///
+ /// Gets the Intelligent Router for pattern-based flow routing (if enabled).
+ ///
+ public IntelligentRouter GetIntelligentRouter() => _intelligentRouter;
+
+ ///
+ /// Gets the Adaptive Sampling Engine (if enabled).
+ ///
+ public AdaptiveSamplingEngine GetAdaptiveSampler() => _adaptiveSampler;
+
+ ///
+ /// Gets the Anomaly Detector (if enabled).
+ ///
+ public AnomalyDetector GetAnomalyDetector() => _anomalyDetector;
+
+ ///
+ /// Gets the Context Snapshot Collector (if enabled).
+ ///
+ public ContextSnapshotCollector GetContextSnapshots() => _contextSnapshots;
+
+ ///
+ /// Gets or creates the Dead Letter Queue for handling failed logs.
+ ///
+ public DeadLetterQueue GetDeadLetterQueue()
+ {
+ if (_deadLetterQueue == null)
+ {
+ _deadLetterQueue = new DeadLetterQueue();
+ }
+ return _deadLetterQueue;
+ }
+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private LogLevel EffectiveMinLevel() =>
_dynamicLevel != null ? _dynamicLevel.CurrentLevel : _minimumLevel;
@@ -413,6 +467,13 @@ namespace EonaCat.LogStack
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ProcessLogEvent(ref LogEventBuilder builder)
{
+ // Check adaptive sampling first - if sampling is enabled and this log is dropped, return early
+ if (_adaptiveSampler != null && !_adaptiveSampler.ShouldSample())
+ {
+ Interlocked.Increment(ref _totalDroppedCount);
+ return;
+ }
+
// Apply boosters
lock (_boosters)
{
@@ -443,11 +504,30 @@ namespace EonaCat.LogStack
}
var logEvent = builder.Build();
- Interlocked.Increment(ref _totalLoggedCount);
-
- if (!_asyncChannel!.Writer.TryWrite(logEvent))
- {
- Interlocked.Increment(ref _totalDroppedCount);
+
+ // Record sampling metric
+ if (_adaptiveSampler != null)
+ {
+ _adaptiveSampler.RecordLogEvent();
+ }
+
+ // Detect anomalies
+ if (_anomalyDetector != null)
+ {
+ _anomalyDetector.AnalyzeEvent(logEvent);
+ }
+
+ // Capture context snapshots on critical logs
+ if (_contextSnapshots != null)
+ {
+ _contextSnapshots.CaptureIfNeeded(logEvent);
+ }
+
+ Interlocked.Increment(ref _totalLoggedCount);
+
+ if (!_asyncChannel!.Writer.TryWrite(logEvent))
+ {
+ Interlocked.Increment(ref _totalDroppedCount);
}
}
@@ -458,18 +538,46 @@ namespace EonaCat.LogStack
{
await foreach (var logEvent in reader.ReadAllAsync(cancellationToken).ConfigureAwait(false))
{
- var flows = _concurrentFlows;
- foreach (var flow in flows)
+ // If intelligent router is enabled, use it for routing
+ if (_intelligentRouter != null)
{
try
{
- var result = await flow.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
- if (result == WriteResult.Dropped)
+ await _intelligentRouter.RouteAsync(logEvent).ConfigureAwait(false);
+ }
+ catch { /* Router should handle its own errors */ }
+ }
+ else
+ {
+ // Default routing to all flows
+ var flows = _concurrentFlows;
+ foreach (var flow in flows)
+ {
+ try
{
- Interlocked.Increment(ref _totalDroppedCount);
+ var result = await flow.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
+ if (result == WriteResult.Dropped)
+ {
+ Interlocked.Increment(ref _totalDroppedCount);
+
+ // Enqueue to DLQ if available
+ if (_deadLetterQueue != null)
+ {
+ _deadLetterQueue.Enqueue(logEvent, $"Dropped by flow '{flow.Name}'");
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ // Enqueue to DLQ if available
+ if (_deadLetterQueue != null)
+ {
+ _deadLetterQueue.Enqueue(logEvent,
+ $"Exception in flow '{flow.Name}': {ex.Message}",
+ ex);
+ }
}
}
- catch { }
}
}
}
@@ -557,6 +665,48 @@ namespace EonaCat.LogStack
///
public LoggingMetrics GetMetricsCollector() => _metrics;
+ ///
+ /// Gets comprehensive statistics about all superior features.
+ ///
+ public SuperiorFeaturesStats GetSuperiorFeaturesStats()
+ {
+ var stats = new SuperiorFeaturesStats();
+
+ if (_intelligentRouter != null)
+ {
+ stats.RouterStats = _intelligentRouter.GetStats();
+ stats.IntelligentRoutingEnabled = true;
+ }
+
+ if (_adaptiveSampler != null)
+ {
+ stats.SamplingMetrics = _adaptiveSampler.GetMetrics();
+ stats.AdaptiveSamplingEnabled = true;
+ }
+
+ if (_anomalyDetector != null)
+ {
+ stats.AnomalyStats = _anomalyDetector.GetStats();
+ stats.RecentAnomalies = _anomalyDetector.GetRecentAnomalies(5);
+ stats.AnomalyDetectionEnabled = true;
+ }
+
+ if (_contextSnapshots != null)
+ {
+ stats.SnapshotStats = _contextSnapshots.GetStats();
+ stats.ContextSnapshotsEnabled = true;
+ }
+
+ if (_deadLetterQueue != null)
+ {
+ stats.DlqStats = _deadLetterQueue.GetStats();
+ stats.DlqFailureReasons = _deadLetterQueue.GetFailureReasonStats();
+ stats.DeadLetterQueueEnabled = true;
+ }
+
+ return stats;
+ }
+
public async ValueTask DisposeAsync()
{
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/AdaptiveSamplingEngine.cs b/EonaCat.LogStack/EonaCatLoggerCore/AdaptiveSamplingEngine.cs
new file mode 100644
index 0000000..6e7dc99
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/AdaptiveSamplingEngine.cs
@@ -0,0 +1,314 @@
+using EonaCat.LogStack.Core;
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+// 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.
+
+namespace EonaCat.LogStack.Sampling;
+
+///
+/// Adaptive sampling engine that dynamically adjusts sampling rates based on log volume and system resources.
+/// Superior to static sampling as it prevents log loss during normal operation while reducing overhead during peaks.
+///
+public class AdaptiveSamplingEngine
+{
+ private double _samplingRate = 1.0; // Sample 100% of logs by default
+ private double _targetThroughput = 10000; // Target 10k logs/sec
+ private long _logCount;
+ private long _lastAdjustmentTicks;
+ private readonly Stopwatch _perfCounter = Stopwatch.StartNew();
+ private readonly double _adjustmentIntervalMs;
+ private readonly SamplingStrategy _strategy;
+ private readonly object _statsLock = new object();
+
+ // CPU and Memory monitoring
+ private double _currentCpuPercent;
+ private double _currentMemoryPercent;
+ private readonly double _cpuThreshold;
+ private readonly double _memoryThreshold;
+ private readonly Process _currentProcess = Process.GetCurrentProcess();
+ private DateTime _lastCpuCheck;
+
+ public AdaptiveSamplingEngine(
+ SamplingStrategy strategy = SamplingStrategy.Throughput,
+ double targetThroughput = 10000,
+ double adjustmentIntervalMs = 5000,
+ double cpuThreshold = 0.80,
+ double memoryThreshold = 0.85)
+ {
+ _strategy = strategy;
+ _targetThroughput = targetThroughput;
+ _adjustmentIntervalMs = adjustmentIntervalMs;
+ _cpuThreshold = cpuThreshold;
+ _memoryThreshold = memoryThreshold;
+ _lastAdjustmentTicks = _perfCounter.ElapsedTicks;
+ _lastCpuCheck = DateTime.UtcNow;
+ }
+
+ ///
+ /// Determines if a log event should be sampled (kept) or dropped.
+ /// Returns true if the event should be logged.
+ ///
+ public bool ShouldSample()
+ {
+ // Periodically adjust sampling rate
+ lock (_statsLock)
+ {
+ if (_perfCounter.ElapsedTicks - _lastAdjustmentTicks >
+ TicksFromMilliseconds(_adjustmentIntervalMs))
+ {
+ AdjustSamplingRate();
+ }
+ }
+
+ // Use random sampling based on current rate
+ if (_samplingRate >= 1.0)
+ return true;
+
+ if (_samplingRate <= 0.0)
+ return false;
+
+ return ThreadSafeRandom.NextDouble() < _samplingRate;
+ }
+
+ ///
+ /// Adjusts the sampling rate based on current metrics and strategy.
+ ///
+ private void AdjustSamplingRate()
+ {
+ _lastAdjustmentTicks = _perfCounter.ElapsedTicks;
+ var currentLogCount = Interlocked.Exchange(ref _logCount, 0);
+
+ UpdateResourceMetrics();
+
+ switch (_strategy)
+ {
+ case SamplingStrategy.Throughput:
+ AdjustForThroughput();
+ break;
+ case SamplingStrategy.Resources:
+ AdjustForResources();
+ break;
+ case SamplingStrategy.Hybrid:
+ AdjustForThroughput();
+ AdjustForResources();
+ break;
+ case SamplingStrategy.Adaptive:
+ AdjustAdaptive();
+ break;
+ }
+
+ // Clamp between 0 and 1
+ _samplingRate = Math.Max(0.0, Math.Min(1.0, _samplingRate));
+ }
+
+ private void AdjustForThroughput()
+ {
+ var elapsedSeconds = _adjustmentIntervalMs / 1000.0;
+ var currentLogCount = 0L;
+ lock (_statsLock)
+ {
+ currentLogCount = Interlocked.Read(ref _logCount);
+ }
+ var actualThroughput = currentLogCount / elapsedSeconds;
+
+ if (actualThroughput > _targetThroughput * 1.2) // 20% over target
+ {
+ // Reduce sampling rate
+ _samplingRate *= 0.9;
+ }
+ else if (actualThroughput < _targetThroughput * 0.8) // 20% under target
+ {
+ // Increase sampling rate
+ _samplingRate *= 1.1;
+ }
+ }
+
+ private void AdjustForResources()
+ {
+ if (_currentCpuPercent > _cpuThreshold || _currentMemoryPercent > _memoryThreshold)
+ {
+ // High resource usage - reduce sampling rate more aggressively
+ _samplingRate *= 0.7;
+ }
+ else if (_currentCpuPercent < _cpuThreshold * 0.5 && _currentMemoryPercent < _memoryThreshold * 0.5)
+ {
+ // Low resource usage - increase sampling rate
+ _samplingRate *= 1.15;
+ }
+ }
+
+ private void AdjustAdaptive()
+ {
+ /* Adaptive strategy: adjust based on both throughput and resources,
+ with memory being more critical than CPU */
+ var cpuFactor = _currentCpuPercent / _cpuThreshold;
+ var memoryFactor = _currentMemoryPercent / _memoryThreshold;
+
+ // Memory pressure has higher weight
+ var resourcePressure = (cpuFactor * 0.4) + (memoryFactor * 0.6);
+
+ if (resourcePressure > 1.0)
+ {
+ _samplingRate *= (1.0 - (resourcePressure - 1.0) * 0.5);
+ }
+ else if (resourcePressure < 0.5)
+ {
+ _samplingRate *= 1.1;
+ }
+ }
+
+ private void UpdateResourceMetrics()
+ {
+ var now = DateTime.UtcNow;
+ if ((now - _lastCpuCheck).TotalMilliseconds < 1000)
+ return; // Update at most once per second to avoid overhead
+
+ _lastCpuCheck = now;
+
+ try
+ {
+ // CPU usage (process)
+ _currentCpuProcess = _currentProcess.TotalProcessorTime.TotalMilliseconds;
+
+ // Simplified: estimate CPU % based on processor count
+ var cpuUsageEstimate = Math.Min(1.0, _currentCpuProcess / (Environment.ProcessorCount * 100));
+ _currentCpuPercent = cpuUsageEstimate;
+
+ // Memory usage
+ var totalMemory = GC.GetTotalMemory(false);
+ var workingSet = _currentProcess.WorkingSet64;
+
+ // Estimate based on available system memory
+ // In production, use MemoryMarshal or other APIs for more precise data
+ _currentMemoryPercent = Math.Min(1.0, workingSet / (8.0 * 1024 * 1024 * 1024)); // Assume 8GB
+ }
+ catch
+ {
+ // If we can't get metrics, don't crash
+ }
+ }
+
+ private double _currentCpuProcess;
+
+ ///
+ /// Sets the target throughput for throughput-based sampling.
+ ///
+ public void SetTargetThroughput(double logsPerSecond)
+ {
+ _targetThroughput = Math.Max(1, logsPerSecond);
+ }
+
+ ///
+ /// Gets current sampling rate (0.0 to 1.0).
+ ///
+ public double GetSamplingRate() => _samplingRate;
+
+ ///
+ /// Gets current resource utilization metrics.
+ ///
+ public SamplingMetrics GetMetrics()
+ {
+ lock (_statsLock)
+ {
+ return new SamplingMetrics
+ {
+ SamplingRate = _samplingRate,
+ CurrentCpuPercent = _currentCpuPercent,
+ CurrentMemoryPercent = _currentMemoryPercent,
+ Strategy = _strategy,
+ TargetThroughput = _targetThroughput,
+ LogsProcessedInLastInterval = Interlocked.Read(ref _logCount)
+ };
+ }
+ }
+
+ ///
+ /// Records a log event (increments counter for sampling decisions).
+ ///
+ public void RecordLogEvent()
+ {
+ Interlocked.Increment(ref _logCount);
+ }
+
+ private static long TicksFromMilliseconds(double ms)
+ {
+ return (long)(ms * Stopwatch.Frequency / 1000.0);
+ }
+}
+
+///
+/// Sampling strategies for adaptive engine.
+///
+public enum SamplingStrategy
+{
+ ///
+ /// Adjusts sampling based on throughput to match target logs/sec
+ ///
+ Throughput,
+
+ ///
+ /// Adjusts sampling based on CPU and memory usage
+ ///
+ Resources,
+
+ ///
+ /// Combines throughput and resource monitoring
+ ///
+ Hybrid,
+
+ ///
+ /// Advanced adaptive strategy that learns optimal rate
+ ///
+ Adaptive
+}
+
+///
+/// Metrics from the adaptive sampling engine.
+///
+public class SamplingMetrics
+{
+ ///
+ /// Current sampling rate (0.0 = drop all, 1.0 = keep all)
+ ///
+ public double SamplingRate { get; set; }
+
+ ///
+ /// Current CPU usage percentage (0.0 to 1.0)
+ ///
+ public double CurrentCpuPercent { get; set; }
+
+ ///
+ /// Current memory usage percentage (0.0 to 1.0)
+ ///
+ public double CurrentMemoryPercent { get; set; }
+
+ ///
+ /// Active sampling strategy
+ ///
+ public SamplingStrategy Strategy { get; set; }
+
+ ///
+ /// Target throughput in logs per second
+ ///
+ public double TargetThroughput { get; set; }
+
+ ///
+ /// Logs processed in last adjustment interval
+ ///
+ public long LogsProcessedInLastInterval { get; set; }
+}
+
+///
+/// Thread-safe random number generator for sampling decisions.
+///
+internal static class ThreadSafeRandom
+{
+ private static readonly ThreadLocal _random = new ThreadLocal(
+ () => new Random(Guid.NewGuid().GetHashCode())
+ );
+
+ public static double NextDouble() => _random.Value.NextDouble();
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/AnomalyDetector.cs b/EonaCat.LogStack/EonaCatLoggerCore/AnomalyDetector.cs
new file mode 100644
index 0000000..83ed16c
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/AnomalyDetector.cs
@@ -0,0 +1,368 @@
+using EonaCat.LogStack.Core;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Diagnostics;
+
+// 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.
+
+namespace EonaCat.LogStack.Anomalies;
+
+///
+/// Real-time anomaly detector that identifies unusual logging patterns and escalates them.
+/// Superior to simple threshold-based alerting as it learns normal behavior over time.
+///
+public class AnomalyDetector
+{
+ private readonly ConcurrentDictionary _categoryStats
+ = new ConcurrentDictionary();
+
+ private readonly ConcurrentDictionary _levelStats
+ = new ConcurrentDictionary();
+
+ private readonly double _standardDeviationThreshold;
+ private readonly int _windowSizeSeconds;
+ private readonly object _statsLock = new object();
+ private long _totalEventsProcessed;
+
+ private readonly List _recentAlerts = new List();
+ private readonly Stopwatch _uptime = Stopwatch.StartNew();
+
+ public event EventHandler AnomalyDetected;
+
+ public AnomalyDetector(
+ double standardDeviationThreshold = 3.0,
+ int windowSizeSeconds = 300)
+ {
+ _standardDeviationThreshold = standardDeviationThreshold;
+ _windowSizeSeconds = windowSizeSeconds;
+ }
+
+ ///
+ /// Analyzes a log event for anomalies.
+ ///
+ public AnomalyAlert AnalyzeEvent(LogEvent logEvent)
+ {
+
+ _totalEventsProcessed++;
+
+ // Track category statistics
+ var categoryStats = _categoryStats.GetOrAdd(logEvent.Category ?? "Unknown",
+ _ => new CategoryStatistics());
+ categoryStats.RecordEvent(logEvent);
+
+ // Track level statistics
+ var levelStats = _levelStats.GetOrAdd(logEvent.Level,
+ _ => new LevelStatistics());
+ levelStats.RecordEvent();
+
+ // Detect anomalies
+ var anomalies = new List();
+
+ // Check for unusual error spike
+ if (logEvent.Level == LogLevel.Error || logEvent.Level == LogLevel.Critical)
+ {
+ var errorRate = levelStats.GetErrorRatePerSecond();
+ if (errorRate > 50) // More than 50 errors per second
+ {
+ anomalies.Add($"High error rate detected: {errorRate:F2} errors/sec");
+ }
+ }
+
+ // Check for category anomalies
+ if (categoryStats.IsAnomalous(_standardDeviationThreshold))
+ {
+ anomalies.Add($"Category '{logEvent.Category}' showing unusual activity");
+ }
+
+ // Check for exception frequency spike
+ if (logEvent.Exception != null)
+ {
+ var exceptionKey = logEvent.Exception.GetType().Name;
+ if (categoryStats.RecordException(exceptionKey) > 5)
+ {
+ anomalies.Add($"Exception '{exceptionKey}' spike detected");
+ }
+ }
+
+ // Check for message repetition
+ if (logEvent.Message.Length > 0)
+ {
+ if (categoryStats.RecordMessageHash(logEvent.Message.ToString().GetHashCode()) > 10)
+ {
+ anomalies.Add("Repeated message pattern detected (possible infinite loop)");
+ }
+ }
+
+ if (anomalies.Any())
+ {
+ var alert = new AnomalyAlert
+ {
+ Timestamp = DateTime.UtcNow,
+ Category = logEvent.Category,
+ Level = logEvent.Level,
+ LogEvent = logEvent,
+ Anomalies = anomalies,
+ AnomalySeverity = DetermineAnomalySeverity(anomalies),
+ SystemMetrics = CaptureSystemMetrics()
+ };
+
+ lock (_statsLock)
+ {
+ _recentAlerts.Add(alert);
+ if (_recentAlerts.Count > 1000)
+ _recentAlerts.RemoveAt(0); // Keep last 1000 alerts
+ }
+
+ AnomalyDetected?.Invoke(this, alert);
+ return alert;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Gets statistics summary for analysis.
+ ///
+ public AnomalyDetectorStats GetStats()
+ {
+ lock (_statsLock)
+ {
+ return new AnomalyDetectorStats
+ {
+ TotalEventsProcessed = _totalEventsProcessed,
+ UniqueCategories = _categoryStats.Count,
+ RecentAnomalies = _recentAlerts.Count,
+ AnomalyRate = _recentAlerts.Count / Math.Max(1, _uptime.Elapsed.TotalMinutes),
+ TopCategories = _categoryStats
+ .OrderByDescending(x => x.Value.EventCount)
+ .Take(5)
+ .Select(x => new CategorySummary
+ {
+ Category = x.Key,
+ EventCount = x.Value.EventCount,
+ IsAnomalous = x.Value.IsAnomalous(_standardDeviationThreshold)
+ })
+ .ToList()
+ };
+ }
+ }
+
+ ///
+ /// Gets recent detected anomalies.
+ ///
+ public List GetRecentAnomalies(int count = 10)
+ {
+ lock (_statsLock)
+ {
+ return _recentAlerts.Skip(Math.Max(0, _recentAlerts.Count - count))
+ .OrderBy(a => a.Timestamp)
+ .ToList();
+ }
+ }
+
+ ///
+ /// Clears all statistics (useful for resetting baseline after deployment).
+ ///
+ public void ResetStatistics()
+ {
+ _categoryStats.Clear();
+ _levelStats.Clear();
+ lock (_statsLock)
+ {
+ _recentAlerts.Clear();
+ }
+ _totalEventsProcessed = 0;
+ }
+
+ private AnomalySeverity DetermineAnomalySeverity(List anomalies)
+ {
+ if (anomalies.Any(a => a.Contains("error rate")))
+ return AnomalySeverity.Critical;
+
+ if (anomalies.Any(a => a.Contains("Exception") || a.Contains("infinite loop")))
+ return AnomalySeverity.High;
+
+ return AnomalySeverity.Medium;
+ }
+
+ private SystemMetrics CaptureSystemMetrics()
+ {
+ var process = Process.GetCurrentProcess();
+ return new SystemMetrics
+ {
+ ProcessMemoryMb = process.WorkingSet64 / (1024 * 1024),
+ ProcessorCount = Environment.ProcessorCount,
+ Uptime = _uptime.Elapsed,
+ ThreadCount = process.Threads.Count,
+ TotalEventsProcessed = _totalEventsProcessed
+ };
+ }
+}
+
+///
+/// Tracks statistics for a log category.
+///
+internal class CategoryStatistics
+{
+ private readonly Queue _recentEventTimes = new Queue();
+ private readonly ConcurrentDictionary _messageHashCounts = new ConcurrentDictionary();
+ private readonly ConcurrentDictionary _exceptionCounts = new ConcurrentDictionary();
+
+ public long EventCount { get; private set; }
+ private long _errorCount;
+ private readonly object _lock = new object();
+
+ public void RecordEvent(LogEvent logEvent)
+ {
+ lock (_lock)
+ {
+ EventCount++;
+ _recentEventTimes.Enqueue(DateTime.UtcNow);
+ if (_recentEventTimes.Count > 1000)
+ _recentEventTimes.Dequeue();
+
+ if (logEvent.Level == LogLevel.Error || logEvent.Level == LogLevel.Critical)
+ _errorCount++;
+ }
+ }
+
+ public int RecordException(string exceptionType)
+ {
+ return _exceptionCounts.AddOrUpdate(exceptionType, 1, (k, v) => v + 1);
+ }
+
+ public int RecordMessageHash(int messageHash)
+ {
+ return _messageHashCounts.AddOrUpdate(messageHash, 1, (k, v) => v + 1);
+ }
+
+ public bool IsAnomalous(double stdDevThreshold)
+ {
+ lock (_lock)
+ {
+ if (_recentEventTimes.Count < 10)
+ return false;
+
+ var now = DateTime.UtcNow;
+ var times = _recentEventTimes.ToList();
+ var recentIntervals = new List();
+
+ for (int i = 1; i < times.Count; i++)
+ {
+ var interval = (now - times[i]).TotalSeconds;
+ if (interval > 0)
+ recentIntervals.Add(interval);
+ }
+
+ if (recentIntervals.Count < 2)
+ return false;
+
+ var mean = recentIntervals.Average();
+ var variance = recentIntervals.Average(x => Math.Pow(x - mean, 2));
+ var stdDev = Math.Sqrt(variance);
+
+ // Event times are anomalous if they're too frequent (low stdDev) or erratic (high stdDev)
+ return stdDev < 0.1 || stdDev > stdDevThreshold;
+ }
+ }
+}
+
+///
+/// Tracks statistics for a log level.
+///
+internal class LevelStatistics
+{
+ private readonly Queue _eventTimes = new Queue();
+ private readonly object _lock = new object();
+ private long _errorCount;
+
+ public void RecordEvent()
+ {
+ lock (_lock)
+ {
+ _errorCount++;
+ _eventTimes.Enqueue(DateTime.UtcNow);
+ if (_eventTimes.Count > 10000)
+ _eventTimes.Dequeue();
+ }
+ }
+
+ public double GetErrorRatePerSecond()
+ {
+ lock (_lock)
+ {
+ if (_eventTimes.Count < 2)
+ return 0;
+
+ var now = DateTime.UtcNow;
+ var span = (now - _eventTimes.Peek()).TotalSeconds;
+
+ if (span < 1)
+ return _eventTimes.Count;
+
+ return _eventTimes.Count / span;
+ }
+ }
+}
+
+///
+/// An alert raised when an anomaly is detected.
+///
+public class AnomalyAlert
+{
+ public DateTime Timestamp { get; set; }
+ public string Category { get; set; }
+ public LogLevel Level { get; set; }
+ public LogEvent LogEvent { get; set; }
+ public List Anomalies { get; set; } = new List();
+ public AnomalySeverity AnomalySeverity { get; set; }
+ public SystemMetrics SystemMetrics { get; set; }
+}
+
+///
+/// Severity of detected anomaly.
+///
+public enum AnomalySeverity
+{
+ Low,
+ Medium,
+ High,
+ Critical
+}
+
+///
+/// System metrics captured when anomaly detected.
+///
+public class SystemMetrics
+{
+ public long ProcessMemoryMb { get; set; }
+ public int ProcessorCount { get; set; }
+ public TimeSpan Uptime { get; set; }
+ public int ThreadCount { get; set; }
+ public long TotalEventsProcessed { get; set; }
+}
+
+///
+/// Statistics about anomaly detector.
+///
+public class AnomalyDetectorStats
+{
+ public long TotalEventsProcessed { get; set; }
+ public int UniqueCategories { get; set; }
+ public int RecentAnomalies { get; set; }
+ public double AnomalyRate { get; set; }
+ public List TopCategories { get; set; } = new List();
+}
+
+///
+/// Summary of a category's status.
+///
+public class CategorySummary
+{
+ public string Category { get; set; }
+ public long EventCount { get; set; }
+ public bool IsAnomalous { get; set; }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/ContextSnapshotCollector.cs b/EonaCat.LogStack/EonaCatLoggerCore/ContextSnapshotCollector.cs
new file mode 100644
index 0000000..9ae3765
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/ContextSnapshotCollector.cs
@@ -0,0 +1,392 @@
+using EonaCat.LogStack.Core;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+
+// 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.
+
+namespace EonaCat.LogStack.Diagnostics;
+
+///
+/// Context Snapshot Collector - captures heap snapshots and thread stacks on critical errors.
+/// Superior to simple error logging as it provides full context for debugging.
+///
+public class ContextSnapshotCollector
+{
+ private readonly List _snapshots = new List();
+ private readonly int _maxSnapshots;
+ private readonly object _snapshotsLock = new object();
+ private readonly LogLevel _triggerLevel;
+ private long _totalSnapshotsCaptured;
+
+ public event EventHandler SnapshotCaptured;
+
+ public ContextSnapshotCollector(
+ LogLevel triggerLevel = LogLevel.Critical,
+ int maxSnapshots = 100)
+ {
+ _triggerLevel = triggerLevel;
+ _maxSnapshots = Math.Max(10, maxSnapshots);
+ }
+
+ ///
+ /// Captures a context snapshot if the log event meets trigger conditions.
+ ///
+ public ContextSnapshot CaptureIfNeeded(LogEvent logEvent)
+ {
+ if (logEvent.Level < _triggerLevel)
+ return null;
+
+ return CaptureSnapshot(logEvent);
+ }
+
+ ///
+ /// Captures a full context snapshot for the given log event.
+ ///
+ public ContextSnapshot CaptureSnapshot(LogEvent logEvent)
+ {
+
+ var snapshot = new ContextSnapshot
+ {
+ CapturedAt = DateTime.UtcNow,
+ LogEvent = logEvent,
+ ProcessInfo = CaptureProcessInfo(),
+ MemoryInfo = CaptureMemoryInfo(),
+ ThreadInfo = CaptureThreadInfo(),
+ StackTraces = CaptureStackTraces(),
+ EnvironmentVars = CaptureEnvironmentSnapshot()
+ };
+
+ lock (_snapshotsLock)
+ {
+ _snapshots.Add(snapshot);
+ if (_snapshots.Count > _maxSnapshots)
+ _snapshots.RemoveAt(0);
+
+ _totalSnapshotsCaptured++;
+ }
+
+ SnapshotCaptured?.Invoke(this, snapshot);
+ return snapshot;
+ }
+
+ ///
+ /// Gets the most recent snapshot.
+ ///
+ public ContextSnapshot GetLatestSnapshot()
+ {
+ lock (_snapshotsLock)
+ {
+ return _snapshots.LastOrDefault();
+ }
+ }
+
+ ///
+ /// Gets all captured snapshots.
+ ///
+ public List GetSnapshots(int skip = 0, int take = 10)
+ {
+ lock (_snapshotsLock)
+ {
+ return _snapshots.Skip(skip).Take(take).ToList();
+ }
+ }
+
+ ///
+ /// Gets a snapshot by timestamp window.
+ ///
+ public ContextSnapshot GetSnapshotNear(DateTime timestamp)
+ {
+ lock (_snapshotsLock)
+ {
+ return _snapshots
+ .OrderBy(s => Math.Abs((s.CapturedAt - timestamp).TotalSeconds))
+ .FirstOrDefault();
+ }
+ }
+
+ ///
+ /// Gets statistics about captured snapshots.
+ ///
+ public SnapshotCollectorStats GetStats()
+ {
+ lock (_snapshotsLock)
+ {
+ var levelDistribution = _snapshots
+ .GroupBy(s => s.LogEvent.Level)
+ .ToDictionary(g => g.Key, g => g.Count());
+
+ return new SnapshotCollectorStats
+ {
+ TotalCaptured = _totalSnapshotsCaptured,
+ CurrentSnapshots = _snapshots.Count,
+ MaxCapacity = _maxSnapshots,
+ LevelDistribution = levelDistribution,
+ OldestSnapshot = _snapshots.FirstOrDefault()?.CapturedAt,
+ NewestSnapshot = _snapshots.LastOrDefault()?.CapturedAt
+ };
+ }
+ }
+
+ ///
+ /// Clears all snapshots.
+ ///
+ public int ClearSnapshots()
+ {
+ lock (_snapshotsLock)
+ {
+ var count = _snapshots.Count;
+ _snapshots.Clear();
+ return count;
+ }
+ }
+
+ private ProcessSnapshot CaptureProcessInfo()
+ {
+ var process = Process.GetCurrentProcess();
+ return new ProcessSnapshot
+ {
+ ProcessId = process.Id,
+ ProcessName = process.ProcessName,
+ StartTime = process.StartTime,
+ TotalProcessorTime = process.TotalProcessorTime,
+ UserProcessorTime = process.UserProcessorTime,
+ BasePriority = process.BasePriority,
+ Handles = 0, // Not available in .NET Standard
+ ThreadCount = process.Threads.Count
+ };
+ }
+
+ private MemorySnapshot CaptureMemoryInfo()
+ {
+ var totalMemory = GC.GetTotalMemory(false);
+ var process = Process.GetCurrentProcess();
+
+ var gen0Collections = GC.CollectionCount(0);
+ var gen1Collections = GC.CollectionCount(1);
+ var gen2Collections = GC.CollectionCount(2);
+
+ return new MemorySnapshot
+ {
+ ManagedHeapBytes = totalMemory,
+ ProcessWorkingSetBytes = process.WorkingSet64,
+ ProcessPrivateMemoryBytes = process.PrivateMemorySize64,
+ Gen0Collections = gen0Collections,
+ Gen1Collections = gen1Collections,
+ Gen2Collections = gen2Collections,
+ TotalAllocatedBytes = totalMemory, // Simplified fallback
+ IsHighMemoryPressure = false // Conservative estimate
+ };
+ }
+
+ private ThreadSnapshot CaptureThreadInfo()
+ {
+ var process = Process.GetCurrentProcess();
+ var threads = process.Threads;
+ var threadDetails = new List();
+
+ try
+ {
+ foreach (ProcessThread t in threads)
+ {
+ threadDetails.Add(new ThreadDetail
+ {
+ ThreadId = t.Id,
+ State = t.ThreadState.ToString(),
+ WaitReason = t.WaitReason.ToString(),
+ Priority = t.CurrentPriority
+ });
+ }
+ }
+ catch
+ {
+ // Some properties may not be available on all platforms
+ }
+
+ return new ThreadSnapshot
+ {
+ TotalThreadCount = threads.Count,
+ ThreadIds = threadDetails,
+ ManagedThreadCount = 0 // ThreadPool.ThreadCount not available in .NET Standard
+ };
+ }
+
+ private Dictionary CaptureStackTraces()
+ {
+ var stacks = new Dictionary();
+
+ try
+ {
+ // Get current thread stack
+ var currentThread = Thread.CurrentThread;
+ var stackFrames = new StackTrace(true).GetFrames();
+
+ if (stackFrames != null)
+ {
+ stacks[currentThread.ManagedThreadId] = stackFrames
+ .Select(f => f.GetMethod()?.Name ?? "Unknown")
+ .ToArray();
+ }
+ }
+ catch
+ {
+ // If we can't get stack traces, just skip
+ }
+
+ return stacks;
+ }
+
+ private Dictionary CaptureEnvironmentSnapshot()
+ {
+ var env = new Dictionary();
+
+ // Capture selected environment variables (not all for security)
+ var safeVars = new[]
+ {
+ "DOTNET_VERSION",
+ "PROCESSOR_COUNT",
+ "OS",
+ "RUNNING_UNDER_DOCKER",
+ "ASPNETCORE_ENVIRONMENT"
+ };
+
+ foreach (var varName in safeVars)
+ {
+ var varValue = Environment.GetEnvironmentVariable(varName);
+ if (varValue != null)
+ env[varName] = varValue;
+ }
+
+ // Add runtime info
+ env["RUNTIME_VERSION"] = RuntimeInformation.FrameworkDescription;
+ env["PROCESSOR_COUNT"] = Environment.ProcessorCount.ToString();
+
+ return env;
+ }
+}
+
+///
+/// A captured context snapshot.
+///
+public class ContextSnapshot
+{
+ ///
+ /// When this snapshot was captured.
+ ///
+ public DateTime CapturedAt { get; set; }
+
+ ///
+ /// The log event that triggered this snapshot.
+ ///
+ public LogEvent LogEvent { get; set; }
+
+ ///
+ /// Process information at time of capture.
+ ///
+ public ProcessSnapshot ProcessInfo { get; set; }
+
+ ///
+ /// Memory information at time of capture.
+ ///
+ public MemorySnapshot MemoryInfo { get; set; }
+
+ ///
+ /// Thread information at time of capture.
+ ///
+ public ThreadSnapshot ThreadInfo { get; set; }
+
+ ///
+ /// Stack traces for active threads.
+ ///
+ public Dictionary StackTraces { get; set; }
+
+ ///
+ /// Environment snapshot.
+ ///
+ public Dictionary EnvironmentVars { get; set; }
+}
+
+///
+/// Process information snapshot.
+///
+public class ProcessSnapshot
+{
+ public int ProcessId { get; set; }
+ public string ProcessName { get; set; }
+ public DateTime StartTime { get; set; }
+ public TimeSpan TotalProcessorTime { get; set; }
+ public TimeSpan UserProcessorTime { get; set; }
+ public int BasePriority { get; set; }
+ public int Handles { get; set; }
+ public int ThreadCount { get; set; }
+}
+
+///
+/// Memory information snapshot.
+///
+public class MemorySnapshot
+{
+ public long ManagedHeapBytes { get; set; }
+ public long ProcessWorkingSetBytes { get; set; }
+ public long ProcessPrivateMemoryBytes { get; set; }
+ public int Gen0Collections { get; set; }
+ public int Gen1Collections { get; set; }
+ public int Gen2Collections { get; set; }
+ public long TotalAllocatedBytes { get; set; }
+ public bool IsHighMemoryPressure { get; set; }
+}
+
+///
+/// Thread information snapshot.
+///
+public class ThreadSnapshot
+{
+ public int TotalThreadCount { get; set; }
+ public List ThreadIds { get; set; }
+ public int ManagedThreadCount { get; set; }
+}
+
+///
+/// Detail about a single thread.
+///
+public class ThreadDetail
+{
+ public int ThreadId { get; set; }
+ public string State { get; set; }
+ public string WaitReason { get; set; }
+ public int Priority { get; set; }
+}
+
+///
+/// Statistics about the snapshot collector.
+///
+public class SnapshotCollectorStats
+{
+ public long TotalCaptured { get; set; }
+ public int CurrentSnapshots { get; set; }
+ public int MaxCapacity { get; set; }
+ public Dictionary LevelDistribution { get; set; }
+ public DateTime? OldestSnapshot { get; set; }
+ public DateTime? NewestSnapshot { get; set; }
+}
+
+///
+/// Runtime information helper.
+///
+internal static class RuntimeInformation
+{
+ public static string FrameworkDescription
+ {
+ get
+ {
+#if NET6_0_OR_GREATER
+ return System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription;
+#else
+ return ".NET " + Environment.Version;
+#endif
+ }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/DeadLetterQueue.cs b/EonaCat.LogStack/EonaCatLoggerCore/DeadLetterQueue.cs
new file mode 100644
index 0000000..b46f5b7
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/DeadLetterQueue.cs
@@ -0,0 +1,307 @@
+using EonaCat.LogStack.Core;
+using EonaCat.LogStack.Flows;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+// 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.
+
+namespace EonaCat.LogStack.DeadLettering;
+
+///
+/// Dead Letter Queue (DLQ) for capturing failed log attempts and enabling replay.
+/// Superior to data loss as it preserves logs that couldn't be delivered to any flow.
+///
+public class DeadLetterQueue
+{
+ private readonly ConcurrentQueue _queue = new ConcurrentQueue();
+ private readonly int _maxCapacity;
+ private int _currentCount;
+ private long _totalDropped;
+ private long _totalEnqueued;
+ private readonly object _statsLock = new object();
+
+ public event EventHandler EventEnqueued;
+ public event EventHandler EventReplayed;
+
+ public DeadLetterQueue(int maxCapacity = 100000)
+ {
+ _maxCapacity = Math.Max(1000, maxCapacity);
+ }
+
+ ///
+ /// Enqueues a failed log event with the reason for failure.
+ ///
+ public bool Enqueue(LogEvent logEvent, string failureReason, Exception failureException = null)
+ {
+
+ if (_currentCount >= _maxCapacity)
+ {
+ lock (_statsLock)
+ {
+ _totalDropped++;
+ }
+ return false;
+ }
+
+ var dlEvent = new DeadLetterEvent
+ {
+ LogEvent = logEvent,
+ EnqueuedAt = DateTime.UtcNow,
+ FailureReason = failureReason ?? "Unknown",
+ FailureException = failureException?.ToString(),
+ RetryCount = 0
+ };
+
+ _queue.Enqueue(dlEvent);
+ _currentCount++;
+
+ lock (_statsLock)
+ {
+ _totalEnqueued++;
+ }
+
+ EventEnqueued?.Invoke(this, dlEvent);
+ return true;
+ }
+
+ ///
+ /// Gets the count of events in the DLQ.
+ ///
+ public int GetCount() => _currentCount;
+
+ ///
+ /// Peeks at the next event without removing it.
+ ///
+ public bool TryPeek(out DeadLetterEvent dlEvent)
+ {
+ return _queue.TryPeek(out dlEvent);
+ }
+
+ ///
+ /// Dequeues the next event.
+ ///
+ public bool TryDequeue(out DeadLetterEvent dlEvent)
+ {
+ if (_queue.TryDequeue(out var evt))
+ {
+ _currentCount--;
+ dlEvent = evt;
+ return true;
+ }
+
+ dlEvent = null;
+ return false;
+ }
+
+ ///
+ /// Gets all events in the DLQ without removing them.
+ ///
+ public List GetAll(int skip = 0, int take = 100)
+ {
+ return _queue.Skip(skip).Take(take).ToList();
+ }
+
+ ///
+ /// Replays a dead letter event to specified flows.
+ ///
+ public async Task ReplayAsync(DeadLetterEvent dlEvent, IEnumerable targetFlows)
+ {
+ if (dlEvent == null)
+ throw new ArgumentNullException(nameof(dlEvent));
+
+ dlEvent.RetryCount++;
+ dlEvent.LastRetryAt = DateTime.UtcNow;
+
+ var flowsList = targetFlows.ToList();
+ var tasks = new List>();
+
+ foreach (var flow in flowsList)
+ {
+ tasks.Add(flow.BlastAsync(dlEvent.LogEvent));
+ }
+
+ var results = await Task.WhenAll(tasks).ConfigureAwait(false);
+ var success = results.Any(r => r == WriteResult.Success);
+
+ if (success)
+ {
+ dlEvent.ReplayedAt = DateTime.UtcNow;
+ dlEvent.ReplaySuccessful = true;
+ EventReplayed?.Invoke(this, new DeadLetterEventReplayed
+ {
+ Event = dlEvent,
+ WasSuccessful = true,
+ FlowsTargeted = flowsList.Count
+ });
+ }
+
+ return success;
+ }
+
+ ///
+ /// Bulk replay of oldest events (useful for periodic retry jobs).
+ ///
+ public async Task ReplayOldestAsync(int count, IEnumerable targetFlows)
+ {
+ var toReplay = GetAll(0, count);
+ int successCount = 0;
+
+ foreach (var dlEvent in toReplay)
+ {
+ if (await ReplayAsync(dlEvent, targetFlows))
+ {
+ successCount++;
+ // Remove from queue if successful
+ _currentCount--;
+ }
+ }
+
+ return successCount;
+ }
+
+ ///
+ /// Clears all events from the DLQ.
+ ///
+ public int Clear()
+ {
+ var count = _currentCount;
+ while (_queue.TryDequeue(out _))
+ {
+ _currentCount--;
+ }
+ return count;
+ }
+
+ ///
+ /// Gets statistics about the DLQ.
+ ///
+ public DeadLetterQueueStats GetStats()
+ {
+ lock (_statsLock)
+ {
+ return new DeadLetterQueueStats
+ {
+ CurrentQueueSize = _currentCount,
+ MaxCapacity = _maxCapacity,
+ TotalEnqueued = _totalEnqueued,
+ TotalDropped = _totalDropped,
+ QueueUtilizationPercent = (_currentCount * 100.0) / _maxCapacity,
+ OldestEventAge = _queue.TryPeek(out var oldest)
+ ? DateTime.UtcNow - oldest.EnqueuedAt
+ : TimeSpan.Zero
+ };
+ }
+ }
+
+ ///
+ /// Gets events grouped by failure reason.
+ ///
+ public Dictionary GetFailureReasonStats()
+ {
+ var stats = new Dictionary();
+ foreach (var evt in _queue)
+ {
+ var reason = evt.FailureReason ?? "Unknown";
+ if (!stats.ContainsKey(reason))
+ stats[reason] = 0;
+ stats[reason]++;
+ }
+ return stats.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
+ }
+}
+
+///
+/// Represents a dead letter event.
+///
+public class DeadLetterEvent
+{
+ ///
+ /// The original log event that failed to be delivered.
+ ///
+ public LogEvent LogEvent { get; set; }
+
+ ///
+ /// When this event was enqueued to the DLQ.
+ ///
+ public DateTime EnqueuedAt { get; set; }
+
+ ///
+ /// Reason why the log event failed.
+ ///
+ public string FailureReason { get; set; }
+
+ ///
+ /// Exception details if available.
+ ///
+ public string FailureException { get; set; }
+
+ ///
+ /// Number of times this event has been retried.
+ ///
+ public int RetryCount { get; set; }
+
+ ///
+ /// When this event was last retried.
+ ///
+ public DateTime? LastRetryAt { get; set; }
+
+ ///
+ /// When this event was successfully replayed (if at all).
+ ///
+ public DateTime? ReplayedAt { get; set; }
+
+ ///
+ /// Whether the replay was successful.
+ ///
+ public bool ReplaySuccessful { get; set; }
+}
+
+///
+/// Event raised when a dead letter event is replayed.
+///
+public class DeadLetterEventReplayed
+{
+ public DeadLetterEvent Event { get; set; }
+ public bool WasSuccessful { get; set; }
+ public int FlowsTargeted { get; set; }
+}
+
+///
+/// Statistics about the dead letter queue.
+///
+public class DeadLetterQueueStats
+{
+ ///
+ /// Current number of events in the queue.
+ ///
+ public int CurrentQueueSize { get; set; }
+
+ ///
+ /// Maximum capacity of the queue.
+ ///
+ public int MaxCapacity { get; set; }
+
+ ///
+ /// Total events ever enqueued.
+ ///
+ public long TotalEnqueued { get; set; }
+
+ ///
+ /// Total events dropped due to capacity.
+ ///
+ public long TotalDropped { get; set; }
+
+ ///
+ /// Queue utilization as percentage.
+ ///
+ public double QueueUtilizationPercent { get; set; }
+
+ ///
+ /// Age of the oldest event in queue.
+ ///
+ public TimeSpan OldestEventAge { get; set; }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/Flows/DlqFlow.cs b/EonaCat.LogStack/EonaCatLoggerCore/Flows/DlqFlow.cs
new file mode 100644
index 0000000..24d50e3
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/Flows/DlqFlow.cs
@@ -0,0 +1,203 @@
+using EonaCat.LogStack.Core;
+using EonaCat.LogStack.DeadLettering;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+// 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.
+
+namespace EonaCat.LogStack.Flows;
+
+///
+/// Dead Letter Queue Flow - captures log events that failed to be delivered to other flows.
+/// Enables analysis and replay of failed logs.
+///
+public class DlqFlow : FlowBase
+{
+ private readonly DeadLetterQueue _dlq;
+ private readonly List _backupFlows = new List();
+ private readonly object _flowsLock = new object();
+ private int _disposed;
+
+ public DlqFlow(
+ string name = "DLQ",
+ LogLevel minimumLevel = LogLevel.Warning,
+ int maxCapacity = 100000)
+ : base(name, minimumLevel)
+ {
+ _dlq = new DeadLetterQueue(maxCapacity);
+ }
+
+ ///
+ /// Gets the underlying dead letter queue for inspection and replay.
+ ///
+ public DeadLetterQueue GetDeadLetterQueue() => _dlq;
+
+ ///
+ /// Adds a backup flow to attempt retrying failed events.
+ ///
+ public DlqFlow AddBackupFlow(IFlow flow)
+ {
+ if (flow == null) throw new ArgumentNullException(nameof(flow));
+ lock (_flowsLock)
+ {
+ _backupFlows.Add(flow);
+ }
+ return this;
+ }
+
+ ///
+ /// Blasts a single log event to the DLQ.
+ ///
+ public override async Task BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
+ {
+ if (_disposed != 0)
+ return WriteResult.Dropped;
+
+ if (!IsLogLevelEnabled(logEvent))
+ return WriteResult.Success;
+
+ // Try backup flows first before enqueueing to DLQ
+ List backupFlows;
+ lock (_flowsLock)
+ {
+ backupFlows = new List(_backupFlows);
+ }
+
+ bool anyBackupSucceeded = false;
+ var failureReasons = new List();
+
+ foreach (var backupFlow in backupFlows)
+ {
+ try
+ {
+ var result = await backupFlow.BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
+ if (result == WriteResult.Success)
+ {
+ anyBackupSucceeded = true;
+ break;
+ }
+ failureReasons.Add($"Backup flow '{backupFlow.Name}': failed");
+ }
+ catch (Exception ex)
+ {
+ failureReasons.Add($"Backup flow '{backupFlow.Name}' exception: {ex.Message}");
+ }
+ }
+
+ if (anyBackupSucceeded)
+ {
+ BlastedCount++;
+ return WriteResult.Success;
+ }
+
+ // All backups failed, enqueue to DLQ
+ var failureReason = string.Join("; ", failureReasons);
+ if (failureReason.Length > 500)
+ failureReason = failureReason.Substring(0, 500) + "...";
+
+ var enqueued = _dlq.Enqueue(logEvent, failureReason ?? "Unknown failure");
+
+ if (enqueued)
+ {
+ BlastedCount++;
+ return WriteResult.Success;
+ }
+
+ DroppedCount++;
+ return WriteResult.Dropped;
+ }
+
+ ///
+ /// Blasts a batch of log events to the DLQ.
+ ///
+ public override async Task BlastBatchAsync(ReadOnlyMemory logEvents, CancellationToken cancellationToken = default)
+ {
+ if (_disposed != 0)
+ return WriteResult.Dropped;
+
+ var events = logEvents.ToArray();
+ int successCount = 0;
+
+ foreach (var logEvent in events)
+ {
+ var result = await BlastAsync(logEvent, cancellationToken).ConfigureAwait(false);
+ if (result == WriteResult.Success)
+ successCount++;
+ }
+
+ return successCount > 0 ? WriteResult.Success : WriteResult.Dropped;
+ }
+
+ ///
+ /// Flushes the DLQ (does nothing, but required by interface).
+ ///
+ public override Task FlushAsync(CancellationToken cancellationToken = default)
+ {
+ return Task.CompletedTask;
+ }
+
+ ///
+ /// Gets statistics about this DLQ flow.
+ ///
+ public DeadLetterQueueStats GetStatistics()
+ {
+ return _dlq.GetStats();
+ }
+
+ ///
+ /// Attempts to replay queued dead letters to backup flows.
+ ///
+ public async Task ReplayOldestAsync(int count)
+ {
+ List backupFlows;
+ lock (_flowsLock)
+ {
+ backupFlows = new List(_backupFlows);
+ }
+
+ return await _dlq.ReplayOldestAsync(count, (IEnumerable)backupFlows).ConfigureAwait(false);
+ }
+
+ ///
+ /// Gets all dead letter events.
+ ///
+ public List GetDeadLetters(int skip = 0, int take = 100)
+ {
+ return _dlq.GetAll(skip, take);
+ }
+
+ ///
+ /// Clears all dead letters from the queue.
+ ///
+ public int ClearDeadLetters()
+ {
+ return _dlq.Clear();
+ }
+
+ public override async ValueTask DisposeAsync()
+ {
+ if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
+ return;
+
+ List flowsToDispose;
+ lock (_flowsLock)
+ {
+ flowsToDispose = new List(_backupFlows);
+ _backupFlows.Clear();
+ }
+
+ foreach (var flow in flowsToDispose)
+ {
+ try
+ {
+ await flow.DisposeAsync().ConfigureAwait(false);
+ }
+ catch { }
+ }
+
+ await base.DisposeAsync().ConfigureAwait(false);
+ }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/IntelligentRouter.cs b/EonaCat.LogStack/EonaCatLoggerCore/IntelligentRouter.cs
new file mode 100644
index 0000000..e5a07f4
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/IntelligentRouter.cs
@@ -0,0 +1,277 @@
+using EonaCat.LogStack.Core;
+using EonaCat.LogStack.Flows;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+// 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.
+
+namespace EonaCat.LogStack.Routing;
+
+///
+/// Intelligent message router that directs log events to different flows based on pattern matching and rules.
+/// This is superior to simple level-based routing as it can apply complex business logic.
+///
+public class IntelligentRouter : IAsyncDisposable
+{
+ private readonly List _rules = new List();
+ private readonly Dictionary _flowRoutes = new Dictionary();
+ private readonly object _rulesLock = new object();
+
+ public IntelligentRouter()
+ {
+ }
+
+ ///
+ /// Adds a routing rule that matches messages based on pattern and routes them to specific flows.
+ ///
+ public IntelligentRouter AddRule(RoutingRule rule)
+ {
+ if (rule == null) throw new ArgumentNullException(nameof(rule));
+ lock (_rulesLock)
+ {
+ _rules.Add(rule);
+ // Sort by priority descending so highest priority rules are checked first
+ _rules.Sort((a, b) => b.Priority.CompareTo(a.Priority));
+ }
+ return this;
+ }
+
+ ///
+ /// Adds a rule with fluent API support.
+ ///
+ public IntelligentRouter AddRule(
+ string name,
+ Func matcher,
+ IEnumerable targetFlows,
+ int priority = 0,
+ bool stopIfMatched = false)
+ {
+ var rule = new RoutingRule
+ {
+ Name = name,
+ Matcher = matcher,
+ TargetFlows = targetFlows?.ToList() ?? new List(),
+ Priority = priority,
+ StopIfMatched = stopIfMatched
+ };
+ return AddRule(rule);
+ }
+
+ ///
+ /// Adds a regex pattern-based routing rule.
+ ///
+ public IntelligentRouter AddPatternRule(
+ string name,
+ string messagePattern,
+ IEnumerable targetFlows,
+ int priority = 0,
+ RegexOptions regexOptions = RegexOptions.IgnoreCase)
+ {
+ var regex = new Regex(messagePattern, regexOptions);
+ return AddRule(
+ name,
+ logEvent => regex.IsMatch(logEvent.Message.ToString()),
+ targetFlows,
+ priority
+ );
+ }
+
+ ///
+ /// Adds a category-based routing rule (routes logs from specific categories).
+ ///
+ public IntelligentRouter AddCategoryRule(
+ string category,
+ IEnumerable targetFlows,
+ LogLevel? minimumLevel = null)
+ {
+ return AddRule(
+ $"Category:{category}",
+ logEvent => logEvent.Category == category && (minimumLevel == null || logEvent.Level >= minimumLevel),
+ targetFlows,
+ priority: 10
+ );
+ }
+
+ ///
+ /// Adds an exception type-based routing rule.
+ ///
+ public IntelligentRouter AddExceptionRule(
+ string exceptionTypeName,
+ IEnumerable targetFlows)
+ {
+ return AddRule(
+ $"Exception:{exceptionTypeName}",
+ logEvent => logEvent.Exception?.GetType().Name == exceptionTypeName,
+ targetFlows,
+ priority: 20,
+ stopIfMatched: true
+ );
+ }
+
+ ///
+ /// Registers a flow that can be targeted by routing rules.
+ ///
+ public IntelligentRouter RegisterFlow(string flowName, IFlow flow)
+ {
+ if (string.IsNullOrEmpty(flowName)) throw new ArgumentNullException(nameof(flowName));
+ if (flow == null) throw new ArgumentNullException(nameof(flow));
+
+ lock (_rulesLock)
+ {
+ _flowRoutes[flowName] = new FlowRoute { Flow = flow, IsEnabled = true };
+ }
+ return this;
+ }
+
+ ///
+ /// Enables or disables a specific flow by name.
+ ///
+ public IntelligentRouter SetFlowEnabled(string flowName, bool enabled)
+ {
+ lock (_rulesLock)
+ {
+ if (_flowRoutes.TryGetValue(flowName, out var route))
+ {
+ route.IsEnabled = enabled;
+ }
+ }
+ return this;
+ }
+
+ ///
+ /// Routes a log event to appropriate flows based on rules.
+ /// Returns true if at least one flow accepted the message.
+ ///
+ public async Task RouteAsync(LogEvent logEvent)
+ {
+ List activeRules;
+ Dictionary activeFlows;
+
+ lock (_rulesLock)
+ {
+ activeRules = new List(_rules);
+ activeFlows = new Dictionary(_flowRoutes);
+ }
+
+ var targetFlowNames = new HashSet();
+ bool shouldStop = false;
+
+ // Apply rules in priority order
+ foreach (var rule in activeRules)
+ {
+ if (rule.Matcher(logEvent))
+ {
+ foreach (var flowName in rule.TargetFlows)
+ {
+ targetFlowNames.Add(flowName);
+ }
+
+ if (rule.StopIfMatched)
+ {
+ shouldStop = true;
+ break;
+ }
+ }
+ }
+
+ // Route to target flows
+ bool anySuccess = false;
+ var tasks = new List();
+
+ foreach (var flowName in targetFlowNames)
+ {
+ if (activeFlows.TryGetValue(flowName, out var flowRoute) && flowRoute.IsEnabled)
+ {
+ tasks.Add(flowRoute.Flow.BlastAsync(logEvent)
+ .ContinueWith(t =>
+ {
+ if (t.Status == TaskStatus.RanToCompletion && t.Result == WriteResult.Success)
+ anySuccess = true;
+ }));
+ }
+ }
+
+ if (tasks.Any())
+ {
+ await Task.WhenAll(tasks).ConfigureAwait(false);
+ }
+
+ return anySuccess || targetFlowNames.Count == 0; // Return true if no rules matched (default routing)
+ }
+
+ ///
+ /// Gets statistics about the router.
+ ///
+ public RouterStats GetStats()
+ {
+ lock (_rulesLock)
+ {
+ return new RouterStats
+ {
+ TotalRules = _rules.Count,
+ RegisteredFlows = _flowRoutes.Count,
+ EnabledFlows = _flowRoutes.Values.Count(f => f.IsEnabled)
+ };
+ }
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ lock (_rulesLock)
+ {
+ _rules.Clear();
+ _flowRoutes.Clear();
+ }
+ }
+
+ private class FlowRoute
+ {
+ public IFlow Flow { get; set; }
+ public bool IsEnabled { get; set; }
+ }
+}
+
+///
+/// Defines a routing rule for intelligent message routing.
+///
+public class RoutingRule
+{
+ ///
+ /// Name of this rule for identification/debugging.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Function that determines if this rule matches a log event.
+ ///
+ public Func Matcher { get; set; }
+
+ ///
+ /// Names of flows this rule routes to.
+ ///
+ public List TargetFlows { get; set; } = new List();
+
+ ///
+ /// Priority of this rule (higher priority rules are evaluated first).
+ ///
+ public int Priority { get; set; } = 0;
+
+ ///
+ /// If true, stops evaluating further rules after this one matches.
+ ///
+ public bool StopIfMatched { get; set; } = false;
+}
+
+///
+/// Statistics about the intelligent router.
+///
+public class RouterStats
+{
+ public int TotalRules { get; set; }
+ public int RegisteredFlows { get; set; }
+ public int EnabledFlows { get; set; }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/PerformanceInsights/PerformanceAnalyzer.cs b/EonaCat.LogStack/EonaCatLoggerCore/PerformanceInsights/PerformanceAnalyzer.cs
new file mode 100644
index 0000000..6f495a4
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/PerformanceInsights/PerformanceAnalyzer.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace EonaCat.LogStack.PerformanceInsights
+{
+ ///
+ /// Analyzes performance metrics and provides optimization insights.
+ ///
+ public sealed class PerformanceAnalyzer
+ {
+ private readonly PerformanceInsightsCollector _collector;
+
+ public PerformanceAnalyzer(PerformanceInsightsCollector collector = null)
+ {
+ _collector = collector ?? new PerformanceInsightsCollector();
+ }
+
+ ///
+ /// Analyzes current performance state and returns insights
+ ///
+ public PerformanceAnalysis Analyze()
+ {
+ var snapshot = _collector.GetSnapshot();
+ var recommendations = _collector.GetRecommendations();
+
+ var analysis = new PerformanceAnalysis
+ {
+ AnalyzedAt = DateTime.UtcNow,
+ Uptime = snapshot.Uptime,
+ TotalOperations = snapshot.TotalOperations,
+ AverageErrorRate = snapshot.ErrorRatePercent,
+ Recommendations = recommendations,
+ Summary = GenerateSummary(snapshot, recommendations)
+ };
+
+ return analysis;
+ }
+
+ ///
+ /// Generates a summary string of the analysis
+ ///
+ private string GenerateSummary(PerformanceInsightsSnapshot snapshot, List recommendations)
+ {
+ var lines = new List
+ {
+ $"Performance Analysis - {snapshot.CapturedAt:O}",
+ $"Uptime: {snapshot.Uptime.TotalSeconds:F2}s",
+ $"Total Operations: {snapshot.TotalOperations}",
+ $"Total Errors: {snapshot.TotalErrors}",
+ $"Error Rate: {snapshot.ErrorRatePercent:F2}%",
+ "",
+ "Top Slowest Operations:"
+ };
+
+ foreach (var op in snapshot.SlowestOperations.Take(3))
+ {
+ lines.Add($" - {op.Name}: {op.AverageDurationMs:F2}ms (min: {op.MinDurationMs:F2}ms, max: {op.MaxDurationMs:F2}ms)");
+ }
+
+ if (snapshot.MostErrorsOperations.Any())
+ {
+ lines.Add("");
+ lines.Add("Operations with Most Errors:");
+ foreach (var op in snapshot.MostErrorsOperations.Take(3))
+ {
+ lines.Add($" - {op.Name}: {op.ErrorCount} errors ({op.ErrorRatePercent:F2}% error rate)");
+ }
+ }
+
+ if (recommendations.Any())
+ {
+ lines.Add("");
+ lines.Add("Recommendations:");
+ foreach (var rec in recommendations)
+ {
+ lines.Add($" [{rec.Severity}] {rec.Category}: {rec.Message}");
+ foreach (var op in rec.AffectedOperations.Take(3))
+ {
+ lines.Add($" - {op}");
+ }
+ }
+ }
+
+ return string.Join(Environment.NewLine, lines);
+ }
+ }
+
+ ///
+ /// Performance analysis result
+ ///
+ public sealed class PerformanceAnalysis
+ {
+ public DateTime AnalyzedAt { get; set; }
+ public TimeSpan Uptime { get; set; }
+ public long TotalOperations { get; set; }
+ public double AverageErrorRate { get; set; }
+ public List Recommendations { get; set; } = new();
+ public string Summary { get; set; }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/PerformanceInsights/PerformanceInsightsCollector.cs b/EonaCat.LogStack/EonaCatLoggerCore/PerformanceInsights/PerformanceInsightsCollector.cs
new file mode 100644
index 0000000..7d512e1
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/PerformanceInsights/PerformanceInsightsCollector.cs
@@ -0,0 +1,199 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace EonaCat.LogStack.PerformanceInsights
+{
+ ///
+ /// Collects performance insights from multiple flows and operations.
+ /// Detects bottlenecks and provides recommendations.
+ ///
+ public sealed class PerformanceInsightsCollector
+ {
+ private readonly ConcurrentDictionary _trackers = new();
+ private long _totalOperations;
+ private long _totalErrors;
+ private DateTime _startTime = DateTime.UtcNow;
+
+ ///
+ /// Gets or creates a tracker for the given name
+ ///
+ public PerformanceTracker GetOrCreateTracker(string name)
+ {
+ return _trackers.GetOrAdd(name, _ => new PerformanceTracker(name));
+ }
+
+ ///
+ /// Gets a tracker if it exists
+ ///
+ public PerformanceTracker? GetTracker(string name)
+ {
+ return _trackers.TryGetValue(name, out var tracker) ? tracker : null;
+ }
+
+ ///
+ /// Records an operation
+ ///
+ public void RecordOperation(string name, long durationTicks, long byteCount = 0, bool isError = false)
+ {
+ var tracker = GetOrCreateTracker(name);
+ tracker.RecordOperation(durationTicks, byteCount, isError);
+
+ _totalOperations++;
+ if (isError) _totalErrors++;
+ }
+
+ ///
+ /// Gets all current trackers
+ ///
+ public IReadOnlyCollection GetAllTrackers()
+ {
+ return _trackers.Values.ToList().AsReadOnly();
+ }
+
+ ///
+ /// Gets current insights snapshot
+ ///
+ public PerformanceInsightsSnapshot GetSnapshot()
+ {
+ var uptime = DateTime.UtcNow - _startTime;
+ var metrics = _trackers.Values.Select(t => t.GetMetrics()).ToList();
+
+ var slowestOps = metrics
+ .OrderByDescending(m => m.AverageDurationMs)
+ .Take(5)
+ .ToList();
+
+ var mostErrors = metrics
+ .OrderByDescending(m => m.ErrorCount)
+ .Take(5)
+ .ToList();
+
+ var topThroughput = metrics
+ .OrderByDescending(m => m.ThroughputOpsPerSec)
+ .Take(5)
+ .ToList();
+
+ return new PerformanceInsightsSnapshot
+ {
+ Uptime = uptime,
+ TotalOperations = _totalOperations,
+ TotalErrors = _totalErrors,
+ ErrorRatePercent = _totalOperations > 0 ? (_totalErrors * 100.0) / _totalOperations : 0,
+ AllMetrics = metrics,
+ SlowestOperations = slowestOps,
+ MostErrorsOperations = mostErrors,
+ HighestThroughputOperations = topThroughput,
+ CapturedAt = DateTime.UtcNow
+ };
+ }
+
+ ///
+ /// Gets performance recommendations based on current metrics
+ ///
+ public List GetRecommendations()
+ {
+ var recommendations = new List();
+ var snapshot = GetSnapshot();
+
+ // Check for high error rates
+ if (snapshot.ErrorRatePercent > 5)
+ {
+ recommendations.Add(new PerformanceRecommendation
+ {
+ Category = "ErrorRate",
+ Severity = Severity.High,
+ Message = $"Error rate is {snapshot.ErrorRatePercent:F2}%. Consider investigating error causes.",
+ AffectedOperations = snapshot.MostErrorsOperations.Select(m => m.Name).ToList()
+ });
+ }
+
+ // Check for slow operations
+ var verySlowOps = snapshot.AllMetrics
+ .Where(m => m.AverageDurationMs > 1000)
+ .ToList();
+
+ if (verySlowOps.Any())
+ {
+ recommendations.Add(new PerformanceRecommendation
+ {
+ Category = "SlowOperations",
+ Severity = Severity.Medium,
+ Message = "Some operations are very slow (>1000ms avg). Consider optimization or async batching.",
+ AffectedOperations = verySlowOps.Select(m => m.Name).ToList()
+ });
+ }
+
+ // Check for high variance
+ var highVarianceOps = snapshot.AllMetrics
+ .Where(m => m.MaxDurationMs > m.AverageDurationMs * 10)
+ .ToList();
+
+ if (highVarianceOps.Any())
+ {
+ recommendations.Add(new PerformanceRecommendation
+ {
+ Category = "HighVariance",
+ Severity = Severity.Low,
+ Message = "High variance detected in operation times. May indicate GC pauses or resource contention.",
+ AffectedOperations = highVarianceOps.Select(m => m.Name).ToList()
+ });
+ }
+
+ return recommendations;
+ }
+
+ ///
+ /// Resets all trackers
+ ///
+ public void Reset()
+ {
+ foreach (var tracker in _trackers.Values)
+ {
+ tracker.Reset();
+ }
+ _totalOperations = 0;
+ _totalErrors = 0;
+ _startTime = DateTime.UtcNow;
+ }
+ }
+
+ ///
+ /// Snapshot of performance insights
+ ///
+ public sealed class PerformanceInsightsSnapshot
+ {
+ public TimeSpan Uptime { get; set; }
+ public long TotalOperations { get; set; }
+ public long TotalErrors { get; set; }
+ public double ErrorRatePercent { get; set; }
+ public List AllMetrics { get; set; } = new();
+ public List SlowestOperations { get; set; } = new();
+ public List MostErrorsOperations { get; set; } = new();
+ public List HighestThroughputOperations { get; set; } = new();
+ public DateTime CapturedAt { get; set; }
+ }
+
+ ///
+ /// Performance recommendation
+ ///
+ public sealed class PerformanceRecommendation
+ {
+ public string Category { get; set; }
+ public Severity Severity { get; set; }
+ public string Message { get; set; }
+ public List AffectedOperations { get; set; } = new();
+ }
+
+ ///
+ /// Severity level
+ ///
+ public enum Severity
+ {
+ Low,
+ Medium,
+ High,
+ Critical
+ }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/PerformanceInsights/PerformanceTracker.cs b/EonaCat.LogStack/EonaCatLoggerCore/PerformanceInsights/PerformanceTracker.cs
new file mode 100644
index 0000000..e09a58d
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/PerformanceInsights/PerformanceTracker.cs
@@ -0,0 +1,160 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace EonaCat.LogStack.PerformanceInsights
+{
+ ///
+ /// Tracks performance metrics for flows and operations.
+ /// Measures latency, throughput, and allocations with minimal overhead.
+ ///
+ public sealed class PerformanceTracker
+ {
+ private long _operationCount;
+ private long _totalDurationTicks;
+ private long _minDurationTicks = long.MaxValue;
+ private long _maxDurationTicks;
+ private long _errorCount;
+ private long _byteCount;
+ private readonly object _lock = new();
+
+ ///
+ /// Gets the operation name
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets total operations recorded
+ ///
+ public long OperationCount => _operationCount;
+
+ ///
+ /// Gets total duration in milliseconds
+ ///
+ public double TotalDurationMs => _totalDurationTicks * 1000.0 / Stopwatch.Frequency;
+
+ ///
+ /// Gets average duration in milliseconds
+ ///
+ public double AverageDurationMs => _operationCount > 0 ? TotalDurationMs / _operationCount : 0;
+
+ ///
+ /// Gets minimum duration in milliseconds
+ ///
+ public double MinDurationMs => _minDurationTicks == long.MaxValue ? 0 : _minDurationTicks * 1000.0 / Stopwatch.Frequency;
+
+ ///
+ /// Gets maximum duration in milliseconds
+ ///
+ public double MaxDurationMs => _maxDurationTicks * 1000.0 / Stopwatch.Frequency;
+
+ ///
+ /// Gets throughput in operations per second
+ ///
+ public double ThroughputOpsPerSec => TotalDurationMs > 0 ? (_operationCount / TotalDurationMs) * 1000 : 0;
+
+ ///
+ /// Gets total bytes processed
+ ///
+ public long ByteCount => _byteCount;
+
+ ///
+ /// Gets error count
+ ///
+ public long ErrorCount => _errorCount;
+
+ ///
+ /// Gets error rate percentage
+ ///
+ public double ErrorRatePercent => _operationCount > 0 ? (_errorCount * 100.0) / _operationCount : 0;
+
+ public PerformanceTracker(string name)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ }
+
+ ///
+ /// Records an operation with its duration
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void RecordOperation(long durationTicks, long byteCount = 0, bool isError = false)
+ {
+ lock (_lock)
+ {
+ _operationCount++;
+ _totalDurationTicks += durationTicks;
+ if (durationTicks < _minDurationTicks) _minDurationTicks = durationTicks;
+ if (durationTicks > _maxDurationTicks) _maxDurationTicks = durationTicks;
+ if (byteCount > 0) _byteCount += byteCount;
+ if (isError) _errorCount++;
+ }
+ }
+
+ ///
+ /// Resets all metrics
+ ///
+ public void Reset()
+ {
+ lock (_lock)
+ {
+ _operationCount = 0;
+ _totalDurationTicks = 0;
+ _minDurationTicks = long.MaxValue;
+ _maxDurationTicks = 0;
+ _errorCount = 0;
+ _byteCount = 0;
+ }
+ }
+
+ ///
+ /// Gets a snapshot of current metrics
+ ///
+ public PerformanceMetrics GetMetrics()
+ {
+ lock (_lock)
+ {
+ return new PerformanceMetrics
+ {
+ Name = Name,
+ OperationCount = _operationCount,
+ TotalDurationMs = TotalDurationMs,
+ AverageDurationMs = AverageDurationMs,
+ MinDurationMs = MinDurationMs,
+ MaxDurationMs = MaxDurationMs,
+ ThroughputOpsPerSec = ThroughputOpsPerSec,
+ ByteCount = _byteCount,
+ ErrorCount = _errorCount,
+ ErrorRatePercent = ErrorRatePercent,
+ CapturedAt = DateTime.UtcNow
+ };
+ }
+ }
+ }
+
+ ///
+ /// Snapshot of performance metrics at a point in time
+ ///
+ public sealed class PerformanceMetrics
+ {
+ public string Name { get; set; }
+ public long OperationCount { get; set; }
+ public double TotalDurationMs { get; set; }
+ public double AverageDurationMs { get; set; }
+ public double MinDurationMs { get; set; }
+ public double MaxDurationMs { get; set; }
+ public double ThroughputOpsPerSec { get; set; }
+ public long ByteCount { get; set; }
+ public long ErrorCount { get; set; }
+ public double ErrorRatePercent { get; set; }
+ public DateTime CapturedAt { get; set; }
+
+ ///
+ /// Gets a formatted string representation
+ ///
+ public override string ToString()
+ {
+ return $"{Name} | Ops: {OperationCount} | Avg: {AverageDurationMs:F2}ms | Thrput: {ThroughputOpsPerSec:F2} ops/s | Errors: {ErrorCount} ({ErrorRatePercent:F2}%)";
+ }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/Tracing/SpanFactory.cs b/EonaCat.LogStack/EonaCatLoggerCore/Tracing/SpanFactory.cs
new file mode 100644
index 0000000..cbed4cb
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/Tracing/SpanFactory.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+
+namespace EonaCat.LogStack.Tracing
+{
+ ///
+ /// Factory for creating and managing TracingSpans with automatic context management.
+ /// Supports parent-child relationships and automatic scope management.
+ ///
+ public sealed class SpanFactory
+ {
+ private readonly List _activeSpans = new();
+ private readonly object _activeLock = new();
+
+ ///
+ /// Creates a new span with auto-context inheritance
+ ///
+ public TracingSpan CreateSpan(string name, string kind = "internal")
+ {
+ var context = TraceContextManager.Current;
+ var span = new TracingSpan(name, context) { Kind = kind };
+
+ lock (_activeLock)
+ {
+ _activeSpans.Add(span);
+ }
+
+ return span;
+ }
+
+ ///
+ /// Creates a span and returns a scope that manages its lifetime
+ ///
+ public IDisposable CreateSpanScope(string name, string kind = "internal")
+ {
+ var span = CreateSpan(name, kind);
+ return new SpanScope(span, this);
+ }
+
+ ///
+ /// Creates a span with a child trace context
+ ///
+ public TracingSpan CreateChildSpan(string name, string kind = "internal")
+ {
+ using (TraceContextManager.CreateChildScope())
+ {
+ return CreateSpan(name, kind);
+ }
+ }
+
+ ///
+ /// Gets all active spans
+ ///
+ public IReadOnlyList GetActiveSpans()
+ {
+ lock (_activeLock)
+ {
+ return _activeSpans.AsReadOnly();
+ }
+ }
+
+ ///
+ /// Executes action within a span
+ ///
+ public void ExecuteInSpan(string name, Action action, string kind = "internal")
+ {
+ using (CreateSpanScope(name, kind))
+ {
+ action();
+ }
+ }
+
+ ///
+ /// Executes function within a span, returns result and span
+ ///
+ public (T Result, TracingSpan Span) ExecuteInSpan(string name, Func func, string kind = "internal")
+ {
+ var span = CreateSpan(name, kind);
+ try
+ {
+ var result = func();
+ span.End();
+ return (result, span);
+ }
+ catch (Exception ex)
+ {
+ span.RecordException(ex);
+ span.End(SpanStatus.Error, ex.Message);
+ throw;
+ }
+ }
+
+ ///
+ /// Removes a span from active list
+ ///
+ internal void RemoveSpan(TracingSpan span)
+ {
+ lock (_activeLock)
+ {
+ _activeSpans.Remove(span);
+ }
+ }
+
+ ///
+ /// Scope that manages span lifetime
+ ///
+ private sealed class SpanScope : IDisposable
+ {
+ private readonly TracingSpan _span;
+ private readonly SpanFactory _factory;
+ private bool _disposed;
+
+ public SpanScope(TracingSpan span, SpanFactory factory)
+ {
+ _span = span;
+ _factory = factory;
+ }
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+
+ if (_span.Status == SpanStatus.Unset)
+ {
+ _span.End(SpanStatus.Ok);
+ }
+
+ _factory.RemoveSpan(_span);
+ }
+ }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/Tracing/TraceContext.cs b/EonaCat.LogStack/EonaCatLoggerCore/Tracing/TraceContext.cs
new file mode 100644
index 0000000..176c223
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/Tracing/TraceContext.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Collections.Generic;
+
+namespace EonaCat.LogStack.Tracing
+{
+ ///
+ /// Represents a trace context for distributed tracing across async boundaries.
+ /// Contains trace ID, span ID, parent span ID, and baggage for propagation.
+ ///
+ public sealed class TraceContext
+ {
+ ///
+ /// Gets or sets the trace ID (root identifier for entire trace)
+ ///
+ public string TraceId { get; set; }
+
+ ///
+ /// Gets or sets the span ID (current operation identifier)
+ ///
+ public string SpanId { get; set; }
+
+ ///
+ /// Gets or sets the parent span ID (parent operation identifier, if any)
+ ///
+ public string? ParentSpanId { get; set; }
+
+ ///
+ /// Gets or sets the correlation ID for request tracking
+ ///
+ public string? CorrelationId { get; set; }
+
+ ///
+ /// Gets or sets baggage (key-value pairs to propagate across spans)
+ ///
+ public Dictionary Baggage { get; set; } = new();
+
+ ///
+ /// Gets or sets the trace flags (e.g., sampling decision)
+ ///
+ public byte TraceFlags { get; set; } = 0x01;
+
+ ///
+ /// Gets the creation timestamp
+ ///
+ public DateTime CreatedAt { get; } = DateTime.UtcNow;
+
+ ///
+ /// Gets whether this trace is sampled for export
+ ///
+ public bool IsSampled => (TraceFlags & 0x01) != 0;
+
+ ///
+ /// Creates a new TraceContext with a generated trace ID
+ ///
+ public TraceContext()
+ {
+ TraceId = GenerateId();
+ SpanId = GenerateId();
+ }
+
+ ///
+ /// Creates a child TraceContext from this context
+ ///
+ public TraceContext CreateChild()
+ {
+ return new TraceContext
+ {
+ TraceId = TraceId,
+ ParentSpanId = SpanId,
+ SpanId = GenerateId(),
+ CorrelationId = CorrelationId,
+ TraceFlags = TraceFlags,
+ Baggage = new Dictionary(Baggage)
+ };
+ }
+
+ ///
+ /// Generates a random ID for trace/span
+ ///
+ private static string GenerateId()
+ {
+ return Guid.NewGuid().ToString("N").Substring(0, 16);
+ }
+
+ ///
+ /// Creates a copy of this TraceContext for propagation
+ ///
+ public TraceContext Clone()
+ {
+ return new TraceContext
+ {
+ TraceId = TraceId,
+ SpanId = SpanId,
+ ParentSpanId = ParentSpanId,
+ CorrelationId = CorrelationId,
+ TraceFlags = TraceFlags,
+ Baggage = new Dictionary(Baggage)
+ };
+ }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/Tracing/TraceContextManager.cs b/EonaCat.LogStack/EonaCatLoggerCore/Tracing/TraceContextManager.cs
new file mode 100644
index 0000000..78559f5
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/Tracing/TraceContextManager.cs
@@ -0,0 +1,162 @@
+using System;
+using System.Threading;
+
+namespace EonaCat.LogStack.Tracing
+{
+ ///
+ /// Manages trace context across async boundaries using AsyncLocal.
+ /// Enables automatic trace/span propagation without explicit passing.
+ ///
+ public sealed class TraceContextManager
+ {
+ private static readonly AsyncLocal _current = new();
+ private static readonly TraceContextManager _instance = new();
+
+ ///
+ /// Gets the singleton instance
+ ///
+ public static TraceContextManager Instance => _instance;
+
+ ///
+ /// Gets the current trace context (creates new if not set)
+ ///
+ public static TraceContext Current
+ {
+ get => _current.Value ??= new TraceContext();
+ set => _current.Value = value;
+ }
+
+ ///
+ /// Gets the current trace context without creating
+ ///
+ public static TraceContext? TryGetCurrent() => _current.Value;
+
+ ///
+ /// Sets the trace context
+ ///
+ public static void SetCurrent(TraceContext context)
+ {
+ if (context == null) throw new ArgumentNullException(nameof(context));
+ _current.Value = context;
+ }
+
+ ///
+ /// Clears the current trace context
+ ///
+ public static void Clear()
+ {
+ _current.Value = null;
+ }
+
+ ///
+ /// Creates a new child context and sets it as current
+ ///
+ public static IDisposable CreateChildScope()
+ {
+ var parent = Current;
+ var child = parent.CreateChild();
+ _current.Value = child;
+ return new TraceScope(parent);
+ }
+
+ ///
+ /// Executes action with a new child trace context
+ ///
+ public static void WithChild(Action action)
+ {
+ using (CreateChildScope())
+ {
+ action();
+ }
+ }
+
+ ///
+ /// Executes function with a new child trace context
+ ///
+ public static T WithChild(Func func)
+ {
+ using (CreateChildScope())
+ {
+ return func();
+ }
+ }
+
+ ///
+ /// Gets the current trace ID
+ ///
+ public static string GetTraceId() => Current.TraceId;
+
+ ///
+ /// Gets the current span ID
+ ///
+ public static string GetSpanId() => Current.SpanId;
+
+ ///
+ /// Gets the current correlation ID
+ ///
+ public static string? GetCorrelationId() => Current.CorrelationId;
+
+ ///
+ /// Sets the correlation ID
+ ///
+ public static void SetCorrelationId(string correlationId)
+ {
+ Current.CorrelationId = correlationId;
+ }
+
+ ///
+ /// Adds baggage data
+ ///
+ public static void AddBaggage(string key, string value)
+ {
+ Current.Baggage[key] = value;
+ }
+
+ ///
+ /// Gets baggage value
+ ///
+ public static string? GetBaggage(string key)
+ {
+ return Current.Baggage.TryGetValue(key, out var value) ? value : null;
+ }
+
+ ///
+ /// Removes baggage value
+ ///
+ public static bool RemoveBaggage(string key)
+ {
+ return Current.Baggage.Remove(key);
+ }
+
+ ///
+ /// Sets sampling decision
+ ///
+ public static void SetSampled(bool sampled)
+ {
+ Current.TraceFlags = sampled ? (byte)0x01 : (byte)0x00;
+ }
+
+ ///
+ /// Gets sampling decision
+ ///
+ public static bool IsSampled() => Current.IsSampled;
+
+ ///
+ /// Scope disposed when exiting
+ ///
+ private sealed class TraceScope : IDisposable
+ {
+ private readonly TraceContext _parent;
+
+ public TraceScope(TraceContext parent)
+ {
+ _parent = parent;
+ }
+
+ public void Dispose()
+ {
+ _current.Value = _parent;
+ }
+ }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/Tracing/TracingSpan.cs b/EonaCat.LogStack/EonaCatLoggerCore/Tracing/TracingSpan.cs
new file mode 100644
index 0000000..43921c6
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/Tracing/TracingSpan.cs
@@ -0,0 +1,211 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+
+namespace EonaCat.LogStack.Tracing
+{
+ ///
+ /// Enhanced span representation for distributed tracing.
+ /// Tracks operation timing, status, and linked operations.
+ ///
+ public sealed class TracingSpan : IDisposable
+ {
+ private readonly Stopwatch _stopwatch = Stopwatch.StartNew();
+ private readonly List _exceptions = new();
+ private bool _disposed;
+
+ ///
+ /// Gets the span name
+ ///
+ public string Name { get; }
+
+ ///
+ /// Gets the span kind (internal, server, client, producer, consumer)
+ ///
+ public string Kind { get; set; } = "internal";
+
+ ///
+ /// Gets the trace context
+ ///
+ public TraceContext Context { get; }
+
+ ///
+ /// Gets or sets the span status
+ ///
+ public SpanStatus Status { get; set; } = SpanStatus.Unset;
+
+ ///
+ /// Gets or sets the status description
+ ///
+ public string? StatusDescription { get; set; }
+
+ ///
+ /// Gets the start time
+ ///
+ public DateTime StartTime { get; }
+
+ ///
+ /// Gets the end time (if ended)
+ ///
+ public DateTime? EndTime { get; private set; }
+
+ ///
+ /// Gets the duration in milliseconds
+ ///
+ public double DurationMs { get; private set; }
+
+ ///
+ /// Gets attributes attached to span
+ ///
+ public Dictionary Attributes { get; } = new();
+
+ ///
+ /// Gets events recorded on span
+ ///
+ public List Events { get; } = new();
+
+ ///
+ /// Gets linked spans
+ ///
+ public List Links { get; } = new();
+
+ ///
+ /// Gets recorded exceptions
+ ///
+ public IReadOnlyList Exceptions => _exceptions.AsReadOnly();
+
+ ///
+ /// Creates a new tracing span
+ ///
+ public TracingSpan(string name, TraceContext? context = null)
+ {
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ Context = context ?? TraceContextManager.Current;
+ StartTime = DateTime.UtcNow;
+ }
+
+ ///
+ /// Records an event on the span
+ ///
+ public void AddEvent(string eventName, Dictionary? attributes = null)
+ {
+ Events.Add(new SpanEvent
+ {
+ Name = eventName,
+ Timestamp = DateTime.UtcNow,
+ Attributes = attributes ?? new Dictionary()
+ });
+ }
+
+ ///
+ /// Records an exception on the span
+ ///
+ public void RecordException(Exception exception, Dictionary? attributes = null)
+ {
+ if (exception == null) return;
+
+ _exceptions.Add(exception);
+ Status = SpanStatus.Error;
+ StatusDescription = exception.Message;
+
+ var eventAttrs = attributes ?? new Dictionary();
+ eventAttrs["exception.type"] = exception.GetType().FullName;
+ eventAttrs["exception.message"] = exception.Message;
+ eventAttrs["exception.stacktrace"] = exception.StackTrace ?? "";
+
+ AddEvent("exception", eventAttrs);
+ }
+
+ ///
+ /// Sets a span attribute
+ ///
+ public void SetAttribute(string key, object value)
+ {
+ Attributes[key] = value;
+ }
+
+ ///
+ /// Sets multiple attributes
+ ///
+ public void SetAttributes(Dictionary attributes)
+ {
+ if (attributes == null) return;
+ foreach (var kvp in attributes)
+ {
+ Attributes[kvp.Key] = kvp.Value;
+ }
+ }
+
+ ///
+ /// Adds a linked span
+ ///
+ public void AddLink(string linkedTraceId, string linkedSpanId, Dictionary? attributes = null)
+ {
+ Links.Add(new SpanLink
+ {
+ TraceId = linkedTraceId,
+ SpanId = linkedSpanId,
+ Attributes = attributes ?? new Dictionary()
+ });
+ }
+
+ ///
+ /// Marks span as ended with success
+ ///
+ public void End(SpanStatus status = SpanStatus.Ok, string? description = null)
+ {
+ if (_disposed) return;
+
+ _stopwatch.Stop();
+ EndTime = DateTime.UtcNow;
+ DurationMs = _stopwatch.Elapsed.TotalMilliseconds;
+ Status = status;
+ StatusDescription = description;
+ }
+
+ ///
+ /// Gets span as W3C trace context header value
+ ///
+ public string ToW3CTraceContext()
+ {
+ return $"{Context.TraceId}-{Context.SpanId}-{(byte)Context.TraceFlags:x2}";
+ }
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+ End();
+ }
+ }
+
+ ///
+ /// Span status enumeration
+ ///
+ public enum SpanStatus
+ {
+ Unset = 0,
+ Ok = 1,
+ Error = 2
+ }
+
+ ///
+ /// Represents an event recorded on a span
+ ///
+ public sealed class SpanEvent
+ {
+ public string Name { get; set; }
+ public DateTime Timestamp { get; set; }
+ public Dictionary Attributes { get; set; } = new();
+ }
+
+ ///
+ /// Represents a link to another span
+ ///
+ public sealed class SpanLink
+ {
+ public string TraceId { get; set; }
+ public string SpanId { get; set; }
+ public Dictionary Attributes { get; set; } = new();
+ }
+}
diff --git a/EonaCat.LogStack/Extensions/ServiceCollectionExtensions.cs b/EonaCat.LogStack/Extensions/ServiceCollectionExtensions.cs
index c92b1c5..de5fea3 100644
--- a/EonaCat.LogStack/Extensions/ServiceCollectionExtensions.cs
+++ b/EonaCat.LogStack/Extensions/ServiceCollectionExtensions.cs
@@ -1,6 +1,10 @@
using EonaCat.LogStack.Configuration;
using EonaCat.LogStack.Core;
using EonaCat.LogStack.Logging;
+using EonaCat.LogStack.DependencyInjection;
+using EonaCat.LogStack.Telemetry;
+using EonaCat.LogStack.Tracing;
+using EonaCat.LogStack.PerformanceInsights;
using Microsoft.Extensions.DependencyInjection;
using System;
@@ -337,4 +341,98 @@ public static class ServiceCollectionExtensions
services.AddSingleton();
return services;
}
+
+ ///
+ /// Registers named logger factory for per-category loggers in DI
+ ///
+ public static IServiceCollection AddEonaCatNamedLoggers(
+ this IServiceCollection services,
+ LoggerDIOptions? options = null)
+ {
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+
+ options ??= new LoggerDIOptions();
+ services.AddSingleton(options);
+
+ // Register telemetry components if enabled
+ if (options.EnableTelemetry)
+ {
+ services.AddSingleton();
+ }
+
+ if (options.EnableTracing)
+ {
+ services.AddSingleton();
+ }
+
+ if (options.EnablePerformanceMonitoring)
+ {
+ services.AddSingleton();
+ }
+
+ if (options.EnableHealthMonitoring)
+ {
+ services.AddSingleton();
+ }
+
+ return services;
+ }
+
+ ///
+ /// Enables distributed tracing support
+ ///
+ public static IServiceCollection AddEonaCatTracing(
+ this IServiceCollection services)
+ {
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ return services;
+ }
+
+ ///
+ /// Enables performance monitoring and insights
+ ///
+ public static IServiceCollection AddEonaCatPerformanceMonitoring(
+ this IServiceCollection services)
+ {
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+
+ services.AddSingleton();
+ services.AddSingleton();
+ return services;
+ }
+
+ ///
+ /// Enables health monitoring
+ ///
+ public static IServiceCollection AddEonaCatHealthMonitoring(
+ this IServiceCollection services)
+ {
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+
+ services.AddSingleton();
+ services.AddSingleton();
+ return services;
+ }
+
+ ///
+ /// Enables unified telemetry aggregation
+ ///
+ public static IServiceCollection AddEonaCatTelemetryAggregation(
+ this IServiceCollection services)
+ {
+ if (services == null)
+ throw new ArgumentNullException(nameof(services));
+
+ services.AddSingleton();
+ services.AddSingleton();
+ return services;
+ }
}
diff --git a/EonaCat.LogStack/LogBuilder.cs b/EonaCat.LogStack/LogBuilder.cs
index 915a506..eb6ac33 100644
--- a/EonaCat.LogStack/LogBuilder.cs
+++ b/EonaCat.LogStack/LogBuilder.cs
@@ -796,6 +796,73 @@ public sealed class LogBuilder
return this;
}
+ ///
+ /// Stores recent log events in memory for querying and diagnostics
+ ///
+ public LogBuilder WriteToQueryableStorage(
+ int maxCapacity = 10000,
+ LogLevel minimumLevel = LogLevel.Trace)
+ {
+ _flows.Add(new LocalStorageQueryFlow(maxCapacity, minimumLevel));
+ return this;
+ }
+
+ ///
+ /// Tracks and correlates related events across flows using correlation IDs
+ ///
+ public LogBuilder WriteToCorrelationTracking(
+ TimeSpan? correlationWindow = null,
+ LogLevel minimumLevel = LogLevel.Trace)
+ {
+ _flows.Add(new CorrelatedEventFlow(
+ correlationWindow: correlationWindow,
+ minimumLevel: minimumLevel));
+ return this;
+ }
+
+ ///
+ /// Monitors performance metrics and detects anomalies using statistical analysis
+ ///
+ public LogBuilder WriteToPerformanceMonitoring(
+ IFlow? alertFlow = null,
+ int windowSize = 100,
+ double standardDeviationThreshold = 2.0)
+ {
+ _flows.Add(new PerformanceAnomalyDetectorFlow(
+ alertFlow,
+ windowSize,
+ standardDeviationThreshold));
+ return this;
+ }
+
+ ///
+ /// Adds a filtered flow using a named preset (ErrorOnly, PerformanceLogs, SecurityLogs, etc.)
+ ///
+ public LogBuilder WriteToFilteredFlow(
+ IFlow targetFlow,
+ string presetName,
+ LogLevel minimumLevel = LogLevel.Trace)
+ {
+ var preset = LogFilterPresetRegistry.Get(presetName)
+ ?? throw new ArgumentException($"Preset '{presetName}' not found. Available presets: {string.Join(", ", LogFilterPresetRegistry.GetAvailablePresets())}", nameof(presetName));
+ _flows.Add(new LogFilterPresetFlow(targetFlow, preset, minimumLevel));
+ return this;
+ }
+
+ ///
+ /// Adds a custom filtered flow using a predicate function
+ ///
+ public LogBuilder WriteToCustomFilteredFlow(
+ IFlow targetFlow,
+ string name,
+ Func predicate,
+ LogLevel minimumLevel = LogLevel.Trace)
+ {
+ var preset = new CustomPreset(name, predicate);
+ _flows.Add(new LogFilterPresetFlow(targetFlow, preset, minimumLevel));
+ return this;
+ }
+
///
/// Boost logs with machine name
///
diff --git a/EonaCat.LogStack/Policies/AutoPolicies.cs b/EonaCat.LogStack/Policies/AutoPolicies.cs
new file mode 100644
index 0000000..ce14a8b
--- /dev/null
+++ b/EonaCat.LogStack/Policies/AutoPolicies.cs
@@ -0,0 +1,177 @@
+using System;
+using System.Collections.Generic;
+
+namespace EonaCat.LogStack.Policies
+{
+ ///
+ /// Automatic batching policy for flows
+ ///
+ public sealed class AutoBatchingPolicy
+ {
+ public int MinimumBatchSize { get; set; } = 10;
+ public int MaximumBatchSize { get; set; } = 100;
+ public TimeSpan FlushInterval { get; set; } = TimeSpan.FromSeconds(5);
+ public bool AdaptBatchSize { get; set; } = true;
+ public double ScalingFactor { get; set; } = 1.5;
+ }
+
+ ///
+ /// Automatic flow scaling policy
+ ///
+ public sealed class AutoScalingPolicy
+ {
+ public bool Enabled { get; set; } = true;
+ public long BytesPerSecondThreshold { get; set; } = 10_000_000; // 10MB/s
+ public double ErrorRateThreshold { get; set; } = 0.05; // 5%
+ public TimeSpan EvaluationInterval { get; set; } = TimeSpan.FromSeconds(10);
+ public int MaxBufferMultiplier { get; set; } = 5;
+ }
+
+ ///
+ /// Automatic retention policy for log files
+ ///
+ public sealed class AutoRetentionPolicy
+ {
+ public bool Enabled { get; set; } = true;
+ public TimeSpan MaxAge { get; set; } = TimeSpan.FromDays(30);
+ public long MaxSize { get; set; } = 10_737_418_240; // 10GB
+ public int MaxFileCount { get; set; } = 1000;
+ public TimeSpan CleanupInterval { get; set; } = TimeSpan.FromHours(1);
+ public bool CompressArchives { get; set; } = true;
+ }
+
+ ///
+ /// Automatic optimization policy for performance
+ ///
+ public sealed class AutoOptimizationPolicy
+ {
+ public bool Enabled { get; set; } = true;
+ public TimeSpan AnalysisInterval { get; set; } = TimeSpan.FromMinutes(5);
+ public double SlowOperationThresholdPercentile { get; set; } = 0.95;
+ public bool AutoAdjustBufferSizes { get; set; } = true;
+ public bool AutoAdjustBatchSizes { get; set; } = true;
+ }
+
+ ///
+ /// Collection of auto-policies for logger
+ ///
+ public sealed class AutoPolicies
+ {
+ public AutoBatchingPolicy Batching { get; } = new();
+ public AutoScalingPolicy Scaling { get; } = new();
+ public AutoRetentionPolicy Retention { get; } = new();
+ public AutoOptimizationPolicy Optimization { get; } = new();
+ }
+
+ ///
+ /// Policy engine for automatic optimization
+ ///
+ public sealed class AutoPolicyEngine
+ {
+ private readonly AutoPolicies _policies;
+ private DateTime _lastAnalysis = DateTime.UtcNow;
+ private DateTime _lastCleanup = DateTime.UtcNow;
+
+ public AutoPolicyEngine(AutoPolicies policies = null)
+ {
+ _policies = policies ?? new AutoPolicies();
+ }
+
+ ///
+ /// Evaluates policies and returns recommended actions
+ ///
+ public List EvaluatePolicies()
+ {
+ var actions = new List();
+ var now = DateTime.UtcNow;
+
+ // Check optimization policy
+ if (_policies.Optimization.Enabled &&
+ now - _lastAnalysis > _policies.Optimization.AnalysisInterval)
+ {
+ actions.Add(new PolicyAction
+ {
+ Type = PolicyActionType.AnalyzePerformance,
+ Reason = "Scheduled performance analysis"
+ });
+ _lastAnalysis = now;
+ }
+
+ // Check retention policy
+ if (_policies.Retention.Enabled &&
+ now - _lastCleanup > _policies.Retention.CleanupInterval)
+ {
+ actions.Add(new PolicyAction
+ {
+ Type = PolicyActionType.CleanupRetentionPolicy,
+ Reason = "Scheduled retention cleanup"
+ });
+ _lastCleanup = now;
+ }
+
+ return actions;
+ }
+
+ ///
+ /// Gets all active policies as string
+ ///
+ public string GetPoliciesSummary()
+ {
+ var lines = new List
+ {
+ "=== Auto-Policies Configuration ===",
+ "",
+ "Batching:",
+ $" Min Batch: {_policies.Batching.MinimumBatchSize}",
+ $" Max Batch: {_policies.Batching.MaximumBatchSize}",
+ $" Flush Interval: {_policies.Batching.FlushInterval.TotalSeconds:F1}s",
+ $" Adaptive: {_policies.Batching.AdaptBatchSize}",
+ "",
+ "Scaling:",
+ $" Enabled: {_policies.Scaling.Enabled}",
+ $" Throughput Threshold: {_policies.Scaling.BytesPerSecondThreshold / 1_000_000}MB/s",
+ $" Error Rate Threshold: {_policies.Scaling.ErrorRateThreshold:P}",
+ "",
+ "Retention:",
+ $" Enabled: {_policies.Retention.Enabled}",
+ $" Max Age: {_policies.Retention.MaxAge.TotalDays} days",
+ $" Max Size: {_policies.Retention.MaxSize / 1_073_741_824}GB",
+ $" Max Files: {_policies.Retention.MaxFileCount}",
+ "",
+ "Optimization:",
+ $" Enabled: {_policies.Optimization.Enabled}",
+ $" Analysis Interval: {_policies.Optimization.AnalysisInterval.TotalMinutes:F1}m",
+ $" Auto-Adjust Buffer: {_policies.Optimization.AutoAdjustBufferSizes}",
+ $" Auto-Adjust Batch: {_policies.Optimization.AutoAdjustBatchSizes}"
+ };
+
+ return string.Join(Environment.NewLine, lines);
+ }
+ }
+
+ ///
+ /// Policy action types
+ ///
+ public enum PolicyActionType
+ {
+ None = 0,
+ AnalyzePerformance = 1,
+ CleanupRetentionPolicy = 2,
+ AdjustBatchSize = 3,
+ AdjustBufferSize = 4,
+ AlertHighErrorRate = 5,
+ AlertHighThroughput = 6,
+ CompressArchives = 7
+ }
+
+ ///
+ /// Represents a policy action to execute
+ ///
+ public sealed class PolicyAction
+ {
+ public PolicyActionType Type { get; set; }
+ public string? Reason { get; set; }
+ public Dictionary Parameters { get; set; } = new();
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ }
+}
diff --git a/EonaCat.LogStack/SuperiorFeaturesStats.cs b/EonaCat.LogStack/SuperiorFeaturesStats.cs
new file mode 100644
index 0000000..aa1ff97
--- /dev/null
+++ b/EonaCat.LogStack/SuperiorFeaturesStats.cs
@@ -0,0 +1,105 @@
+using EonaCat.LogStack.Anomalies;
+using EonaCat.LogStack.DeadLettering;
+using EonaCat.LogStack.Diagnostics;
+using EonaCat.LogStack.Routing;
+using EonaCat.LogStack.Sampling;
+using System;
+using System.Collections.Generic;
+
+// 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.
+
+namespace EonaCat.LogStack;
+
+///
+/// Aggregated statistics from all superior features.
+///
+public class SuperiorFeaturesStats
+{
+ ///
+ /// Whether intelligent routing is enabled.
+ ///
+ public bool IntelligentRoutingEnabled { get; set; }
+
+ ///
+ /// Statistics from the intelligent router.
+ ///
+ public RouterStats RouterStats { get; set; }
+
+ ///
+ /// Whether adaptive sampling is enabled.
+ ///
+ public bool AdaptiveSamplingEnabled { get; set; }
+
+ ///
+ /// Metrics from the adaptive sampler.
+ ///
+ public SamplingMetrics SamplingMetrics { get; set; }
+
+ ///
+ /// Whether anomaly detection is enabled.
+ ///
+ public bool AnomalyDetectionEnabled { get; set; }
+
+ ///
+ /// Statistics from the anomaly detector.
+ ///
+ public AnomalyDetectorStats AnomalyStats { get; set; }
+
+ ///
+ /// Recent anomalies that were detected.
+ ///
+ public List RecentAnomalies { get; set; }
+
+ ///
+ /// Whether context snapshots are enabled.
+ ///
+ public bool ContextSnapshotsEnabled { get; set; }
+
+ ///
+ /// Statistics from the context snapshot collector.
+ ///
+ public SnapshotCollectorStats SnapshotStats { get; set; }
+
+ ///
+ /// Whether the dead letter queue is enabled.
+ ///
+ public bool DeadLetterQueueEnabled { get; set; }
+
+ ///
+ /// Statistics from the dead letter queue.
+ ///
+ public DeadLetterQueueStats DlqStats { get; set; }
+
+ ///
+ /// Failure reasons grouped by count.
+ ///
+ public Dictionary DlqFailureReasons { get; set; }
+
+ ///
+ /// Gets a summary string of all enabled features.
+ ///
+ public string GetEnabledFeaturesSummary()
+ {
+ var features = new List();
+
+ if (IntelligentRoutingEnabled)
+ features.Add($"IntelligentRouting({RouterStats?.TotalRules ?? 0} rules)");
+
+ if (AdaptiveSamplingEnabled)
+ features.Add($"AdaptiveSampling({SamplingMetrics?.SamplingRate:P1} rate)");
+
+ if (AnomalyDetectionEnabled)
+ features.Add($"AnomalyDetection({AnomalyStats?.RecentAnomalies ?? 0} recent)");
+
+ if (ContextSnapshotsEnabled)
+ features.Add($"ContextSnapshots({SnapshotStats?.CurrentSnapshots ?? 0} snapshots)");
+
+ if (DeadLetterQueueEnabled)
+ features.Add($"DeadLetterQueue({DlqStats?.CurrentQueueSize ?? 0}/{DlqStats?.MaxCapacity ?? 0})");
+
+ return "Superior Features: " + (features.Count > 0
+ ? string.Join(", ", features)
+ : "None enabled");
+ }
+}
diff --git a/EonaCat.LogStack/Telemetry/HealthMonitor.cs b/EonaCat.LogStack/Telemetry/HealthMonitor.cs
new file mode 100644
index 0000000..1c41385
--- /dev/null
+++ b/EonaCat.LogStack/Telemetry/HealthMonitor.cs
@@ -0,0 +1,239 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace EonaCat.LogStack.Telemetry
+{
+ ///
+ /// Monitors the health status of the logger and flows.
+ /// Tracks degradation, failures, and recovery events.
+ ///
+ public sealed class HealthMonitor
+ {
+ private Dictionary _componentHealth = new();
+ private OverallHealth _overallHealth = new();
+ private readonly object _lock = new();
+
+ ///
+ /// Gets overall health status
+ ///
+ public HealthStatus OverallStatus
+ {
+ get
+ {
+ lock (_lock)
+ {
+ return _overallHealth.Status;
+ }
+ }
+ }
+
+ ///
+ /// Gets health status of a component
+ ///
+ public ComponentHealth? GetComponentHealth(string name)
+ {
+ lock (_lock)
+ {
+ return _componentHealth.TryGetValue(name, out var health) ? health : null;
+ }
+ }
+
+ ///
+ /// Updates health of a component
+ ///
+ public void UpdateComponentHealth(string name, HealthStatus status, string? message = null)
+ {
+ lock (_lock)
+ {
+ var now = DateTime.UtcNow;
+
+ if (!_componentHealth.TryGetValue(name, out var health))
+ {
+ health = new ComponentHealth { Name = name };
+ _componentHealth[name] = health;
+ }
+
+ var previousStatus = health.Status;
+ health.Status = status;
+ health.Message = message;
+ health.LastUpdated = now;
+
+ if (status != previousStatus)
+ {
+ health.StatusChangedAt = now;
+ health.StatusChangeCount++;
+ }
+
+ RecalculateOverallHealth();
+ }
+ }
+
+ ///
+ /// Records an error event
+ ///
+ public void RecordError(string componentName, Exception exception)
+ {
+ lock (_lock)
+ {
+ if (!_componentHealth.TryGetValue(componentName, out var health))
+ {
+ health = new ComponentHealth { Name = componentName };
+ _componentHealth[componentName] = health;
+ }
+
+ health.ErrorCount++;
+ health.LastErrorAt = DateTime.UtcNow;
+ health.LastError = exception;
+
+ if (health.ErrorCount > 10)
+ {
+ health.Status = HealthStatus.Degraded;
+ }
+ if (health.ErrorCount > 50)
+ {
+ health.Status = HealthStatus.Unhealthy;
+ }
+
+ RecalculateOverallHealth();
+ }
+ }
+
+ ///
+ /// Recovers a component
+ ///
+ public void RecoverComponent(string componentName)
+ {
+ lock (_lock)
+ {
+ if (_componentHealth.TryGetValue(componentName, out var health))
+ {
+ health.Status = HealthStatus.Healthy;
+ health.ErrorCount = 0;
+ health.Message = null;
+ RecalculateOverallHealth();
+ }
+ }
+ }
+
+ ///
+ /// Gets all component health statuses
+ ///
+ public IReadOnlyDictionary GetAllComponentHealth()
+ {
+ lock (_lock)
+ {
+ return new Dictionary(_componentHealth);
+ }
+ }
+
+ ///
+ /// Gets health snapshot
+ ///
+ public HealthSnapshot GetSnapshot()
+ {
+ lock (_lock)
+ {
+ return new HealthSnapshot
+ {
+ OverallStatus = _overallHealth.Status,
+ OverallMessage = _overallHealth.Message,
+ CapturedAt = DateTime.UtcNow,
+ Components = new Dictionary(_componentHealth),
+ TotalComponents = _componentHealth.Count,
+ HealthyComponents = _componentHealth.Count(kvp => kvp.Value.Status == HealthStatus.Healthy),
+ DegradedComponents = _componentHealth.Count(kvp => kvp.Value.Status == HealthStatus.Degraded),
+ UnhealthyComponents = _componentHealth.Count(kvp => kvp.Value.Status == HealthStatus.Unhealthy)
+ };
+ }
+ }
+
+ private void RecalculateOverallHealth()
+ {
+ if (_componentHealth.Count == 0)
+ {
+ _overallHealth.Status = HealthStatus.Unknown;
+ return;
+ }
+
+ // If any component is unhealthy, overall is unhealthy
+ if (_componentHealth.Values.Any(h => h.Status == HealthStatus.Unhealthy))
+ {
+ _overallHealth.Status = HealthStatus.Unhealthy;
+ _overallHealth.Message = "One or more components are unhealthy";
+ }
+ // If any component is degraded, overall is degraded
+ else if (_componentHealth.Values.Any(h => h.Status == HealthStatus.Degraded))
+ {
+ _overallHealth.Status = HealthStatus.Degraded;
+ _overallHealth.Message = "One or more components are degraded";
+ }
+ // If all are healthy, overall is healthy
+ else if (_componentHealth.Values.All(h => h.Status == HealthStatus.Healthy))
+ {
+ _overallHealth.Status = HealthStatus.Healthy;
+ _overallHealth.Message = "All components are healthy";
+ }
+ else
+ {
+ _overallHealth.Status = HealthStatus.Unknown;
+ }
+ }
+ }
+
+ ///
+ /// Health status enumeration
+ ///
+ public enum HealthStatus
+ {
+ Unknown = 0,
+ Healthy = 1,
+ Degraded = 2,
+ Unhealthy = 3
+ }
+
+ ///
+ /// Health status of a single component
+ ///
+ public sealed class ComponentHealth
+ {
+ public string Name { get; set; }
+ public HealthStatus Status { get; set; } = HealthStatus.Unknown;
+ public string? Message { get; set; }
+ public DateTime LastUpdated { get; set; } = DateTime.UtcNow;
+ public DateTime? StatusChangedAt { get; set; }
+ public int StatusChangeCount { get; set; }
+ public long ErrorCount { get; set; }
+ public DateTime? LastErrorAt { get; set; }
+ public Exception? LastError { get; set; }
+ }
+
+ ///
+ /// Overall health information
+ ///
+ public sealed class OverallHealth
+ {
+ public HealthStatus Status { get; set; } = HealthStatus.Unknown;
+ public string? Message { get; set; }
+ }
+
+ ///
+ /// Snapshot of health status
+ ///
+ public sealed class HealthSnapshot
+ {
+ public HealthStatus OverallStatus { get; set; }
+ public string? OverallMessage { get; set; }
+ public DateTime CapturedAt { get; set; }
+ public Dictionary Components { get; set; } = new();
+ public int TotalComponents { get; set; }
+ public int HealthyComponents { get; set; }
+ public int DegradedComponents { get; set; }
+ public int UnhealthyComponents { get; set; }
+
+ public override string ToString()
+ {
+ return $"{OverallStatus} | Total: {TotalComponents} | Healthy: {HealthyComponents} | Degraded: {DegradedComponents} | Unhealthy: {UnhealthyComponents}";
+ }
+ }
+}
diff --git a/EonaCat.LogStack/Telemetry/TelemetryAggregator.cs b/EonaCat.LogStack/Telemetry/TelemetryAggregator.cs
new file mode 100644
index 0000000..c829e6b
--- /dev/null
+++ b/EonaCat.LogStack/Telemetry/TelemetryAggregator.cs
@@ -0,0 +1,293 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace EonaCat.LogStack.Telemetry
+{
+ ///
+ /// Aggregates telemetry data from multiple sources and flows.
+ /// Correlates metrics and provides unified telemetry snapshots.
+ ///
+ public sealed class TelemetryAggregator
+ {
+ private readonly ConcurrentDictionary _metrics = new();
+ private readonly ConcurrentDictionary _gauges = new();
+ private readonly List _events = new();
+ private readonly HealthMonitor _healthMonitor = new();
+ private readonly object _eventsLock = new();
+ private long _eventCount;
+ private DateTime _startTime = DateTime.UtcNow;
+
+ ///
+ /// Gets the health monitor
+ ///
+ public HealthMonitor HealthMonitor => _healthMonitor;
+
+ ///
+ /// Records a counter metric
+ ///
+ public void RecordCounter(string name, long value = 1, Dictionary? tags = null)
+ {
+ var key = $"{name}:{TagsKey(tags)}";
+ _metrics.AddOrUpdate(key,
+ new MetricValue { Name = name, Value = value, Tags = tags ?? new() },
+ (_, existing) => new MetricValue
+ {
+ Name = name,
+ Value = existing.Value + value,
+ Tags = tags ?? new()
+ });
+ _eventCount++;
+ }
+
+ ///
+ /// Records a gauge metric
+ ///
+ public void RecordGauge(string name, double value, Dictionary? tags = null)
+ {
+ var key = $"{name}:{TagsKey(tags)}";
+ _gauges[key] = new GaugeValue { Name = name, Value = value, Tags = tags ?? new(), RecordedAt = DateTime.UtcNow };
+ }
+
+ ///
+ /// Records a histogram metric
+ ///
+ public void RecordHistogram(string name, double value, Dictionary? tags = null)
+ {
+ var key = $"{name}_histogram:{TagsKey(tags)}";
+ var metric = _metrics.GetOrAdd(key, new HistogramValue { Name = name, Tags = tags ?? new() });
+ if (metric is HistogramValue histogram)
+ {
+ histogram.Record(value);
+ }
+ }
+
+ ///
+ /// Records a telemetry event
+ ///
+ public void RecordEvent(TelemetryEvent evt)
+ {
+ lock (_eventsLock)
+ {
+ _events.Add(evt);
+ if (_events.Count > 10000) // Keep last 10k events
+ {
+ _events.RemoveRange(0, 1000);
+ }
+ }
+ }
+
+ ///
+ /// Gets metric value
+ ///
+ public MetricValue? GetMetric(string name)
+ {
+ foreach (var kvp in _metrics.Where(kvp => kvp.Value.Name == name))
+ {
+ return kvp.Value;
+ }
+ return null;
+ }
+
+ ///
+ /// Gets gauge value
+ ///
+ public GaugeValue? GetGauge(string name)
+ {
+ foreach (var kvp in _gauges.Where(kvp => kvp.Value.Name == name))
+ {
+ return kvp.Value;
+ }
+ return null;
+ }
+
+ ///
+ /// Gets all current metrics
+ ///
+ public IReadOnlyCollection GetAllMetrics()
+ {
+ return _metrics.Values.ToList().AsReadOnly();
+ }
+
+ ///
+ /// Gets all current gauges
+ ///
+ public IReadOnlyCollection GetAllGauges()
+ {
+ return _gauges.Values.ToList().AsReadOnly();
+ }
+
+ ///
+ /// Gets recent events
+ ///
+ public IReadOnlyList GetRecentEvents(int count = 100)
+ {
+ lock (_eventsLock)
+ {
+ var start = Math.Max(0, _events.Count - count);
+ return _events.Skip(start).ToList().AsReadOnly();
+ }
+ }
+
+ ///
+ /// Gets aggregated telemetry snapshot
+ ///
+ public AggregatedTelemetrySnapshot GetSnapshot()
+ {
+ var uptime = DateTime.UtcNow - _startTime;
+ var healthSnapshot = _healthMonitor.GetSnapshot();
+
+ // Calculate derived metrics
+ var loggedCounter = GetMetric("logs_logged");
+ var droppedCounter = GetMetric("logs_dropped");
+
+ return new AggregatedTelemetrySnapshot
+ {
+ Uptime = uptime,
+ CapturedAt = DateTime.UtcNow,
+ TotalEvents = _eventCount,
+ MetricCount = _metrics.Count,
+ GaugeCount = _gauges.Count,
+ RecordedMetrics = _metrics.Values.ToList(),
+ RecordedGauges = _gauges.Values.ToList(),
+ HealthStatus = healthSnapshot,
+ LogsLogged = loggedCounter?.Value ?? 0,
+ LogsDropped = droppedCounter?.Value ?? 0,
+ RecentEventsCount = _events.Count
+ };
+ }
+
+ ///
+ /// Exports metrics in prometheus-like format
+ ///
+ public string ExportPrometheusFormat()
+ {
+ var lines = new List { "# HELP EonaCat.LogStack telemetry metrics" };
+
+ foreach (var metric in _metrics.Values.GroupBy(m => m.Name))
+ {
+ lines.Add($"# TYPE {metric.Key} gauge");
+ foreach (var m in metric)
+ {
+ var tagsStr = m.Tags.Any() ? "{" + string.Join(",", m.Tags.Select(t => $"{t.Key}=\"{t.Value}\"")) + "}" : "";
+ lines.Add($"{metric.Key}{tagsStr} {m.Value}");
+ }
+ }
+
+ foreach (var gauge in _gauges.Values.GroupBy(g => g.Name))
+ {
+ lines.Add($"# TYPE {gauge.Key} gauge");
+ foreach (var g in gauge)
+ {
+ var tagsStr = g.Tags.Any() ? "{" + string.Join(",", g.Tags.Select(t => $"{t.Key}=\"{t.Value}\"")) + "}" : "";
+ lines.Add($"{gauge.Key}{tagsStr} {g.Value}");
+ }
+ }
+
+ return string.Join("\n", lines);
+ }
+
+ ///
+ /// Resets all metrics
+ ///
+ public void Reset()
+ {
+ _metrics.Clear();
+ _gauges.Clear();
+ lock (_eventsLock)
+ {
+ _events.Clear();
+ }
+ _eventCount = 0;
+ _startTime = DateTime.UtcNow;
+ }
+
+ private static string TagsKey(Dictionary? tags)
+ {
+ if (tags == null || tags.Count == 0) return "";
+ return string.Join(",", tags.OrderBy(t => t.Key).Select(t => $"{t.Key}={t.Value}"));
+ }
+ }
+
+ ///
+ /// Represents a metric value
+ ///
+ public class MetricValue
+ {
+ public string Name { get; set; }
+ public long Value { get; set; }
+ public Dictionary Tags { get; set; } = new();
+ }
+
+ ///
+ /// Represents a gauge value
+ ///
+ public sealed class GaugeValue
+ {
+ public string Name { get; set; }
+ public double Value { get; set; }
+ public Dictionary Tags { get; set; } = new();
+ public DateTime RecordedAt { get; set; }
+ }
+
+ ///
+ /// Histogram metric value
+ ///
+ public sealed class HistogramValue : MetricValue
+ {
+ private readonly List _values = new();
+ public IReadOnlyList Values => _values.AsReadOnly();
+ public double Min { get; private set; } = double.MaxValue;
+ public double Max { get; private set; } = double.MinValue;
+ public double Sum { get; private set; }
+ public double Mean => Values.Count > 0 ? Sum / Values.Count : 0;
+ public double P95 => CalculatePercentile(0.95);
+ public double P99 => CalculatePercentile(0.99);
+
+ public void Record(double value)
+ {
+ _values.Add(value);
+ Value++;
+ Sum += value;
+ if (value < Min) Min = value;
+ if (value > Max) Max = value;
+ if (_values.Count > 1000) // Keep reasonable history
+ {
+ _values.RemoveRange(0, 100);
+ }
+ }
+
+ private double CalculatePercentile(double percentile)
+ {
+ if (_values.Count == 0) return 0;
+ var sorted = _values.OrderBy(v => v).ToList();
+ var index = (int)((percentile / 100.0) * sorted.Count);
+ index = Math.Min(index, sorted.Count - 1);
+ return sorted[index];
+ }
+ }
+
+ ///
+ /// Aggregated telemetry snapshot
+ ///
+ public sealed class AggregatedTelemetrySnapshot
+ {
+ public TimeSpan Uptime { get; set; }
+ public DateTime CapturedAt { get; set; }
+ public long TotalEvents { get; set; }
+ public int MetricCount { get; set; }
+ public int GaugeCount { get; set; }
+ public List RecordedMetrics { get; set; } = new();
+ public List RecordedGauges { get; set; } = new();
+ public HealthSnapshot HealthStatus { get; set; }
+ public long LogsLogged { get; set; }
+ public long LogsDropped { get; set; }
+ public int RecentEventsCount { get; set; }
+
+ public override string ToString()
+ {
+ return $"Uptime: {Uptime.TotalSeconds:F2}s | Events: {TotalEvents} | Health: {HealthStatus.OverallStatus} | Logged: {LogsLogged} | Dropped: {LogsDropped}";
+ }
+ }
+}
diff --git a/README.md b/README.md
index d8014e8..c37949b 100644
--- a/README.md
+++ b/README.md
@@ -31,26 +31,398 @@ It features a rich fluent API for routing log events to dozens of destinations -
- **Lazy initialization** - Flows are only initialized when first used, reducing startup overhead.
-### Telemetry & Observability
+## Telemetry & Observability
-EonaCat.LogStack includes built-in telemetry primitives for production monitoring:
+EonaCat.LogStack includes **built-in, zero-dependency telemetry** for comprehensive 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.
+### Enhanced Telemetry System
+
+#### Metrics Collection & Aggregation
+
+Track logging operations with built-in metrics:
-Example:
```csharp
-var options = new TelemetryOptions
-{
- Enabled = true,
- IncludeRuntimeMetrics = true,
- IncludeTraceMetrics = true
-};
+var aggregator = new EonaCat.LogStack.Telemetry.TelemetryAggregator();
+
+// Record counters
+aggregator.RecordCounter("logs_logged", 1);
+aggregator.RecordCounter("logs_dropped", 1);
+
+// Record gauges (point-in-time measurements)
+aggregator.RecordGauge("memory_usage_mb", GC.GetTotalMemory(false) / 1024 / 1024);
+
+// Record histograms (with percentiles)
+aggregator.RecordHistogram("request_duration_ms", 42);
+
+// Get snapshot
+var snapshot = aggregator.GetSnapshot();
+Console.WriteLine($"Uptime: {snapshot.Uptime.TotalSeconds}s");
+Console.WriteLine($"Total Events: {snapshot.TotalEvents}");
+Console.WriteLine($"Logs Logged: {snapshot.LogsLogged}");
+Console.WriteLine($"Logs Dropped: {snapshot.LogsDropped}");
```
+#### Health Monitoring
+
+Track component health and detect degradation:
+
+```csharp
+var health = new EonaCat.LogStack.Telemetry.HealthMonitor();
+
+// Update component health
+health.UpdateComponentHealth("EmailFlow", HealthStatus.Healthy);
+health.UpdateComponentHealth("ElasticsearchFlow", HealthStatus.Degraded, "High latency detected");
+
+// Record errors automatically impact health
+health.RecordError("DatabaseFlow", new TimeoutException("Connection timeout"));
+
+// Get health snapshot
+var snapshot = health.GetSnapshot();
+Console.WriteLine($"Overall Status: {snapshot.OverallStatus}");
+Console.WriteLine($"Healthy: {snapshot.HealthyComponents}");
+Console.WriteLine($"Degraded: {snapshot.DegradedComponents}");
+console.WriteLine($"Unhealthy: {snapshot.UnhealthyComponents}");
+```
+
+#### Distributed Tracing
+
+Enable end-to-end request tracing across async boundaries:
+
+```csharp
+// Enable tracing in DI
+services.AddEonaCatTracing();
+
+// Use trace context manager
+var traceId = TraceContextManager.GetTraceId();
+TraceContextManager.SetCorrelationId("order-123");
+TraceContextManager.AddBaggage("UserId", "user-456");
+
+// Create spans for operations
+var spanFactory = sp.GetRequiredService();
+
+using (spanFactory.CreateSpanScope("ProcessOrder"))
+{
+ // Operation code here
+ // Automatically tracked with context propagation
+}
+
+// Access current trace context
+var context = TraceContextManager.Current;
+Console.WriteLine($"Trace: {context.TraceId}");
+Console.WriteLine($"Span: {context.SpanId}");
+Console.WriteLine($"Parent: {context.ParentSpanId}");
+```
+
+#### Performance Monitoring & Insights
+
+Automatic performance analysis with bottleneck detection:
+
+```csharp
+var insights = new EonaCat.LogStack.PerformanceInsights.PerformanceInsightsCollector();
+
+// Track operations
+var sw = System.Diagnostics.Stopwatch.StartNew();
+// ... operation ...
+sw.Stop();
+insights.RecordOperation("DatabaseQuery", sw.ElapsedTicks, byteCount: 1024, isError: false);
+
+// Get performance analysis
+var analysis = new EonaCat.LogStack.PerformanceInsights.PerformanceAnalyzer(insights).Analyze();
+
+Console.WriteLine(analysis.Summary);
+/* Output:
+Performance Analysis - 2026-03-27T09:15:00Z
+Uptime: 300.45s
+Total Operations: 15234
+Total Errors: 2
+
+Top Slowest Operations:
+ - DatabaseQuery: 245.32ms (min: 10.12ms, max: 1523.45ms)
+ - HttpRequest: 156.78ms (min: 45.23ms, max: 892.34ms)
+
+Operations with Most Errors:
+ - ExternalAPI: 2 errors (0.13% error rate)
+
+Recommendations:
+ [High] ErrorRate: Error rate is 0.01%. Consider investigating error causes.
+ [Medium] SlowOperations: Some operations are very slow (>1000ms avg).
+*/
+```
+
+### Advanced DI Integration
+
+#### Named Loggers per Category
+
+Use different logger configurations per component:
+
+```csharp
+// Register named loggers
+services.AddEonaCatNamedLoggers(new LoggerDIOptions
+{
+ EnableTelemetry = true,
+ EnableTracing = true,
+ EnablePerformanceMonitoring = true,
+ EagerlyInitializeNamedLoggers = true
+});
+
+// Later, get loggers by name
+var namedLoggerFactory = sp.GetRequiredService();
+
+var apiLogger = namedLoggerFactory.GetLogger("API", builder =>
+ builder.WithMinimumLevel(LogLevel.Debug));
+
+var dbLogger = namedLoggerFactory.GetLogger("Database", builder =>
+ builder.WithMinimumLevel(LogLevel.Information));
+```
+
+#### Composite Loggers
+
+Combine multiple loggers into one:
+
+```csharp
+var logger1 = new LogBuilder("Console").WriteToConsole().Build();
+var logger2 = new LogBuilder("File").WriteToFile("./logs").Build();
+
+var composite = new EonaCat.LogStack.DependencyInjection.CompositeLogger(logger1, logger2);
+
+composite.Information("This goes to both console and file");
+```
+
+#### Logger Decorators
+
+Add cross-cutting concerns to loggers:
+
+```csharp
+var chain = new EonaCat.LogStack.DependencyInjection.DecoratorChain()
+ .Add(new PerformanceDecorator())
+ .Add(new SecurityDecorator());
+
+var decoratedLogger = chain.Apply(baseLogger);
+```
+
+### Premium Features
+
+#### Automatic Policy Engine
+
+Enable automatic optimization:
+
+```csharp
+var policies = new EonaCat.LogStack.Policies.AutoPolicies
+{
+ Batching = new AutoBatchingPolicy
+ {
+ MinimumBatchSize = 10,
+ MaximumBatchSize = 100,
+ AdaptBatchSize = true
+ },
+ Scaling = new AutoScalingPolicy
+ {
+ Enabled = true,
+ BytesPerSecondThreshold = 10_000_000,
+ ErrorRateThreshold = 0.05
+ },
+ Retention = new AutoRetentionPolicy
+ {
+ MaxAge = TimeSpan.FromDays(30),
+ MaxSize = 10_737_418_240,
+ CompressArchives = true
+ }
+};
+
+var engine = new EonaCat.LogStack.Policies.AutoPolicyEngine(policies);
+var actions = engine.EvaluatePolicies();
+
+foreach (var action in actions)
+{
+ Console.WriteLine($"Action: {action.Type} - {action.Reason}");
+}
+
+Console.WriteLine(engine.GetPoliciesSummary());
+```
+
+#### Common Logging Patterns
+
+Pre-built patterns for common scenarios:
+
+```csharp
+using EonaCat.LogStack.Patterns;
+
+// HTTP Request logging
+var reqLogger = new RequestResponseLogger(logger);
+reqLogger.LogRequest("GET", "/api/users", "req-123");
+reqLogger.LogResponse(200, 42, "req-123");
+
+// Database operation logging
+var dbLogger = new DatabaseOperationLogger(logger);
+dbLogger.LogQuery("SELECT", "Users");
+dbLogger.LogQueryTiming("SELECT", 156, rowsAffected: 500);
+dbLogger.LogConnection("opened");
+
+// Service initialization logging
+var initLogger = new ServiceInitializationLogger(logger, "OrderService");
+initLogger.LogInitializationStart();
+initLogger.LogComponentInit("DatabaseConnection", success: true);
+initLogger.LogComponentInit("CacheConnection", success: true);
+initLogger.LogInitializationComplete(TimeSpan.FromMilliseconds(450));
+
+// Performance measurement
+var perfLogger = new PerformanceLogger(logger, thresholdMs: 1000);
+perfLogger.LogTiming("UserQuery", 245);
+perfLogger.LogMemoryUsage(52_428_800, 104_857_600);
+
+// Timed scope for automatic duration logging
+using (var scope = perfLogger.StartTimedScope("OrderProcessing"))
+{
+ // ... processing code ...
+} // Automatically logs duration
+```
+
+#### Auto-Initialization
+
+Automatic logger initialization by type:
+
+```csharp
+var provider = new EonaCat.LogStack.Utilities.AutoInitializingLoggerProvider(loggerFactory);
+
+// Get logger for type (auto-creates if needed)
+var logger = provider.GetLoggerForType();
+
+// Or generic
+var logger = provider.GetLoggerForType(typeof(PaymentProcessor));
+
+// Static provider for global access
+EonaCat.LogStack.Utilities.StaticLoggerProvider.Initialize(loggerFactory);
+var logger = EonaCat.LogStack.Utilities.StaticLoggerProvider.GetLoggerFor();
+```
+
+#### Log Processing Utilities
+
+Advanced log event processing:
+
+```csharp
+// Batch processing
+var processor = new EonaCat.LogStack.Utilities.LogBatchProcessor(
+ batchSize: 50,
+ flushInterval: TimeSpan.FromSeconds(5),
+ processor: batch => Console.WriteLine($"Processing {batch.Count} events"));
+
+// Intelligent routing
+var router = new EonaCat.LogStack.Utilities.LogRouter();
+router.RegisterLevelRoute(LogLevel.Error, evt => SendAlert(evt));
+router.RegisterCategoryRoute("Security", evt => AuditLog(evt));
+router.RegisterDefaultRoute(evt => Console.WriteLine(evt.Message));
+
+// Event filtering
+var filter = new EonaCat.LogStack.Utilities.LogEventFilter()
+ .AddLevelFilter(LogLevel.Warning)
+ .AddCategoryFilter("API")
+ .AddMessageFilter("timeout");
+
+var filtered = filter.Filter(allEvents);
+```
+
+### DI Configuration Examples
+
+#### Complete Setup
+
+```csharp
+services.AddEonaCatLogging("MyApp", builder =>
+ builder
+ .WithMinimumLevel(LogLevel.Information)
+ .WriteToConsole()
+ .WriteToFile("./logs")
+ .BoostWithCorrelationId());
+
+// Enable all advanced features
+services.AddEonaCatNamedLoggers(new LoggerDIOptions
+{
+ EnableTelemetry = true,
+ EnableTracing = true,
+ EnablePerformanceMonitoring = true,
+ EnableHealthMonitoring = true
+});
+
+services.AddEonaCatTracing();
+services.AddEonaCatPerformanceMonitoring();
+services.AddEonaCatHealthMonitoring();
+services.AddEonaCatTelemetryAggregation();
+```
+
+#### Usage in Application
+
+```csharp
+public class OrderService
+{
+ private readonly ILogger _logger;
+ private readonly TelemetryAggregator _telemetry;
+ private readonly SpanFactory _spans;
+ private readonly PerformanceAnalyzer _perf;
+
+ public OrderService(
+ ILogger logger,
+ TelemetryAggregator telemetry,
+ SpanFactory spans,
+ PerformanceAnalyzer perf)
+ {
+ _logger = logger;
+ _telemetry = telemetry;
+ _spans = spans;
+ _perf = perf;
+ }
+
+ public async Task ProcessOrderAsync(string orderId)
+ {
+ using var span = _spans.CreateSpanScope("ProcessOrder");
+ span.SetAttribute("orderId", orderId);
+
+ try
+ {
+ _logger.Information("Processing order",
+ ("OrderId", orderId),
+ ("Timestamp", DateTime.UtcNow));
+
+ _telemetry.RecordCounter("orders.processed", 1);
+
+ // ... processing logic ...
+
+ _telemetry.RecordGauge("order.revenue", 99.99);
+ return order;
+ }
+ catch (Exception ex)
+ {
+ _telemetry.RecordCounter("orders.failed", 1);
+ span.RecordException(ex);
+ _logger.Error(ex, "Order processing failed", ("OrderId", orderId));
+ throw;
+ }
+ }
+}
+```
+
+### Telemetry Export
+
+Export metrics to external systems:
+
+```csharp
+// Prometheus-format export
+var prometheus = aggregator.ExportPrometheusFormat();
+File.WriteAllText("metrics.txt", prometheus);
+
+// Custom export
+var snapshot = aggregator.GetSnapshot();
+await httpClient.PostAsJsonAsync(
+ "https://monitoring.example.com/metrics",
+ new
+ {
+ snapshot.Uptime,
+ snapshot.TotalEvents,
+ snapshot.HealthStatus,
+ Metrics = snapshot.RecordedMetrics
+ });
+```
+
+
+
## Supported Targets
- .NET Standard 2.1