EonaCat.LogStack
0.0.9
Prefix Reserved
See the version list below for details.
dotnet add package EonaCat.LogStack --version 0.0.9
NuGet\Install-Package EonaCat.LogStack -Version 0.0.9
<PackageReference Include="EonaCat.LogStack" Version="0.0.9" />
<PackageVersion Include="EonaCat.LogStack" Version="0.0.9" />
<PackageReference Include="EonaCat.LogStack" />
paket add EonaCat.LogStack --version 0.0.9
#r "nuget: EonaCat.LogStack, 0.0.9"
#:package EonaCat.LogStack@0.0.9
#addin nuget:?package=EonaCat.LogStack&version=0.0.9
#tool nuget:?package=EonaCat.LogStack&version=0.0.9
EonaCat.LogStack
EonaCat.LogStack is a flow-based, high-performance logging library for .NET, designed for zero-allocation logging paths and superior memory efficiency. It features a rich fluent API for routing log events to dozens of destinations - from console and file to Slack, Discord, Redis, Elasticsearch, and beyond.
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 -
AggressiveInliningthroughout,StringBuilderpooling, andref-based builder pattern for minimal GC pressure during logging. - Async-first design - All flows implement
IAsyncDisposableandFlushAsync()for proper async resource cleanup and batching.
Advanced Features
- Resilience patterns - Built-in
RetryFlowwith exponential backoff,FailoverFlowfor primary/secondary failover, andThrottledFlowfor 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.
Supported Targets
- .NET Standard 2.1
- .NET 8.0
- .NET 9.0
- .NET 10.0
- .NET Framework 4.8
Installation
dotnet add package EonaCat.LogStack
Quick Start
Minimal - Console + File
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:
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
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
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
// 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
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 |
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
// 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
// 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
// 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
// Store last 1000 events in memory
.WriteToMemory(capacity: 1000, minimumLevel: LogLevel.Information)
// Access stored events
var memoryFlow = logger.GetFlowOfType<MemoryFlow>();
var events = memoryFlow.GetEvents();
Slack
.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
.WriteToDiscord(
webHookUrl: "https://discordapp.com/api/webhooks/YOUR/WEBHOOK",
botName: "ErrorBot")
Email Digest
.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.)
.WriteToDatabase(
connectionFactory: () => new SqlConnection("connection-string"),
tableName: "ApplicationLogs",
batchSize: 10)
Elasticsearch
.WriteToElasticSearch(
elasticSearchUrl: "https://elasticsearch.company.com:9200",
indexName: "myapp-logs",
batchSize: 100)
Redis (Pub/Sub + List)
// 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
// 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
.WriteToHttp(
endpoint: "https://logs.company.com/ingest",
batchSize: 50,
batchInterval: TimeSpan.FromSeconds(2),
headers: new Dictionary<string, string>
{
["Authorization"] = "Bearer token123",
["X-API-Key"] = "secret"
})
Audit Trail (Tamper-Evident)
// 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)
// 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
.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
.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
.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
// Single key/value pair
.BoostWithCustomText("Environment", "Production")
.BoostWithCustomText("ServiceVersion", "2.0.1")
// Callback-based booster for dynamic data
.Boost("RequestInfo", () => new Dictionary<string, object?>
{
["UserId"] = GetCurrentUserId(),
["TenantId"] = GetCurrentTenantId(),
["ApiVersion"] = GetApiVersion()
})
// Custom booster implementation
.Boost(new MyCustomBooster())
Complete Booster Configuration Example
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<string, object?>
{
["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:
{
"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
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
// 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
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)
// 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.
.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.
.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.
.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:
10log 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.
.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.
.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
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
.WriteToEncryptedFile(
directory: "./secure-logs",
filePrefix: "encrypted",
password: "YourStrongPassword123!",
maxFileSize: 50 * 1024 * 1024)
Decrypting Logs
// 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
.WriteToAudit(
directory: "./audit",
filePrefix: "audit",
auditLevel: AuditLevel.WarningAndAbove, // Warning, Error, Critical
includeProperties: true)
Verification
// 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
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
// 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:
logger.Information("User {User} logged in from {IP}", user, ipAddress);
Nested Property Access
Access properties of objects using dot notation:
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:
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:
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:
// 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:
// 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:
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:
// 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:
// 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:
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:
// 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:
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:
// 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:
// 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
// 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:
// 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:
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
// 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.
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.
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.
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.
// 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.
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.
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();
Diagnostics
Real-Time Metrics
// 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.
.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<string, object>
{
["ActiveRequests"] = GetActiveRequestCount(),
["CacheHitRate"] = GetCacheHitRate(),
["DatabasePoolSize"] = GetDbPoolSize()
})
Monitoring Health
// 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
// 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:
await using var logger = new LogBuilder("MyApp")
.WriteToFile("./logs")
.Build();
logger.Information("Application running");
// At scope exit: FlushAsync() called automatically, then DisposeAsync()
Best Practices
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
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
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
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
// 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
public class MyCustomFlow : FlowBase
{
public MyCustomFlow() : base("MyCustomFlow", LogLevel.Trace) { }
public override Task<WriteResult> 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
public class BatchedCustomFlow : FlowBase
{
private readonly List<LogEvent> _batch = new(100);
private readonly object _lock = new();
public BatchedCustomFlow() : base("BatchedCustom", LogLevel.Trace) { }
public override Task<WriteResult> 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<WriteResult> 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
public class RetryableCustomFlow : FlowBase
{
private const int MaxRetries = 3;
public RetryableCustomFlow() : base("RetryableCustom", LogLevel.Trace) { }
public override async Task<WriteResult> 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
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
// 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
// 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
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
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
.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
var host = builder.Build();
// Graceful shutdown: flush logs before exit
var lifetime = host.Services.GetRequiredService<IHostApplicationLifetime>();
lifetime.ApplicationStopping.Register(async () =>
{
var logger = host.Services.GetRequiredService<ILogger>();
await logger.FlushAsync();
});
await host.RunAsync();
8. Monitor Drop Rates
_ = 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
// 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
.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)
.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
.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
// 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:
- Verify endpoint connectivity:
telnet host port - Check authentication (API keys, certificates)
- Enable retry and failover patterns
- Ensure events pass level filters
.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
maxFileSizeandmaxDirectorySize - Enable compression
- Set retention policy
.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
// 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
{ "level": "info", "message": "Hello world", "source": "MyApp", "host": "srv-01" }
POST /ingest - batch
[
{ "level": "warn", "message": "Disk at 80%", "source": "monitor" },
{ "level": "error", "message": "DB timeout", "source": "api", "exception": "TimeoutException…" }
]
GET /metrics response
{
"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
MinimumLevel = ServerLogLevel.Warning // only Warning / Error / Critical are stored
Levels in order: Trace → Debug → Information → Warning → Error → Critical
Rate limiting
RateLimitPerSecond = 100 // per remote IP:port, 0 = disabled
Dropped messages are counted in Metrics.TotalDropped and raise the LogDropped event.
Metrics
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
server.LogWritten += line => NotifyDashboard(line);
server.LogDropped += line => Metrics.Increment("dropped");
Graceful shutdown
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:
services.AddEonaCatLogging();
This registers:
ILoggerFactory- For creating category-specific loggersMicrosoft.Extensions.Logging.ILoggerFactory- For Microsoft.Extensions.Logging compatibilityILogger- For injecting the default logger
2. Registration with Log Level and Timestamp Mode
services.AddEonaCatLogging(
minimumLevel: LogLevel.Information,
timestampMode: TimestampMode.Local);
3. Registration with Pre-built EonaCatLogStack
If you've already created an EonaCatLogStack instance:
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<EonaCatLogStack>:
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:
services.AddEonaCatLogging("MyApplication", builder =>
{
builder
.WithMinimumLevel(LogLevel.Information)
.WithTimestampMode(TimestampMode.Local)
.WriteToConsole()
.WriteToFile("./logs")
.BoostWithMachineName()
.BoostWithProcessId()
.BoostWithCorrelationId();
});
Or with the default "Application" category:
services.AddEonaCatLogging(builder =>
{
builder
.WriteToConsole()
.WriteToFile("./logs");
});
6. Registration with Factory Method (Advanced)
For advanced scenarios where you need access to the service provider:
services.AddEonaCatLoggingFactory("MyApplication", builder =>
{
builder
.WriteToConsole()
.WriteToFile("./logs");
});
Using in Your Application
Injecting ILoggerFactory
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
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
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:
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
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)
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)
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
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
Use LogBuilder for Configuration: The fluent LogBuilder API is the most readable and maintainable approach.
Register Early: Register logging in
Program.csbefore other services that depend on logging.Use Appropriate Log Levels:
Trace- Very detailed diagnostic infoDebug- Debug-level diagnostic infoInformation- General informational messagesWarning- Warning messagesError- Error messagesCritical- Critical failures
Inject Specific Types: Prefer injecting
ILoggerFactoryto create category-specific loggers rather than injecting a single shared logger.Use Categories: Create loggers with meaningful category names:
var logger = loggerFactory.CreateLogger("MyApp.Services.UserService");Enable Correlation IDs: For distributed tracing scenarios:
builder.BoostWithCorrelationId()
Configuration Examples
Minimal Setup (Console Only)
services.AddEonaCatLogging(b => b.WriteToConsole());
Development Environment
services.AddEonaCatLogging(builder =>
{
builder
.WithMinimumLevel(LogLevel.Debug)
.WriteToConsole(useColors: true)
.WriteToFile("./logs")
.BoostWithMachineName()
.BoostWithThreadId();
});
Production Environment
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();
});
Diagnostics
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:
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:
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:
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:
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:
var collector = app.Services.GetRequiredService<AdvancedMetricsCollector>();
// Register loggers for collection
var logStack = app.Services.GetRequiredService<EonaCatLogStack>();
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
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
var builder = Host.CreateApplicationBuilder();
builder.Services.AddEonaCatLogging(b =>
{
b.WithMinimumLevel(LogLevel.Information)
.WriteToConsole()
.WriteToFile("./logs")
.BoostWithCorrelationId();
});
With HostApplicationBuilder (Recommended)
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:
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<ILoggerFactory>().CreateLogger("Database");
var secLogger = app.Services.GetRequiredService<ILoggerFactory>().CreateLogger("Security");
With Metrics Collection
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<AdvancedMetricsCollector>();
var metrics = collector.GetAggregatedMetrics();
Console.WriteLine(metrics);
Using Microsoft.Extensions.Logging
The registered adapters allow you to use standard Microsoft logging interfaces:
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
private readonly Microsoft.Extensions.Logging.ILogger<UserController> _logger;
public UserController(Microsoft.Extensions.Logging.ILogger<UserController> logger)
{
_logger = logger;
}
[HttpGet("{id}")]
public async Task<IActionResult> 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:
var factory = app.Services.GetRequiredService<AdvancedLoggerFactory>();
// 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:
var loggerFactory = app.Services.GetRequiredService<ILoggerFactory>();
await loggerFactory.DisposeAsync();
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| .NET Framework | net48 is compatible. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETFramework 4.8
- EonaCat.Json (>= 2.2.3)
- EonaCat.Versioning.Helpers (>= 1.5.0)
- Microsoft.CSharp (>= 4.7.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.9)
- Microsoft.Extensions.Hosting (>= 10.0.9)
- Microsoft.Extensions.Logging (>= 10.0.9)
- Microsoft.Extensions.Logging.Console (>= 10.0.9)
- System.Net.Http (>= 4.3.4)
- System.Threading.Channels (>= 10.0.9)
-
.NETStandard 2.1
- EonaCat.Json (>= 2.2.3)
- EonaCat.Versioning.Helpers (>= 1.5.0)
- Microsoft.CSharp (>= 4.7.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.9)
- Microsoft.Extensions.Hosting (>= 10.0.9)
- Microsoft.Extensions.Logging (>= 10.0.9)
- Microsoft.Extensions.Logging.Console (>= 10.0.9)
- System.Net.Http (>= 4.3.4)
- System.Threading.Channels (>= 10.0.9)
-
net8.0
- EonaCat.Json (>= 2.2.3)
- EonaCat.Versioning.Helpers (>= 1.5.0)
- Microsoft.CSharp (>= 4.7.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.9)
- Microsoft.Extensions.Hosting (>= 10.0.9)
- Microsoft.Extensions.Logging (>= 10.0.9)
- Microsoft.Extensions.Logging.Console (>= 10.0.9)
- System.Net.Http (>= 4.3.4)
- System.Threading.Channels (>= 10.0.9)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on EonaCat.LogStack:
| Package | Downloads |
|---|---|
|
EonaCat.LogStack.OpenTelemetryFlow
EonaCat OpenTelemetry Flow for LogStack |
|
|
EonaCat.LogStack.Flows.WindowsEventLog
EonaCat Windows EventLog Flow for LogStack |
GitHub repositories
This package is not used by any popular GitHub repositories.
Public release version