Structured logging infrastructure with Serilog for the Clywell platform. Provides log enrichers, configuration helpers, and performance-optimized logging utilities with zero business logic - can be used in any .NET application.
✅ Serilog Integration - Fluent configuration for Serilog with best practices
✅ Correlation & Request Tracking - Automatic correlation IDs and request IDs for distributed tracing
✅ Performance Optimized - IsEnabled() checks prevent unnecessary string allocations
✅ Sensitive Data Redaction - Automatic redaction of passwords, credit cards, API keys
✅ Multiple Sinks - Console, File, Seq, Application Insights
✅ Environment Enrichers - Machine name, thread info, custom properties
✅ Execution Time Logging - Built-in helpers for measuring operation duration
✅ ASP.NET Core Middleware - Track correlation IDs and request IDs across HTTP requests
✅ .NET 10.0+ - Modern C# features and latest language version
✅ 80%+ Test Coverage - Comprehensive unit tests with FluentAssertions
dotnet add package Clywell.Core.Loggingusing Clywell.Core.Logging.Extensions;
var builder = WebApplication.CreateBuilder(args);
// Add Clywell logging with defaults
builder.AddClywellLogging();
var app = builder.Build();
// Add correlation ID and request ID tracking
app.UseClywellRequestTracking();
// Add Serilog request logging
app.UseClywellRequestLogging();
app.MapGet("/", (ILogger<Program> logger) =>
{
logger.LogInformation("Hello from Clywell logging!");
return "Hello World!";
});
app.Run();builder.AddClywellLogging(config =>
{
config
.WithMinimumLevel(LogEventLevel.Debug)
.WithConsoleSink(useJson: true)
.WithFileSink("logs/app-.txt")
.WithSeqSink("http://localhost:5341")
.WithApplicationInsightsSink()
.WithClywellDefaults(); // Adds all enrichers
});using Clywell.Core.Logging.Configuration;
using Serilog;
var logger = ClywellLoggerConfiguration.Create()
.WithMinimumLevel(LogEventLevel.Information)
.WithConsoleSink()
.WithCorrelationId()
.WithSensitiveDataRedaction()
.Build();
Log.Logger = logger;
Log.Information("Application started");Adds a correlation ID to every log entry for distributed tracing:
using Clywell.Core.Logging.Enrichers;
// Set correlation ID manually
CorrelationIdEnricher.CurrentCorrelationId = "custom-correlation-id";
logger.LogInformation("This log will have the correlation ID");
// Or use middleware (automatic)
app.UseClywellRequestTracking();Adds a unique request ID to every log entry:
using Clywell.Core.Logging.Enrichers;
// Set request ID manually
RequestIdEnricher.CurrentRequestId = "custom-request-id";
logger.LogInformation("This log will have the request ID");
// Or use middleware (automatic)
app.UseClywellRequestTracking();Automatically redacts sensitive data from logs:
logger.LogInformation("User password: {Password}", "mySecret123");
// Output: User password: ***REDACTED***
logger.LogInformation("Card: {CardNumber}", "4532-1234-5678-9010");
// Output: Card: ***REDACTED***Supported patterns:
- Credit cards (4532-1234-5678-9010)
- Social Security Numbers (123-45-6789)
- Passwords (password: secret)
- API Keys (api_key: abc123)
- Email/password combos
using Clywell.Core.Logging.Extensions;
// Only creates the string if Debug is enabled
logger.LogDebugIfEnabled(() => $"Expensive operation: {GetExpensiveData()}");
// Only creates the string if Trace is enabled
logger.LogTraceIfEnabled(() => $"Very detailed trace: {GetTraceData()}");// Sync operation
var result = logger.LogExecutionTime("DatabaseQuery", () =>
{
return database.QueryData();
});
// Output: DatabaseQuery completed in 45ms
// Async operation
var result = await logger.LogExecutionTimeAsync("ApiCall", async () =>
{
return await httpClient.GetAsync("https://api.example.com");
});
// Output: ApiCall completed in 230msusing (logger.BeginTimedScope("ProcessOrder", new Dictionary<string, object>
{
["OrderId"] = orderId,
["CustomerId"] = customerId
}))
{
// Process order
}
// Output: Starting ProcessOrder with properties { OrderId: 123, CustomerId: 456 }
// Output: Completed ProcessOrder in 1234msapp.UseMiddleware<CorrelationIdMiddleware>();
// Or use the extension
app.UseClywellRequestTracking();Features:
- Reads
X-Correlation-IDheader from request - Generates new GUID if not provided
- Adds
X-Correlation-IDto response headers - Stores in
HttpContext.Items["CorrelationId"] - Automatically enriches all logs
app.UseMiddleware<RequestIdMiddleware>();
// Or use the extension
app.UseClywellRequestTracking();Features:
- Reads
X-Request-IDheader from request - Generates new GUID if not provided
- Adds
X-Request-IDto response headers - Stores in
HttpContext.Items["RequestId"] - Automatically enriches all logs
builder.AddClywellLogging(config =>
{
config
.WithMinimumLevel(LogEventLevel.Debug)
.WithConsoleSink() // Human-readable
.WithFileSink("logs/dev-.txt")
.WithClywellDefaults();
});builder.AddClywellLogging(config =>
{
config
.WithMinimumLevel(LogEventLevel.Information)
.WithConsoleSink(useJson: true) // JSON for container logs
.WithApplicationInsightsSink() // Azure monitoring
.WithClywellDefaults()
.OverrideMinimumLevel("Microsoft", LogEventLevel.Warning) // Reduce noise
.OverrideMinimumLevel("System", LogEventLevel.Warning);
});var logger = ClywellLoggerConfiguration.Create(configuration)
.WithMinimumLevel(LogEventLevel.Debug)
.WithConsoleSink(useJson: false)
.WithFileSink("logs/app-.txt", RollingInterval.Day)
.WithSeqSink("http://localhost:5341")
.WithApplicationInsightsSink(connectionString)
.WithCorrelationId()
.WithRequestId()
.WithEnvironmentEnrichers()
.WithSensitiveDataRedaction()
.OverrideMinimumLevel("Microsoft.EntityFrameworkCore", LogEventLevel.Warning)
.Build();
Log.Logger = logger;{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
},
"Seq": {
"ServerUrl": "http://localhost:5341"
}
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}"
}
},
{
"Name": "File",
"Args": {
"path": "logs/app-.txt",
"rollingInterval": "Day",
"retainedFileCountLimit": 31
}
}
],
"Enrich": ["FromLogContext", "WithMachineName"]
},
"ApplicationInsights": {
"ConnectionString": "InstrumentationKey=..."
}
}Then read from configuration:
builder.AddClywellLogging(config =>
{
config.ReadFromConfiguration()
.WithClywellDefaults();
});The package includes comprehensive unit tests with 80%+ code coverage:
dotnet testTest coverage:
- ✅ Correlation ID enricher
- ✅ Request ID enricher
- ✅ Sensitive data redaction
- ✅ Configuration builder
- ✅ Performance logging extensions
- ✅ Middleware (correlation & request IDs)
// ❌ BAD: String interpolation
logger.LogInformation($"User {userId} logged in");
// ✅ GOOD: Structured logging
logger.LogInformation("User {UserId} logged in", userId);// ❌ BAD: Always creates the string
logger.LogDebug($"Query result: {JsonSerializer.Serialize(largeObject)}");
// ✅ GOOD: Only creates string if Debug is enabled
logger.LogDebugIfEnabled(() => $"Query result: {JsonSerializer.Serialize(largeObject)}");// ❌ BAD: Manual timing
var stopwatch = Stopwatch.StartNew();
var result = await database.QueryAsync();
stopwatch.Stop();
logger.LogInformation("Query took {ElapsedMs}ms", stopwatch.ElapsedMilliseconds);
// ✅ GOOD: Built-in timing
var result = await logger.LogExecutionTimeAsync("DatabaseQuery",
() => database.QueryAsync());// Required for correlation/request IDs
app.UseClywellRequestTracking();
app.UseClywellRequestLogging();- .NET 10.0+
- Serilog 4.0+
- ASP.NET Core 10.0+ (for middleware)
- Serilog
- Serilog.AspNetCore
- Serilog.Sinks.Console
- Serilog.Sinks.File
- Serilog.Sinks.Seq
- Serilog.Sinks.ApplicationInsights
- Microsoft.Extensions.Logging.Abstractions
MIT License - See LICENSE file
This is an infrastructure package with no business logic. Contributions are welcome!
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Ensure 80%+ code coverage
- Submit a pull request
For issues and questions:
- GitHub Issues: clywell/clywell-logging
- Documentation: docs/getting-started.md
- Add structured log viewer UI
- Support for custom enrichers via configuration
- Integration with OpenTelemetry
- Performance benchmarks
- More sink integrations (Elasticsearch, Datadog)
Built with ❤️ by the Clywell team