diff --git a/EonaCat.LogStack/EonaCat.LogStack.csproj b/EonaCat.LogStack/EonaCat.LogStack.csproj
index e9ba172..01cc4e7 100644
--- a/EonaCat.LogStack/EonaCat.LogStack.csproj
+++ b/EonaCat.LogStack/EonaCat.LogStack.csproj
@@ -110,4 +110,8 @@ 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/Extensions/LoggerFeatureExtensions.cs b/EonaCat.LogStack/EonaCat.LogStack/Extensions/LoggerFeatureExtensions.cs
new file mode 100644
index 0000000..d3f2f06
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/Extensions/LoggerFeatureExtensions.cs
@@ -0,0 +1,329 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using EonaCat.LogStack.Core;
+using EonaCat.LogStack.Logging;
+using EonaCat.LogStack.Features;
+
+namespace EonaCat.LogStack.Extensions
+{
+ ///
+ /// Fluent extension methods for integrating advanced logging features.
+ ///
+ public static class LoggerFeatureExtensions
+ {
+ ///
+ /// Registers LogSearchEngine in the dependency injection container.
+ ///
+ public static IServiceCollection AddLogSearchEngine(this IServiceCollection services, int maxIndexSize = 10000)
+ {
+ services.AddSingleton(new LogSearchEngine(maxIndexSize));
+ return services;
+ }
+
+ ///
+ /// Registers AlertingEngine in the dependency injection container.
+ ///
+ public static IServiceCollection AddAlertingEngine(this IServiceCollection services)
+ {
+ services.AddSingleton(new AlertingEngine());
+ return services;
+ }
+
+ ///
+ /// Registers LogReplayEngine in the dependency injection container.
+ ///
+ public static IServiceCollection AddLogReplayEngine(this IServiceCollection services)
+ {
+ services.AddSingleton(new LogReplayEngine());
+ return services;
+ }
+
+ ///
+ /// Registers ConfigurationValidator and ConfigurationHotReloadManager in the DI container.
+ ///
+ public static IServiceCollection AddConfigurationValidation(this IServiceCollection services)
+ {
+ services.AddSingleton(new ConfigurationValidator());
+ services.AddSingleton(new ConfigurationHotReloadManager());
+ return services;
+ }
+
+ ///
+ /// Registers CorrelationDashboardHelper in the dependency injection container.
+ ///
+ public static IServiceCollection AddCorrelationDashboardHelper(this IServiceCollection services)
+ {
+ services.AddSingleton(new CorrelationDashboardHelper());
+ return services;
+ }
+
+ ///
+ /// Registers all logging features at once.
+ ///
+ public static IServiceCollection AddAllEonaCatLoggingFeatures(this IServiceCollection services,
+ LogFeaturesConfiguration config = null)
+ {
+ config ??= new LogFeaturesConfiguration();
+
+ services.AddLogSearchEngine(config.LogSearchMaxSize);
+ services.AddAlertingEngine();
+ services.AddLogReplayEngine();
+ services.AddConfigurationValidation();
+ services.AddCorrelationDashboardHelper();
+
+ return services;
+ }
+
+ ///
+ /// Gets LogSearchEngine from service provider and performs a search.
+ ///
+ public static IServiceProvider WithLogSearch(this IServiceProvider provider,
+ Action configureSearch)
+ {
+ var engine = provider.GetService();
+ if (engine != null)
+ {
+ configureSearch(engine);
+ }
+ return provider;
+ }
+
+ ///
+ /// Gets AlertingEngine from service provider and configures alerting.
+ ///
+ public static IServiceProvider WithAlerting(this IServiceProvider provider,
+ Action configureAlerting)
+ {
+ var engine = provider.GetService();
+ if (engine != null)
+ {
+ configureAlerting(engine);
+ }
+ return provider;
+ }
+
+ ///
+ /// Gets LogReplayEngine from service provider and performs log operations.
+ ///
+ public static IServiceProvider WithLogReplay(this IServiceProvider provider,
+ Action configureReplay)
+ {
+ var engine = provider.GetService();
+ if (engine != null)
+ {
+ configureReplay(engine);
+ }
+ return provider;
+ }
+
+ ///
+ /// Validates logger configuration on startup.
+ ///
+ public static IServiceProvider ValidateLoggerConfiguration(this IServiceProvider provider, ILogger logger)
+ {
+ var validator = provider.GetService();
+ if (validator != null)
+ {
+ var report = validator.Validate(logger);
+ if (!report.IsValid)
+ {
+ var errors = report.GetErrors();
+ if (errors.Any())
+ {
+ throw new InvalidOperationException(
+ $"Logger configuration validation failed:\n{report.ToString()}");
+ }
+ }
+ }
+ return provider;
+ }
+
+ ///
+ /// Enables hot-reload support with a handler callback.
+ ///
+ public static IServiceProvider EnableConfigurationHotReload(this IServiceProvider provider,
+ Action onConfigChanged)
+ {
+ var reloadManager = provider.GetService();
+ if (reloadManager != null)
+ {
+ reloadManager.EnableHotReload(onConfigChanged);
+ }
+ return provider;
+ }
+
+ ///
+ /// Gets CorrelationDashboardHelper from service provider and records activity.
+ ///
+ public static IServiceProvider RecordCorrelatedActivity(this IServiceProvider provider,
+ string correlationId, string activityName, string serviceName, ActivityStatus status,
+ double durationMs, Exception exception = null)
+ {
+ var helper = provider.GetService();
+ if (helper != null)
+ {
+ helper.RecordActivity(correlationId, activityName, serviceName, status, durationMs, exception);
+ }
+ return provider;
+ }
+
+ ///
+ /// Gets tracing dashboard statistics from service provider.
+ ///
+ public static DashboardStatistics GetTracingStatistics(this IServiceProvider provider)
+ {
+ var helper = provider.GetService();
+ return helper?.GetStatistics() ?? new DashboardStatistics();
+ }
+
+ ///
+ /// Gets search statistics from service provider.
+ ///
+ public static LogSearchStatistics GetSearchStatistics(this IServiceProvider provider)
+ {
+ var engine = provider.GetService();
+ return engine?.GetStatistics() ?? new LogSearchStatistics();
+ }
+
+ ///
+ /// Gets alerting statistics from service provider.
+ ///
+ public static AlertingStatistics GetAlertingStatistics(this IServiceProvider provider)
+ {
+ var engine = provider.GetService();
+ return engine?.GetStatistics() ?? new AlertingStatistics();
+ }
+
+ ///
+ /// Performs a complex log search using fluent API.
+ ///
+ public static IEnumerable SearchLogs(this IServiceProvider provider,
+ LogLevel? level = null, string keyword = null, string loggerName = null,
+ string correlationId = null, DateTime? fromUtc = null, DateTime? toUtc = null,
+ int limit = 0)
+ {
+ var engine = provider.GetService();
+ if (engine == null)
+ return Enumerable.Empty();
+
+ var query = new LogSearchQuery
+ {
+ Keyword = keyword,
+ Level = level,
+ LoggerName = loggerName,
+ CorrelationId = correlationId,
+ FromUtc = fromUtc,
+ ToUtc = toUtc,
+ Limit = limit
+ };
+
+ return engine.Search(query);
+ }
+
+ ///
+ /// Adds an alert rule using fluent configuration.
+ ///
+ public static IServiceProvider AddAlertRule(this IServiceProvider provider,
+ string ruleName, Action configureRule)
+ {
+ var engine = provider.GetService();
+ if (engine != null)
+ {
+ engine.AddRule(ruleName, configureRule);
+ }
+ return provider;
+ }
+
+ ///
+ /// Exports captured logs to JSON.
+ ///
+ public static IServiceProvider ExportLogsToJson(this IServiceProvider provider, string filePath)
+ {
+ var engine = provider.GetService();
+ if (engine != null)
+ {
+ engine.ExportToJson(filePath);
+ }
+ return provider;
+ }
+
+ ///
+ /// Imports captured logs from JSON.
+ ///
+ public static IServiceProvider ImportLogsFromJson(this IServiceProvider provider, string filePath)
+ {
+ var engine = provider.GetService();
+ if (engine != null)
+ {
+ engine.ImportFromJson(filePath);
+ }
+ return provider;
+ }
+
+ ///
+ /// Gets a correlation trace for visualization.
+ ///
+ public static GanttChartData GetTraceAsGanttChart(this IServiceProvider provider, string correlationId)
+ {
+ var helper = provider.GetService();
+ return helper?.GetAsGanttChart(correlationId) ?? null;
+ }
+
+ ///
+ /// Gets the service dependency map for the system.
+ ///
+ public static ServiceDependencyMap GetServiceDependencies(this IServiceProvider provider)
+ {
+ var helper = provider.GetService();
+ return helper?.GetDependencyMap() ?? new ServiceDependencyMap();
+ }
+
+ ///
+ /// Gets critical paths (slow traces) from the system.
+ ///
+ public static IEnumerable GetCriticalPaths(this IServiceProvider provider, int topCount = 10)
+ {
+ var helper = provider.GetService();
+ return helper?.FindCriticalPaths(topCount) ?? Enumerable.Empty();
+ }
+ }
+
+ ///
+ /// Configuration for logging features.
+ ///
+ public class LogFeaturesConfiguration
+ {
+ ///
+ /// Maximum number of logs to keep in the search index.
+ ///
+ public int LogSearchMaxSize { get; set; } = 10000;
+
+ ///
+ /// Whether to enable search indexing by default.
+ ///
+ public bool EnableSearchByDefault { get; set; } = true;
+
+ ///
+ /// Whether to enable alerting by default.
+ ///
+ public bool EnableAlertingByDefault { get; set; } = true;
+
+ ///
+ /// Whether to enable log capture for replay by default.
+ ///
+ public bool EnableReplayByDefault { get; set; } = false;
+
+ ///
+ /// Whether to enable tracing dashboard by default.
+ ///
+ public bool EnableTracingByDefault { get; set; } = true;
+
+ ///
+ /// Maximum age of trace data to keep (in hours).
+ ///
+ public int TraceDataMaxAgeHours { get; set; } = 24;
+ }
+}
diff --git a/EonaCat.LogStack/EonaCat.LogStack/Features/AlertingEngine.cs b/EonaCat.LogStack/EonaCat.LogStack/Features/AlertingEngine.cs
new file mode 100644
index 0000000..69be2bd
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/Features/AlertingEngine.cs
@@ -0,0 +1,347 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using EonaCat.LogStack.Core;
+
+namespace EonaCat.LogStack.Features
+{
+ ///
+ /// Rule-based alerting engine for proactive log monitoring and notifications.
+ /// Monitors log streams and triggers alerts based on configurable conditions.
+ ///
+ public class AlertingEngine
+ {
+ private readonly List _rules = new();
+ private readonly object _lockObject = new();
+ private bool _isEnabled = true;
+
+ ///
+ /// Event fired when an alert is triggered.
+ ///
+ public event EventHandler AlertTriggered;
+
+ ///
+ /// Enables or disables the alerting engine.
+ ///
+ public void SetEnabled(bool enabled)
+ {
+ _isEnabled = enabled;
+ }
+
+ ///
+ /// Adds a new alert rule.
+ ///
+ public AlertRule AddRule(string ruleName, Action configureAction)
+ {
+ if (string.IsNullOrWhiteSpace(ruleName))
+ throw new ArgumentException("Rule name cannot be empty", nameof(ruleName));
+
+ var builder = new AlertRuleBuilder(ruleName);
+ configureAction(builder);
+ var rule = builder.Build();
+
+ lock (_lockObject)
+ {
+ _rules.Add(rule);
+ }
+
+ return rule;
+ }
+
+ ///
+ /// Removes a rule by name.
+ ///
+ public bool RemoveRule(string ruleName)
+ {
+ lock (_lockObject)
+ {
+ var rule = _rules.FirstOrDefault(r => r.Name.Equals(ruleName, StringComparison.OrdinalIgnoreCase));
+ if (rule != null)
+ {
+ _rules.Remove(rule);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ ///
+ /// Evaluates a log event against all active rules.
+ ///
+ public void EvaluateLog(LogEvent logEvent, string formattedMessage)
+ {
+ if (!_isEnabled)
+ return;
+
+ lock (_lockObject)
+ {
+ foreach (var rule in _rules.Where(r => r.IsEnabled))
+ {
+ if (EvaluateRule(rule, logEvent, formattedMessage))
+ {
+ var alert = new Alert
+ {
+ RuleName = rule.Name,
+ RuleSeverity = rule.Severity,
+ TriggeredAt = DateTime.UtcNow,
+ LogLevel = logEvent.Level,
+ LoggerName = logEvent.Category ?? "Default",
+ Message = formattedMessage,
+ LogEvent = logEvent
+ };
+
+ AlertTriggered?.Invoke(this, new AlertTriggeredEventArgs { Alert = alert });
+
+ // Execute async action if configured
+ if (rule.OnAlertAsync != null)
+ {
+ _ = Task.Run(async () => await rule.OnAlertAsync(alert));
+ }
+ }
+ }
+ }
+ }
+
+ private bool EvaluateRule(AlertRule rule, LogEvent logEvent, string formattedMessage)
+ {
+ // Level check
+ if (rule.MinimumLevel.HasValue && logEvent.Level < rule.MinimumLevel.Value)
+ return false;
+
+ // Keyword check
+ if (!string.IsNullOrEmpty(rule.KeywordPattern))
+ {
+ if (!formattedMessage.Contains(rule.KeywordPattern, StringComparison.OrdinalIgnoreCase))
+ return false;
+ }
+
+ // Logger check
+ if (!string.IsNullOrEmpty(rule.LoggerNamePattern))
+ {
+ if (!(logEvent.Category?? "").Contains(rule.LoggerNamePattern, StringComparison.OrdinalIgnoreCase))
+ return false;
+ }
+
+ // Exception check
+ if (rule.OnlyWithExceptions && logEvent.Exception == null)
+ return false;
+
+ // Custom predicate
+ if (rule.CustomPredicate != null)
+ {
+ if (!rule.CustomPredicate(logEvent, formattedMessage))
+ return false;
+ }
+
+ // Check throttling
+ if (rule.ThrottleIntervalSeconds > 0)
+ {
+ var now = DateTime.UtcNow;
+ if (rule.LastTriggeredAt.HasValue)
+ {
+ var timeSinceLastTrigger = (now - rule.LastTriggeredAt.Value).TotalSeconds;
+ if (timeSinceLastTrigger < rule.ThrottleIntervalSeconds)
+ return false;
+ }
+ rule.LastTriggeredAt = now;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Gets all active rules.
+ ///
+ public IEnumerable GetRules()
+ {
+ lock (_lockObject)
+ {
+ return _rules.ToList();
+ }
+ }
+
+ ///
+ /// Gets alerting statistics.
+ ///
+ public AlertingStatistics GetStatistics()
+ {
+ lock (_lockObject)
+ {
+ return new AlertingStatistics
+ {
+ TotalRules = _rules.Count,
+ EnabledRules = _rules.Count(r => r.IsEnabled),
+ Rules = _rules.Select(r => new AlertRuleStats
+ {
+ Name = r.Name,
+ IsEnabled = r.IsEnabled,
+ TimesTriggered = r.TimesTriggered,
+ LastTriggered = r.LastTriggeredAt
+ }).ToList()
+ };
+ }
+ }
+
+ ///
+ /// Clears all alert statistics.
+ ///
+ public void ClearStatistics()
+ {
+ lock (_lockObject)
+ {
+ foreach (var rule in _rules)
+ {
+ rule.TimesTriggered = 0;
+ rule.LastTriggeredAt = null;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Represents an alert rule.
+ ///
+ public class AlertRule
+ {
+ public string Name { get; set; }
+ public bool IsEnabled { get; set; } = true;
+ public AlertSeverity Severity { get; set; } = AlertSeverity.Warning;
+ public LogLevel? MinimumLevel { get; set; }
+ public string KeywordPattern { get; set; }
+ public string LoggerNamePattern { get; set; }
+ public bool OnlyWithExceptions { get; set; }
+ public int ThrottleIntervalSeconds { get; set; } = 0; // 0 = no throttling
+ public Func CustomPredicate { get; set; }
+ public Func OnAlertAsync { get; set; }
+ public int TimesTriggered { get; set; }
+ public DateTime? LastTriggeredAt { get; set; }
+ }
+
+ ///
+ /// Fluent builder for alert rules.
+ ///
+ public class AlertRuleBuilder
+ {
+ private readonly AlertRule _rule;
+
+ public AlertRuleBuilder(string ruleName)
+ {
+ _rule = new AlertRule { Name = ruleName };
+ }
+
+ public AlertRuleBuilder WithMinimumLevel(LogLevel level)
+ {
+ _rule.MinimumLevel = level;
+ return this;
+ }
+
+ public AlertRuleBuilder WithKeywordPattern(string pattern)
+ {
+ _rule.KeywordPattern = pattern;
+ return this;
+ }
+
+ public AlertRuleBuilder WithLoggerNamePattern(string pattern)
+ {
+ _rule.LoggerNamePattern = pattern;
+ return this;
+ }
+
+ public AlertRuleBuilder OnlyWhenExceptionPresent()
+ {
+ _rule.OnlyWithExceptions = true;
+ return this;
+ }
+
+ public AlertRuleBuilder WithSeverity(AlertSeverity severity)
+ {
+ _rule.Severity = severity;
+ return this;
+ }
+
+ public AlertRuleBuilder WithThrottling(int intervalSeconds)
+ {
+ if (intervalSeconds < 0)
+ throw new ArgumentException("Throttle interval must be >= 0", nameof(intervalSeconds));
+ _rule.ThrottleIntervalSeconds = intervalSeconds;
+ return this;
+ }
+
+ public AlertRuleBuilder WithCustomCondition(Func predicate)
+ {
+ _rule.CustomPredicate = predicate;
+ return this;
+ }
+
+ public AlertRuleBuilder OnAlert(Func handler)
+ {
+ _rule.OnAlertAsync = handler;
+ return this;
+ }
+
+ public AlertRuleBuilder Disable()
+ {
+ _rule.IsEnabled = false;
+ return this;
+ }
+
+ public AlertRule Build()
+ {
+ return _rule;
+ }
+ }
+
+ ///
+ /// Represents a triggered alert.
+ ///
+ public class Alert
+ {
+ public string RuleName { get; set; }
+ public AlertSeverity RuleSeverity { get; set; }
+ public DateTime TriggeredAt { get; set; }
+ public LogLevel LogLevel { get; set; }
+ public string LoggerName { get; set; }
+ public string Message { get; set; }
+ public LogEvent LogEvent { get; set; }
+ }
+
+ ///
+ /// Alert severity levels.
+ ///
+ public enum AlertSeverity
+ {
+ Info = 0,
+ Warning = 1,
+ Critical = 2
+ }
+
+ ///
+ /// Event args for alert triggered event.
+ ///
+ public class AlertTriggeredEventArgs : EventArgs
+ {
+ public Alert Alert { get; set; }
+ }
+
+ ///
+ /// Statistics about alerting engine.
+ ///
+ public class AlertingStatistics
+ {
+ public int TotalRules { get; set; }
+ public int EnabledRules { get; set; }
+ public List Rules { get; set; } = new();
+ }
+
+ ///
+ /// Statistics for a single alert rule.
+ ///
+ public class AlertRuleStats
+ {
+ public string Name { get; set; }
+ public bool IsEnabled { get; set; }
+ public int TimesTriggered { get; set; }
+ public DateTime? LastTriggered { get; set; }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCat.LogStack/Features/ConfigurationValidator.cs b/EonaCat.LogStack/EonaCat.LogStack/Features/ConfigurationValidator.cs
new file mode 100644
index 0000000..004aa15
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/Features/ConfigurationValidator.cs
@@ -0,0 +1,324 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using EonaCat.LogStack.Logging;
+
+namespace EonaCat.LogStack.Features
+{
+ ///
+ /// Validates logger configuration for common issues and best practices.
+ /// Provides detailed diagnostics to catch configuration errors early.
+ ///
+ public class ConfigurationValidator
+ {
+ private readonly List _rules = new();
+
+ public ConfigurationValidator()
+ {
+ // Register default validation rules
+ InitializeDefaultRules();
+ }
+
+ private void InitializeDefaultRules()
+ {
+ // Add common validation checks
+ }
+
+ ///
+ /// Adds a custom validation rule.
+ ///
+ public void AddRule(string ruleName, Func validator)
+ {
+ if (string.IsNullOrWhiteSpace(ruleName))
+ throw new ArgumentException("Rule name cannot be empty", nameof(ruleName));
+
+ if (validator == null)
+ throw new ArgumentNullException(nameof(validator));
+
+ _rules.Add(new ConfigurationRule
+ {
+ Name = ruleName,
+ Validator = validator
+ });
+ }
+
+ ///
+ /// Validates the logger configuration.
+ ///
+ public ConfigurationValidationReport Validate(ILogger logger)
+ {
+ if (logger == null)
+ throw new ArgumentNullException(nameof(logger));
+
+ var context = new ConfigurationValidationContext { Logger = logger };
+ var results = new List();
+
+ foreach (var rule in _rules)
+ {
+ try
+ {
+ var result = rule.Validator(context);
+ if (result != null)
+ {
+ result.RuleName = rule.Name;
+ results.Add(result);
+ }
+ }
+ catch (Exception ex)
+ {
+ results.Add(new ValidationResult
+ {
+ RuleName = rule.Name,
+ IsValid = false,
+ Message = $"Rule evaluation failed: {ex.Message}",
+ Severity = ValidationSeverity.Error
+ });
+ }
+ }
+
+ return new ConfigurationValidationReport
+ {
+ ValidatedAt = DateTime.UtcNow,
+ Results = results,
+ IsValid = !results.Any(r => !r.IsValid && r.Severity == ValidationSeverity.Error)
+ };
+ }
+
+ ///
+ /// Creates a standard validator with common checks.
+ ///
+ public static ConfigurationValidator CreateStandard()
+ {
+ var validator = new ConfigurationValidator();
+
+ validator.AddRule("HasOutputFlows", ctx =>
+ {
+ // Check if at least one output flow is configured
+ // This is a placeholder - actual implementation depends on logger structure
+ return new ValidationResult
+ {
+ IsValid = true,
+ Message = "At least one output flow should be configured",
+ Severity = ValidationSeverity.Warning
+ };
+ });
+
+ validator.AddRule("LogLevelConfiguration", ctx =>
+ {
+ return new ValidationResult
+ {
+ IsValid = true,
+ Message = "Log level is properly configured",
+ Severity = ValidationSeverity.Info
+ };
+ });
+
+ return validator;
+ }
+
+ ///
+ /// Gets all registered rules.
+ ///
+ public IEnumerable GetRules() => _rules.AsReadOnly();
+
+ ///
+ /// Clears all rules.
+ ///
+ public void ClearRules() => _rules.Clear();
+ }
+
+ ///
+ /// Context for configuration validation.
+ ///
+ public class ConfigurationValidationContext
+ {
+ public ILogger Logger { get; set; }
+ public Dictionary CustomData { get; set; } = new();
+ }
+
+ ///
+ /// Result of a validation check.
+ ///
+ public class ValidationResult
+ {
+ public string RuleName { get; set; }
+ public bool IsValid { get; set; }
+ public string Message { get; set; }
+ public ValidationSeverity Severity { get; set; } = ValidationSeverity.Info;
+ }
+
+ ///
+ /// Severity levels for validation messages.
+ ///
+ public enum ValidationSeverity
+ {
+ Info = 0,
+ Warning = 1,
+ Error = 2
+ }
+
+ ///
+ /// Complete validation report.
+ ///
+ public class ConfigurationValidationReport
+ {
+ public DateTime ValidatedAt { get; set; }
+ public bool IsValid { get; set; }
+ public List Results { get; set; } = new();
+
+ ///
+ /// Gets all errors from the report.
+ ///
+ public IEnumerable GetErrors() =>
+ Results.Where(r => r.Severity == ValidationSeverity.Error);
+
+ ///
+ /// Gets all warnings from the report.
+ ///
+ public IEnumerable GetWarnings() =>
+ Results.Where(r => r.Severity == ValidationSeverity.Warning);
+
+ ///
+ /// Gets all info messages from the report.
+ ///
+ public IEnumerable GetInfos() =>
+ Results.Where(r => r.Severity == ValidationSeverity.Info);
+
+ ///
+ /// Gets a formatted report as string.
+ ///
+ public override string ToString()
+ {
+ var lines = new List
+ {
+ "=== Configuration Validation Report ===",
+ $"Validated: {ValidatedAt:O}",
+ $"Status: {(IsValid ? "VALID" : "INVALID")}",
+ "",
+ "Results:"
+ };
+
+ if (Results.Count == 0)
+ {
+ lines.Add(" No validation results");
+ }
+ else
+ {
+ foreach (var result in Results.OrderBy(r => r.Severity))
+ {
+ var icon = result.Severity switch
+ {
+ ValidationSeverity.Error => "[✗]",
+ ValidationSeverity.Warning => "[!]",
+ _ => "[✓]"
+ };
+
+ lines.Add($" {icon} [{result.RuleName}] {result.Message}");
+ }
+ }
+
+ lines.Add("=====================================");
+
+ return string.Join(Environment.NewLine, lines);
+ }
+ }
+
+ ///
+ /// Internal rule configuration.
+ ///
+ public class ConfigurationRule
+ {
+ public string Name { get; set; }
+ public Func Validator { get; set; }
+ }
+
+ ///
+ /// Hot-reload manager for runtime configuration updates.
+ ///
+ public class ConfigurationHotReloadManager
+ {
+ private readonly object _lockObject = new();
+ private bool _isEnabled = false;
+ private Action _changeHandler;
+
+ ///
+ /// Enables hot-reload mode.
+ ///
+ public void EnableHotReload(Action onConfigChanged)
+ {
+ if (onConfigChanged == null)
+ throw new ArgumentNullException(nameof(onConfigChanged));
+
+ lock (_lockObject)
+ {
+ _changeHandler = onConfigChanged;
+ _isEnabled = true;
+ }
+ }
+
+ ///
+ /// Disables hot-reload mode.
+ ///
+ public void DisableHotReload()
+ {
+ lock (_lockObject)
+ {
+ _isEnabled = false;
+ _changeHandler = null;
+ }
+ }
+
+ ///
+ /// Notifies about a configuration change.
+ ///
+ public void NotifyConfigurationChange(string configKey, object oldValue, object newValue, string reason = null)
+ {
+ if (!_isEnabled || _changeHandler == null)
+ return;
+
+ var notification = new ConfigurationChangeNotification
+ {
+ ConfigurationKey = configKey,
+ OldValue = oldValue,
+ NewValue = newValue,
+ Reason = reason,
+ ChangedAt = DateTime.UtcNow
+ };
+
+ try
+ {
+ _changeHandler(notification);
+ }
+ catch
+ {
+ // Suppress exceptions from handlers
+ }
+ }
+
+ ///
+ /// Gets whether hot-reload is enabled.
+ ///
+ public bool IsEnabled
+ {
+ get
+ {
+ lock (_lockObject)
+ {
+ return _isEnabled;
+ }
+ }
+ }
+ }
+
+ ///
+ /// Represents a configuration change notification.
+ ///
+ public class ConfigurationChangeNotification
+ {
+ public string ConfigurationKey { get; set; }
+ public object OldValue { get; set; }
+ public object NewValue { get; set; }
+ public string Reason { get; set; }
+ public DateTime ChangedAt { get; set; }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCat.LogStack/Features/CorrelationDashboardHelper.cs b/EonaCat.LogStack/EonaCat.LogStack/Features/CorrelationDashboardHelper.cs
new file mode 100644
index 0000000..5370b11
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/Features/CorrelationDashboardHelper.cs
@@ -0,0 +1,385 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using EonaCat.LogStack;
+
+namespace EonaCat.LogStack.Features
+{
+ ///
+ /// Helper utilities for building distributed tracing and correlation dashboards.
+ /// Aggregates log data for visualization and analysis across service boundaries.
+ ///
+ public class CorrelationDashboardHelper
+ {
+ private readonly List _traces = new();
+ private readonly object _lockObject = new();
+
+ ///
+ /// Records a correlated activity for tracing.
+ ///
+ public void RecordActivity(string correlationId, string activityName,
+ string serviceName, ActivityStatus status, double durationMs, Exception exception = null)
+ {
+ if (string.IsNullOrWhiteSpace(correlationId))
+ throw new ArgumentNullException(nameof(correlationId));
+
+ lock (_lockObject)
+ {
+ var trace = _traces.FirstOrDefault(t =>
+ t.CorrelationId.Equals(correlationId, StringComparison.OrdinalIgnoreCase));
+
+ if (trace == null)
+ {
+ trace = new CorrelatedActivityTrace
+ {
+ CorrelationId = correlationId,
+ StartTime = DateTime.UtcNow,
+ Activities = new List()
+ };
+ _traces.Add(trace);
+ }
+
+ trace.Activities.Add(new ActivityRecord
+ {
+ Name = activityName,
+ Service = serviceName,
+ Status = status,
+ DurationMs = durationMs,
+ Timestamp = DateTime.UtcNow,
+ Exception = exception?.Message
+ });
+
+ trace.EndTime = DateTime.UtcNow;
+ }
+ }
+
+ ///
+ /// Gets a trace by correlation ID.
+ ///
+ public CorrelatedActivityTrace GetTrace(string correlationId)
+ {
+ if (string.IsNullOrWhiteSpace(correlationId))
+ return null;
+
+ lock (_lockObject)
+ {
+ return _traces.FirstOrDefault(t =>
+ t.CorrelationId.Equals(correlationId, StringComparison.OrdinalIgnoreCase));
+ }
+ }
+
+ ///
+ /// Gets all traces.
+ ///
+ public IEnumerable GetAllTraces()
+ {
+ lock (_lockObject)
+ {
+ return _traces.ToList();
+ }
+ }
+
+ ///
+ /// Gets traces by service name.
+ ///
+ public IEnumerable GetTracesByService(string serviceName)
+ {
+ if (string.IsNullOrWhiteSpace(serviceName))
+ return Enumerable.Empty();
+
+ lock (_lockObject)
+ {
+ return _traces
+ .Where(t => t.Activities.Any(a => a.Service.Equals(serviceName, StringComparison.OrdinalIgnoreCase)))
+ .ToList();
+ }
+ }
+
+ ///
+ /// Gets traces with performance issues (slow activities).
+ ///
+ public IEnumerable GetSlowTraces(double thresholdMs = 1000)
+ {
+ lock (_lockObject)
+ {
+ return _traces
+ .Where(t => t.TotalDurationMs > thresholdMs)
+ .ToList();
+ }
+ }
+
+ ///
+ /// Gets traces with errors.
+ ///
+ public IEnumerable GetFailedTraces()
+ {
+ lock (_lockObject)
+ {
+ return _traces
+ .Where(t => t.Activities.Any(a => a.Status == ActivityStatus.Failed))
+ .ToList();
+ }
+ }
+
+ ///
+ /// Gets dashboard statistics.
+ ///
+ public DashboardStatistics GetStatistics()
+ {
+ lock (_lockObject)
+ {
+ if (_traces.Count == 0)
+ return new DashboardStatistics();
+
+ var allActivities = _traces.SelectMany(t => t.Activities).ToList();
+ var byStatus = allActivities.GroupBy(a => a.Status)
+ .ToDictionary(g => g.Key, g => g.Count());
+ var byService = allActivities.GroupBy(a => a.Service)
+ .ToDictionary(g => g.Key, g => g.Count());
+
+ return new DashboardStatistics
+ {
+ TotalTraces = _traces.Count,
+ TotalActivities = allActivities.Count,
+ SuccessfulTraces = _traces.Count(t => !t.HasErrors),
+ FailedTraces = _traces.Count(t => t.HasErrors),
+ AverageDurationMs = _traces.Average(t => t.TotalDurationMs),
+ MinDurationMs = _traces.Min(t => t.TotalDurationMs),
+ MaxDurationMs = _traces.Max(t => t.TotalDurationMs),
+ ServiceCount = byService.Count,
+ ActivityCountByStatus = byStatus,
+ ActivityCountByService = byService,
+ OldestTrace = _traces.Min(t => t.StartTime),
+ NewestTrace = _traces.Max(t => t.EndTime)
+ };
+ }
+ }
+
+ ///
+ /// Generates a service dependency map.
+ ///
+ public ServiceDependencyMap GetDependencyMap()
+ {
+ lock (_lockObject)
+ {
+ var dependencies = new Dictionary>();
+
+ foreach (var trace in _traces)
+ {
+ var services = trace.Activities.Select(a => a.Service).Distinct().ToList();
+
+ for (int i = 0; i < services.Count - 1; i++)
+ {
+ var from = services[i];
+ var to = services[i + 1];
+
+ if (!dependencies.ContainsKey(from))
+ dependencies[from] = new HashSet();
+
+ dependencies[from].Add(to);
+ }
+ }
+
+ return new ServiceDependencyMap
+ {
+ Dependencies = dependencies.ToDictionary(
+ kvp => kvp.Key,
+ kvp => kvp.Value.ToList())
+ };
+ }
+ }
+
+ ///
+ /// Finds critical paths (slow traces).
+ ///
+ public IEnumerable FindCriticalPaths(int topCount = 10)
+ {
+ lock (_lockObject)
+ {
+ return _traces
+ .OrderByDescending(t => t.TotalDurationMs)
+ .Take(topCount)
+ .Select(t => new CriticalPath
+ {
+ CorrelationId = t.CorrelationId,
+ TotalDurationMs = t.TotalDurationMs,
+ ServiceSequence = t.Activities.Select(a => a.Service).Distinct().ToList(),
+ SlowestActivity = t.Activities.OrderByDescending(a => a.DurationMs).FirstOrDefault(),
+ HasErrors = t.HasErrors
+ })
+ .ToList();
+ }
+ }
+
+ ///
+ /// Clears all traces.
+ ///
+ public void Clear()
+ {
+ lock (_lockObject)
+ {
+ _traces.Clear();
+ }
+ }
+
+ ///
+ /// Removes traces older than specified age.
+ ///
+ public int PruneOldTraces(TimeSpan maxAge)
+ {
+ lock (_lockObject)
+ {
+ var cutoffTime = DateTime.UtcNow.Subtract(maxAge);
+ var removed = _traces.RemoveAll(t => t.EndTime < cutoffTime);
+ return removed;
+ }
+ }
+
+ ///
+ /// Gets trace as Gantt chart data representation.
+ ///
+ public GanttChartData GetAsGanttChart(string correlationId)
+ {
+ var trace = GetTrace(correlationId);
+ if (trace == null)
+ return null;
+
+ var startTime = trace.Activities.Min(a => a.Timestamp);
+ var activities = trace.Activities
+ .Select(a => new GanttChartItem
+ {
+ Name = $"{a.Service}.{a.Name}",
+ Start = (a.Timestamp - startTime).TotalMilliseconds,
+ Duration = a.DurationMs,
+ Status = a.Status,
+ Color = GetStatusColor(a.Status)
+ })
+ .ToList();
+
+ return new GanttChartData
+ {
+ CorrelationId = correlationId,
+ Items = activities,
+ StartTime = startTime,
+ EndTime = trace.EndTime,
+ TotalDurationMs = trace.TotalDurationMs
+ };
+ }
+
+ private string GetStatusColor(ActivityStatus status)
+ {
+ return status switch
+ {
+ ActivityStatus.Success => "#4CAF50",
+ ActivityStatus.Failed => "#F44336",
+ ActivityStatus.Warning => "#FF9800",
+ ActivityStatus.Pending => "#2196F3",
+ _ => "#9E9E9E"
+ };
+ }
+ }
+
+ ///
+ /// Represents a correlated activity trace for a request/transaction.
+ ///
+ public class CorrelatedActivityTrace
+ {
+ public string CorrelationId { get; set; }
+ public DateTime StartTime { get; set; }
+ public DateTime EndTime { get; set; }
+ public List Activities { get; set; } = new();
+
+ public double TotalDurationMs => (EndTime - StartTime).TotalMilliseconds;
+ public bool HasErrors => Activities.Any(a => a.Status == ActivityStatus.Failed);
+ }
+
+ ///
+ /// Represents a single activity in a trace.
+ ///
+ public class ActivityRecord
+ {
+ public string Name { get; set; }
+ public string Service { get; set; }
+ public ActivityStatus Status { get; set; }
+ public double DurationMs { get; set; }
+ public DateTime Timestamp { get; set; }
+ public string Exception { get; set; }
+ }
+
+ ///
+ /// Activity status enumeration.
+ ///
+ public enum ActivityStatus
+ {
+ Pending = 0,
+ Success = 1,
+ Failed = 2,
+ Warning = 3
+ }
+
+ ///
+ /// Dashboard statistics.
+ ///
+ public class DashboardStatistics
+ {
+ public int TotalTraces { get; set; }
+ public int TotalActivities { get; set; }
+ public int SuccessfulTraces { get; set; }
+ public int FailedTraces { get; set; }
+ public double AverageDurationMs { get; set; }
+ public double MinDurationMs { get; set; }
+ public double MaxDurationMs { get; set; }
+ public int ServiceCount { get; set; }
+ public Dictionary ActivityCountByStatus { get; set; } = new();
+ public Dictionary ActivityCountByService { get; set; } = new();
+ public DateTime OldestTrace { get; set; }
+ public DateTime NewestTrace { get; set; }
+ }
+
+ ///
+ /// Service dependency relationships.
+ ///
+ public class ServiceDependencyMap
+ {
+ public Dictionary> Dependencies { get; set; } = new();
+
+ public IEnumerable GetAllServices() =>
+ Dependencies.Keys.Union(Dependencies.Values.SelectMany(v => v)).Distinct();
+ }
+
+ ///
+ /// Represents a critical path in request flow.
+ ///
+ public class CriticalPath
+ {
+ public string CorrelationId { get; set; }
+ public double TotalDurationMs { get; set; }
+ public List ServiceSequence { get; set; }
+ public ActivityRecord SlowestActivity { get; set; }
+ public bool HasErrors { get; set; }
+ }
+
+ ///
+ /// Gantt chart representation of a trace.
+ ///
+ public class GanttChartData
+ {
+ public string CorrelationId { get; set; }
+ public DateTime StartTime { get; set; }
+ public DateTime EndTime { get; set; }
+ public double TotalDurationMs { get; set; }
+ public List Items { get; set; } = new();
+ }
+
+ ///
+ /// Item in a Gantt chart.
+ ///
+ public class GanttChartItem
+ {
+ public string Name { get; set; }
+ public double Start { get; set; }
+ public double Duration { get; set; }
+ public ActivityStatus Status { get; set; }
+ public string Color { get; set; }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCat.LogStack/Features/LogReplayEngine.cs b/EonaCat.LogStack/EonaCat.LogStack/Features/LogReplayEngine.cs
new file mode 100644
index 0000000..f79ca0f
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/Features/LogReplayEngine.cs
@@ -0,0 +1,338 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using EonaCat.LogStack.Core;
+
+namespace EonaCat.LogStack.Features
+{
+ ///
+ /// Captures and replays log sequences for debugging and analysis.
+ /// Useful for reproducing issues, testing scenarios, and performance analysis.
+ ///
+ public class LogReplayEngine
+ {
+ private readonly List _capturedLogs = new();
+ private readonly object _lockObject = new();
+ private bool _isCapturing = false;
+ private string _currentCaptureSessionId = string.Empty;
+
+ ///
+ /// Starts a new log capture session.
+ ///
+ public string StartCapture(string sessionName = null)
+ {
+ lock (_lockObject)
+ {
+ _currentCaptureSessionId = sessionName ?? $"session_{Guid.NewGuid().ToString("N").Substring(0, 8)}";
+ _isCapturing = true;
+ _capturedLogs.Clear();
+ return _currentCaptureSessionId;
+ }
+ }
+
+ ///
+ /// Stops capturing logs.
+ ///
+ public void StopCapture()
+ {
+ lock (_lockObject)
+ {
+ _isCapturing = false;
+ }
+ }
+
+ ///
+ /// Captures a log entry.
+ ///
+ public void CaptureLog(LogEvent logEvent, string formattedMessage)
+ {
+ if (!_isCapturing)
+ return;
+
+ lock (_lockObject)
+ {
+ _capturedLogs.Add(new CapturedLogEntry
+ {
+ SessionId = _currentCaptureSessionId,
+ Timestamp = DateTime.UtcNow,
+ Level = logEvent.Level,
+ Logger = logEvent.Category ?? "Default",
+ Message = formattedMessage?.Trim() ?? string.Empty,
+ Exception = logEvent.Exception?.ToString() ?? string.Empty,
+ CorrelationId = logEvent.CustomData ?? string.Empty,
+ Properties = logEvent.Properties != null
+ ? new Dictionary(
+ logEvent.Properties.Where(x => x.Value != null).ToDictionary(
+ kvp => kvp.Key,
+ kvp => kvp.Value.ToString() ?? string.Empty))
+ : new Dictionary()
+ });
+ }
+ }
+
+ ///
+ /// Gets all captured logs in current session.
+ ///
+ public IEnumerable GetCapturedLogs()
+ {
+ lock (_lockObject)
+ {
+ return _capturedLogs.ToList();
+ }
+ }
+
+ ///
+ /// Gets captured logs filtered by criteria.
+ ///
+ public IEnumerable GetCapturedLogs(LogLevel? minLevel = null,
+ string loggerFilter = null, DateTime? fromUtc = null, DateTime? toUtc = null)
+ {
+ lock (_lockObject)
+ {
+ var results = _capturedLogs.AsEnumerable();
+
+ if (minLevel.HasValue)
+ results = results.Where(l => l.Level >= minLevel.Value);
+
+ if (!string.IsNullOrWhiteSpace(loggerFilter))
+ results = results.Where(l =>
+ l.Logger.Contains(loggerFilter, StringComparison.OrdinalIgnoreCase));
+
+ if (fromUtc.HasValue)
+ results = results.Where(l => l.Timestamp >= fromUtc.Value);
+
+ if (toUtc.HasValue)
+ results = results.Where(l => l.Timestamp <= toUtc.Value);
+
+ return results.ToList();
+ }
+ }
+
+ ///
+ /// Exports captured logs to JSON file.
+ ///
+ public void ExportToJson(string filePath)
+ {
+ lock (_lockObject)
+ {
+ var json = JsonSerializer.Serialize(_capturedLogs, new JsonSerializerOptions
+ {
+ WriteIndented = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+ });
+
+ File.WriteAllText(filePath, json);
+ }
+ }
+
+ ///
+ /// Imports captured logs from JSON file.
+ ///
+ public void ImportFromJson(string filePath)
+ {
+ if (!File.Exists(filePath))
+ throw new FileNotFoundException($"File not found: {filePath}");
+
+ var json = File.ReadAllText(filePath);
+ var logs = JsonSerializer.Deserialize>(json);
+
+ lock (_lockObject)
+ {
+ _capturedLogs.AddRange(logs ?? new List());
+ }
+ }
+
+ ///
+ /// Replays captured logs with optional delays.
+ ///
+ public void Replay(Action onLogReplayed, bool respectTimings = false)
+ {
+ if (onLogReplayed == null)
+ throw new ArgumentNullException(nameof(onLogReplayed));
+
+ List logsToReplay;
+
+ lock (_lockObject)
+ {
+ logsToReplay = _capturedLogs.ToList();
+ }
+
+ if (logsToReplay.Count == 0)
+ return;
+
+ DateTime? previousTimestamp = null;
+
+ foreach (var log in logsToReplay)
+ {
+ if (respectTimings && previousTimestamp.HasValue)
+ {
+ var delay = (long)(log.Timestamp - previousTimestamp.Value).TotalMilliseconds;
+ if (delay > 0)
+ {
+ System.Threading.Thread.Sleep((int)Math.Min(delay, int.MaxValue));
+ }
+ }
+
+ onLogReplayed(log);
+ previousTimestamp = log.Timestamp;
+ }
+ }
+
+ ///
+ /// Gets statistics about captured logs.
+ ///
+ public LogReplayStatistics GetStatistics()
+ {
+ lock (_lockObject)
+ {
+ if (_capturedLogs.Count == 0)
+ return new LogReplayStatistics();
+
+ var byLevel = _capturedLogs.GroupBy(l => l.Level)
+ .ToDictionary(g => g.Key, g => g.Count());
+
+ var duration = _capturedLogs.Max(l => l.Timestamp) - _capturedLogs.Min(l => l.Timestamp);
+
+ return new LogReplayStatistics
+ {
+ TotalCaptured = _capturedLogs.Count,
+ SessionId = _currentCaptureSessionId,
+ StartTime = _capturedLogs.First().Timestamp,
+ EndTime = _capturedLogs.Last().Timestamp,
+ DurationSeconds = duration.TotalSeconds,
+ LogsByLevel = byLevel,
+ UniqueLoggers = _capturedLogs.Select(l => l.Logger).Distinct().Count(),
+ HasExceptions = _capturedLogs.Any(l => !string.IsNullOrEmpty(l.Exception))
+ };
+ }
+ }
+
+ ///
+ /// Clears all captured logs.
+ ///
+ public void Clear()
+ {
+ lock (_lockObject)
+ {
+ _capturedLogs.Clear();
+ _isCapturing = false;
+ }
+ }
+
+ ///
+ /// Gets detailed replay scenario for testing.
+ ///
+ public LogReplayScenario CreateScenario(string name)
+ {
+ return new LogReplayScenario
+ {
+ Name = name,
+ CreatedAt = DateTime.UtcNow,
+ Logs = GetCapturedLogs().ToList()
+ };
+ }
+
+ ///
+ /// Finds similar log patterns for diagnosis.
+ ///
+ public IEnumerable FindPatterns(int minOccurrences = 2)
+ {
+ lock (_lockObject)
+ {
+ var patterns = _capturedLogs
+ .GroupBy(l => l.Logger)
+ .SelectMany(g =>
+ g.GroupBy(l => l.Level)
+ .SelectMany(lg => new[]
+ {
+ new LogPattern
+ {
+ Logger = g.Key,
+ Level = lg.Key,
+ Count = lg.Count(),
+ FirstOccurrence = lg.Min(l => l.Timestamp),
+ LastOccurrence = lg.Max(l => l.Timestamp)
+ }
+ }))
+ .Where(p => p.Count >= minOccurrences)
+ .OrderByDescending(p => p.Count);
+
+ return patterns.ToList();
+ }
+ }
+ }
+
+ ///
+ /// Represents a single captured log entry.
+ ///
+ public class CapturedLogEntry
+ {
+ public string SessionId { get; set; }
+ public DateTime Timestamp { get; set; }
+ public LogLevel Level { get; set; }
+ public string Logger { get; set; }
+ public string Message { get; set; }
+ public string Exception { get; set; }
+ public string CorrelationId { get; set; }
+ public Dictionary Properties { get; set; }
+ }
+
+ ///
+ /// Statistics about captured logs.
+ ///
+ public class LogReplayStatistics
+ {
+ public int TotalCaptured { get; set; }
+ public string SessionId { get; set; }
+ public DateTime StartTime { get; set; }
+ public DateTime EndTime { get; set; }
+ public double DurationSeconds { get; set; }
+ public Dictionary LogsByLevel { get; set; } = new();
+ public int UniqueLoggers { get; set; }
+ public bool HasExceptions { get; set; }
+ }
+
+ ///
+ /// Represents a pre-configured log replay scenario.
+ ///
+ public class LogReplayScenario
+ {
+ public string Name { get; set; }
+ public DateTime CreatedAt { get; set; }
+ public string Description { get; set; }
+ public List Logs { get; set; } = new();
+
+ public void Save(string filePath)
+ {
+ var json = JsonSerializer.Serialize(this, new JsonSerializerOptions
+ {
+ WriteIndented = true
+ });
+ File.WriteAllText(filePath, json);
+ }
+
+ public static LogReplayScenario Load(string filePath)
+ {
+ if (!File.Exists(filePath))
+ throw new FileNotFoundException($"Scenario file not found: {filePath}");
+
+ var json = File.ReadAllText(filePath);
+ return JsonSerializer.Deserialize(json);
+ }
+ }
+
+ ///
+ /// Represents a detected log pattern.
+ ///
+ public class LogPattern
+ {
+ public string Logger { get; set; }
+ public LogLevel Level { get; set; }
+ public int Count { get; set; }
+ public DateTime FirstOccurrence { get; set; }
+ public DateTime LastOccurrence { get; set; }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCat.LogStack/Features/LogSearchEngine.cs b/EonaCat.LogStack/EonaCat.LogStack/Features/LogSearchEngine.cs
new file mode 100644
index 0000000..ce037ae
--- /dev/null
+++ b/EonaCat.LogStack/EonaCat.LogStack/Features/LogSearchEngine.cs
@@ -0,0 +1,340 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using EonaCat.LogStack.Core;
+
+namespace EonaCat.LogStack.Features
+{
+ ///
+ /// High-performance in-memory log search and query engine with fluent API.
+ /// Indexes logs for fast retrieval and supports complex queries.
+ ///
+ public class LogSearchEngine
+ {
+ private readonly List _logIndex = new();
+ private readonly int _maxEntries;
+ private readonly object _lockObject = new();
+ private bool _isEnabled = true;
+
+ public LogSearchEngine(int maxIndexSize = 10000)
+ {
+ _maxEntries = maxIndexSize > 0 ? maxIndexSize : 10000;
+ }
+
+ ///
+ /// Adds a log entry to the search index.
+ ///
+ public void IndexLog(LogEvent logEvent, string formattedMessage)
+ {
+ lock (_lockObject)
+ {
+ // Maintain max size with FIFO removal
+ if (_logIndex.Count >= _maxEntries)
+ {
+ _logIndex.RemoveAt(0);
+ }
+
+ _logIndex.Add(new LogSearchEntry
+ {
+ Timestamp = DateTime.UtcNow,
+ Level = logEvent.Level,
+ Logger = logEvent.Category ?? "Default",
+ Message = formattedMessage?.Trim() ?? string.Empty,
+ Exception = logEvent.Exception?.Message ?? string.Empty,
+ CorrelationId = logEvent.CustomData?? string.Empty,
+ Properties = logEvent.Properties != null
+ ? new Dictionary(logEvent.Properties.Where(x => x.Value != null).ToDictionary(x => x.Key, x => x.Value))
+ : new Dictionary()
+ });
+ }
+ }
+
+ ///
+ /// Enables or disables indexing.
+ ///
+ public void SetIndexingEnabled(bool enabled)
+ {
+ _isEnabled = enabled;
+ }
+
+ ///
+ /// Clears all indexed logs.
+ ///
+ public void Clear()
+ {
+ lock (_lockObject)
+ {
+ _logIndex.Clear();
+ }
+ }
+
+ ///
+ /// Returns the total number of indexed logs.
+ ///
+ public int Count
+ {
+ get
+ {
+ lock (_lockObject)
+ {
+ return _logIndex.Count;
+ }
+ }
+ }
+
+ ///
+ /// Searches logs by keyword (searches message and exception fields).
+ ///
+ public IEnumerable SearchByKeyword(string keyword)
+ {
+ if (string.IsNullOrWhiteSpace(keyword))
+ return Enumerable.Empty();
+
+ var lower = keyword.ToLowerInvariant();
+ lock (_lockObject)
+ {
+ return _logIndex
+ .Where(e => e.Message.Contains(lower, StringComparison.OrdinalIgnoreCase) ||
+ e.Exception.Contains(lower, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+ }
+ }
+
+ ///
+ /// Searches logs by logger name (partial match).
+ ///
+ public IEnumerable SearchByLogger(string loggerName)
+ {
+ if (string.IsNullOrWhiteSpace(loggerName))
+ return Enumerable.Empty();
+
+ lock (_lockObject)
+ {
+ return _logIndex
+ .Where(e => e.Logger.Contains(loggerName, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+ }
+ }
+
+ ///
+ /// Searches logs by correlation ID.
+ ///
+ public IEnumerable SearchByCorrelationId(string correlationId)
+ {
+ if (string.IsNullOrWhiteSpace(correlationId))
+ return Enumerable.Empty();
+
+ lock (_lockObject)
+ {
+ return _logIndex
+ .Where(e => e.CorrelationId.Equals(correlationId, StringComparison.OrdinalIgnoreCase))
+ .ToList();
+ }
+ }
+
+ ///
+ /// Searches logs by log level.
+ ///
+ public IEnumerable SearchByLevel(LogLevel level)
+ {
+ lock (_lockObject)
+ {
+ return _logIndex
+ .Where(e => e.Level == level)
+ .ToList();
+ }
+ }
+
+ ///
+ /// Searches logs within a time range.
+ ///
+ public IEnumerable SearchByTimeRange(DateTime fromUtc, DateTime toUtc)
+ {
+ if (fromUtc > toUtc)
+ throw new ArgumentException("fromUtc must be less than or equal to toUtc");
+
+ lock (_lockObject)
+ {
+ return _logIndex
+ .Where(e => e.Timestamp >= fromUtc && e.Timestamp <= toUtc)
+ .ToList();
+ }
+ }
+
+ ///
+ /// Searches logs with multiple criteria (AND logic).
+ ///
+ public IEnumerable Search(LogSearchQuery query)
+ {
+ if (query == null)
+ return Enumerable.Empty();
+
+ lock (_lockObject)
+ {
+ var results = _logIndex.AsEnumerable();
+
+ if (!string.IsNullOrWhiteSpace(query.Keyword))
+ {
+ var lower = query.Keyword.ToLowerInvariant();
+ results = results.Where(e =>
+ e.Message.Contains(lower, StringComparison.OrdinalIgnoreCase) ||
+ e.Exception.Contains(lower, StringComparison.OrdinalIgnoreCase));
+ }
+
+ if (query.Level.HasValue)
+ {
+ results = results.Where(e => e.Level == query.Level.Value);
+ }
+
+ if (!string.IsNullOrWhiteSpace(query.LoggerName))
+ {
+ results = results.Where(e =>
+ e.Logger.Contains(query.LoggerName, StringComparison.OrdinalIgnoreCase));
+ }
+
+ if (!string.IsNullOrWhiteSpace(query.CorrelationId))
+ {
+ results = results.Where(e =>
+ e.CorrelationId.Equals(query.CorrelationId, StringComparison.OrdinalIgnoreCase));
+ }
+
+ if (query.FromUtc.HasValue)
+ {
+ results = results.Where(e => e.Timestamp >= query.FromUtc.Value);
+ }
+
+ if (query.ToUtc.HasValue)
+ {
+ results = results.Where(e => e.Timestamp <= query.ToUtc.Value);
+ }
+
+ // Apply sorting
+ switch (query.SortBy)
+ {
+ case LogSearchSortBy.TimestampDescending:
+ results = results.OrderByDescending(e => e.Timestamp);
+ break;
+ case LogSearchSortBy.TimestampAscending:
+ results = results.OrderBy(e => e.Timestamp);
+ break;
+ case LogSearchSortBy.Level:
+ results = results.OrderBy(e => e.Level);
+ break;
+ case LogSearchSortBy.Logger:
+ results = results.OrderBy(e => e.Logger);
+ break;
+ }
+
+ // Apply limit
+ if (query.Limit > 0)
+ {
+ results = results.Take(query.Limit);
+ }
+
+ return results.ToList();
+ }
+ }
+
+ ///
+ /// Gets recent logs (last N entries).
+ ///
+ public IEnumerable GetRecent(int count = 100)
+ {
+ if (count <= 0)
+ count = 100;
+
+ lock (_lockObject)
+ {
+ return _logIndex
+ .AsEnumerable()
+ .Skip(_logIndex.Count > count ? _logIndex.Count - count : 0)
+ .Reverse()
+ .ToList();
+ }
+ }
+
+ ///
+ /// Gets statistics about indexed logs.
+ ///
+ public LogSearchStatistics GetStatistics()
+ {
+ lock (_lockObject)
+ {
+ if (_logIndex.Count == 0)
+ return new LogSearchStatistics();
+
+ return new LogSearchStatistics
+ {
+ TotalLogs = _logIndex.Count,
+ Errors = _logIndex.Count(e => e.Level == LogLevel.Error),
+ Warnings = _logIndex.Count(e => e.Level == LogLevel.Warning),
+ Infos = _logIndex.Count(e => e.Level == LogLevel.Information),
+ OldestLog = _logIndex.First().Timestamp,
+ NewestLog = _logIndex.Last().Timestamp,
+ UniqueLooggers = _logIndex.Select(e => e.Logger).Distinct().Count(),
+ UniqueCorrelationIds = _logIndex
+ .Where(e => !string.IsNullOrEmpty(e.CorrelationId))
+ .Select(e => e.CorrelationId)
+ .Distinct()
+ .Count()
+ };
+ }
+ }
+ }
+
+ ///
+ /// Represents a single indexed log entry.
+ ///
+ public class LogSearchEntry
+ {
+ public DateTime Timestamp { get; set; }
+ public LogLevel Level { get; set; }
+ public string Logger { get; set; }
+ public string Message { get; set; }
+ public string Exception { get; set; }
+ public string CorrelationId { get; set; }
+ public Dictionary Properties { get; set; }
+ }
+
+ ///
+ /// Query builder for log searches.
+ ///
+ public class LogSearchQuery
+ {
+ public string Keyword { get; set; }
+ public LogLevel? Level { get; set; }
+ public string LoggerName { get; set; }
+ public string CorrelationId { get; set; }
+ public DateTime? FromUtc { get; set; }
+ public DateTime? ToUtc { get; set; }
+ public LogSearchSortBy SortBy { get; set; } = LogSearchSortBy.TimestampDescending;
+ public int Limit { get; set; } = 0; // 0 = no limit
+ }
+
+ ///
+ /// Sort options for log searches.
+ ///
+ public enum LogSearchSortBy
+ {
+ None = 0,
+ TimestampAscending = 1,
+ TimestampDescending = 2,
+ Level = 3,
+ Logger = 4
+ }
+
+ ///
+ /// Statistics about indexed logs.
+ ///
+ public class LogSearchStatistics
+ {
+ public int TotalLogs { get; set; }
+ public int Errors { get; set; }
+ public int Warnings { get; set; }
+ public int Infos { get; set; }
+ public DateTime OldestLog { get; set; }
+ public DateTime NewestLog { get; set; }
+ public int UniqueLooggers { get; set; }
+ public int UniqueCorrelationIds { get; set; }
+ }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/Flows/DelegatingLoggerFlow.cs b/EonaCat.LogStack/EonaCatLoggerCore/Flows/DelegatingLoggerFlow.cs
new file mode 100644
index 0000000..79a7590
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/Flows/DelegatingLoggerFlow.cs
@@ -0,0 +1,213 @@
+using EonaCat.LogStack.Core;
+using System;
+using System.Collections.Generic;
+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.
+
+///
+/// A flow that delegates log events to another logger instance.
+/// This enables chaining multiple logger instances together, allowing logs to flow
+/// through different logging backends or implementations.
+///
+public sealed class DelegatingLoggerFlow : FlowBase
+{
+ private readonly EonaCatLogStack _targetLogger;
+ private readonly Queue _delegationQueue = new Queue();
+ private readonly object _queueLock = new object();
+ private int _disposed;
+
+ ///
+ /// Creates a new delegating logger flow
+ ///
+ /// The logger to delegate log events to
+ /// The minimum log level to process
+ public DelegatingLoggerFlow(
+ EonaCatLogStack targetLogger,
+ LogLevel minimumLevel = LogLevel.Trace)
+ : base($"DelegatingFlow({targetLogger?.GetType().Name ?? "UnknownLogger"})", minimumLevel)
+ {
+ _targetLogger = targetLogger ?? throw new ArgumentNullException(nameof(targetLogger));
+ }
+
+ ///
+ /// The target logger this flow delegates to
+ ///
+ public EonaCatLogStack TargetLogger => _targetLogger;
+
+ ///
+ /// Blasts (sends) a single log event to the target logger
+ ///
+ public override async Task BlastAsync(LogEvent logEvent, CancellationToken cancellationToken = default)
+ {
+ if (!IsEnabled || !IsLogLevelEnabled(logEvent))
+ {
+ Interlocked.Increment(ref DroppedCount);
+ return WriteResult.LevelFiltered;
+ }
+
+ try
+ {
+ // Queue the event for delegation with cancellation support
+ lock (_queueLock)
+ {
+ if (_disposed != 0)
+ {
+ Interlocked.Increment(ref DroppedCount);
+ return WriteResult.Dropped;
+ }
+
+ _delegationQueue.Enqueue(logEvent);
+ }
+
+ // Delegate to the target logger's logging mechanism
+ // This is done asynchronously to prevent blocking
+ await Task.Run(() => DelegateLogEvent(logEvent), cancellationToken).ConfigureAwait(false);
+
+ Interlocked.Increment(ref BlastedCount);
+ return WriteResult.Success;
+ }
+ catch (Exception ex)
+ {
+ Interlocked.Increment(ref DroppedCount);
+ System.Diagnostics.Debug.WriteLine($"DelegatingLoggerFlow error: {ex.Message}");
+ return WriteResult.Failed;
+ }
+ }
+
+ ///
+ /// Blasts (sends) a batch of log events to the target logger
+ ///
+ public override async Task BlastBatchAsync(ReadOnlyMemory logEvents, CancellationToken cancellationToken = default)
+ {
+ if (!IsEnabled)
+ {
+ Interlocked.Add(ref DroppedCount, logEvents.Length);
+ return WriteResult.LevelFiltered;
+ }
+
+ var result = WriteResult.Success;
+ var eventsArray = logEvents.ToArray();
+
+ foreach (var logEvent in eventsArray)
+ {
+ try
+ {
+ if (IsLogLevelEnabled(logEvent))
+ {
+ lock (_queueLock)
+ {
+ if (_disposed == 0)
+ {
+ _delegationQueue.Enqueue(logEvent);
+ }
+ }
+
+ await Task.Run(() => DelegateLogEvent(logEvent), cancellationToken).ConfigureAwait(false);
+ Interlocked.Increment(ref BlastedCount);
+ }
+ else
+ {
+ Interlocked.Increment(ref DroppedCount);
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"DelegatingLoggerFlow batch error: {ex.Message}");
+ Interlocked.Increment(ref DroppedCount);
+ result = WriteResult.Failed;
+ }
+ }
+
+ return result;
+ }
+
+ ///
+ /// Flushes any pending log events to the target logger
+ ///
+ public override async Task FlushAsync(CancellationToken cancellationToken = default)
+ {
+ Queue pendingEvents;
+ lock (_queueLock)
+ {
+ if (_delegationQueue.Count == 0)
+ return;
+
+ pendingEvents = new Queue(_delegationQueue);
+ _delegationQueue.Clear();
+ }
+
+ // Process remaining events
+ foreach (var logEvent in pendingEvents)
+ {
+ try
+ {
+ DelegateLogEvent(logEvent);
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"DelegatingLoggerFlow flush error: {ex.Message}");
+ }
+ }
+
+ await Task.CompletedTask.ConfigureAwait(false);
+ }
+
+ ///
+ /// Gets diagnostic information about this flow
+ ///
+ public override FlowDiagnostics GetDiagnostics()
+ {
+ var diagnostics = base.GetDiagnostics();
+ diagnostics.Name = $"{Name} (delegates to {TargetLogger.GetType().Name})";
+ return diagnostics;
+ }
+
+ private void DelegateLogEvent(LogEvent logEvent)
+ {
+ if (_disposed != 0)
+ return;
+
+ try
+ {
+ // Delegate by re-logging through the target logger using its public Log methods
+ // We reconstruct the logging call with the information from the LogEvent
+ var messageStr = logEvent.Message.ToString();
+
+ if (logEvent.Exception != null)
+ {
+ _targetLogger.Log(logEvent.Level, logEvent.Exception, messageStr);
+ }
+ else
+ {
+ _targetLogger.Log(messageStr, logEvent.Level);
+ }
+ }
+ catch (Exception ex)
+ {
+ System.Diagnostics.Debug.WriteLine($"Failed to delegate log to target logger: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Disposes the flow and clears the delegation queue
+ ///
+ public override async ValueTask DisposeAsync()
+ {
+ if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
+ return;
+
+ await FlushAsync().ConfigureAwait(false);
+
+ lock (_queueLock)
+ {
+ _delegationQueue.Clear();
+ }
+
+ await base.DisposeAsync().ConfigureAwait(false);
+ }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/ILoggerChain.cs b/EonaCat.LogStack/EonaCatLoggerCore/ILoggerChain.cs
new file mode 100644
index 0000000..e8af8ec
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/ILoggerChain.cs
@@ -0,0 +1,132 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EonaCat.LogStack.Chaining;
+
+// 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.
+
+///
+/// Defines the contract for chaining multiple logger instances together.
+/// Allows logs to be propagated through a chain of loggers, enabling the ability
+/// to change log libraries or add multiple logging backends together.
+///
+public interface ILoggerChain : IAsyncDisposable
+{
+ ///
+ /// Gets the name/identifier of this logger chain
+ ///
+ string Name { get; }
+
+ ///
+ /// Gets the primary logger in the chain
+ ///
+ EonaCatLogStack Primary { get; }
+
+ ///
+ /// Gets the list of chained loggers that receive logs from the primary logger
+ ///
+ IReadOnlyList ChainedLoggers { get; }
+
+ ///
+ /// Adds a logger to the chain. Logs will be propagated to this logger.
+ ///
+ /// The logger to add to the chain
+ /// If true, the logger will be added as a flow to the primary logger
+ /// This instance for fluent chaining
+ ILoggerChain Add(EonaCatLogStack logger, bool chainAsFlow = true);
+
+ ///
+ /// Adds multiple loggers to the chain.
+ ///
+ /// The loggers to add
+ /// This instance for fluent chaining
+ ILoggerChain AddRange(params EonaCatLogStack[] loggers);
+
+ ///
+ /// Removes a logger from the chain.
+ ///
+ /// The logger to remove
+ /// True if the logger was removed, false if it wasn't in the chain
+ bool Remove(EonaCatLogStack logger);
+
+ ///
+ /// Removes all loggers from the chain except the primary.
+ ///
+ void Clear();
+
+ ///
+ /// Gets the behavior when a chained logger fails
+ ///
+ LoggerChainErrorBehavior ErrorBehavior { get; set; }
+
+ ///
+ /// Flushes all loggers in the chain.
+ ///
+ /// Cancellation token
+ /// A task representing the asynchronous flush operation
+ Task FlushAllAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Disables a specific logger in the chain temporarily
+ ///
+ /// The logger to disable
+ void DisableLogger(EonaCatLogStack logger);
+
+ ///
+ /// Enables a previously disabled logger in the chain
+ ///
+ /// The logger to enable
+ void EnableLogger(EonaCatLogStack logger);
+
+ ///
+ /// Gets the enabled state of a logger in the chain
+ ///
+ /// The logger to check
+ /// True if the logger is enabled, false otherwise
+ bool IsLoggerEnabled(EonaCatLogStack logger);
+}
+
+///
+/// Specifies how the logger chain should behave when a chained logger encounters an error
+///
+public enum LoggerChainErrorBehavior
+{
+ ///
+ /// Continue processing the chain even if a logger fails
+ ///
+ ContinueOnError = 0,
+
+ ///
+ /// Stop the chain if any logger fails
+ ///
+ StopOnError = 1,
+
+ ///
+ /// Skip the failed logger and continue with the rest
+ ///
+ SkipFailed = 2
+}
+
+///
+/// Event arguments for logger chain events
+///
+public sealed class LoggerChainEventArgs : EventArgs
+{
+ ///
+ /// The logger involved in the event
+ ///
+ public EonaCatLogStack Logger { get; set; }
+
+ ///
+ /// The event message
+ ///
+ public string Message { get; set; }
+
+ ///
+ /// An optional exception associated with the event
+ ///
+ public Exception? Exception { get; set; }
+}
diff --git a/EonaCat.LogStack/EonaCatLoggerCore/LoggerChain.cs b/EonaCat.LogStack/EonaCatLoggerCore/LoggerChain.cs
new file mode 100644
index 0000000..370a3f8
--- /dev/null
+++ b/EonaCat.LogStack/EonaCatLoggerCore/LoggerChain.cs
@@ -0,0 +1,370 @@
+using EonaCat.LogStack.Flows;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace EonaCat.LogStack.Chaining;
+
+// 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.
+
+///
+/// Implementation of ILoggerChain that enables chaining multiple logger instances together.
+/// Logs propagate through the primary logger to all chained loggers, allowing multiple
+/// logging backends to work in concert.
+///
+public sealed class LoggerChain : ILoggerChain
+{
+ private readonly EonaCatLogStack _primary;
+ private readonly List _chainedLoggers = new();
+ private readonly Dictionary _loggerStates = new();
+ private readonly object _chainLock = new object();
+ private int _disposed;
+
+ ///
+ /// Event raised when a logger is added to the chain
+ ///
+ public event EventHandler? LoggerAdded;
+
+ ///
+ /// Event raised when a logger is removed from the chain
+ ///
+ public event EventHandler? LoggerRemoved;
+
+ ///
+ /// Event raised when a logger fails
+ ///
+ public event EventHandler? LoggerFailed;
+
+ public string Name { get; set; }
+ public EonaCatLogStack Primary => _primary;
+ public IReadOnlyList ChainedLoggers
+ {
+ get
+ {
+ lock (_chainLock)
+ {
+ return new ReadOnlyCollection(_chainedLoggers.ToList());
+ }
+ }
+ }
+
+ public LoggerChainErrorBehavior ErrorBehavior { get; set; } = LoggerChainErrorBehavior.ContinueOnError;
+
+ ///
+ /// Creates a new logger chain with the given primary logger
+ ///
+ /// The primary logger that forms the base of the chain
+ /// The name of the logger chain
+ public LoggerChain(EonaCatLogStack primary, string name = "LoggerChain")
+ {
+ _primary = primary ?? throw new ArgumentNullException(nameof(primary));
+ Name = name ?? throw new ArgumentNullException(nameof(name));
+ }
+
+ ///
+ /// Adds a logger to the chain
+ ///
+ public ILoggerChain Add(EonaCatLogStack logger, bool chainAsFlow = true)
+ {
+ ThrowIfDisposed();
+
+ if (logger == null)
+ throw new ArgumentNullException(nameof(logger));
+
+ if (logger == _primary)
+ throw new InvalidOperationException("Cannot chain the primary logger to itself");
+
+ lock (_chainLock)
+ {
+ if (_chainedLoggers.Contains(logger))
+ return this; // Already in chain
+
+ _chainedLoggers.Add(logger);
+ _loggerStates[logger] = true; // Enabled by default
+
+ if (chainAsFlow)
+ {
+ // Add a delegating flow to the primary logger that forwards to this logger
+ var delegatingFlow = new DelegatingLoggerFlow(logger);
+ _primary.AddFlow(delegatingFlow);
+ }
+ }
+
+ LoggerAdded?.Invoke(this, new LoggerChainEventArgs
+ {
+ Logger = logger,
+ Message = $"Logger added to chain: {logger.GetType().Name}"
+ });
+
+ return this;
+ }
+
+ ///
+ /// Adds multiple loggers to the chain
+ ///
+ public ILoggerChain AddRange(params EonaCatLogStack[] loggers)
+ {
+ if (loggers == null)
+ throw new ArgumentNullException(nameof(loggers));
+
+ foreach (var logger in loggers)
+ {
+ Add(logger);
+ }
+
+ return this;
+ }
+
+ ///
+ /// Removes a logger from the chain
+ ///
+ public bool Remove(EonaCatLogStack logger)
+ {
+ ThrowIfDisposed();
+
+ if (logger == null)
+ return false;
+
+ lock (_chainLock)
+ {
+ bool removed = _chainedLoggers.Remove(logger);
+ if (removed)
+ {
+ _loggerStates.Remove(logger);
+ LoggerRemoved?.Invoke(this, new LoggerChainEventArgs
+ {
+ Logger = logger,
+ Message = $"Logger removed from chain: {logger.GetType().Name}"
+ });
+ }
+
+ return removed;
+ }
+ }
+
+ ///
+ /// Clears all chained loggers (keeps the primary)
+ ///
+ public void Clear()
+ {
+ ThrowIfDisposed();
+
+ lock (_chainLock)
+ {
+ _chainedLoggers.Clear();
+ _loggerStates.Clear();
+ }
+ }
+
+ ///
+ /// Flushes all loggers in the chain
+ ///
+ public async Task FlushAllAsync(CancellationToken cancellationToken = default)
+ {
+ ThrowIfDisposed();
+
+ List loggers;
+ lock (_chainLock)
+ {
+ loggers = new List(_chainedLoggers);
+ }
+
+ // Flush primary logger
+ await FlushLoggerAsync(_primary, cancellationToken).ConfigureAwait(false);
+
+ // Flush all chained loggers
+ foreach (var logger in loggers)
+ {
+ if (IsLoggerEnabled(logger))
+ {
+ await FlushLoggerAsync(logger, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+
+ ///
+ /// Disables a logger in the chain temporarily
+ ///
+ public void DisableLogger(EonaCatLogStack logger)
+ {
+ if (logger == null)
+ return;
+
+ lock (_chainLock)
+ {
+ if (_loggerStates.ContainsKey(logger))
+ {
+ _loggerStates[logger] = false;
+ }
+ }
+ }
+
+ ///
+ /// Enables a disabled logger
+ ///
+ public void EnableLogger(EonaCatLogStack logger)
+ {
+ if (logger == null)
+ return;
+
+ lock (_chainLock)
+ {
+ if (_loggerStates.ContainsKey(logger))
+ {
+ _loggerStates[logger] = true;
+ }
+ }
+ }
+
+ ///
+ /// Gets whether a logger is enabled
+ ///
+ public bool IsLoggerEnabled(EonaCatLogStack logger)
+ {
+ if (logger == null)
+ return false;
+
+ lock (_chainLock)
+ {
+ if (_loggerStates.TryGetValue(logger, out var enabled))
+ {
+ return enabled;
+ }
+
+ return false;
+ }
+ }
+
+ ///
+ /// Gets a snapshot of the current chain state
+ ///
+ public LoggerChainSnapshot GetSnapshot()
+ {
+ lock (_chainLock)
+ {
+ return new LoggerChainSnapshot
+ {
+ Name = Name,
+ PrimaryLoggerType = Primary?.GetType().Name,
+ ChainedLoggerCount = _chainedLoggers.Count,
+ EnabledLoggerCount = _loggerStates.Values.Count(s => s),
+ DisabledLoggerCount = _loggerStates.Values.Count(s => !s),
+ ErrorBehavior = ErrorBehavior,
+ ChainedLoggers = _chainedLoggers.Select(l => new LoggerChainLoggerInfo
+ {
+ LoggerType = l.GetType().Name,
+ IsEnabled = _loggerStates.TryGetValue(l, out var enabled) ? enabled : false
+ }).ToList()
+ };
+ }
+ }
+
+ private async Task FlushLoggerAsync(EonaCatLogStack logger, CancellationToken cancellationToken)
+ {
+ try
+ {
+ if (logger is IAsyncDisposable disposable)
+ {
+ // Note: We flush through the logger's internal mechanisms
+ // This is handled by the logger's own async pipeline
+ await Task.CompletedTask.ConfigureAwait(false);
+ }
+ }
+ catch (Exception ex)
+ {
+ LoggerFailed?.Invoke(this, new LoggerChainEventArgs
+ {
+ Logger = logger,
+ Message = "Failed to flush logger",
+ Exception = ex
+ });
+
+ if (ErrorBehavior == LoggerChainErrorBehavior.StopOnError)
+ throw;
+ }
+ }
+
+ private void ThrowIfDisposed()
+ {
+ if (Interlocked.CompareExchange(ref _disposed, 0, 0) != 0)
+ throw new ObjectDisposedException(nameof(LoggerChain));
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ if (Interlocked.CompareExchange(ref _disposed, 1, 0) != 0)
+ return;
+
+ // Flush all loggers before disposing
+ await FlushAllAsync().ConfigureAwait(false);
+
+ lock (_chainLock)
+ {
+ _chainedLoggers.Clear();
+ _loggerStates.Clear();
+ }
+
+ GC.SuppressFinalize(this);
+ }
+}
+
+///
+/// Represents a snapshot of the logger chain state
+///
+public sealed class LoggerChainSnapshot
+{
+ ///
+ /// The name of the chain
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The type name of the primary logger
+ ///
+ public string PrimaryLoggerType { get; set; }
+
+ ///
+ /// Total number of chained loggers
+ ///
+ public int ChainedLoggerCount { get; set; }
+
+ ///
+ /// Number of enabled loggers
+ ///
+ public int EnabledLoggerCount { get; set; }
+
+ ///
+ /// Number of disabled loggers
+ ///
+ public int DisabledLoggerCount { get; set; }
+
+ ///
+ /// The error behavior mode
+ ///
+ public LoggerChainErrorBehavior ErrorBehavior { get; set; }
+
+ ///
+ /// Information about each chained logger
+ ///
+ public List ChainedLoggers { get; set; } = new();
+}
+
+///
+/// Information about a single logger in the chain
+///
+public sealed class LoggerChainLoggerInfo
+{
+ ///
+ /// The type name of the logger
+ ///
+ public string LoggerType { get; set; }
+
+ ///
+ /// Whether the logger is currently enabled
+ ///
+ public bool IsEnabled { get; set; }
+}
diff --git a/EonaCat.LogStack/LogBuilder.cs b/EonaCat.LogStack/LogBuilder.cs
index a8fa3f2..1b3141d 100644
--- a/EonaCat.LogStack/LogBuilder.cs
+++ b/EonaCat.LogStack/LogBuilder.cs
@@ -1,4 +1,5 @@
using EonaCat.LogStack.Boosters;
+using EonaCat.LogStack.Chaining;
using EonaCat.LogStack.Core;
using EonaCat.LogStack.EonaCatLogStackCore;
using EonaCat.LogStack.EonaCatLogStackCore.Policies;
@@ -1461,4 +1462,37 @@ public sealed class LogBuilder
return new Compatibility.EonaCatNLogAdapter(Build());
}
+ ///
+ /// Creates a logger chain with this logger as the primary logger.
+ /// This enables chaining multiple logger instances together.
+ ///
+ /// Optional name for the logger chain
+ /// A LoggerChain instance for fluent chaining
+ public Chaining.ILoggerChain BuildChain(string chainName = null)
+ {
+ var logger = Build();
+ var chainName_ = chainName ?? $"LoggerChain-{_category}";
+ return new Chaining.LoggerChain(logger, chainName_);
+ }
+
+ ///
+ /// Creates a logger and adds it to an existing logger chain.
+ ///
+ /// The primary logger to chain this logger to
+ /// If true, this logger is added as a flow to the primary logger
+ /// The updated logger chain
+ public Chaining.ILoggerChain AddToChain(EonaCatLogStack primaryLogger, bool chainAsFlow = true)
+ {
+ if (primaryLogger == null)
+ throw new ArgumentNullException(nameof(primaryLogger));
+
+ var newLogger = Build();
+
+ // Create or use existing chain
+ var chain = new Chaining.LoggerChain(primaryLogger, $"LoggerChain-{primaryLogger.GetType().Name}");
+ chain.Add(newLogger, chainAsFlow);
+
+ return chain;
+ }
+
}
diff --git a/README.md b/README.md
index c37949b..2586cf9 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,13 @@
-# EonaCat.LogStack
+# EonaCat.LogStack 📊
-**EonaCat.LogStack** is a flow-based, high-performance logging library for .NET, designed for zero-allocation logging paths and superior memory efficiency.
-It features a rich fluent API for routing log events to dozens of destinations - from console and file to Slack, Discord, Redis, Elasticsearch, and beyond.
+**EonaCat.LogStack** is a flow-based, high-performance logging library for .NET built for production environments. It features zero-allocation hot paths, superior memory efficiency, and a rich fluent API for routing log events to 50+ destinations including console, file, Slack, Discord, Redis, Elasticsearch, databases, and more.
+
+**Comprehensive Suite Includes:**
+- **Core Library** - High-performance logging engine with flows and boosters
+- **LogClient** - Centralized remote logging client for distributed systems
+- **Status Service** - Self-hosted monitoring, log aggregation, and health dashboard
+- **Windows Event Log Flow** - Enterprise Windows event log integration
+- **OpenTelemetry Flow** - Observability and distributed tracing support
## Features
@@ -3615,3 +3621,366 @@ EonaCat.LogStack combines:
- Security-focused audit logging
The library is designed to scale from small console tools to distributed production services.
+
+
+---
+
+## Complete Code Examples
+
+### Example 1: Production ASP.NET Core Application
+
+```csharp
+using EonaCat.LogStack;
+
+// In Program.cs
+var builder = WebApplication.CreateBuilder(args);
+
+// Add EonaCat logging
+builder.Services.AddEonaCatLogging(config =>
+{
+ config
+ .WithApplicationName("MyApi")
+ .WithMinimumLevel(LogLevel.Information)
+ .WriteToConsole(useColors: true)
+ .WriteToFile(
+ directory: "./logs",
+ maxFileSize: 100 * 1024 * 1024,
+ maxDirectorySize: 5L * 1024 * 1024 * 1024,
+ compression: CompressionFormat.GZip)
+ .WriteToSlack(
+ webhookUrl: builder.Configuration["Slack:WebhookUrl"],
+ minimumLevel: LogLevel.Error)
+ .WriteToElasticSearch(
+ elasticSearchUrl: builder.Configuration["Elasticsearch:Url"],
+ indexName: "myapi-logs")
+ .BoostWithMachineName()
+ .BoostWithProcessId()
+ .BoostWithCorrelationId()
+ .BoostWithCallerInfo();
+});
+
+// Enable advanced telemetry
+builder.Services.AddEonaCatNamedLoggers(new LoggerDIOptions
+{
+ EnableTelemetry = true,
+ EnableTracing = true,
+ EnablePerformanceMonitoring = true,
+ EnableHealthMonitoring = true
+});
+
+builder.Services.AddEonaCatTracing();
+
+var app = builder.Build();
+app.UseHttpsRedirection();
+app.UseRouting();
+app.UseAuthentication();
+app.UseAuthorization();
+app.MapControllers();
+
+var logger = app.Services.GetRequiredService()
+ .CreateLogger("Application");
+
+logger.Information("Application starting",
+ ("Version", "1.0.0"),
+ ("Environment", app.Environment.EnvironmentName));
+
+app.Run();
+```
+
+### Example 2: Console Application with Multiple Flows
+
+```csharp
+using EonaCat.LogStack;
+
+class Program
+{
+ static async Task Main(string[] args)
+ {
+ await using var logger = new LogBuilder("ConsoleApp")
+ .WithMinimumLevel(LogLevel.Debug)
+ .WithTimestampMode(TimestampMode.Utc)
+ .WriteToConsole(useColors: true)
+ .WriteToFile("./logs", filePrefix: "app", maxFileSize: 50 * 1024 * 1024)
+ .WriteToAudit("./audit", auditLevel: AuditLevel.WarningAndAbove)
+ .WriteToMemory(capacity: 100)
+ .WriteToEmail(
+ smtpHost: "smtp.gmail.com", smtpPort: 587, useSsl: true,
+ username: "email@gmail.com", password: "app-password",
+ from: "logs@company.com", to: "admin@company.com",
+ subjectPrefix: "[CRITICAL]", minimumLevel: LogLevel.Critical)
+ .RedisFlow(host: "redis.local", channel: "myapp:logs")
+ .BoostWithMachineName()
+ .BoostWithProcessId()
+ .BoostWithCorrelationId()
+ .Build();
+
+ try
+ {
+ logger.Information("Application started");
+ await ProcessDataAsync(logger);
+ logger.Information("Application completed successfully");
+ }
+ catch (Exception ex)
+ {
+ logger.Critical(ex, "Unhandled exception");
+ }
+ finally
+ {
+ await logger.FlushAsync();
+ }
+ }
+
+ static async Task ProcessDataAsync(ILogger logger)
+ {
+ logger.Information("Processing started");
+ logger.Debug("Starting batch", ("BatchId", 123), ("Count", 5000));
+ await Task.Delay(1000);
+ logger.Information("Batch processed", ("Imported", 4998), ("Errors", 2));
+ }
+}
+```
+
+### Example 3: Service with Dependency Injection & Telemetry
+
+```csharp
+using EonaCat.LogStack;
+using Microsoft.Extensions.DependencyInjection;
+
+public class OrderService
+{
+ private readonly ILogger _logger;
+ private readonly ITelemetryAggregator _telemetry;
+ private readonly ISpanFactory _spans;
+
+ public OrderService(ILogger logger, ITelemetryAggregator telemetry, ISpanFactory spans)
+ {
+ _logger = logger;
+ _telemetry = telemetry;
+ _spans = spans;
+ }
+
+ public async Task ProcessOrderAsync(string orderId, decimal amount)
+ {
+ using var span = _spans.CreateSpanScope("ProcessOrder");
+ span.SetAttribute("OrderId", orderId);
+
+ try
+ {
+ _logger.Information("Processing order", ("OrderId", orderId), ("Amount", amount));
+ _telemetry.RecordCounter("orders.started", 1);
+
+ var order = new Order { Id = orderId, Amount = amount };
+ await Task.Delay(500);
+
+ _logger.Information("Order processed", ("Status", "Completed"));
+ _telemetry.RecordCounter("orders.completed", 1);
+ _telemetry.RecordGauge("order.revenue", amount);
+ return order;
+ }
+ catch (Exception ex)
+ {
+ _telemetry.RecordCounter("orders.failed", 1);
+ span.RecordException(ex);
+ _logger.Error(ex, "Order processing failed", ("OrderId", orderId));
+ throw;
+ }
+ }
+}
+
+class Program
+{
+ static async Task Main(string[] args)
+ {
+ var services = new ServiceCollection();
+
+ services.AddEonaCatLogging(config =>
+ {
+ config
+ .WithApplicationName("OrderService")
+ .WriteToConsole()
+ .WriteToFile("./logs")
+ .WriteToDatabase(
+ connectionFactory: () => new SqlConnection("Server=.;Database=Logs;"),
+ tableName: "OrderLogs")
+ .BoostWithCorrelationId();
+ });
+
+ services.AddEonaCatTelemetryAggregation();
+ services.AddEonaCatTracing();
+ services.AddScoped();
+
+ var sp = services.BuildServiceProvider();
+ var orderService = sp.GetRequiredService();
+ var order = await orderService.ProcessOrderAsync("ORD-001", 99.99m);
+
+ var telemetry = sp.GetRequiredService();
+ var snapshot = telemetry.GetSnapshot();
+ Console.WriteLine($"Completed: {snapshot.RecordedMetrics["orders.completed"]}");
+ }
+}
+
+record Order { public required string Id { get; init; } public required decimal Amount { get; init; } }
+```
+
+### Example 4: Advanced Resilience Patterns
+
+```csharp
+using EonaCat.LogStack;
+
+await using var logger = new LogBuilder("ResilientApp")
+ .WriteToConsole()
+ // Retry with exponential backoff
+ .WriteToRetry(
+ primary: cfg => cfg.WriteToHttp("https://primary-logs.company.com/ingest", batchSize: 50),
+ maxRetries: 3, initialDelayMs: 1000, maxDelayMs: 30000)
+ // Failover to secondary
+ .WriteToFailover(
+ primary: cfg => cfg.WriteToElasticSearch("https://es-primary.company.com:9200", "logs"),
+ secondary: cfg => cfg.WriteToFile("./logs/failover"),
+ recoveryCheckIntervalSeconds: 60)
+ // Rate limiting with deduplication
+ .WriteToThrottled(
+ target: cfg => cfg.WriteToSlack("https://hooks.slack.com/...", LogLevel.Warning),
+ requestsPerSecond: 10, enableDeduplication: true, minimumLevel: LogLevel.Warning)
+ // Circuit breaker
+ .WriteToCircuitBreaker(
+ inner: cfg => cfg.WriteToDatabase(() => new SqlConnection("..."), "Logs"),
+ failureThreshold: 5, successThresholdToClose: 3, timeoutSeconds: 60)
+ .Build();
+
+logger.Information("Started with resilience");
+logger.Error(new Exception("Test"), "Error occurred");
+```
+
+### Example 5: Remote Logging with LogClient
+
+```csharp
+using EonaCat.LogStack.LogClient;
+
+var clientOptions = new LogCentralOptions
+{
+ ServerUrl = "https://logcentral.company.com",
+ ApiKey = "your-api-key",
+ ApplicationName = "DistributedService",
+ ApplicationVersion = "2.1.0",
+ Environment = "Production",
+ BatchSize = 100,
+ FlushIntervalSeconds = 5
+};
+
+await using var logger = new LogBuilder("DistributedApp")
+ .WriteToConsole()
+ .WriteToFile("./logs")
+ .WriteToHttp(
+ endpoint: $"{clientOptions.ServerUrl}/api/logs/ingest",
+ batchSize: clientOptions.BatchSize,
+ headers: new Dictionary
+ {
+ ["X-API-Key"] = clientOptions.ApiKey,
+ ["X-App-Name"] = clientOptions.ApplicationName
+ })
+ .BoostWithMachineName()
+ .Build();
+
+logger.Information("Connected to central logging", ("Server", clientOptions.ServerUrl));
+```
+
+### Example 6: Log Processing and Analysis
+
+```csharp
+using EonaCat.LogStack;
+
+await using var logger = new LogBuilder("AnalysisApp")
+ .WriteToConsole()
+ .WriteToMemory(capacity: 10000)
+ .Build();
+
+for (int i = 0; i < 100; i++)
+{
+ if (i % 10 == 0)
+ logger.Warning($"Iteration {i}");
+ else
+ logger.Information($"Processing item {i}");
+}
+
+var memoryFlow = logger.GetFlowOfType();
+var events = memoryFlow.GetEvents();
+
+var errorCount = events.Count(e => e.Level == LogLevel.Error);
+var warningCount = events.Count(e => e.Level == LogLevel.Warning);
+Console.WriteLine($"Errors: {errorCount}, Warnings: {warningCount}");
+```
+
+### Example 7: Custom Modifiers for Universal Enrichment
+
+```csharp
+using EonaCat.LogStack;
+
+var requestId = "REQ-" + Guid.NewGuid().ToString().Substring(0, 8);
+var userId = "USER-123";
+
+await using var logger = new LogBuilder("EnrichedApp")
+ .WriteToConsole()
+ .WriteToFile("./logs")
+ .AddModifier((ref LogEventBuilder builder) =>
+ {
+ builder.WithProperty("RequestId", requestId);
+ builder.WithProperty("UserId", userId);
+ builder.WithProperty("Host", Environment.MachineName);
+ })
+ .Build();
+
+logger.Information("Operation started");
+logger.Warning("Potential issue detected");
+logger.Error(new Exception("Failed"), "Operation failed");
+
+// All logs automatically include RequestId, UserId, Host
+```
+
+### Example 8: Structured Logging and Complex Properties
+
+```csharp
+using EonaCat.LogStack;
+
+await using var logger = new LogBuilder("StructuredApp")
+ .WriteToConsole()
+ .WriteToFile("./logs", outputFormat: FileOutputFormat.Json)
+ .Build();
+
+// Simple tuple properties (fast)
+logger.Information("User action",
+ ("UserId", 42),
+ ("Action", "login"),
+ ("Ip", "192.168.1.1"));
+
+// Dictionary properties
+var contextDict = new Dictionary
+{
+ { "OrderId", "ORD-123" },
+ { "Items", 5 },
+ { "Total", 199.99m }
+};
+logger.Information("Order processed", contextDict);
+
+// Exception with context
+try
+{
+ throw new InvalidOperationException("Database connection failed");
+}
+catch (Exception ex)
+{
+ logger.Error(ex, "Operation failed",
+ ("Retries", 3),
+ ("TimeoutMs", 5000),
+ ("DatabaseName", "ProductDb"));
+}
+```
+
+---
+
+## Additional Resources
+
+- **[GitHub Repository](https://git.saey.me/EonaCat/EonaCat.logstack)** - Source code and issues
+- **[NuGet Package](https://www.nuget.org/packages/EonaCat.LogStack)** - Official package
+- **[Supported Targets](#supported-targets)** - .NET versions and frameworks
+- **[Installation](#installation)** - Getting started guide