logging
codewithmukesh/dotnet-claude-kit
Health checks, correlation IDs, and observability wiring for .NET 10 services.
What is logging?
Provides the observability glue for .NET 10: health check endpoints (/health/live and /health/ready), correlation ID middleware for request tracing, and log-level strategy. Use this when setting up observability from scratch, wiring health checks, or establishing request tracing across services.
- Separate liveness and readiness health check endpoints to distinguish restart-worthy failures from traffic-routing decisions
- Inject correlation IDs into every request and log entry via middleware, enabling request tracing across service boundaries
- Define log-level strategy (Debug for dev, Information for staging, Warning default for production) to balance signal and cost
- Provide patterns for propagating correlation IDs on outgoing HttpClient calls
- Clarify the ownership model: Serilog setup lives in the serilog skill, OpenTelemetry in the opentelemetry skill, and cross-cutting concerns here
How to install logging
npx skills add https://github.com/codewithmukesh/dotnet-claude-kit --skill loggingHow to use logging
- 1.Add the CorrelationIdMiddleware class to your project and register it early in the request pipeline with app.UseMiddleware<CorrelationIdMiddleware>()
- 2.Call builder.Services.AddHealthChecks() and register dependency checks (database, cache, message broker) with the 'ready' tag
- 3.Map two health check endpoints: /health/live with Predicate = _ => false (no checks, just liveness), and /health/ready with Predicate = check => check.Tags.Contains("ready")
- 4.For outgoing HttpClient calls, use a DelegatingHandler to propagate the X-Correlation-Id header (see httpclient-factory skill)
- 5.Set log-level defaults: Debug in development, Information in staging, Warning in production; use MinimumLevel.Override for namespace-specific tuning (see serilog skill)
Use cases
- Setting up health check endpoints for Kubernetes or container orchestrators to distinguish liveness probes from readiness probes
- Adding request correlation IDs to trace a single user complaint through logs across multiple services
- Establishing log-level defaults that avoid Information-level noise in production while keeping diagnostic detail in development
- Wiring correlation ID propagation on outgoing HTTP calls via DelegatingHandler
- Choosing between Debug, Information, Warning, Error, and Fatal log levels for different environments
- Backend engineers building .NET 10 microservices
- DevOps and SRE teams configuring health checks for orchestrators
- Teams adopting structured logging and distributed tracing
- Developers debugging cross-service request flows
logging FAQ
Liveness failure signals the orchestrator to restart the container; readiness failure signals to stop routing traffic. Conflating them causes cascading restarts when a slow dependency (e.g., database) recovers slowly.
Use a DelegatingHandler in your HttpClient factory to read the correlation ID from the current request context (via HttpContext.Items or LogContext) and attach it as the X-Correlation-Id header on outgoing calls. See the httpclient-factory skill.
No. Information-level request noise at scale is expensive and drowns signals. Use Warning as the production default and override specific namespaces (e.g., business events) to Information via MinimumLevel.Override in the serilog skill.
Load the serilog skill, which owns the two-stage AddSerilog() bootstrap. This skill provides the cross-cutting concerns (health checks, correlation IDs, log-level strategy) that sit on top.
Call AddCheck<T>() on the IHealthChecksBuilder returned by AddHealthChecks(), tag it with 'ready' if it affects readiness, and map it to the appropriate endpoint.
Full instructions (SKILL.md)
Source of truth, from codewithmukesh/dotnet-claude-kit.
name: logging
description: >
Observability overview and glue for .NET 10: how the pieces fit together,
plus the cross-cutting parts owned here — ASP.NET health check endpoints
(/health), correlation IDs, and log-level strategy. For deep Serilog setup
load serilog; for traces and metrics load opentelemetry. Load this
skill when setting up observability from scratch, wiring health check
endpoints or correlation IDs, or when the user says "logging",
"observability", "monitoring setup", "liveness", "readiness", or "ILogger".
Logging & Observability
Core Principles
- Structured logging with Serilog — Every log entry is a structured event with named properties, not a formatted string. This enables searching, filtering, and alerting. All setup (two-stage bootstrap,
AddSerilog(), sinks, enrichers) lives in the serilog skill — that skill'sAddSerilog()-over-UseSerilog()guidance is canonical. - OpenTelemetry for distributed tracing — Traces connect requests across services; metrics track system health over time. Full setup lives in the opentelemetry skill.
- Health checks for operational readiness — Every service exposes
/healthendpoints for load balancers and orchestrators. Liveness and readiness are separate questions and separate endpoints. - Correlation IDs for request tracing — Every request gets a unique ID that flows through all log entries and downstream service calls, so one user complaint maps to one filtered log stream.
Patterns
How the Pieces Fit Together
| Concern | Owner | Skill |
|---|---|---|
| Structured application logs | Serilog (AddSerilog()) | serilog |
| Request summary logging | UseSerilogRequestLogging() | serilog |
| Traces + metrics + OTLP export | OpenTelemetry SDK | opentelemetry |
| Health endpoints, correlation IDs, log-level strategy | This skill | logging |
Wire logging first (you need logs to debug the rest), then health checks, then tracing.
Correlation IDs
// Middleware to set correlation ID
public class CorrelationIdMiddleware(RequestDelegate next)
{
private const string CorrelationIdHeader = "X-Correlation-Id";
public async Task InvokeAsync(HttpContext context)
{
var correlationId = context.Request.Headers[CorrelationIdHeader].FirstOrDefault()
?? Guid.NewGuid().ToString();
context.Items["CorrelationId"] = correlationId;
context.Response.Headers[CorrelationIdHeader] = correlationId;
using (LogContext.PushProperty("CorrelationId", correlationId))
{
await next(context);
}
}
}
// Program.cs — register early so every downstream log carries the ID
app.UseMiddleware<CorrelationIdMiddleware>();
Why middleware: pushing the property once at the pipeline edge attaches it to every log event in the request scope — no per-call-site plumbing. Propagate the same header on outgoing HttpClient calls via a DelegatingHandler (see the httpclient-factory skill).
Health Checks
// Program.cs
builder.Services.AddHealthChecks()
.AddNpgSql(builder.Configuration.GetConnectionString("Default")!,
name: "database", tags: ["ready"])
.AddRedis(builder.Configuration.GetConnectionString("Redis")!,
name: "redis", tags: ["ready"])
.AddRabbitMQ(builder.Configuration.GetConnectionString("RabbitMq")!,
name: "rabbitmq", tags: ["ready"]);
// Map endpoints
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false // No dependency checks — just "am I running?"
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});
Why two endpoints: liveness failing means "restart me"; readiness failing means "stop sending traffic". Conflating them makes a slow database restart your app in a loop.
Log-Level Strategy
| Level | Use for | Environment default |
|---|---|---|
| Debug | Diagnostic detail, payload dumps (never PII in prod) | Development only |
| Information | Business events: order placed, job completed | Dev + staging |
| Warning | Recoverable anomalies: retry fired, fallback used | Everywhere — production default |
| Error | Failed operations that need attention | Everywhere |
| Fatal/Critical | App cannot continue | Everywhere |
Why Warning as the production default: Information-level request noise at scale costs real money in log storage and drowns the signals. Keep Information for genuine business events via namespace overrides (see the serilog skill's MinimumLevel.Override pattern).
Anti-patterns
Don't Log Sensitive Data
// BAD — logging credentials
logger.LogInformation("User logged in: {Email} with password {Password}", email, password);
// GOOD — log identifiers, never secrets or PII at Information level
logger.LogInformation("User {UserId} logged in", userId);
Don't Skip Health Check Tags
// BAD — all checks run for liveness AND readiness
app.MapHealthChecks("/health");
// GOOD — separate liveness (am I running?) from readiness (can I serve traffic?)
app.MapHealthChecks("/health/live", new() { Predicate = _ => false });
app.MapHealthChecks("/health/ready", new() { Predicate = c => c.Tags.Contains("ready") });
Don't Re-Implement What the Owning Skill Provides
// BAD — hand-rolling Serilog bootstrap here from memory
builder.Host.UseSerilog(...); // legacy API — the serilog skill forbids this
// GOOD — load the serilog skill and use its two-stage AddSerilog() bootstrap
builder.Services.AddSerilog((services, lc) => lc.ReadFrom.Configuration(builder.Configuration)...);
Decision Guide
| Scenario | Recommendation |
|---|---|
| Application logging setup | Load serilog — AddSerilog() two-stage bootstrap |
| Distributed tracing / metrics | Load opentelemetry — OTLP exporter |
| Custom business metrics | IMeterFactory + counters/histograms (opentelemetry skill) |
| Request tracing | Correlation ID middleware (this skill) |
| Container health | /health/live and /health/ready endpoints (this skill) |
| Log storage | Seq (development), Elastic/Grafana/OTLP backend (production) |
| Log levels | Debug in dev, Information in staging, Warning default in production |
Related skills
More from codewithmukesh/dotnet-claude-kit and the wider catalog.

messaging
Asynchronous messaging patterns for .NET: Wolverine and MassTransit, outbox, sagas, and broker configuration.

migrate
Guided, safe migration workflow for EF Core schema changes, .NET upgrades, and NuGet updates with rollback strategies.

minimal-api
Build type-safe HTTP endpoints with .NET 10 minimal APIs, auto-discovery, and OpenAPI documentation.

modern-csharp
Modern C# 14 and .NET 10 language features: primary constructors, records, pattern matching, spans, and the field keyword.

openapi
Built-in OpenAPI support for .NET 10 with document generation, transformers, and security schemes—no Swashbuckle needed.

opentelemetry
Distributed tracing, metrics, and logs for .NET 10 with OpenTelemetry and OTLP export.