brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
loggingObservability 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 ch…
Independent GitHub snapshot · Automated classification · Original documentation governs
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".AddSerilog(), sinks, enrichers) lives in the serilog skill — that skill's AddSerilog()-over-UseSerilog() guidance is canonical./health endpoints for load balancers and orchestrators. Liveness and readiness are separate questions and separate endpoints.| 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.
// 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).
// 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.
| 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).
// 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);
// 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") });
// 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)...);
| 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 |