Lukdrasil.StepUpLogging
1.9.0
dotnet add package Lukdrasil.StepUpLogging --version 1.9.0
NuGet\Install-Package Lukdrasil.StepUpLogging -Version 1.9.0
<PackageReference Include="Lukdrasil.StepUpLogging" Version="1.9.0" />
<PackageVersion Include="Lukdrasil.StepUpLogging" Version="1.9.0" />
<PackageReference Include="Lukdrasil.StepUpLogging" />
paket add Lukdrasil.StepUpLogging --version 1.9.0
#r "nuget: Lukdrasil.StepUpLogging, 1.9.0"
#:package Lukdrasil.StepUpLogging@1.9.0
#addin nuget:?package=Lukdrasil.StepUpLogging&version=1.9.0
#tool nuget:?package=Lukdrasil.StepUpLogging&version=1.9.0
Lukdrasil.StepUpLogging
Dynamic step-up logging for ASP.NET Core with Serilog - automatically increase log verbosity when errors occur, with minimal performance overhead.
Features
✅ Flexible step-up modes - Auto (production), AlwaysOn (dev), Disabled (minimal)
✅ Automatic step-up on errors - Triggers detailed logging when Error level logs are detected
✅ Pre-error buffering - In-memory per-request log buffering, flushed when errors occur
✅ OpenTelemetry-first - Primary export via OTLP with optional console/file sinks
✅ OpenTelemetry Activities - Built-in distributed tracing with 6+ instrumentation points (default-enabled)
✅ Minimal overhead - 18-29% faster than standard Serilog in baseline tests
✅ Request body capture - Optional capture during step-up with configurable size limits
✅ Sensitive data redaction - Regex-based redaction for query strings and request bodies
✅ OpenTelemetry metrics - Built-in metrics for monitoring step-up triggers and duration
✅ Manual control - Expose endpoints to manually trigger or check step-up status
✅ .NET 10.0 - Built with modern C# 14 features
Quick Start
Installation
dotnet add package Lukdrasil.StepUpLogging
Basic Setup
Option 1: Configuration from appsettings.json (recommended)
using Lukdrasil.StepUpLogging;
var builder = WebApplication.CreateBuilder(args);
// Automatically loads configuration from appsettings.json "SerilogStepUp" section
builder.AddStepUpLogging();
var app = builder.Build();
app.UseStepUpRequestLogging();
app.Run();
Option 2: Programmatic configuration
using Lukdrasil.StepUpLogging;
var builder = WebApplication.CreateBuilder(args);
builder.AddStepUpLogging(opts =>
{
opts.Mode = StepUpMode.Auto;
opts.BaseLevel = "Warning";
opts.StepUpLevel = "Debug";
opts.EnableConsoleLogging = builder.Environment.IsDevelopment();
});
var app = builder.Build();
app.UseStepUpRequestLogging();
app.Run();
Option 3: Mixed (appsettings.json + programmatic override)
// appsettings.json is loaded first, then overridden by code
builder.AddStepUpLogging(opts =>
{
// Override only specific settings
if (builder.Environment.IsProduction())
{
opts.Mode = StepUpMode.Auto;
opts.EnableConsoleLogging = false;
}
else
{
opts.Mode = StepUpMode.AlwaysOn;
opts.EnableConsoleLogging = true;
}
});
Option 4: Aspire ServiceDefaults Integration
When using Aspire ServiceDefaults which already configures Serilog, use the UseStepUpLogging() extension method on LoggerConfiguration:
// In ServiceDefaults/Extensions.cs
public static TBuilder AddServiceDefaults<TBuilder>(this TBuilder builder)
where TBuilder : IHostApplicationBuilder
{
builder.ConfigureOpenTelemetry();
builder.AddDefaultHealthChecks();
builder.Services.AddServiceDiscovery();
// Configure Serilog with StepUp logging
builder.Services.AddSerilog((services, lc) =>
{
lc.ReadFrom.Configuration(builder.Configuration)
.UseStepUpLogging(builder, opts =>
{
opts.Mode = builder.Environment.IsDevelopment()
? StepUpMode.AlwaysOn
: StepUpMode.Auto;
});
});
return builder;
}
// In your API Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults(); // Includes StepUp logging
var app = builder.Build();
app.UseStepUpRequestLogging(); // Add request logging middleware
app.Run();
Configuration (appsettings.json)
{
"SerilogStepUp": {
"Mode": "Auto",
"BaseLevel": "Warning",
"StepUpLevel": "Information",
"DurationSeconds": 180,
"AlwaysLogRequestSummary": true,
"RequestSummaryLevel": "Information",
"EnableOtlpExporter": true,
"EnableConsoleLogging": false,
"CaptureRequestBody": true,
"MaxBodyCaptureBytes": 16384,
"ExcludePaths": ["/health", "/metrics"],
"RedactionRegexes": [
"password=[^&]*",
"authorization:.*"
],
"EnablePreErrorBuffering": true,
"PreErrorBufferSize": 100,
"PreErrorMaxContexts": 1024,
"EnrichWithExceptionDetails": true,
"EnrichWithThreadId": true,
"EnrichWithProcessId": true,
"EnrichWithMachineName": true
}
}
Request Summary behaviour
When "AlwaysLogRequestSummary" is enabled, the middleware emits a single structured summary event at the configured "RequestSummaryLevel" for every completed HTTP request. The summary contains: HTTP method, request path, response status code, elapsed milliseconds and an optional trace/correlation id. Summary events are marked with the "IsRequestSummary" property and are processed by the library's SummarySink so they are exported independently of the StepUp level switch (base Warning) and the normal step-up flow.
To customise where summaries are written, provide a dedicated summary logger in DI when calling AddStepUpLogging, or configure the default sinks; the library enforces a single DI-managed summary logger to avoid unmanaged CreateLogger instances.
OpenTelemetry Configuration
StepUpLogging uses standard OpenTelemetry environment variables for OTLP configuration. OTLP endpoint and protocol are configured exclusively via environment variables and cannot be overridden programmatically:
| Environment Variable | Purpose | Default |
|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT |
OTLP collector endpoint | http://localhost:4317 |
OTEL_EXPORTER_OTLP_PROTOCOL |
Protocol: grpc or http/protobuf |
grpc |
OTEL_EXPORTER_OTLP_HEADERS |
Headers (format: key1=value1,key2=value2) |
(none) |
OTEL_RESOURCE_ATTRIBUTES |
Resource attributes (format: key1=value1,key2=value2) |
(none) |
Example with environment variables:
# Docker or Kubernetes deployment
export OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4317
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer xyz,X-Custom=value"
export OTEL_RESOURCE_ATTRIBUTES="service.name=MyApi,deployment.environment=production"
dotnet MyApp.dll
For other OTLP options (like additional headers or resource attributes), use environment variables or configure via appsettings.json.
Step-Up Modes
Auto (default - Production)
- Logs at
BaseLevel(Warning) during normal operation - Automatically steps up to
StepUpLevel(Information) when errors occur - Returns to
BaseLevelafter configured duration
AlwaysOn (Development)
- Always logs at
StepUpLevel(Information) - Useful for local development to see all detailed logs
- Step-up triggers are ignored (already at max verbosity)
Disabled (Minimal Logging)
- Always logs at
BaseLevel(Warning) - Step-up mechanism is completely disabled
- Error triggers are ignored
// Development configuration example
{
"SerilogStepUp": {
"Mode": "AlwaysOn",
"StepUpLevel": "Debug",
"EnableConsoleLogging": true
}
}
Request Logging
The UseStepUpRequestLogging() middleware enriches each request with detailed context:
Captured Information
- RequestPath - Normalized request path (trailing slashes removed)
- QueryString - Query parameters (redacted based on
RedactionRegexes) - RouteParameters - Route parameter values (e.g.,
{id},{role}) - Headers - HTTP request headers with automatic redaction of sensitive headers
- RequestBody - POST/PUT/PATCH bodies (when
CaptureRequestBodyis enabled and logging is stepped-up)
Example Log Output
{
"Timestamp": "2025-01-08T12:34:56.789Z",
"Level": "Information",
"MessageTemplate": "HTTP {Method} {Path} responded {StatusCode} in {Elapsed:0.00}ms",
"RequestPath": "/api/users/123",
"RouteParameters": {
"id": "123"
},
"QueryString": "?filter=active&sort=name",
"Headers": {
"content-type": "application/json",
"user-agent": "Mozilla/5.0",
"authorization": "[REDACTED]",
"cookie": "[REDACTED]"
},
"RequestBody": "{\"name\": \"John Doe\", \"password\": \"[REDACTED]\"}"
}
Sensitive Header Redaction
Built-in redacted headers:
AuthorizationCookieX-API-KeyX-Auth-TokenX-Access-TokenAuthorization-TokenProxy-AuthorizationWWW-AuthenticateSec-WebSocket-Key
Add custom sensitive headers via configuration:
appsettings.json:
{
"SerilogStepUp": {
"AdditionalSensitiveHeaders": [
"X-Custom-Secret",
"X-Internal-Token",
"X-Database-Password"
]
}
}
Programmatically:
builder.AddStepUpLogging(opts =>
{
opts.AdditionalSensitiveHeaders = new[]
{
"X-Custom-Secret",
"X-Internal-Token"
};
});
OpenTelemetry Activities & Instrumentation
StepUpLogging automatically instruments your requests with OpenTelemetry Activity objects for distributed tracing. This feature is enabled by default but can be disabled if needed.
Activity Instrumentation Points
The library creates activities at these key points:
| Activity | Type | Description | Triggered |
|---|---|---|---|
LogRequest |
Server |
Request processing span | Every HTTP request |
TriggerStepUp |
Internal |
Step-up event triggered | When error detected |
PerformStepDown |
Internal |
Step-down event executed | When duration expires |
ApplyRedaction |
Internal |
Sensitive data redaction | Per redaction pattern |
CaptureRequestBody |
Internal |
Request body capture | When step-up active |
FlushBufferedEvents |
Internal |
Pre-error buffer flush | When error occurs |
Enable/Disable Activities
Enabled by default - Activities are created automatically when OTEL is registered:
{
"SerilogStepUp": {
"EnableActivityInstrumentation": true
}
}
Disable if needed (opt-out):
builder.AddStepUpLogging(opts =>
{
opts.EnableActivityInstrumentation = false; // Disable activity creation
});
Zero Overhead When OTEL Not Registered
When OpenTelemetry is not registered in the service container:
- Activities are created but not propagated
- No performance overhead (activities are internal)
- Enable/disable flag has no effect
Example with Aspire Observability
var builder = WebApplication.CreateBuilder(args);
// ConfigureOpenTelemetry() automatically registers traces
builder.AddServiceDefaults();
builder.AddStepUpLogging(opts =>
{
opts.EnableActivityInstrumentation = true; // Default
});
var app = builder.Build();
app.UseStepUpRequestLogging();
app.Run();
// Activities now visible in:
// - Grafana Tempo / Jaeger
// - Application Insights
// - Custom OTEL collectors
Manual Control
Add endpoints to manually trigger step-up or check status:
app.MapPost("/stepup/trigger", (StepUpLoggingController controller) =>
{
controller.Trigger();
return Results.Ok(new { message = "Step-up activated" });
});
app.MapGet("/stepup/status", (StepUpLoggingController controller) =>
{
return Results.Ok(new { active = controller.IsSteppedUp });
});
Pre-Error Buffering
Pre-error buffering automatically captures recent log events in memory per request/activity and flushes them when an error occurs. This provides context around the error without increasing log verbosity in normal operation.
How It Works
- Buffering phase: All non-error logs are stored in a ring buffer per OpenTelemetry trace ID (one buffer per request)
- Flush trigger: When an
ErrororFatallog is emitted, the buffer flushes all captured events from that request to the output - Memory management: Uses LRU eviction to prevent unbounded memory growth (configurable limits on buffer size and active contexts)
Configuration
Enable via appsettings.json:
{
"SerilogStepUp": {
"EnablePreErrorBuffering": true,
"PreErrorBufferSize": 100,
"PreErrorMaxContexts": 1024
}
}
| Option | Default | Description |
|---|---|---|
EnablePreErrorBuffering |
true |
Enable/disable pre-error buffering |
PreErrorBufferSize |
100 |
Max events to retain per request before oldest are dropped |
PreErrorMaxContexts |
1024 |
Max concurrent request contexts to track; older ones are evicted |
Enable programmatically:
builder.AddStepUpLogging(opts =>
{
opts.EnablePreErrorBuffering = true;
opts.PreErrorBufferSize = 200; // Capture more events per request
opts.PreErrorMaxContexts = 512; // Fewer concurrent requests in memory
});
Benefits
- Diagnostics: See what happened before an error (request headers, SQL queries, business logic) without enabling debug logging for all requests
- Production-safe: Buffering is per-request; no global state that could consume unbounded memory
- Configurable: Tune buffer size and context limits based on your memory budget and traffic patterns
- Automatic: No code changes needed; works transparently in the logging pipeline
Example Scenario
Without buffering:
[Warning] Request started: GET /api/users/123
[Error] User not found (id=123)
With buffering:
[Information] Request started: GET /api/users/123
[Debug] Querying user database: SELECT * FROM Users WHERE Id = @id
[Debug] Query parameters: @id = 123
[Debug] Database response: no rows
[Error] User not found (id=123)
All Debug/Information events are buffered internally; when the error occurs, they are flushed to provide diagnostic context.
Common Scenarios
Production with Auto Step-Up
{
"SerilogStepUp": {
"Mode": "Auto",
"BaseLevel": "Warning",
"StepUpLevel": "Information",
"DurationSeconds": 300,
"EnableOtlpExporter": true,
"OtlpEndpoint": "http://otel-collector:4317",
"OtlpResourceAttributes": {
"service.name": "ProductionAPI",
"deployment.environment": "production"
}
}
}
Local Development (Always Verbose)
{
"SerilogStepUp": {
"Mode": "AlwaysOn",
"StepUpLevel": "Debug",
"EnableConsoleLogging": true,
"EnableOtlpExporter": false,
"CaptureRequestBody": true
}
}
Environment-Specific Configuration
builder.AddStepUpLogging(opts =>
{
opts.Mode = builder.Environment.IsDevelopment()
? StepUpMode.AlwaysOn
: StepUpMode.Auto;
opts.StepUpLevel = builder.Environment.IsDevelopment() ? "Debug" : "Information";
opts.EnableConsoleLogging = builder.Environment.IsDevelopment();
// Production: use OTLP, Development: use console
opts.EnableOtlpExporter = !builder.Environment.IsDevelopment();
if (builder.Environment.IsProduction())
{
opts.OtlpEndpoint = Environment.GetEnvironmentVariable("OTEL_ENDPOINT")
?? "http://otel-collector:4317";
opts.OtlpHeaders["Authorization"] = "Bearer " +
Environment.GetEnvironmentVariable("OTEL_TOKEN");
}
});
With Authentication Headers
# Use environment variables for OTLP authentication
export OTEL_EXPORTER_OTLP_ENDPOINT=http://secure-collector:4317
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer xyz,X-API-Key=secret"
export OTEL_RESOURCE_ATTRIBUTES="service.name=MyAPI,service.version=1.2.3,deployment.environment=production"
dotnet MyApp.dll
Multiple Resource Attributes
# Use environment variable for multiple resource attributes
export OTEL_RESOURCE_ATTRIBUTES="service.name=payment-service,service.version=2.1.0,service.namespace=ecommerce,deployment.environment=production,cloud.provider=azure,cloud.region=westeurope,k8s.cluster.name=prod-cluster,k8s.namespace.name=payment"
dotnet MyApp.dll
Performance
Benchmark results (k6 load test, 50 VUs, 3 minutes):
| Metric | Standard Serilog | StepUpLogging | Improvement |
|---|---|---|---|
| Avg Latency | 1.19 ms | 0.98 ms | -18% ⚡ |
| P95 Latency | 2.12 ms | 1.64 ms | -23% ⚡ |
| Throughput | 165.77 req/s | 166.21 req/s | +0.3% |
See full performance test results.
How It Works
- Normal operation: Logs at
BaseLevel(e.g., Warning) - Error detected:
StepUpTriggerSinkautomatically triggers step-up - Step-up active: Logs at
StepUpLevel(e.g., Information) for configured duration - Auto restore: Returns to
BaseLevelafter duration expires
Export Architecture
Primary: OpenTelemetry OTLP (Production-ready)
- Logs exported to OTLP collector (default:
localhost:4317) - Supports both gRPC and HTTP protocols
- Structured logging with full trace context correlation
- Resource attributes for service identification
Fallback: Console Logging (Development/Legacy)
- Enable via
EnableConsoleLogging: truein configuration - Useful for local development or direct log collection
- Outputs CompactJSON format
Optional: File Sink (Archival/Compliance)
- Daily rolling files with 30-day retention
- Enable via
logFilePathparameter inAddStepUpLogging()
Configuration Options
| Option | Default | Environment Variable | Description |
|---|---|---|---|
| Step-Up Behavior | |||
Mode |
Auto |
- | Step-up mode: Auto, AlwaysOn, Disabled |
BaseLevel |
"Warning" |
- | Normal log level |
StepUpLevel |
"Information" |
- | Elevated log level during step-up |
DurationSeconds |
180 |
- | How long step-up remains active (Auto mode) |
| Pre-Error Buffering | |||
EnablePreErrorBuffering |
true |
- | Enable/disable pre-error buffering |
PreErrorBufferSize |
100 |
- | Max events per request before oldest are dropped |
PreErrorMaxContexts |
1024 |
- | Max concurrent request contexts; older ones are evicted |
| OpenTelemetry Instrumentation | |||
EnableActivityInstrumentation |
true |
- | Enable/disable Activity creation (default-enabled, opt-out) |
| OpenTelemetry | |||
EnableOtlpExporter |
true |
- | Export logs to OTLP endpoint |
| (Endpoint/Protocol) | (env only) | OTEL_EXPORTER_OTLP_ENDPOINT, OTEL_EXPORTER_OTLP_PROTOCOL |
OTLP configuration (environment variables only) |
| Additional Sinks | |||
EnableConsoleLogging |
false |
- | Enable console output (dev scenarios) |
| Enrichers | |||
EnrichWithExceptionDetails |
true |
- | Enrich logs with structured exception details |
EnrichWithThreadId |
false |
- | Include thread ID in log events |
EnrichWithProcessId |
false |
- | Include process ID in log events |
EnrichWithMachineName |
true |
- | Include machine name in log events |
EnrichWithEnvironment |
true |
- | Include environment name (Development/Production) |
| Request Logging | |||
CaptureRequestBody |
false |
- | Capture POST/PUT/PATCH bodies during step-up |
MaxBodyCaptureBytes |
16384 |
- | Max bytes to capture from request body |
ExcludePaths |
["/health", "/metrics"] |
- | Paths to exclude from logging |
RedactionRegexes |
[] |
- | Regex patterns for redacting sensitive data |
AdditionalSensitiveHeaders |
[] |
- | Custom header names to redact in request logging |
| Service Identification | |||
ServiceVersion |
null |
APP_VERSION |
Service version for enrichment |
OpenTelemetry Activities & Metrics
Activities
When EnableActivityInstrumentation is enabled (default), these activities are created:
- Request-level:
LogRequest(server-side span) tracking entire HTTP request - Operation-level:
TriggerStepUp,PerformStepDown,FlushBufferedEvents(internal operations) - Sub-operation:
ApplyRedaction,CaptureRequestBody(child spans of LogRequest)
Activities include W3C trace context tags and semantic conventions:
http.scheme- Protocol (http/https)http.host- Host header valuesecurity.redaction_applied- Whether redaction was performed
Metrics
Exposed metrics for monitoring:
stepup_trigger_total- Total number of step-up triggersstepup_active- Whether step-up is currently active (0 or 1)stepup_duration_seconds- Duration histogram of step-up windowsrequest_body_captured_total- Number of requests with captured bodyrequest_redaction_applied_total- Number of requests with redaction appliedbuffer_events_total- Total events buffered by pre-error bufferbuffer_flushed_events_total- Events flushed due to errorbuffer_flush_total- Number of buffer flush operationsbuffer_evicted_contexts_total- Contexts evicted due to LRU pressure
License
MIT © Lukdrasil
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- OpenTelemetry.Exporter.OpenTelemetryProtocol (>= 1.15.0)
- OpenTelemetry.Extensions.Hosting (>= 1.15.0)
- Serilog.AspNetCore (>= 10.0.0)
- Serilog.Enrichers.Environment (>= 3.0.1)
- Serilog.Enrichers.OpenTelemetry (>= 1.0.1)
- Serilog.Enrichers.Process (>= 3.0.0)
- Serilog.Enrichers.Thread (>= 4.0.0)
- Serilog.Exceptions (>= 8.4.0)
- Serilog.Expressions (>= 5.0.0)
- Serilog.Formatting.Compact (>= 3.0.0)
- Serilog.Sinks.Async (>= 2.1.0)
- Serilog.Sinks.OpenTelemetry (>= 4.2.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|
Added AlwaysLogRequestSummary option and SummarySink to emit per-request structured summaries independently of the step-up level; added EmitRequestSummary API and tests.