# EonaCat.LogStack **EonaCat.LogStack** flow-based logging library for .NET, designed for zero-allocation logging paths and superior memory efficiency. It features a rich fluent API for routing log events to dozens of destinations — from console and file to Slack, Discord, Redis, Elasticsearch, and beyond. --- ## Features - **Flow-based architecture** — route log events to one or many output destinations simultaneously - **Booster system** — enrich every log event with contextual metadata (machine name, process ID, thread info, memory, uptime, correlation IDs, and more) - **Pre-build modifiers** — intercept and mutate log events before they are written - **Zero-allocation hot path** — `AggressiveInlining` throughout, `StringBuilder` pooling, and `ref`-based builder pattern - **Async-first** — all flows implement `IAsyncDisposable` and `FlushAsync` - **Resilience built-in** — retry, failover, throttling, and rolling buffer flows - **Tamper-evident audit trail** — SHA-256 hash-chained audit files - **Encrypted file logging** — AES-encrypted log files with a built-in decrypt utility - **Compression** — GZip-compressed rolled log files - **Category routing** — split logs into separate files per category or log level - **Diagnostics** — live counters (total logged, total dropped, per-flow stats) --- ## Supported Targets - .NET Standard 2.1 - .NET 8.0 - .NET Framework 4.8 --- ## Installation ```bash dotnet add package EonaCat.LogStack ``` --- ## Quick Start ```csharp await using var logger = LogBuilder.CreateDefault("MyApp"); logger.Information("Application started"); logger.Warning("Low memory warning"); logger.Error(ex, "Unexpected error occurred"); ``` `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 customised 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(); ``` --- ## Logging Methods ```csharp logger.Trace("Verbose trace message"); logger.Debug("Debug detail"); logger.Information("Something happened"); logger.Warning("Potential problem"); logger.Warning(ex, "Warning with exception"); logger.Error("Something failed"); logger.Error(ex, "Error with exception"); logger.Critical("System is going down"); logger.Critical(ex, "Critical failure"); // With structured properties logger.Log(LogLevel.Information, "User logged in", ("UserId", 42), ("IP", "192.168.1.1")); ``` --- ## Available Flows ### Flows can be extended with custom implementations of `IFlow`, but here are the built-in options: | Flow | Method | Description | |------|--------|-------------| | Console | `WriteToConsole()` | Colored console output | | File | `WriteToFile()` | Batched, rotated, compressed file output | | Encrypted File | `WriteToEncryptedFile()` | AES-encrypted log files | | Memory | `WriteToMemory()` | In-memory ring buffer | | Audit | `WriteToAudit()` | Tamper-evident hash-chained audit trail | | Database | `WriteToDatabase()` | ADO.NET database sink | | HTTP | `WriteToHttp()` | Generic HTTP endpoint (batched) | | Webhook | `WriteToWebhook()` | Generic webhook POST | | Email | `WriteToEmail()` | HTML digest emails via SMTP | | Slack | `WriteToSlack()` | Slack incoming webhooks | | Discord | `WriteToDiscord()` | Discord webhooks | | Microsoft Teams | `WriteToMicrosoftTeams()` | Teams incoming webhooks | | Telegram | `WriteToTelegram()` | Telegram bot messages | | SignalR | `WriteToSignalR()` | Real-time SignalR hub push | | Redis | `RedisFlow()` | Redis Pub/Sub + optional List persistence | | Elasticsearch | `WriteToElasticSearch()` | Elasticsearch index | | Splunk | `WriteToSplunkFlow()` | Splunk HEC | | 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) | | UDP | `WriteToUdp()` | Raw UDP datagrams | | SNMP Trap | `WriteToSnmpTrap()` | SNMP v2c traps | | Zabbix | `WriteToZabbixFlow()` | Zabbix trapper protocol | | EventLog | `WriteToEventLogFlow()` | Remote event log forwarding | | Rolling Buffer | `WriteToRollingBuffer()` | Circular buffer with trigger-based flush | | Throttled | `WriteToThrottled()` | Token-bucket rate limiting + deduplication | | Retry | `WriteToRetry()` | Automatic retry with exponential back-off | | Failover | `WriteToFailover()` | Primary/secondary failover | | Diagnostics | `WriteDiagnostics()` | Periodic diagnostic snapshots | | Status | `WriteToStatusFlow()` | Service health monitoring | --- ## Available Boosters Boosters enrich every log event with additional properties before it reaches any flow. ```csharp new LogBuilder("MyApp") .BoostWithMachineName() // host name .BoostWithProcessId() // PID .BoostWithThreadId() // managed thread ID .BoostWithThreadName() // thread name .BoostWithUser() // current OS user .BoostWithApp() // app name and base directory .BoostWithApplication("MyApp", "2.0.0") // explicit name + version .BoostWithEnvironment("Production") .BoostWithOS() // OS description .BoostWithFramework() // .NET runtime description .BoostWithMemory() // working set in MB .BoostWithUptime() // process uptime in seconds .BoostWithProcStart() // process start time .BoostWithDate() // current date (yyyy-MM-dd) .BoostWithTime() // current time (HH:mm:ss.fff) .BoostWithTicks() // current timestamp ticks .BoostWithCorrelationId() // Activity.Current correlation ID .BoostWithCustomText("env", "prod") // arbitrary key/value .Boost("myBooster", () => new Dictionary { ["key"] = "val" }) ... ``` --- ## Pre-Build Modifiers Modifiers run after boosters and can mutate or cancel a log event before it is dispatched to flows: ```csharp logger.AddModifier((ref LogEventBuilder builder) => { builder.WithProperty("RequestId", Guid.NewGuid().ToString()); }); ``` --- ## Resilience Patterns ### Retry with exponential back-off ```csharp .WriteToRetry( primaryFlow: new HttpFlow("https://logs.example.com"), maxRetries: 5, initialDelay: TimeSpan.FromMilliseconds(200), exponentialBackoff: true) ``` ### Primary / secondary failover ```csharp .WriteToFailover( primaryFlow: new ElasticSearchFlow("https://es-prod:9200"), secondaryFlow: new FileFlow("./fallback-logs")) ``` ### Token-bucket throttling with deduplication ```csharp .WriteToThrottled( inner: new SlackFlow(webhookUrl), burstCapacity: 10, refillPerSecond: 1.0, deduplicate: true, dedupWindow: TimeSpan.FromSeconds(60)) ``` ### Rolling buffer — flush context on error ```csharp .WriteToRollingBuffer( capacity: 500, triggerLevel: LogLevel.Error, triggerTarget: new FileFlow("./error-context")) ``` --- ## Encrypted File Logging ```csharp .WriteToEncryptedFile("./secure-logs", password: "s3cr3t") ``` To decrypt later: ```csharp LogBuilder.DecryptFile( encryptedPath: "./secure-logs/log.enc", outputPath: "./secure-logs/log.txt", password: "s3cr3t"); ``` --- ## Audit Trail 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. ```csharp .WriteToAudit( directory: "./audit", auditLevel: AuditLevel.WarningAndAbove, includeProperties: true) ``` Verify integrity at any time: ```csharp bool intact = AuditFlow.Verify("./audit/audit.audit"); ``` --- ## Log Message Template Both `ConsoleFlow` and `FileFlow` accept a customisable template string: ``` [{ts}] [{tz}] [Host: {host}] [Category: {category}] [Thread: {thread}] [{logtype}] {message}{props} ``` | Token | Description | |-------|-------------| | `{ts}` | Timestamp (yyyy-MM-dd HH:mm:ss.fff) | | `{tz}` | Timezone (UTC or local name) | | `{host}` | Machine name | | `{category}` | Logger category | | `{thread}` | Managed thread ID | | `{pid}` | Process ID | | `{logtype}` | Log level label (INFO, WARN, ERROR, …) | | `{message}` | Log message text | | `{props}` | Structured properties as key=value pairs | | `{newline}` | Line break | --- ## Diagnostics ```csharp var diag = logger.GetDiagnostics(); Console.WriteLine($"Logged: {diag.TotalLogged}, Dropped: {diag.TotalDropped}"); ``` --- ## Flushing and Disposal ```csharp // Flush all pending events await logger.FlushAsync(); // Dispose (flushes automatically) await logger.DisposeAsync(); ``` --- ## Events ```csharp logger.OnLog += (sender, msg) => { // Fired for every log event that passes filters Console.WriteLine($"[Event] {msg.Level}: {msg.Message}"); }; ``` --- ## Custom Flows Implement `IFlow` (or extend `FlowBase`) to create your own destination: ```csharp public class MyFlow : FlowBase { public MyFlow() : base("MyFlow", LogLevel.Trace) { } public override Task BlastAsync(LogEvent logEvent, CancellationToken ct = default) { // Write logEvent somewhere return Task.FromResult(WriteResult.Success); } public override Task FlushAsync(CancellationToken ct = default) => Task.CompletedTask; } // Register with: new LogBuilder("App").WriteTo(new MyFlow()).Build(); ```