From dt-brigid
Observability for .NET 10 applications. Covers Serilog structured logging, OpenTelemetry traces and metrics, health checks, and correlation IDs. Load this skill when setting up logging, tracing, metrics, or health monitoring, or when the user mentions "Serilog", "logging", "structured log", "OpenTelemetry", "traces", "metrics", "health check", "correlation ID", "observability", "telemetry", "log enrichment", or "ILogger".
npx claudepluginhub dreamteam-hq/brigid --plugin dt-brigidThis skill uses the workspace's default tool permissions.
1. **Structured logging with Serilog** — Every log entry is a structured event with named properties, not a formatted string. This enables searching, filtering, and alerting.
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Searches prompts.chat for AI prompt templates by keyword or category, retrieves by ID with variable handling, and improves prompts via AI. Use for discovering or enhancing prompts.
Checks Next.js compilation errors using a running Turbopack dev server after code edits. Fixes actionable issues before reporting complete. Replaces `next build`.
/health endpoints for load balancers and orchestrators.// Program.cs
builder.Host.UseSerilog((context, loggerConfig) =>
{
loggerConfig
.ReadFrom.Configuration(context.Configuration)
.Enrich.FromLogContext()
.Enrich.WithMachineName()
.Enrich.WithProperty("Application", "MyApp.Api")
.WriteTo.Console(outputTemplate:
"[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")
.WriteTo.Seq(context.Configuration["Seq:Url"] ?? "http://localhost:5341");
});
// After building the app
app.UseSerilogRequestLogging(options =>
{
options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>
{
diagnosticContext.Set("UserId",
httpContext.User.FindFirstValue(ClaimTypes.NameIdentifier) ?? "anonymous");
};
});
// GOOD — structured logging with message template
logger.LogInformation("Processing order {OrderId} for customer {CustomerId}",
orderId, customerId);
// GOOD — include relevant context
logger.LogWarning("Payment failed for order {OrderId}. Attempt {Attempt} of {MaxAttempts}",
orderId, attempt, maxAttempts);
// GOOD — log exceptions with structured data
logger.LogError(exception, "Failed to process order {OrderId}", orderId);
// 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
app.UseMiddleware<CorrelationIdMiddleware>();
For full OpenTelemetry setup (metrics, tracing, OTLP export), see the opentelemetry skill. The logging skill focuses on structured logging with Serilog. OpenTelemetry handles the export pipeline.
// 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")
});
// BAD — allocates string even if level is disabled, breaks structured logging
logger.LogInformation($"Order {orderId} created for {customerId}");
// GOOD — message template with named parameters
logger.LogInformation("Order {OrderId} created for {CustomerId}", orderId, customerId);
// BAD — logging credentials
logger.LogInformation("User logged in: {Email} with password {Password}", email, password);
// GOOD — never log secrets, passwords, tokens, or PII
logger.LogInformation("User logged in: {Email}", email);
// 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") });
| Scenario | Recommendation |
|---|---|
| Application logging | Serilog with structured logging |
| Distributed tracing | OpenTelemetry with OTLP exporter |
| Custom business metrics | IMeterFactory + counters/histograms |
| Request tracing | Correlation ID middleware |
| Container health | /health/live and /health/ready endpoints |
| Log storage | Seq (development), Elastic/Grafana (production) |
| Log levels | Debug in dev, Information in staging, Warning in production |