📝 Key Insights: Replace plain text logs with structured, queryable JSON logs. Use Elasticsearch, Kibana, or Seq for powerful analysis.
Text logs are impossible to search. Structured logging changes that.
🚀 Setup Serilog
dotnet add package Serilog dotnet add package Serilog.Extensions.Hosting dotnet add package Serilog.Sinks.Console dotnet add package Serilog.Sinks.File dotnet add package Serilog.Sinks.ElasticSearch dotnet add package Serilog.Enrichers.Environment dotnet add package Serilog.Enrichers.Thread dotnet add package Serilog.Enrichers.Process
💡 Program.cs
using Serilog;
using Serilog.Events;
var builder = WebApplication.CreateBuilder(args);
// ✅ Configure Serilog
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Information()
.MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
.MinimumLevel.Override("System", LogEventLevel.Warning)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithEnvironmentName()
.Enrich.WithThreadId()
.Enrich.WithProcessId()
.Enrich.WithProperty("Application", "MyAPI")
.WriteTo.Console()
.WriteTo.File("logs/myapp-.txt",
rollingInterval: RollingInterval.Day,
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}"
)
.WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200"))
{
IndexFormat = "myapp-logs-{0:yyyy.MM.dd}",
AutoRegisterTemplate = true,
NumberOfShards = 1,
NumberOfReplicas = 0,
ModifyConnectionSettings = connection => connection
.ServerCertificateValidationCallback((sender, cert, chain, errors) => true)
})
.CreateLogger();
// ✅ Replace default logging
builder.Logging.ClearProviders();
builder.Logging.AddSerilog();
var app = builder.Build();
// ✅ Use Serilog for request logging
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("UserAgent", httpContext.Request.Headers["User-Agent"]);
diagnosticContext.Set("ClientIP", httpContext.Connection.RemoteIpAddress?.ToString());
diagnosticContext.Set("CorrelationId", httpContext.TraceIdentifier);
};
options.GetLevel = (httpContext, elapsed, ex) =>
{
if (ex != null) return LogEventLevel.Error;
if (httpContext.Response.StatusCode >= 500) return LogEventLevel.Error;
if (httpContext.Response.StatusCode >= 400) return LogEventLevel.Warning;
return LogEventLevel.Information;
};
});
// ✅ Example endpoints
app.MapGet("/api/orders/{id}", async (int id, ILogger logger) =>
{
// ✅ Structured logging
logger.LogInformation("Fetching order {OrderId} for user {UserId}", id, "john");
if (id <= 0)
{
logger.LogWarning("Invalid order ID {OrderId}", id);
return Results.BadRequest("Invalid order ID");
}
// Simulate work
var order = new { Id = id, Customer = "John", Total = 99.99m };
return Results.Ok(order);
});
// ✅ With context enrichment
app.MapPost("/api/orders", async (Order order, ILogger logger) =>
{
using (LogContext.PushProperty("OrderId", order.Id))
using (LogContext.PushProperty("CustomerId", order.CustomerId))
using (LogContext.PushProperty("TotalAmount", order.Total))
{
// ✅ All logs include these properties
logger.LogInformation("Processing order");
// Business logic
await ProcessOrderAsync(order);
logger.LogInformation("Order processed successfully");
return Results.Ok(order);
}
});
app.Run();
💡 Expert Tip: Query in Kibana
# ✅ Search errors for specific order error AND OrderId:12345 # ✅ Find slow requests (> 1000ms) duration:>1000 AND status:200 # ✅ Group by user and count errors SELECT UserId, COUNT(*) FROM myapp-logs-* WHERE Level=''Error'' GROUP BY UserId # ✅ Find specific exception type ExceptionType:''NullReferenceException'' # ✅ Correlation ID tracking CorrelationId:''00-12345-67890-00000''
