# EonaCat.LogStack πŸ“Š **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 ### Core Architecture - **Flow-based system** - Route log events to multiple destinations simultaneously. Each flow is independent and can be configured with its own level filters and batching strategy. - **Booster system** - Automatically enrich log events with contextual metadata like machine name, process ID, thread info, memory usage, uptime, correlation IDs, and custom properties. - **Pre-build modifiers** - Intercept and mutate log events before they reach any flow using a chainable modifier system. - **Zero-allocation hot path** - `AggressiveInlining` throughout, `StringBuilder` pooling, and `ref`-based builder pattern for minimal GC pressure during logging. - **Async-first design** - All flows implement `IAsyncDisposable` and `FlushAsync()` for proper async resource cleanup and batching. ### Advanced Features - **Resilience patterns** - Built-in `RetryFlow` with exponential backoff, `FailoverFlow` for primary/secondary failover, and `ThrottledFlow` for rate limiting with deduplication. - **Tamper-evident audit** - SHA-256 hash-chained audit files where deleting or modifying any past entry invalidates all subsequent hashes. Built-in integrity verification. - **Encrypted file logging** - AES-encrypted log files with password protection. Includes `DecryptFile()` utility to decrypt files on demand. - **Log compression** - Automatic GZip compression of rolled-over log files to conserve disk space. - **Category/level-based routing** - Split logs into separate files per category or log level for better organization. - **Rolling buffers** - Circular buffer with trigger-based flushing. When a critical event occurs, buffer context (previous N lines) is forwarded to a secondary flow. - **Diagnostics & metrics** - Real-time counters for total logged, total dropped, per-flow statistics, and flow-specific performance data. - **Live level control** - Dynamically change log level at runtime without recreating the logger. - **Structured logging** - First-class support for structured properties (tuples, dictionaries) that are included in JSON output where applicable. ### Performance & Reliability - **Configurable backpressure** - Choose between Wait, Drop, or Block strategies when log queues are full. - **Batch processing** - Most flows support configurable batch sizes and intervals for efficient I/O. - **Rate limiting & deduplication** - Protect high-latency sinks (email, Slack, HTTP) from log storms using token-bucket rate limiting and optional message deduplication. - **Memory flow** - Store recent logs in a circular in-memory buffer for quick diagnostics or fallback output. - **Lazy initialization** - Flows are only initialized when first used, reducing startup overhead. ## Telemetry & Observability EonaCat.LogStack includes **built-in, zero-dependency telemetry** for comprehensive production monitoring: ### Enhanced Telemetry System #### Metrics Collection & Aggregation Track logging operations with built-in metrics: ```csharp 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 - .NET 8.0 - .NET 9.0 - .NET 10.0 - .NET Framework 4.8 ## Installation ```bash dotnet add package EonaCat.LogStack ``` ## Quick Start ### Minimal - Console + File ```csharp await using var logger = LogBuilder.CreateDefault("MyApp"); logger.Information("Application started"); logger.Warning("Low memory warning"); logger.Error(ex, "Unexpected error occurred"); // Automatic cleanup on disposal ``` `CreateDefault` creates a logger writing to both the console and a `./logs` directory, enriched with machine name and process ID. ### Fluent Configuration Build a fully customized logger using `LogBuilder`: ```csharp await using var logger = new LogBuilder("MyApp") .WithMinimumLevel(LogLevel.Debug) .WithTimestampMode(TimestampMode.Utc) .WriteToConsole(useColors: true) .WriteToFile("./logs", filePrefix: "app", maxFileSize: 50 * 1024 * 1024) .WriteToSlack("https://hooks.slack.com/services/...") .BoostWithMachineName() .BoostWithProcessId() .BoostWithCorrelationId() .Build(); try { logger.Information("Application started"); // Your application code... } finally { await logger.FlushAsync(); await logger.DisposeAsync(); } ``` ## Logging Methods ### Basic Logging ```csharp logger.Trace("Verbose trace message"); logger.Debug("Debug detail"); logger.Information("Something happened"); logger.Warning("Potential problem"); logger.Error("Something failed"); logger.Critical("System is going down"); ``` ### Logging with Exceptions ```csharp try { // risky operation } catch (Exception ex) { logger.Warning(ex, "Operation failed, attempting retry"); logger.Error(ex, "Operation failed after retries"); logger.Critical(ex, "Critical failure, shutting down"); } ``` ### Structured Properties ```csharp // With tuples (preferred for performance) logger.Log(LogLevel.Information, "User logged in", ("UserId", 42), ("IP", "192.168.1.1"), ("Session", "abc-123")); // Properties appear in most flows (database, JSON output, etc.) // In file output: `UserId=42, IP=192.168.1.1, Session=abc-123` ``` ### Custom Log Event Modification ```csharp logger.AddModifier((ref LogEventBuilder builder) => { builder.WithProperty("RequestId", HttpContext.TraceIdentifier); builder.WithProperty("UserId", User.Id); }); logger.Information("Processing request"); // RequestId and UserId added automatically ``` ## Available Flows Flows are the destinations where log events are written. Each flow is independent and can have its own level filter and configuration. ### Flows Overview | Flow | Method | Description | |------|--------|-------------| | Console | `WriteToConsole()` | Colored console output with customizable templates | | File | `WriteToFile()` | Batched, rotated, compressed file output with retention policies | | Encrypted File | `WriteToEncryptedFile()` | AES-encrypted log files with password protection | | Memory | `WriteToMemory()` | In-memory ring buffer for quick access and diagnostics | | Audit | `WriteToAudit()` | Tamper-evident hash-chained audit trail with verification | | Database | `WriteToDatabase()` | ADO.NET database sink with custom table support | | HTTP | `WriteToHttp()` | Generic HTTP endpoint with custom headers and batching | | Webhook | `WriteToWebhook()` | Generic webhook POST endpoint | | Email | `WriteToEmail()` | HTML digest emails via SMTP with configurable batching | | Slack | `WriteToSlack()` | Slack incoming webhooks with message formatting | | Discord | `WriteToDiscord()` | Discord webhooks with embed formatting | | Microsoft Teams | `WriteToMicrosoftTeams()` | Teams incoming webhooks with adaptive cards | | Telegram | `WriteToTelegram()` | Telegram bot messages | | SignalR | `WriteToSignalR()` | Real-time SignalR hub push for live dashboards | | Redis | `RedisFlow()` | Redis Pub/Sub + optional List persistence with reconnect | | Elasticsearch | `WriteToElasticSearch()` | Elasticsearch index with custom index names | | Splunk | `WriteToSplunkFlow()` | Splunk HEC (HTTP Event Collector) | | Graylog | `WriteToGraylogFlow()` | GELF over UDP or TCP | | Syslog UDP | `WriteToSyslogUdp()` | RFC-5424 Syslog over UDP | | Syslog TCP | `WriteToSyslogTcp()` | RFC-5424 Syslog over TCP with optional TLS | | TCP | `WriteToTcp()` | Raw TCP with optional TLS support | | UDP | `WriteToUdp()` | Raw UDP datagrams | | SNMP Trap | `WriteToSnmpTrap()` | SNMP v2c traps for network monitoring | | Zabbix | `WriteToZabbixFlow()` | Zabbix trapper protocol | | EventLog | `WriteToEventLogFlow()` | Remote Windows event log forwarding | | Rolling Buffer | `WriteToRollingBuffer()` | Circular buffer with trigger-based context flushing | | Throttled | `WriteToThrottled()` | Token-bucket rate limiting with optional deduplication | | Retry | `WriteToRetry()` | Automatic retry with exponential back-off | | Failover | `WriteToFailover()` | Primary/secondary failover with recovery detection | | Diagnostics | `WriteDiagnostics()` | Periodic diagnostic snapshots and metrics | | Status | `WriteToStatusFlow()` | Service health monitoring | | Conditional | `WriteToConditional()` | Route logs based on custom predicates | | Circuit Breaker | `WriteToCircuitBreaker()` | Protect against cascading failures | ### Flow Examples #### Console Output ```csharp // Basic colored output .WriteToConsole(useColors: true) // Minimal console (no colors) .WriteToConsole(useColors: false) // Only warnings and above to console .WriteToConsole(minimumLevel: LogLevel.Warning, useColors: true) ``` #### File Output ```csharp // Basic file logging .WriteToFile("./logs") // Custom configuration .WriteToFile( directory: "./logs", filePrefix: "myapp", maxFileSize: 100 * 1024 * 1024, // 100 MB maxDirectorySize: 10L * 1024 * 1024 * 1024, // 10 GB total flushIntervalInMilliSeconds: 2000, batchSize: 50, compression: CompressionFormat.GZip, outputFormat: FileOutputFormat.Text) // Category-based routing (separate files per category) .WriteToFile( directory: "./logs", useCategoryRouting: true) // Level-based routing (separate files per log level) .WriteToFile( directory: "./logs", logLevelsForSeparateFiles: new[] { LogLevel.Error, LogLevel.Critical }) ``` #### Encrypted File Output ```csharp // Encrypt logs with password .WriteToEncryptedFile( directory: "./secure-logs", password: "MySecurePassword123!") // Decrypt later when needed LogBuilder.DecryptFile( encryptedPath: "./secure-logs/log.enc", outputPath: "./secure-logs/log.txt", password: "MySecurePassword123!"); ``` #### In-Memory Buffer ```csharp // Store last 1000 events in memory .WriteToMemory(capacity: 1000, minimumLevel: LogLevel.Information) // Access stored events var memoryFlow = logger.GetFlowOfType(); var events = memoryFlow.GetEvents(); ``` #### Slack ```csharp .WriteToSlack("https://hooks.slack.com/services/YOUR/WEBHOOK/URL") // Only errors to Slack .WriteToSlack( webhookUrl: "https://hooks.slack.com/services/YOUR/WEBHOOK/URL", minimumLevel: LogLevel.Error) ``` #### Discord ```csharp .WriteToDiscord( webHookUrl: "https://discordapp.com/api/webhooks/YOUR/WEBHOOK", botName: "ErrorBot") ``` #### Email Digest ```csharp .WriteToEmail( smtpHost: "smtp.gmail.com", smtpPort: 587, useSsl: true, username: "your-email@gmail.com", password: "app-password", from: "logs@company.com", to: "ops@company.com", subjectPrefix: "[Production Alerts]", digestMinutes: 5, // Send every 5 minutes flushOnCritical: true, // Send immediately on Critical minimumLevel: LogLevel.Error) ``` #### Database (SQL Server, PostgreSQL, MySQL, etc.) ```csharp .WriteToDatabase( connectionFactory: () => new SqlConnection("connection-string"), tableName: "ApplicationLogs", batchSize: 10) ``` #### Elasticsearch ```csharp .WriteToElasticSearch( elasticSearchUrl: "https://elasticsearch.company.com:9200", indexName: "myapp-logs", batchSize: 100) ``` #### Redis (Pub/Sub + List) ```csharp // Pub/Sub only (real-time subscribers) .RedisFlow( host: "redis.company.com", port: 6379, channel: "eonacat:logs") // Pub/Sub + List persistence (subscribers + history) .RedisFlow( host: "redis.company.com", port: 6379, password: "redis-password", database: 0, channel: "eonacat:logs", listKey: "eonacat:logs:history", maxListLength: 10000) ``` #### Syslog ```csharp // RFC-5424 Syslog over UDP .WriteToSyslogUdp( host: "syslog.company.com", port: 514) // RFC-5424 Syslog over TCP with TLS .WriteToSyslogTcp( host: "syslog.company.com", port: 514, useTls: true) ``` #### HTTP Endpoint ```csharp .WriteToHttp( endpoint: "https://logs.company.com/ingest", batchSize: 50, batchInterval: TimeSpan.FromSeconds(2), headers: new Dictionary { ["Authorization"] = "Bearer token123", ["X-API-Key"] = "secret" }) ``` #### Audit Trail (Tamper-Evident) ```csharp // Record warnings and above in hash-chained audit trail .WriteToAudit( directory: "./audit", auditLevel: AuditLevel.WarningAndAbove, includeProperties: true) // Verify audit file integrity bool isIntact = AuditFlow.Verify("./audit/audit.audit"); if (!isIntact) Console.WriteLine("Audit trail has been tampered with!"); ``` #### Rolling Buffer (Context-on-Error) ```csharp // Buffer 500 events; on Error, flush preceding 100 events to file .WriteToRollingBuffer( capacity: 500, minimumLevel: LogLevel.Trace, triggerLevel: LogLevel.Error, triggerTarget: new FileFlow("./error-context"), preContextLines: 100) ``` ## Available Boosters Boosters automatically enrich every log event with additional properties before it reaches any flow. They run once per log event and can add system information, application context, and custom data. ### System Information Boosters ```csharp .BoostWithMachineName() // Add computer/host name .BoostWithProcessId() // Add current process ID .BoostWithThreadId() // Add managed thread ID .BoostWithThreadName() // Add thread name (if set) .BoostWithUser() // Add current OS user name .BoostWithOS() // Add OS description and version .BoostWithFramework() // Add .NET runtime description .BoostWithMemory() // Add process working set in MB .BoostWithUptime() // Add process uptime in seconds .BoostWithProcStart() // Add process start time (DateTime) ``` ### Date/Time Boosters ```csharp .BoostWithDate() // Add current date (yyyy-MM-dd) .BoostWithTime() // Add current time (HH:mm:ss.fff) .BoostWithTicks() // Add current timestamp ticks (for precise timing) ``` ### Application Context Boosters ```csharp .BoostWithApp() // Add app name and base directory (auto-detected) .BoostWithApplication("MyApp", "2.0.0") // Add explicit app name + version .BoostWithEnvironment("Production") // Add environment name (Prod/Dev/Test) .BoostWithCorrelationId() // Add Activity.Current correlation ID for distributed tracing ``` ### Custom Data Boosters ```csharp // Single key/value pair .BoostWithCustomText("Environment", "Production") .BoostWithCustomText("ServiceVersion", "2.0.1") // Callback-based booster for dynamic data .Boost("RequestInfo", () => new Dictionary { ["UserId"] = GetCurrentUserId(), ["TenantId"] = GetCurrentTenantId(), ["ApiVersion"] = GetApiVersion() }) // Custom booster implementation .Boost(new MyCustomBooster()) ``` ### Complete Booster Configuration Example ```csharp var logger = new LogBuilder("MyApplication") // System info .BoostWithMachineName() .BoostWithProcessId() .BoostWithThreadId() .BoostWithOS() .BoostWithFramework() .BoostWithMemory() // Application context .BoostWithApplication("MyApp", "1.0.0") .BoostWithEnvironment("Production") .BoostWithCorrelationId() // Startup info .BoostWithProcStart() .BoostWithUptime() // Custom context .BoostWithCustomText("DataCenter", "US-East-1") .BoostWithCustomText("InstanceId", Environment.MachineName) .Boost("Request", () => new Dictionary { ["TraceId"] = Activity.Current?.Id ?? HttpContext?.TraceIdentifier, ["UserId"] = CurrentUser?.Id, }) .WriteToConsole() .WriteToFile("./logs") .Build(); ``` ### What Boosters Add to Logs When you enable boosters, they add structured properties to each log event. In file output, these appear as: ``` [2026-03-27 09:15:00.123] [INFO] [Application=MyApp, Version=1.0.0, Environment=Production, Machine=srv-01, PID=1234, ThreadId=5] User logged in successfully ``` In JSON outputs (database, Elasticsearch, etc.), boosters add properties like: ```json { "timestamp": "2026-03-27T09:15:00.123Z", "level": "Information", "message": "User logged in successfully", "machine": "srv-01", "processId": 1234, "threadId": 5, "userId": 42, "application": "MyApp", "version": "1.0.0", "environment": "Production" } ``` ## Pre-Build Modifiers Modifiers run after boosters and can mutate or cancel a log event before it is dispatched to flows. Use modifiers to add request-scoped data, redact sensitive info, or filter events. ### Basic Modifier Example ```csharp logger.AddModifier((ref LogEventBuilder builder) => { // Add request context builder.WithProperty("RequestId", HttpContext.TraceIdentifier); }); // Now every log will include RequestId automatically logger.Information("Processing request"); ``` ### Multiple Modifiers ```csharp // Add request context logger.AddModifier((ref LogEventBuilder builder) => { builder.WithProperty("RequestId", HttpContext?.TraceIdentifier); builder.WithProperty("UserId", User?.Id); }); // Add custom timing info logger.AddModifier((ref LogEventBuilder builder) => { builder.WithProperty("Timestamp", DateTime.UtcNow); }); // Redact sensitive data (example) logger.AddModifier((ref LogEventBuilder builder) => { if (builder.Message?.Contains("password") ?? false) builder.WithMessage("[REDACTED - contains sensitive data]"); }); ``` ### Canceling Events with Modifiers ```csharp logger.AddModifier((ref LogEventBuilder builder) => { // Example: Skip verbose trace logs in production if (builder.Level == LogLevel.Trace && !IsDebugMode) builder.Cancel(); // Event won't be sent to any flow }); ``` ## Request Context Integration (ASP.NET Core / Razor Pages) ```csharp // In Program.cs, register request context booster builder.Services.AddEonaCatLogging("WebApp", logBuilder => { logBuilder .WriteToConsole() .WriteToFile("./logs") .BoostWithCorrelationId(); // Distributed tracing support }); // In your page or controller public class IndexModel : PageModel { private readonly ILogger _logger; public IndexModel(ILogger logger) { _logger = logger; } public void OnGet() { // Correlation ID is automatically added by booster _logger.Information("Page loaded"); } } ``` ## Resilience Patterns ### Retry with Exponential Back-off Automatically retry failed writes with exponential delays. Useful for flaky remote endpoints. ```csharp .WriteToRetry( primaryFlow: new HttpFlow("https://logs.example.com"), maxRetries: 5, initialDelay: TimeSpan.FromMilliseconds(200), exponentialBackoff: true) // Retry delays: 200ms, 400ms, 800ms, 1.6s, 3.2s ``` ### Primary / Secondary Failover Automatically fall back to a secondary destination if the primary fails. ```csharp .WriteToFailover( primaryFlow: new ElasticSearchFlow("https://es-prod:9200"), secondaryFlow: new FileFlow("./fallback-logs"), recoveryCheckInterval: TimeSpan.FromSeconds(30), failureThreshold: 5) // Switch after 5 consecutive failures ``` ### Token-Bucket Rate Limiting with Deduplication Protect high-latency sinks (email, Slack, HTTP) from log storms. ```csharp .WriteToThrottled( inner: new SlackFlow(webhookUrl), burstCapacity: 10, // Allow 10 events in a burst refillPerSecond: 1.0, // Refill 1 token per second deduplicate: true, // Collapse identical messages dedupWindow: TimeSpan.FromSeconds(60), // Within 60-second window dedupMaxKeys: 1000) // Track up to 1000 unique messages ``` Example behavior: - `10` log errors occur simultaneously β†’ first 10 are sent (burst) - Next error within 1 second is queued (rate-limited to 1/sec) - Identical errors within 60s β†’ counted, then sent as "N duplicate messages" ### Rolling Buffer - Context-on-Error Buffer recent logs and flush context when an error occurs. Useful for debugging transient issues. ```csharp .WriteToRollingBuffer( capacity: 500, // Keep last 500 events minimumLevel: LogLevel.Trace, // Buffer everything triggerLevel: LogLevel.Error, // Trigger on errors triggerTarget: new FileFlow("./error-context"), // Write context to file preContextLines: 100) // Include 100 lines of context before the error ``` Real-world scenario: ``` # In-memory buffer contains: [Trace, Debug, Info, Warning, ...100+ events...] # Error occurs # Rolling buffer flushes: previous 100 logs + the error itself # Result: ./error-context/2026-03-27.log contains the full context ``` ### Circuit Breaker Stop sending to a failing destination and automatically resume when it recovers. ```csharp .WriteToCircuitBreaker( inner: new ElasticSearchFlow("https://es-prod:9200"), failureThreshold: 10, // Open after 10 failures successThreshold: 3, // Close after 3 successes timeout: TimeSpan.FromSeconds(30)) // Check recovery every 30s ``` ### Combining Patterns ```csharp await using var logger = new LogBuilder("Production") .WriteToConsole() // Local file as primary .WriteToFile("./logs") // Elasticsearch with resilience .WriteToRetry( primaryFlow: .WriteToFailover( primaryFlow: new ElasticSearchFlow("https://es-prod:9200"), secondaryFlow: new FileFlow("./fallback")), maxRetries: 3) // Rate-limited Slack for errors only .WriteToThrottled( inner: new SlackFlow(webhookUrl), burstCapacity: 5, refillPerSecond: 0.5, minimumLevel: LogLevel.Error) // Rolling buffer for debugging .WriteToRollingBuffer( capacity: 1000, triggerLevel: LogLevel.Error, triggerTarget: new FileFlow("./error-context")) .BoostWithMachineName() .BoostWithCorrelationId() .Build(); ``` ## Encrypted File Logging Encrypt sensitive logs with AES encryption and password protection. ### Writing Encrypted Logs ```csharp .WriteToEncryptedFile( directory: "./secure-logs", filePrefix: "encrypted", password: "YourStrongPassword123!", maxFileSize: 50 * 1024 * 1024) ``` ### Decrypting Logs ```csharp // Decrypt a specific file when needed LogBuilder.DecryptFile( encryptedPath: "./secure-logs/encrypted.enc", outputPath: "./secure-logs/decrypted.txt", password: "YourStrongPassword123!"); // Now you can read the decrypted logs string logs = File.ReadAllText("./secure-logs/decrypted.txt"); ``` ### Use Cases - Compliance requirements (HIPAA, PCI-DSS) - Storing sensitive user data or API keys in logs - Secure log storage on shared infrastructure - Audit trails with encryption ## Audit Trail (Tamper-Evident) The audit flow produces a tamper-evident file where every entry is SHA-256 hash-chained. Deleting or modifying any past entry invalidates all subsequent hashes. ### Writing Audit Logs ```csharp .WriteToAudit( directory: "./audit", filePrefix: "audit", auditLevel: AuditLevel.WarningAndAbove, // Warning, Error, Critical includeProperties: true) ``` ### Verification ```csharp // Verify file integrity at any time bool intact = AuditFlow.Verify("./audit/audit.audit"); if (!intact) { Console.WriteLine("ERROR: Audit trail has been tampered with!"); // Take appropriate action (alert, disable service, etc.) } ``` ### Audit Levels ```csharp AuditLevel.All // Every log event AuditLevel.WarningAndAbove // Warning, Error, Critical AuditLevel.ErrorAndAbove // Error, Critical AuditLevel.CriticalOnly // Critical only ``` ### Example Audit File ``` 2026-03-27T09:15:00.123Z|WARNING|Low disk space|[hash: sha256(prev_hash + data)] 2026-03-27T09:15:05.456Z|ERROR|Database connection failed|[hash: sha256(prev_hash + data)] 2026-03-27T09:15:10.789Z|CRITICAL|Service shutting down|[hash: sha256(prev_hash + data)] ``` If someone modifies the second entry, the third entry's hash validation fails, indicating tampering. ## Log Message Template Both `ConsoleFlow` and `FileFlow` accept a customizable template string for formatting output. ### Default Template ``` [{ts}] [Host: {host}] [Category: {category}] [Thread: {thread}] [{logtype}] {message}{props} ``` ### Available Tokens | Token | Description | Example | |-------|-------------|---------| | `{ts}` | Timestamp (yyyy-MM-dd HH:mm:ss.fff) | 2026-03-27 09:15:00.123 | | `{tz}` | Timezone (UTC or local name) | UTC or EST | | `{host}` | Machine name | srv-prod-01 | | `{category}` | Logger category | MyApp.Services | | `{thread}` | Managed thread ID | 5 | | `{pid}` | Process ID | 1234 | | `{logtype}` | Log level label | INFO, WARN, ERROR | | `{message}` | Log message text | User login successful | | `{props}` | Structured properties | UserId=42, IP=192.168.1.1 | | `{newline}` | Line break | (actual newline) | ### Custom Templates ```csharp // Minimal template .WriteToFile( directory: "./logs", template: "[{ts}] [{logtype}] {message}") // Output: [2026-03-27 09:15:00.123] [INFO] User login successful // Verbose template with all info .WriteToFile( directory: "./logs", template: "[{ts}] [{tz}] [{logtype}] [Thread={thread}] [PID={pid}] [Host={host}] {category}: {message}{props}") // Output: [2026-03-27 09:15:00.123] [UTC] [INFO] [Thread=5] [PID=1234] [Host=srv-01] MyApp: User login successful UserId=42, IP=192.168.1.1 // JSON-like format .WriteToFile( directory: "./logs", template: "timestamp={ts}|level={logtype}|category={category}|pid={pid}|message={message}{props}") ``` ### Advanced Message Template Features EonaCat.LogStack supports advanced templating beyond basic property placeholders. These features work with message templates used in logging calls: ```csharp logger.Information("User {User} logged in from {IP}", user, ipAddress); ``` #### Nested Property Access Access properties of objects using dot notation: ```csharp var user = new { Name = "John", Address = new { City = "NYC" } }; logger.Information("User {User.Name} lives in {User.Address.City}", user, user.Address); // Output: User John lives in NYC ``` #### Array/Collection Indexing Access specific items in arrays or lists: ```csharp var items = new[] { "apple", "banana", "cherry" }; logger.Information("First item: {Items[0]}, Second: {Items[1]}", items); // Output: First item: apple, Second: banana ``` #### Alignment and Padding Pad values to a specific width for aligned output: ```csharp logger.Information("{Name,20} {Email,-30}", "John", "john@example.com"); // Output: " John john@example.com " // (right-aligned 20 chars) (left-aligned 30 chars) ``` #### String Filters Apply transformations to property values: ```csharp // Uppercase logger.Information("Status: {Status|uppercase}", "pending"); // Output: Status: PENDING // Lowercase logger.Information("Event: {Event|lowercase}", "UserCreated"); // Output: Event: usercreated // Trim whitespace logger.Information("Value: '{Value|trim}'", " spaces "); // Output: Value: 'spaces' // Truncate with ellipsis logger.Information("Description: {Description|truncate:50}", veryLongText); // Output: Description: This is a very long description that … // Reverse string logger.Information("Reversed: {Text|reverse}", "hello"); // Output: Reversed: olleh // Multiple filters (chained) logger.Information("Result: {Input|trim|uppercase}", " hello world "); // Output: Result: HELLO WORLD ``` #### Fallback Values Provide default values when properties are null or missing: ```csharp // Fallback with ?? operator and quotes logger.Information("User: {User??'Anonymous'}", user); // Output: User: Anonymous (if user is null) logger.Information("Email: {Email??'no-email@example.com'}", email); // Output: Email: no-email@example.com (if email is null) ``` #### Conditional Rendering Display different text based on boolean properties: ```csharp logger.Information("Status: {?IsActive:Active|Inactive}", isActive); // Output: Status: Active (if isActive is true) // Output: Status: Inactive (if isActive is false) logger.Information("Result: {?Success:βœ“ Success|βœ— Failed}", success); // Output: Result: βœ“ Success (if success is true) ``` #### Advanced Conditionals Use comparison operators in conditional templates: ```csharp // Advanced conditional token with if syntax // Syntax: {@if:condition:trueOutput|falseOutput} logger.Information("User role: {@if:RoleId>5:Admin|User}", user); // Output: User role: Admin (if RoleId > 5) // Output: User role: User (if RoleId <= 5) logger.Information("Account: {@if:Status==Premium:Premium Member|Standard}", account); // Output: Account: Premium Member (if Status equals 'Premium') // Comparison operators: ==, !=, <, >, <=, >= logger.Information("{@if:Count>=100:Large|Small}", data); logger.Information("{@if:Price<50:Budget|Premium}", item); logger.Information("{@if:IsDeleted!=false:Deleted|Active}", record); ``` #### Loops and Collections Iterate over arrays and collections in templates: ```csharp // Syntax: {@loop:CollectionName:itemTemplate:separator} var items = new[] { "apple", "banana", "cherry" }; logger.Information("Items: {@loop:Items:{Item}|, }", items); // Output: Items: apple, banana, cherry // Custom separator var tags = new[] { "urgent", "high-priority", "production" }; logger.Information("Tags: {@loop:Tags:{Item}| | }", tags); // Output: Tags: urgent | high-priority | production // Complex items var users = new[] { new { Id = 1, Name = "John" }, new { Id = 2, Name = "Jane" } }; logger.Information("Users: {@loop:Users:({Id}:{Name})|, }", users); // Output: Users: (1:John), (2:Jane) ``` #### Math Filters Perform arithmetic operations on numeric properties: ```csharp logger.Information("Total: ${Amount|add:10}", 50); // Output: Total: $60 logger.Information("Discount: ${Price|multiply:0.9}", 100); // Output: Discount: $90 logger.Information("Half: {Value|divide:2}", 100); // Output: Half: 50 logger.Information("Remainder: {Number|modulo:3}", 10); // Output: Remainder: 1 logger.Information("Absolute: {Change|abs}", -15); // Output: Absolute: 15 logger.Information("Rounded: {Value|round:2}", 19.9999); // Output: Rounded: 20 logger.Information("Max: {Value|max:100}", 150); // Output: Max: 100 logger.Information("Floor: {Decimal|floor}", 19.9); // Output: Floor: 19 logger.Information("Ceil: {Decimal|ceil}", 19.1); // Output: Ceil: 20 ``` #### String Manipulation Filters Transform string values with various filters: ```csharp // Case conversion logger.Information("Lower: {Text|lowercase}", "HELLO"); // Output: Lower: hello logger.Information("Upper: {Text|uppercase}", "world"); // Output: Upper: WORLD // Padding logger.Information("Padded: |{Name|pad:15}|", "John"); // Output: Padded: |John | // Repetition logger.Information("Repeated: {Char|repeat:5}", "x"); // Output: Repeated: xxxxx // String replacement logger.Information("Fixed: {Path|replace:old:new}", "/old/path/old"); // Output: Fixed: /new/path/new // Substring operations logger.Information("Skip first 3: {Text|substring:3}", "12345"); // Output: Skip first 3: 45 // Trim variations logger.Information("Trimmed: '{Text|trim}'", " spaces "); // Output: Trimmed: 'spaces' logger.Information("Trim start: '{Text|trimstart}'", " spaces "); // Output: Trim start: 'spaces ' logger.Information("Trim end: '{Text|trimend}'", " spaces "); // Output: Trim end: ' spaces' // String testing logger.Information("Starts with 'user': {Email|startswith:user}", "user@example.com"); // Output: Starts with 'user': true logger.Information("Ends with '.org': {Url|endswith:.org}", "website.org"); // Output: Ends with '.org': true logger.Information("Contains 'app': {Path|contains:app}", "/app/data"); // Output: Contains 'app': true // String reversal logger.Information("Reversed: {Text|reverse}", "hello"); // Output: Reversed: olleh // Split and join logger.Information("Split CSV: {Data|split:,}", "a,b,c"); // Output: Split CSV: a, b, c ``` #### Comparison Filters Compare values and return boolean results: ```csharp logger.Information("Equals: {Status|equals:Active}", status); // Output: Equals: true (if status == 'Active') logger.Information("Less than 100: {Value|lessthan:100}", 50); // Output: Less than 100: true logger.Information("Greater than 50: {Value|greaterthan:50}", 100); // Output: Greater than 50: true logger.Information("Greater or equal: {Count|gte:10}", 15); // Output: Greater or equal: true logger.Information("Less or equal: {Count|lte:20}", 15); // Output: Less or equal: true logger.Information("Not equal: {Type|ne:User}", "Admin"); // Output: Not equal: true ``` #### Date & Time Filters Format and manipulate date/time values: ```csharp // Date formatting logger.Information("Date: {CreatedAt|date:yyyy-MM-dd}", DateTime.Now); // Output: Date: 2026-03-27 logger.Information("Full timestamp: {CreatedAt|date:O}", DateTime.Now); // Output: Full timestamp: 2026-03-27T09:15:00.1234567Z logger.Information("Custom format: {CreatedAt|date:dd/MM/yyyy HH:mm:ss}", DateTime.Now); // Output: Custom format: 27/03/2026 09:15:00 // TimeSpan operations var duration = TimeSpan.FromSeconds(3661); logger.Information("Total seconds: {Duration|timespan:totalseconds}", duration); // Output: Total seconds: 3661 logger.Information("Total minutes: {Duration|timespan:totalminutes}", duration); // Output: Total minutes: 61.0166... logger.Information("Total hours: {Duration|timespan:totalhours}", duration); // Output: Total hours: 1.01388... logger.Information("Days: {Duration|timespan:days}", duration); // Output: Days: 0 logger.Information("Hours: {Duration|timespan:hours}", duration); // Output: Hours: 1 logger.Information("Minutes: {Duration|timespan:minutes}", duration); // Output: Minutes: 1 logger.Information("Seconds: {Duration|timespan:seconds}", duration); // Output: Seconds: 1 ``` #### Chaining Multiple Filters Combine filters for complex transformations: ```csharp // Trim, then uppercase, then truncate logger.Information("Processed: {Input|trim|uppercase|truncate:10}", " hello world "); // Output: Processed: HELLO WOR… // Substring, then lowercase logger.Information("Modified: {Path|substring:5|lowercase}", "/DATA/MyFile.TXT"); // Output: Modified: myfile.txt // Apply multiple math operations logger.Information("Calculated: {Value|multiply:2|add:10|divide:3}", 5); // Output: Calculated: 6.66... (5 * 2 = 10, 10 + 10 = 20, 20 / 3 = 6.66) ``` #### Complete Advanced Template Examples ```csharp // API request logging with advanced features var request = new { Method = "POST", Path = "/api/users", UserId = 42, ResponseTime = 150, Success = true, Tags = new[] { "api", "users", "production" } }; logger.Information( "[{Timestamp|date:HH:mm:ss}] {Method|uppercase} {Path} - User {UserId} - {ResponseTime|pad:5}ms - {?Success:βœ“|βœ—} - Tags: {@loop:Tags:{Item}|, }", DateTime.Now, request.Method, request.Path, request.UserId, request.ResponseTime, request.Success, request.Tags ); // Output: [09:15:00] POST /api/users - User 42 - 150ms - βœ“ - Tags: api, users, production // Conditional status with comparison var operation = new { Name = "DataSync", Status = "Completed", Duration = 45000, RecordsProcessed = 1500, ErrorCount = 0 }; logger.Information( "{Name}: {@if:ErrorCount==0:βœ“ Success|⚠ With Errors} | Duration: {Duration|timespan:totalseconds}s | Records: {RecordsProcessed|add:0} processed", operation.Name, operation.ErrorCount, operation.Duration, operation.RecordsProcessed ); // Output: DataSync: βœ“ Success | Duration: 45s | Records: 1500 processed // Complex nested template var batch = new { Id = "batch-001", Items = new[] { "item1", "item2", "item3" }, Size = 3, Price = 99.99m, Discount = 0.15m }; logger.Information( "Batch {Id}: {?Size>5:Large|Small} batch | Items: {@loop:Items:{Item}|, } | Price: ${Price|multiply:Discount|add:0|round:2}", batch.Id, batch.Size, batch.Items, batch.Price, batch.Discount ); // Output: Batch batch-001: Small batch | Items: item1, item2, item3 | Price: $15.00 ``` #### Template Features Summary The templating engine supports: | Feature | Syntax | Example | |---------|--------|---------| | **Basic Property** | `{PropertyName}` | `{UserId}` | | **Nested Properties** | `{Object.Property.Sub}` | `{User.Address.City}` | | **Array Indexing** | `{Array[Index]}` | `{Items[0]}` | | **Alignment** | `{Value,Width}` | `{Name,20}` | | **Format Specifiers** | `{Value:Format}` | `{Date:yyyy-MM-dd}` | | **String Filters** | `{Value\|Filter}` | `{Text\|uppercase}` | | **Math Filters** | `{Value\|add:10}` | `{Price\|multiply:0.9}` | | **Comparison** | `{Value\|equals:text}` | `{Status\|equals:Active}` | | **Simple Conditionals** | `{?Property:True\|False}` | `{?IsActive:Active\|Inactive}` | | **Advanced Conditionals** | `{@if:Condition:T\|F}` | `{@if:Count>10:High\|Low}` | | **Loops** | `{@loop:Collection:Template}` | `{@loop:Items:{Item}\|, }` | | **Fallback Values** | `{Value??'Default'}` | `{Name??'Unknown'}` | | **Chained Filters** | `{Value\|filter1\|filter2}` | `{Text\|trim\|uppercase}` | | **Destructuring** | `{@Object}` | `{@User}` | #### Format Specifiers Apply .NET format strings to values: ```csharp // Date formatting logger.Information("Date: {CreatedAt:yyyy-MM-dd}", DateTime.Now); // Output: Date: 2026-03-27 // Decimal formatting logger.Information("Price: {Price:C}", 19.99m); // Output: Price: $19.99 // Numeric formatting logger.Information("Count: {Count:D5}", 42); // Output: Count: 00042 ``` #### Destructuring Deep-inspect complex objects to reveal their structure: ```csharp var user = new { Id = 1, Name = "John", Email = "john@example.com" }; // Default destructuring with @ prefix logger.Information("User: {@User}", user); // Output: User: {Id: 1, Name: John, Email: john@example.com} // Force string conversion with $ prefix logger.Information("User: {$User}", user); // Output: User: YourNamespace.User // Nested destructuring var order = new { Id = 1, Customer = new { Name = "John", City = "NYC" }, Items = new[] { "Item1", "Item2" } }; logger.Information("Order: {@Order}", order); // Output: Order: {Id: 1, Customer: {Name: John, City: NYC}, Items: [...]} ``` #### Complete Advanced Template Examples ```csharp // API request logging var request = new { Method = "POST", Path = "/api/users", UserId = 42, ResponseTime = 150, Success = true }; logger.Information( "[{Time|uppercase}] {Method} {Path} - User {UserId} - {ResponseTime,5}ms - {?Success:βœ“|βœ—}", DateTime.Now.ToString("HH:mm:ss"), request.Method, request.Path, request.UserId, request.ResponseTime, request.Success ); // Output: [09:15:00] POST /api/users - User 42 - 150ms - βœ“ // Database operation with fallback logger.Information( "Database query {QueryName??'Unknown'} by user {UserId??'System'} took {Duration|truncate:10}ms", storedProcName, currentUserId, duration ); // Output: Database query sp_GetUsers by user System took 125ms // File processing with conditions logger.Information( "File {FileName} processed: {?HasErrors:⚠ ERRORS|βœ“ OK} - {LineCount,6} lines", file.Name, file.HasErrors, file.LineCount ); // Output: File log.txt processed: βœ“ OK - 1024 lines // Nested property access var company = new { Name = "Acme Corp", HeadOffice = new { City = "New York", Country = "USA" }, Employees = new[] { "John", "Jane", "Jack" } }; logger.Information( "Company {Company.Name} from {Company.HeadOffice.City}, {Company.HeadOffice.Country} - First employee: {Company.Employees[0]}", company ); // Output: Company Acme Corp from New York, USA - First employee: John ``` ## Real-World Configuration Scenarios ### Scenario 1: Development Environment Console output with verbose logging, local files, and no remote sends. ```csharp await using var logger = new LogBuilder("MyApp") .WithMinimumLevel(LogLevel.Debug) .WithTimestampMode(TimestampMode.Local) .WriteToConsole(useColors: true) .WriteToFile("./logs", filePrefix: "dev") .BoostWithThreadId() .BoostWithCorrelationId() .Build(); ``` ### Scenario 2: Production - Multi-Destination with Resilience Files locally, Elasticsearch for search, Slack for alerts, encrypted audit trail. ```csharp await using var logger = new LogBuilder("ProductionApp") .WithMinimumLevel(LogLevel.Information) .WithTimestampMode(TimestampMode.Utc) // Local backup .WriteToFile( directory: "./logs", maxFileSize: 100 * 1024 * 1024, compression: CompressionFormat.GZip) // Primary analytics with fallback .WriteToFailover( primaryFlow: new ElasticSearchFlow("https://elastic.company.com"), secondaryFlow: new FileFlow("./fallback-elastic")) // Rate-limited alerts .WriteToThrottled( inner: new SlackFlow(slackWebhookUrl), burstCapacity: 5, refillPerSecond: 1.0, minimumLevel: LogLevel.Error) // Compliance audit .WriteToAudit( directory: "./audit", auditLevel: AuditLevel.WarningAndAbove) // Encrypted sensitive logs .WriteToEncryptedFile( directory: "./secure-logs", password: Environment.GetEnvironmentVariable("LOG_ENCRYPTION_KEY")) // Diagnostics snapshot .WriteDiagnostics( snapshotInterval: TimeSpan.FromMinutes(5)) // Boosters .BoostWithMachineName() .BoostWithProcessId() .BoostWithApplication("ProductionApp", "1.0.0") .BoostWithEnvironment("Production") .BoostWithCorrelationId() .Build(); ``` ### Scenario 3: Microservices / Distributed Tracing Elasticsearch for centralized logs, Redis for real-time events, correlation IDs. ```csharp await using var logger = new LogBuilder("OrderService") .WithMinimumLevel(LogLevel.Information) // Centralized log storage .WriteToElasticSearch( elasticSearchUrl: "https://elastic-cluster.company.com", indexName: "orderservice-logs") // Real-time event stream .RedisFlow( host: "redis.company.com", channel: "orderservice:logs", listKey: "orderservice:logs:history", maxListLength: 10000) // Local file backup .WriteToFile("./logs") // Correlation tracking for distributed tracing .BoostWithCorrelationId() // Automatically includes Activity.Current?.Id .BoostWithApplication("OrderService", ServiceVersion) .BoostWithProcessId() .BoostWithMachineName() .Build(); ``` ### Scenario 4: Real-Time Dashboard SignalR flow for live log streaming to web dashboard. ```csharp // Backend: Log streaming to SignalR hub await using var logger = new LogBuilder("DashboardApp") .WriteToFile("./logs") .WriteToSignalR( hubUrl: "https://dashboard.company.com/loghub", hubMethod: "ReceiveLog", batchSize: 20, batchIntervalMs: 500, minimumLevel: LogLevel.Warning) // Only send warnings+ to dashboard .Build(); // Frontend: Receive logs in real-time (JavaScript example) const connection = new signalR.HubConnectionBuilder() .withUrl("https://dashboard.company.com/loghub") .withAutomaticReconnect() .build(); connection.on("ReceiveLog", (log) => { console.log(`[${log.level}] ${log.message}`); addToLiveLogUI(log); }); await connection.start(); ``` ### Scenario 5: Compliance & Audit Separate audit trail, encrypted logs, tamper detection. ```csharp await using var logger = new LogBuilder("ComplianceApp") // Tamper-evident audit trail .WriteToAudit( directory: "./compliance/audit", auditLevel: AuditLevel.WarningAndAbove, includeProperties: true) // Encrypted sensitive data .WriteToEncryptedFile( directory: "./compliance/encrypted", password: GetAuditPassword(), maxFileSize: 50 * 1024 * 1024) // Database for detailed analysis .WriteToDatabase( connectionFactory: () => new SqlConnection(connectionString), tableName: "AuditLogs", batchSize: 10) // Regular file backup .WriteToFile("./logs") // Boost with full context .BoostWithUser() .BoostWithMachineName() .BoostWithProcessId() .BoostWithApplication("ComplianceApp", "1.0.0") .Build(); // Periodic verification _ = Task.Run(async () => { while (true) { await Task.Delay(TimeSpan.FromHours(1)); bool isIntact = AuditFlow.Verify("./compliance/audit/audit.audit"); if (!isIntact) { // Alert security team logger.Critical("SECURITY ALERT: Audit trail tampering detected!"); } } }); ``` ### Scenario 6: High-Volume with Batching & Rate Limiting For services handling high log volume, use batching and throttling. ```csharp await using var logger = new LogBuilder("HighVolumeApp") .WriteToThrottled( inner: new HttpFlow("https://logs.company.com/ingest"), burstCapacity: 100, refillPerSecond: 50.0, // 50 logs/sec steady state deduplicate: true, dedupWindow: TimeSpan.FromSeconds(30)) .WriteToFile( directory: "./logs", batchSize: 100, // Batch 100 logs before writing flushIntervalInMilliSeconds: 1000) // Flush every 1 second .RedisFlow( host: "redis.company.com", channel: "app:logs", listKey: "app:logs:history") .Build(); ``` ## OpenTelemetry-style Telemetry EonaCat.LogStack includes a dependency-free telemetry layer inspired by OpenTelemetry concepts. ### Distributed tracing ```csharp var source = new TelemetryActivitySource(); using var span = source.StartScope("ProcessOrder", telemetry => { telemetry.Attributes["order.id"] = "123"; }); ``` Supported tracing concepts: - Trace IDs and Span IDs - Parent span relationships - Span duration measurement - Span attributes - Span events - Status tracking ### Metrics ```csharp var meter = new TelemetryMeter(); meter.Count("requests.total"); meter.Record("request.duration", 25.5); var p95 = meter.GetHistogram("request.duration")?.Percentile(0.95); ``` Telemetry features: - Counters - Histograms - Percentiles - Runtime snapshots - Resource attributes - Baggage/context propagation - HTTP telemetry ingestion ### Resource metadata ```csharp var resource = new TelemetryResource() .Add("service.name", "OrderService") .Add("deployment.environment", "production"); ``` ## Diagnostics ### Real-Time Metrics ```csharp // Get current statistics var stats = logger.GetDiagnostics(); Console.WriteLine($"Total Logged: {stats.TotalLogged}"); Console.WriteLine($"Total Dropped: {stats.TotalDropped}"); Console.WriteLine($"Drop Rate: {(double)stats.TotalDropped / stats.TotalLogged:P2}"); // Per-flow statistics foreach (var flowStat in stats.FlowStats) { Console.WriteLine($"Flow: {flowStat.Name}"); Console.WriteLine($" Processed: {flowStat.Processed}"); Console.WriteLine($" Dropped: {flowStat.Dropped}"); Console.WriteLine($" Errors: {flowStat.ErrorCount}"); } ``` ### Diagnostic Snapshots Enable periodic diagnostic snapshots to track performance over time. ```csharp .WriteDiagnostics( snapshotInterval: TimeSpan.FromMinutes(5), injectIntoEvents: true, // Include metrics in log events writeSnapshotEvents: true, // Write snapshot to logs snapshotCategory: "Diagnostics", forwardTo: new FileFlow("./diagnostics"), // Also forward to file minimumLevel: LogLevel.Information, customMetrics: () => new Dictionary { ["ActiveRequests"] = GetActiveRequestCount(), ["CacheHitRate"] = GetCacheHitRate(), ["DatabasePoolSize"] = GetDbPoolSize() }) ``` ### Monitoring Health ```csharp // Monitor in a background task _ = Task.Run(async () => { while (true) { await Task.Delay(TimeSpan.FromMinutes(1)); var stats = logger.GetDiagnostics(); // Alert if drop rate is high if (stats.TotalLogged > 0) { double dropRate = (double)stats.TotalDropped / stats.TotalLogged; if (dropRate > 0.01) // > 1% drop rate { logger.Warning("High log drop rate detected", ("DropRate", dropRate)); } } // Alert on errors var hasErrors = stats.FlowStats.Any(f => f.ErrorCount > 0); if (hasErrors) { logger.Warning("Some flows are experiencing errors"); } } }); ``` ## Flushing and Disposal All flows support batching, so events may not be written immediately. Use `FlushAsync()` to ensure all pending events are written before shutdown. ### Explicit Flushing ```csharp // Flush all pending events synchronously (up to 5 seconds) await logger.FlushAsync(); // After flush, you can safely dispose await logger.DisposeAsync(); ``` ### Automatic Flushing on Disposal Using `await using` automatically flushes and disposes: ```csharp await using var logger = new LogBuilder("MyApp") .WriteToFile("./logs") .Build(); logger.Information("Application running"); // At scope exit: FlushAsync() called automatically, then DisposeAsync() ``` ### Best Practices ```csharp try { await using var logger = new LogBuilder("MyApp").Build(); // Log events here logger.Information("App started"); // Your application logic await RunApplication(); } catch (Exception ex) { // Ensure errors are logged even if something goes wrong logger?.Critical(ex, "Unexpected error during shutdown"); } finally { // FlushAsync() is called automatically when leaving the using block } ``` ### Custom Disposal Logic ```csharp var logger = new LogBuilder("MyApp").Build(); try { logger.Information("Processing..."); } finally { // Manual control await logger.FlushAsync(); // Perform custom cleanup CleanupResources(); await logger.DisposeAsync(); } ``` ## Events Subscribe to log events for real-time processing or custom handling. ### Basic Event Handler ```csharp logger.OnLog += (sender, message) => { // Fired for every log event that passes filters Console.WriteLine($"[Event] {message.Level}: {message.Message}"); }; logger.Information("This event will be raised"); ``` ### Advanced Event Handling ```csharp logger.OnLog += (sender, message) => { // Log level filtering if (message.Level >= LogLevel.Error) { // Send critical errors to external alert system SendAlert($"{message.Level}: {message.Message}"); } // Category filtering if (message.Category?.Contains("Payment") ?? false) { // Audit sensitive operations LogToAuditSystem(message); } // Property-based filtering if (message.Properties != null && message.Properties.ContainsKey("UserId")) { TrackUserActivity(message.Properties["UserId"], message); } }; ``` ### Multiple Event Handlers ```csharp // Handler 1: Alert on errors logger.OnLog += (sender, msg) => { if (msg.Level == LogLevel.Error) SendSlackAlert(msg); }; // Handler 2: Track metrics logger.OnLog += (sender, msg) => { Metrics.Increment($"logs.{msg.Level.ToString().ToLower()}"); }; // Handler 3: Update dashboard logger.OnLog += (sender, msg) => { Dashboard.AddLog(msg); }; ``` ## Custom Flows Create custom flows by implementing `IFlow` or extending `FlowBase` for complex scenarios. ### Simple Custom Flow ```csharp public class MyCustomFlow : FlowBase { public MyCustomFlow() : base("MyCustomFlow", LogLevel.Trace) { } public override Task BlastAsync(LogEvent logEvent, CancellationToken ct = default) { try { // Your custom logic here string formatted = $"[{logEvent.Timestamp:O}] {logEvent.Level}: {logEvent.Message}"; MyBackendService.SendLog(formatted); return Task.FromResult(WriteResult.Success); } catch (Exception ex) { return Task.FromResult(WriteResult.Failure(ex)); } } public override Task FlushAsync(CancellationToken ct = default) { // Optional: implement batching flush logic return Task.CompletedTask; } } // Register with: new LogBuilder("App") .WriteTo(new MyCustomFlow()) .Build(); ``` ### Advanced Custom Flow with Batching ```csharp public class BatchedCustomFlow : FlowBase { private readonly List _batch = new(100); private readonly object _lock = new(); public BatchedCustomFlow() : base("BatchedCustom", LogLevel.Trace) { } public override Task BlastAsync(LogEvent logEvent, CancellationToken ct = default) { lock (_lock) { _batch.Add(logEvent); if (_batch.Count >= 100) { return FlushBatchAsync(ct); } } return Task.FromResult(WriteResult.Success); } private Task FlushBatchAsync(CancellationToken ct) { try { var toSend = _batch.ToList(); _batch.Clear(); // Send batch to external service MyBackendService.SendLogBatch(toSend); return Task.FromResult(WriteResult.Success); } catch (Exception ex) { return Task.FromResult(WriteResult.Failure(ex)); } } public override async Task FlushAsync(CancellationToken ct = default) { lock (_lock) { if (_batch.Count > 0) { await FlushBatchAsync(ct); } } } public override async ValueTask DisposeAsync() { await FlushAsync(); await base.DisposeAsync(); } } ``` ### Custom Flow with Retry Logic ```csharp public class RetryableCustomFlow : FlowBase { private const int MaxRetries = 3; public RetryableCustomFlow() : base("RetryableCustom", LogLevel.Trace) { } public override async Task BlastAsync(LogEvent logEvent, CancellationToken ct = default) { int attempts = 0; Exception lastEx = null; while (attempts < MaxRetries) { try { await SendToServiceAsync(logEvent, ct); return WriteResult.Success; } catch (Exception ex) { lastEx = ex; attempts++; if (attempts < MaxRetries) await Task.Delay(TimeSpan.FromMilliseconds(Math.Pow(2, attempts) * 100), ct); } } return WriteResult.Failure(lastEx); } private Task SendToServiceAsync(LogEvent logEvent, CancellationToken ct) { // Your implementation return MyBackendService.SendLogAsync(logEvent, ct); } public override Task FlushAsync(CancellationToken ct = default) => Task.CompletedTask; } ``` ## Best Practices & Tips ### 1. **Use Appropriate Log Levels** ```csharp logger.Trace("Very detailed diagnostic info (most verbose)"); logger.Debug("Debug-level diagnostic information"); logger.Information("General informational message (default level)"); logger.Warning("Warning message (potential issue)"); logger.Error("Error occurred, operation failed"); logger.Critical("Critical failure, system may be unstable"); ``` ### 2. **Use Structured Properties for Queryability** ```csharp // Good: Structured properties logger.Information("User logged in", ("UserId", user.Id), ("Email", user.Email), ("IpAddress", request.RemoteIpAddress), ("Timestamp", DateTime.UtcNow)); // Avoid: String interpolation in message logger.Information($"User {user.Id} logged in from {request.RemoteIpAddress}"); ``` ### 3. **Use Categories for Filtering** ```csharp // Create category-specific loggers ILogger serviceLogger = loggerFactory.CreateLogger("MyApp.Services.UserService"); ILogger dataLogger = loggerFactory.CreateLogger("MyApp.Data.Repository"); // Configure file splitting by category .WriteToFile("./logs", useCategoryRouting: true) // Results in: logs/MyApp.Services.UserService.log, logs/MyApp.Data.Repository.log ``` ### 4. **Always Handle Exceptions** ```csharp try { // Risky operation await database.ExecuteAsync(query); } catch (TimeoutException ex) { logger.Warning(ex, "Database timeout, retrying"); } catch (Exception ex) { logger.Error(ex, "Database error, operation failed"); throw; // Re-throw to propagate to caller } ``` ### 5. **Use Correlation IDs for Tracing** ```csharp builder.Services.AddEonaCatLogging("MyApp", logBuilder => { logBuilder.BoostWithCorrelationId(); // Automatic tracing }); // All logs in the same request/operation automatically get the same correlation ID ``` ### 6. **Configure Different Levels per Flow** ```csharp .WithMinimumLevel(LogLevel.Information) // Global minimum .WriteToConsole(minimumLevel: LogLevel.Debug) // More verbose .WriteToFile(minimumLevel: LogLevel.Warning) // Less verbose .WriteToSlack(minimumLevel: LogLevel.Error) // Errors only ``` ### 7. **Use Async Flushing Before Shutdown** ```csharp var host = builder.Build(); // Graceful shutdown: flush logs before exit var lifetime = host.Services.GetRequiredService(); lifetime.ApplicationStopping.Register(async () => { var logger = host.Services.GetRequiredService(); await logger.FlushAsync(); }); await host.RunAsync(); ``` ### 8. **Monitor Drop Rates** ```csharp _ = Task.Run(async () => { while (true) { await Task.Delay(TimeSpan.FromMinutes(1)); var stats = logger.GetDiagnostics(); if (stats.TotalLogged + stats.TotalDropped > 0) { double dropRate = (double)stats.TotalDropped / (stats.TotalLogged + stats.TotalDropped); if (dropRate > 0.01) // > 1% logger.Warning("High drop rate detected", ("DropRate", dropRate)); } } }); ``` ### 9. **Use Rolling Buffers for Post-Mortem** ```csharp // Before: error happens, context is lost // After: rolling buffer captures previous 100 logs .WriteToRollingBuffer( capacity: 500, triggerLevel: LogLevel.Error, triggerTarget: new FileFlow("./error-context")) ``` ### 10. **Encrypt Sensitive Data** ```csharp .WriteToEncryptedFile( directory: "./secure-logs", password: GetEncryptionPasswordFromVault()) ``` ## Troubleshooting ### High CPU Usage **Symptoms**: Excessive CPU while logging **Solutions**: - Increase batch size to reduce I/O operations - Use `WriteToThrottled()` to rate limit - Check if remote endpoints are responding (network latency) ```csharp .WriteToFile("./logs", batchSize: 100, flushIntervalInMilliSeconds: 5000) .WriteToThrottled(inner: new HttpFlow(url), burstCapacity: 50) ``` ### High Memory Usage **Symptoms**: Memory grows linearly with time **Solutions**: - Check if logs are being flushed (batches aren't sent) - Reduce rolling buffer capacity - Enable compression for file logs ```csharp .WriteToFile(compression: CompressionFormat.GZip) .WriteToRollingBuffer(capacity: 250) // Reduce from 500 ``` ### Events Being Dropped **Symptoms**: `TotalDropped > 0` in diagnostics **Causes & Solutions**: - Backpressure queue full β†’ increase capacity or reduce send rate - Flow errors β†’ check flow configuration and connectivity - Log level filters β†’ verify minimumLevel settings ```csharp // Investigate drop reasons var stats = logger.GetDiagnostics(); foreach (var flow in stats.FlowStats.Where(f => f.Dropped > 0)) { logger.Warning($"Flow {flow.Name} dropped {flow.Dropped} events"); } ``` ### Logs Not Reaching Remote Destination **Symptoms**: Local logs exist, but remote endpoint has nothing **Troubleshooting Steps**: 1. Verify endpoint connectivity: `telnet host port` 2. Check authentication (API keys, certificates) 3. Enable retry and failover patterns 4. Ensure events pass level filters ```csharp .WriteToRetry( primaryFlow: new HttpFlow(endpoint), maxRetries: 5) // Or use failover: .WriteToFailover( primaryFlow: new HttpFlow(endpoint), secondaryFlow: new FileFlow("./fallback")) ``` ### File Size Growing Too Large **Symptoms**: Log files grow without bound **Solutions**: - Configure `maxFileSize` and `maxDirectorySize` - Enable compression - Set retention policy ```csharp .WriteToFile( directory: "./logs", maxFileSize: 100 * 1024 * 1024, // 100 MB maxDirectorySize: 10L * 1024 * 1024 * 1024, // 10 GB compression: CompressionFormat.GZip, fileRetentionPolicy: new FileRetentionPolicy { RetentionDays = 30 }) ``` # EonaCat.LogStack.Server A lightweight, multi-transport log server for the EonaCat LogStack ecosystem. ## Quick start ```csharp // Minimal - UDP on port 5555 var server = new Server(); await server.Start(); // Full control via ServerOptions var server = new Server(new ServerOptions { UseTcp = true, UseUdp = true, UseHttp = true, // enables POST /ingest + GET /metrics Port = 5555, HttpPort = 5556, MinimumLevel = ServerLogLevel.Information, // drop Debug/Trace RateLimitPerSecond = 100, // per remote endpoint LogRetentionDays = 30, MaxLogDirectorySize = 10L * 1024 * 1024 * 1024, // 10 GB LogsRootDirectory = "logs", }); server.LogWritten += line => Console.WriteLine("[written] " + line); server.LogDropped += line => Console.WriteLine("[dropped] " + line); await server.Start(); ``` ## Transports | Transport | Default | Notes | |-----------|---------|-------| | TCP | enabled | Streams until connection closes | | UDP | enabled | Max 65 507 bytes per packet | | HTTP | disabled | Enable via `UseHttp = true` | TCP and UDP can run simultaneously on the same port. ## HTTP endpoints (when `UseHttp = true`) | Method | Path | Description | |--------|------|-------------| | `POST` | `/ingest` | Accept a JSON log entry or array | | `GET` | `/metrics` | Return live server metrics as JSON | ### POST /ingest - single entry ```json { "level": "info", "message": "Hello world", "source": "MyApp", "host": "srv-01" } ``` ### POST /ingest - batch ```json [ { "level": "warn", "message": "Disk at 80%", "source": "monitor" }, { "level": "error", "message": "DB timeout", "source": "api", "exception": "TimeoutException…" } ] ``` ### GET /metrics response ```json { "totalReceived": 12345, "totalWritten": 12300, "totalDropped": 45, "totalBytes": 4096000, "activeTcpConnections": 3, "uptimeSeconds": 3600, "startedAt": "2026-03-27T08:00:00Z" } ``` ## Structured JSON parsing If the incoming payload is valid JSON the server parses it and formats each entry before writing: ``` [2026-03-27T09:15:00Z] [ERROR] [MyApp] host=srv-01 trace=abc123 Something went wrong EXCEPTION: System.TimeoutException: The operation timed out. ``` Recognised JSON fields: | Field | Aliases | Description | |-------|---------|-------------| | `timestamp` | - | ISO-8601 timestamp | | `level` | `Level`, `severity`, `Severity` | Log level string | | `message` | `Message` | Log message | | `source` | `application` | App / service name | | `host` | - | Hostname | | `traceId` | - | Distributed trace ID | | `exception` | - | Exception string | Plain-text payloads are written as-is and always bypass the level filter. ## Log level filtering ```csharp MinimumLevel = ServerLogLevel.Warning // only Warning / Error / Critical are stored ``` Levels in order: `Trace β†’ Debug β†’ Information β†’ Warning β†’ Error β†’ Critical` ## Rate limiting ```csharp RateLimitPerSecond = 100 // per remote IP:port, 0 = disabled ``` Dropped messages are counted in `Metrics.TotalDropped` and raise the `LogDropped` event. ## Metrics ```csharp var m = server.GetMetrics(); Console.WriteLine($"Written={m.TotalWritten} Dropped={m.TotalDropped} Uptime={m.Uptime}"); ``` ## Log file layout ``` logs/ 20260327/ EonaCatLogs.log ← active file (≀ 200 MB) EonaCatLogs_1.log ← rolled over 20260326/ EonaCatLogs.log ``` Daily directories older than `LogRetentionDays` are deleted automatically. The total directory is also capped at `MaxLogDirectorySize`. ## Events ```csharp server.LogWritten += line => NotifyDashboard(line); server.LogDropped += line => Metrics.Increment("dropped"); ``` ## Graceful shutdown ```csharp Console.CancelKeyPress += (_, e) => { e.Cancel = true; server.Stop(); }; ``` `Stop()` prints a throughput summary and disposes all listeners cleanly. ## Dependency Injection (DI) registration methods ### 1. Basic Registration (Simplest) Register with default settings: ```csharp services.AddEonaCatLogging(); ``` This registers: - `ILoggerFactory` - For creating category-specific loggers - `Microsoft.Extensions.Logging.ILoggerFactory` - For Microsoft.Extensions.Logging compatibility - `ILogger` - For injecting the default logger ### 2. Registration with Log Level and Timestamp Mode ```csharp services.AddEonaCatLogging( minimumLevel: LogLevel.Information, timestampMode: TimestampMode.Local); ``` ### 3. Registration with Pre-built EonaCatLogStack If you've already created an `EonaCatLogStack` instance: ```csharp var logStack = new EonaCatLogStack("MyApp"); logStack.AddFlow(new ConsoleFlow()); services.AddEonaCatLogging(logStack); ``` ### 4. Registration with Configuration Callback (Recommended) Configure the logger directly with an `Action`: ```csharp services.AddEonaCatLogging(logStack => { logStack.AddFlow(new ConsoleFlow()); logStack.AddFlow(new FileFlow("./logs")); logStack.AddBooster(new MachineNameBooster()); }); ``` ### 5. Registration with LogBuilder (Most Fluent - Recommended) Use the fluent `LogBuilder` API for the most intuitive configuration: ```csharp services.AddEonaCatLogging("MyApplication", builder => { builder .WithMinimumLevel(LogLevel.Information) .WithTimestampMode(TimestampMode.Local) .WriteToConsole() .WriteToFile("./logs") .BoostWithMachineName() .BoostWithProcessId() .BoostWithCorrelationId(); }); ``` Or with the default "Application" category: ```csharp services.AddEonaCatLogging(builder => { builder .WriteToConsole() .WriteToFile("./logs"); }); ``` ### 6. Registration with Factory Method (Advanced) For advanced scenarios where you need access to the service provider: ```csharp services.AddEonaCatLoggingFactory("MyApplication", builder => { builder .WriteToConsole() .WriteToFile("./logs"); }); ``` ## Using in Your Application ### Injecting ILoggerFactory ```csharp public class MyService { private readonly ILoggerFactory _loggerFactory; public MyService(ILoggerFactory loggerFactory) { _loggerFactory = loggerFactory; } public void DoSomething() { var logger = _loggerFactory.CreateLogger("MyService"); logger.Log(LogLevel.Information, "Doing something"); } } ``` ### Injecting ILogger ```csharp public class MyService { private readonly ILogger _logger; public MyService(ILogger logger) { _logger = logger; } public void DoSomething() { _logger.Log(LogLevel.Information, "Doing something"); } } ``` ### Using with Microsoft.Extensions.Logging.ILogger ```csharp public class MyService { private readonly Microsoft.Extensions.Logging.ILogger _logger; public MyService(Microsoft.Extensions.Logging.ILogger logger) { _logger = logger; } public void DoSomething() { _logger.LogInformation("Doing something"); } } ``` ## ASP.NET Core / Razor Pages Integration In your `Program.cs`: ```csharp var builder = WebApplication.CreateBuilder(args); // Add EonaCat LogStack to the service collection builder.Services.AddEonaCatLogging("WebApplication", logBuilder => { logBuilder .WithMinimumLevel(LogLevel.Information) .WriteToConsole(useColors: true) .WriteToFile("./logs") .BoostWithCorrelationId() .BoostWithThreadId(); }); // Rest of your configuration... var app = builder.Build(); // Configure HTTP request pipeline... app.Run(); ``` ### In Razor Page Code-Behind ```csharp public class IndexModel : PageModel { private readonly ILogger _logger; public IndexModel(ILogger logger) { _logger = logger; } public void OnGet() { _logger.Log(LogLevel.Information, "Index page loaded"); } } ``` ## Features ### Multiple Output Destinations (Flows) ```csharp services.AddEonaCatLogging(builder => { builder .WriteToConsole() // Console output .WriteToFile("./logs") // File output .WriteToSlack("https://hooks.slack.com/...") // Slack .WriteToDiscord("https://discordapp.com/...") // Discord .WriteToElasticSearch("http://localhost:9200") // Elasticsearch .WriteToEmail("smtp.gmail.com", 587, ...) // Email .WriteToMicrosoftTeams("https://..."); // Teams }); ``` ### Enriching Log Events (Boosters) ```csharp services.AddEonaCatLogging(builder => { builder .BoostWithMachineName() // Adds machine name .BoostWithProcessId() // Adds process ID .BoostWithThreadId() // Adds thread ID .BoostWithCorrelationId() // Adds correlation ID (for distributed tracing) .BoostWithMemory() // Adds memory usage .BoostWithOS() // Adds OS info .BoostWithUser() // Adds username .BoostWithCustomText("Environment", "Production"); // Custom properties }); ``` ### Log Level Filtering ```csharp services.AddEonaCatLogging(builder => { builder .WithMinimumLevel(LogLevel.Warning) // Only log warnings and above .WriteToConsole(minimumLevel: LogLevel.Information) // More verbose for console .WriteToFile("./logs", minimumLevel: LogLevel.Error); // Only errors to file }); ``` ## Best Practices 1. **Use LogBuilder for Configuration**: The fluent LogBuilder API is the most readable and maintainable approach. 2. **Register Early**: Register logging in `Program.cs` before other services that depend on logging. 3. **Use Appropriate Log Levels**: - `Trace` - Very detailed diagnostic info - `Debug` - Debug-level diagnostic info - `Information` - General informational messages - `Warning` - Warning messages - `Error` - Error messages - `Critical` - Critical failures 4. **Inject Specific Types**: Prefer injecting `ILoggerFactory` to create category-specific loggers rather than injecting a single shared logger. 5. **Use Categories**: Create loggers with meaningful category names: ```csharp var logger = loggerFactory.CreateLogger("MyApp.Services.UserService"); ``` 6. **Enable Correlation IDs**: For distributed tracing scenarios: ```csharp builder.BoostWithCorrelationId() ``` ## Configuration Examples ### Minimal Setup (Console Only) ```csharp services.AddEonaCatLogging(b => b.WriteToConsole()); ``` ### Development Environment ```csharp services.AddEonaCatLogging(builder => { builder .WithMinimumLevel(LogLevel.Debug) .WriteToConsole(useColors: true) .WriteToFile("./logs") .BoostWithMachineName() .BoostWithThreadId(); }); ``` ### Production Environment ```csharp services.AddEonaCatLogging("ProductionApp", builder => { builder .WithMinimumLevel(LogLevel.Information) .WriteToFile("./logs", minimumLevel: LogLevel.Information) .WriteToElasticSearch("https://elastic.company.com") .WriteToSlack("https://hooks.slack.com/...") .BoostWithCorrelationId() .BoostWithMachineName() .BoostWithUser(); }); ``` ## OpenTelemetry-style Telemetry EonaCat.LogStack includes a dependency-free telemetry layer inspired by OpenTelemetry concepts. ### Distributed tracing ```csharp var source = new TelemetryActivitySource(); using var span = source.StartScope("ProcessOrder", telemetry => { telemetry.Attributes["order.id"] = "123"; }); ``` Supported tracing concepts: - Trace IDs and Span IDs - Parent span relationships - Span duration measurement - Span attributes - Span events - Status tracking ### Metrics ```csharp var meter = new TelemetryMeter(); meter.Count("requests.total"); meter.Record("request.duration", 25.5); var p95 = meter.GetHistogram("request.duration")?.Percentile(0.95); ``` Telemetry features: - Counters - Histograms - Percentiles - Runtime snapshots - Resource attributes - Baggage/context propagation - HTTP telemetry ingestion ### Resource metadata ```csharp var resource = new TelemetryResource() .Add("service.name", "OrderService") .Add("deployment.environment", "production"); ``` ## Diagnostics ```csharp public class DiagnosticsService { private readonly ILoggerFactory _loggerFactory; public DiagnosticsService(ILoggerFactory loggerFactory) { _loggerFactory = loggerFactory; } public void PrintDiagnostics() { var diagnostics = _loggerFactory.GetDiagnostics(); Console.WriteLine($"Total Logged: {diagnostics.TotalLogged}"); Console.WriteLine($"Total Dropped: {diagnostics.TotalDropped}"); Console.WriteLine($"Total Exceptions: {diagnostics.TotalExceptions}"); } } ``` ## Statistics & Metrics EonaCat.LogStack provides comprehensive statistics and metrics tracking for monitoring and optimizing your logging infrastructure. ### Per-Level Metrics Track events logged at each level: ```csharp var metrics = loggerFactory.GetMetrics(); Console.WriteLine($"Trace: {metrics.TraceCount}"); Console.WriteLine($"Debug: {metrics.DebugCount}"); Console.WriteLine($"Information: {metrics.InformationCount}"); Console.WriteLine($"Warning: {metrics.WarningCount}"); Console.WriteLine($"Error: {metrics.ErrorCount}"); Console.WriteLine($"Critical: {metrics.CriticalCount}"); ``` ### Performance Metrics Monitor throughput and latency: ```csharp var metrics = loggerFactory.GetMetrics(); Console.WriteLine($"Events/Second: {metrics.WritesPerSecond:F2}"); Console.WriteLine($"Total Bytes: {metrics.TotalBytes:N0}"); Console.WriteLine($"Avg Bytes/Event: {metrics.AverageBytesPerEvent:F2}"); Console.WriteLine($"Success Rate: {metrics.SuccessRate:F2}%"); Console.WriteLine($"Uptime: {TimeSpan.FromMilliseconds(metrics.UptimeMilliseconds):hh\\:mm\\:ss}"); ``` ### Exception Tracking Monitor exceptions logged: ```csharp var metrics = loggerFactory.GetMetrics(); Console.WriteLine($"Total Exceptions: {metrics.TotalExceptions}"); Console.WriteLine($"Exceptions in Errors: {metrics.ErrorCount} errors logged"); ``` ### Flow-Specific Statistics Each flow tracks its own performance: ```csharp var metrics = loggerFactory.GetMetrics(); foreach (var flow in metrics.FlowMetrics) { Console.WriteLine($"{flow.FlowName} ({flow.FlowType}):"); Console.WriteLine($" Processed: {flow.EventsProcessed}"); Console.WriteLine($" Failed: {flow.EventsFailed}"); Console.WriteLine($" Success Rate: {flow.SuccessRate:F2}%"); Console.WriteLine($" Events/Second: {flow.EventsPerSecond:F2}"); Console.WriteLine($" P95 Latency: {flow.P95LatencyMs:F3}ms"); Console.WriteLine($" P99 Latency: {flow.P99LatencyMs:F3}ms"); } ``` ### System-Wide Metrics Collection Use `AdvancedMetricsCollector` to aggregate metrics across multiple loggers: ```csharp var collector = app.Services.GetRequiredService(); // Register loggers for collection var logStack = app.Services.GetRequiredService(); collector.RegisterLogger(logStack); // Get aggregated metrics var aggregated = collector.GetAggregatedMetrics(); Console.WriteLine($"Total Logged (All): {aggregated.TotalLoggedAcrossAll:N0}"); Console.WriteLine($"Overall Success Rate: {aggregated.OverallSuccessRate:F2}%"); // Get performance comparison var comparison = collector.GetPerformanceComparison(); Console.WriteLine($"Highest Throughput: {comparison.HighestThroughputLogger?.WritesPerSecond:F2} events/sec"); Console.WriteLine($"Average Throughput: {comparison.AverageWritesPerSecond:F2} events/sec"); // Get health report var health = collector.GetHealthReport(); Console.WriteLine($"System Status: {health.OverallStatus}"); foreach (var warning in health.Warnings) Console.WriteLine($" ⚠️ {warning}"); ``` ## Dependency Injection Integration EonaCat.LogStack seamlessly integrates with Microsoft Dependency Injection for ASP.NET Core and other .NET applications. ### Basic Registration ```csharp var builder = Host.CreateApplicationBuilder(); builder.Services.AddEonaCatLogging(); ``` This registers: - `ILoggerFactory` (EonaCat interface) - `Microsoft.Extensions.Logging.ILoggerFactory` (standard interface) - `ILogger` (EonaCat interface) - `Microsoft.Extensions.Logging.ILogger` (standard interface) ### With Fluent Configuration ```csharp var builder = Host.CreateApplicationBuilder(); builder.Services.AddEonaCatLogging(b => { b.WithMinimumLevel(LogLevel.Information) .WriteToConsole() .WriteToFile("./logs") .BoostWithCorrelationId(); }); ``` ### With HostApplicationBuilder (Recommended) ```csharp var builder = Host.CreateApplicationBuilder(); builder.AddEonaCatLogging(b => { b.WithMinimumLevel(LogLevel.Information) .WriteToConsole() .WriteToFile("./logs"); }); var app = builder.Build(); ``` ### Advanced Features with AdvancedLoggerFactory For category-specific configuration and context propagation: ```csharp var builder = Host.CreateApplicationBuilder(); builder.Services.AddAdvancedEonaCatLogging(factory => { factory.ConfigureCategory("Database", config => { config.MinimumLevel = LogLevel.Debug; config.Boosters.Add(new CorrelationIdBooster()); }); factory.ConfigureCategory("Security", config => { config.MinimumLevel = LogLevel.Warning; }); }); var app = builder.Build(); // Get loggers var dbLogger = app.Services.GetRequiredService().CreateLogger("Database"); var secLogger = app.Services.GetRequiredService().CreateLogger("Security"); ``` ### With Metrics Collection ```csharp var builder = Host.CreateApplicationBuilder(); builder.AddEonaCatLogging(b => { b.WithMinimumLevel(LogLevel.Information) .WriteToConsole() .WriteToFile("./logs"); }); builder.AddEonaCatMetricsCollection(); var app = builder.Build(); // Access metrics var collector = app.Services.GetRequiredService(); var metrics = collector.GetAggregatedMetrics(); Console.WriteLine(metrics); ``` ### Using Microsoft.Extensions.Logging The registered adapters allow you to use standard Microsoft logging interfaces: ```csharp [ApiController] [Route("api/[controller]")] public class UserController : ControllerBase { private readonly Microsoft.Extensions.Logging.ILogger _logger; public UserController(Microsoft.Extensions.Logging.ILogger logger) { _logger = logger; } [HttpGet("{id}")] public async Task GetUser(int id) { _logger.LogInformation("Fetching user {UserId}", id); // ... } } ``` The request is automatically routed through EonaCat.LogStack flows (file, console, Slack, etc.). ### Context Propagation With AdvancedLoggerFactory, you can propagate context across async operations: ```csharp var factory = app.Services.GetRequiredService(); // Set context in request scope factory.SetContextData("RequestId", context.TraceIdentifier); factory.SetContextData("UserId", user.Id); // Logger can access this context var logger = factory.CreateLogger("MyCategory"); logger.Log(LogLevel.Information, "Processing request"); // Context is included automatically // Clear context when done factory.ClearContextData(); ``` ## Disposing of the Logger The logger is registered as a Singleton in the DI container, so it will be automatically disposed when the application shuts down. You can also manually access and dispose it: ```csharp var loggerFactory = app.Services.GetRequiredService(); await loggerFactory.DisposeAsync(); ``` ## Telemetry Enhancements The telemetry subsystem provides production observability features: - **Runtime health snapshots** - expose processed events, failures, active spans, uptime and custom health checks. - **Telemetry exporters** - plug in exporters for Prometheus, OpenTelemetry collectors, dashboards or custom monitoring platforms. - **Telemetry snapshots** - collect metrics, resource information and health state into a single export model. - **Custom instrumentation support** - extend telemetry with application-specific counters and tracing data. Example: ```csharp var snapshot = new TelemetrySnapshot { Metrics = logger.GetMetrics(), Health = new TelemetryHealth { IsHealthy = true, Checks = { ["storage"] = "ok" } } }; await exporter.ExportAsync(snapshot); ``` ## Telemetry EonaCat.LogStack includes an enhanced telemetry layer focused on operational intelligence: - **Adaptive telemetry engine** - dynamically tracks signals and adjusts analysis based on runtime behavior. - **Built-in anomaly detection** - detects unusual latency, throughput, and value deviations without external analysis systems. - **Zero-dependency telemetry snapshots** - capture complete runtime state without requiring a collector. - **Signal intelligence** - calculates count, min, max, averages, and runtime trends for every signal. - **Telemetry-first logging correlation** - combines logs, metrics, traces, and context into a single pipeline. - **Runtime diagnostics** - exposes health, performance, and flow statistics from the same engine. - **Self-contained observability** - designed to work without mandatory agents or external telemetry infrastructure. Example: ```csharp var telemetry = new AdaptiveTelemetryEngine(); telemetry.Record("request.duration.ms", 42); telemetry.Record("cache.hit.rate", 0.98); var snapshot = telemetry.Snapshot(); ``` --- # Additional Usage Guide This section expands on practical usage patterns, production configuration, telemetry, diagnostics, and operational guidance. ## Complete Application Startup Example A typical production application can configure logging once during startup and reuse the same logger instance: ```csharp await using var logger = new LogBuilder("InventoryService") .WithMinimumLevel(LogLevel.Information) .WithTimestampMode(TimestampMode.Utc) .WriteToConsole(useColors: true) .WriteToFile( directory: "./logs", filePrefix: "inventory", maxFileSize: 250 * 1024 * 1024, compression: CompressionFormat.GZip, batchSize: 100) .WriteToElasticSearch( elasticSearchUrl: "https://elastic.example.com:9200", indexName: "inventory") .BoostWithApplication("InventoryService", "3.0.0") .BoostWithEnvironment("Production") .BoostWithMachineName() .BoostWithProcessId() .BoostWithCorrelationId() .Build(); logger.Information("Service started"); await RunApplicationAsync(); await logger.FlushAsync(); ``` ## Telemetry Explained Telemetry in EonaCat.LogStack provides operational insight into the logging pipeline itself. Telemetry can answer questions such as: - How many log events were created? - Are any events being dropped? - Which flow is slow? - Are remote sinks failing? - How much memory is the process using? - How long are operations taking? ### Enable Telemetry ```csharp var telemetry = new TelemetryOptions { Enabled = true, IncludeRuntimeMetrics = true, IncludeTraceMetrics = true }; ``` ### Runtime Metrics Runtime metrics include information such as: - Process uptime - Memory usage - Garbage collection information - Thread information - CPU/runtime counters where available Example: ```csharp var snapshot = logger.GetTelemetrySnapshot(); Console.WriteLine(snapshot.Runtime.ProcessId); Console.WriteLine(snapshot.Runtime.MemoryUsage); Console.WriteLine(snapshot.Runtime.Uptime); ``` ## Flow Metrics Every flow maintains statistics. Example: ```csharp var diagnostics = logger.GetDiagnostics(); foreach (var flow in diagnostics.FlowStats) { Console.WriteLine(flow.Name); Console.WriteLine($"Processed: {flow.Processed}"); Console.WriteLine($"Dropped: {flow.Dropped}"); Console.WriteLine($"Errors: {flow.ErrorCount}"); } ``` Useful production alerts: ```csharp if (diagnostics.TotalDropped > 0) { logger.Warning( "Logs are being dropped", ("Dropped", diagnostics.TotalDropped)); } ``` ## Distributed Tracing Correlation IDs connect logs from the same request or operation. Enable it: ```csharp new LogBuilder("Api") .BoostWithCorrelationId() .Build(); ``` A request might then produce: ``` TraceId=abc123 Request started TraceId=abc123 Database query executed TraceId=abc123 Request completed ``` This makes debugging distributed applications easier. ## Custom Telemetry Export Telemetry snapshots can be forwarded to custom monitoring systems. Example: ```csharp var snapshot = logger.GetTelemetrySnapshot(); SendToMonitoringSystem(new { Service = snapshot.ServiceName, Environment = snapshot.Environment, Metrics = snapshot.Metrics }); ``` ## Logging HTTP Requests Example request logging: ```csharp logger.Information( "HTTP request completed", ("Method", "GET"), ("Path", "/api/products"), ("StatusCode", 200), ("DurationMs", 42)); ``` Output: ``` HTTP request completed Method=GET Path=/api/products StatusCode=200 DurationMs=42 ``` ## Recommended Production Layout A common production setup: ``` Application | +--> Console (developer visibility) | +--> Local File (fallback) | +--> Elasticsearch (search) | +--> Audit Flow (compliance) | +--> Slack/Teams (alerts) | +--> Diagnostics (monitoring) ``` Example: ```csharp await using var logger = new LogBuilder("Payments") .WriteToConsole() .WriteToFile("./logs") .WriteToAudit( directory: "./audit", auditLevel: AuditLevel.WarningAndAbove) .WriteToThrottled( inner: new SlackFlow(slackUrl), minimumLevel: LogLevel.Error) .WriteDiagnostics( snapshotInterval: TimeSpan.FromMinutes(1)) .BoostWithCorrelationId() .Build(); ``` ## Shutdown Handling Always flush buffered logs during shutdown: ```csharp try { await Application.RunAsync(); } finally { await logger.FlushAsync(); await logger.DisposeAsync(); } ``` ## 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")); } ``` ## Debugging Logging Problems Check diagnostics first: ```csharp var stats = logger.GetDiagnostics(); Console.WriteLine( $"Written: {stats.TotalLogged}"); Console.WriteLine( $"Dropped: {stats.TotalDropped}"); ``` If events are missing: 1. Check minimum log levels. 2. Check flow filters. 3. Check dropped counters. 4. Check remote destination connectivity. 5. Enable retry/failover. ---