skills/observability/serilog-structured/SKILL.md
Use when setting up Serilog structured logging with enrichers and Seq sink.
npx skillsauth add faysilalshareef/dotnet-ai-kit serilog-structuredInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
{PropertyName} — never string interpolation// Program.cs
using Serilog;
// Stage 1: Bootstrap logger for startup errors
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateBootstrapLogger();
try
{
var builder = WebApplication.CreateBuilder(args);
// Stage 2: Full logger with configuration
builder.Host.UseSerilog((context, services, configuration) =>
configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithProperty("Application", "{Domain}Service")
.WriteTo.Console(outputTemplate:
"[{Timestamp:HH:mm:ss} {Level:u3}] " +
"{Message:lj} {Properties:j}" +
"{NewLine}{Exception}")
.WriteTo.Seq(
context.Configuration["Seq:Url"]
?? "http://localhost:5341"));
// Configure services...
var app = builder.Build();
// Request logging middleware
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagnosticContext,
httpContext) =>
{
diagnosticContext.Set("UserId",
httpContext.User.FindFirstValue(
ClaimTypes.NameIdentifier) ?? "anonymous");
diagnosticContext.Set("ClientIp",
httpContext.Connection.RemoteIpAddress?.ToString());
};
});
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Application terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
{
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Warning",
"Microsoft.EntityFrameworkCore.Infrastructure": "Warning",
"System": "Warning"
}
}
},
"Seq": {
"Url": "http://localhost:5341"
}
}
// GOOD: Structured properties — queryable in Seq
logger.LogInformation(
"Order {OrderId} created for customer {CustomerId} " +
"with total {OrderTotal:C}",
orderId, customerId, total);
// BAD: String interpolation — loses structure
logger.LogInformation(
$"Order {orderId} created for customer {customerId}");
// GOOD: Object destructuring with @
logger.LogInformation(
"Processing order {@OrderDetails}",
new { orderId, customerId, ItemCount = items.Count });
// GOOD: Exception as first parameter
logger.LogError(ex,
"Failed to process order {OrderId}", orderId);
// BAD: Exception in message template
logger.LogError(
"Failed to process order {OrderId}: {Error}",
orderId, ex.Message); // loses stack trace
// Push properties for a scope — all logs within include them
using (LogContext.PushProperty("OrderId", orderId))
using (LogContext.PushProperty("CorrelationId",
Activity.Current?.TraceId.ToString()))
{
logger.LogInformation("Starting order processing");
await ProcessItemsAsync(order, ct);
logger.LogInformation("Order processing complete");
// Both logs include OrderId and CorrelationId
}
public sealed class CorrelationIdEnricher(
IHttpContextAccessor httpContextAccessor) : ILogEventEnricher
{
public void Enrich(LogEvent logEvent,
ILogEventPropertyFactory propertyFactory)
{
var correlationId =
httpContextAccessor.HttpContext?
.Request.Headers["X-Correlation-Id"]
.FirstOrDefault()
?? Activity.Current?.TraceId.ToString()
?? Guid.NewGuid().ToString();
logEvent.AddPropertyIfAbsent(
propertyFactory.CreateProperty(
"CorrelationId", correlationId));
}
}
// Registration
builder.Host.UseSerilog((context, services, configuration) =>
configuration
.Enrich.With(
services.GetRequiredService<CorrelationIdEnricher>())
// ... rest of config
);
Verbose — Detailed debugging (not in production)
Debug — Internal state useful for debugging
Information — Normal operation events (request handled, order created)
Warning — Unexpected but handled (retry, degraded, slow query)
Error — Failure that needs attention (exception, failed operation)
Fatal — Application cannot continue (startup failure, data corruption)
// Check if level is enabled before expensive operations
if (logger.IsEnabled(LogLevel.Debug))
{
var serialized = JsonSerializer.Serialize(complexObject);
logger.LogDebug("Request payload: {Payload}", serialized);
}
<PackageReference Include="Serilog.AspNetCore" />
<PackageReference Include="Serilog.Sinks.Seq" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Enrichers.Environment" />
<PackageReference Include="Serilog.Enrichers.Thread" />
$"Order {id}")try/catch around application startupLog.CloseAndFlush() in finally blockSerilog.AspNetCore package in .csprojUseSerilog in Program.csLog.Logger = new LoggerConfiguration() initializationWriteTo.Seq or WriteTo.Console configurationappsettings.json for "Serilog" sectionSerilog.AspNetCore, Serilog.Sinks.SeqProgram.csILogger string interpolation with structured templates| Scenario | Sink | |----------|------| | Development | Console | | Centralized logging | Seq, Elasticsearch, Datadog | | Cloud native | Console (stdout) + aggregator | | File-based | Rolling file (not recommended for containers) |
data-ai
Use when about to claim work is complete, fixed, passing, or ready — before committing, creating PRs, or moving to the next task. Requires running verification commands and confirming output before making any success claims.
development
Use when encountering any bug, test failure, build error, or unexpected behavior — before proposing fixes or making changes.
development
Use when checkpointing, wrapping up, or handing off an AI-assisted development session.
development
Use when following the Specification-Driven Development lifecycle from plan through ship.