logging
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseLogging & 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, , sinks, enrichers) lives in the serilog skill — that skill's
AddSerilog()-over-AddSerilog()guidance is canonical.UseSerilog() - 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 endpoints for load balancers and orchestrators. Liveness and readiness are separate questions and separate endpoints.
/health - 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.
- 基于Serilog的结构化日志 —— 每条日志条目都是带有命名属性的结构化事件,而非格式化字符串。这支持搜索、过滤和告警功能。所有配置(两阶段启动、、输出接收器、增强器)都位于serilog Skill中——该Skill中关于
AddSerilog()优先于AddSerilog()的指导为标准规范。UseSerilog() - 用于分布式追踪的OpenTelemetry —— 追踪可跨服务关联请求;指标用于随时间跟踪系统健康状态。完整配置位于opentelemetry Skill中。
- 用于运行就绪性的健康检查 —— 每个服务都暴露端点供负载均衡器和编排器使用。存活检查(liveness)和就绪检查(readiness)是两个独立的概念,对应独立的端点。
/health - 用于请求追踪的关联ID —— 每个请求都会分配一个唯一ID,该ID会贯穿所有日志条目和下游服务调用,因此用户的一个反馈可对应到一条过滤后的日志流。
Patterns
模式
How the Pieces Fit Together
组件协同方式
| Concern | Owner | Skill |
|---|---|---|
| Structured application logs | Serilog ( | |
| Request summary logging | | |
| Traces + metrics + OTLP export | OpenTelemetry SDK | |
| Health endpoints, correlation IDs, log-level strategy | This skill | |
Wire logging first (you need logs to debug the rest), then health checks, then tracing.
| 关注点 | 负责方 | Skill |
|---|---|---|
| 结构化应用日志 | Serilog( | |
| 请求摘要日志 | | |
| 追踪 + 指标 + OTLP导出 | OpenTelemetry SDK | |
| 健康端点、关联ID、日志级别策略 | 本Skill | |
优先配置日志(调试其他功能需要日志支持),然后是健康检查,最后是追踪。
Correlation IDs
关联ID
csharp
// 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 calls via a (see the httpclient-factory skill).
HttpClientDelegatingHandlercsharp
// 设置关联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 —— 尽早注册,让所有下游日志都携带该ID
app.UseMiddleware<CorrelationIdMiddleware>();为何使用中间件:在管道边缘一次性推送属性,即可将其附加到请求范围内的所有日志事件——无需在每个调用点编写 plumbing 代码。通过在 outgoing 调用中传播相同的头部(详见httpclient-factory Skill)。
DelegatingHandlerHttpClientHealth Checks
健康检查
csharp
// 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.
csharp
// 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"]);
// 映射端点
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
Predicate = _ => false // 不检查依赖项 —— 仅判断“我是否在运行?”
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions
{
Predicate = check => check.Tags.Contains("ready")
});为何设置两个端点:存活检查失败意味着“重启我”;就绪检查失败意味着“停止向我发送流量”。将两者混淆会导致数据库缓慢时应用陷入重启循环。
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 pattern).
MinimumLevel.Override| 级别 | 使用场景 | 默认环境 |
|---|---|---|
| Debug | 诊断细节、负载转储(生产环境中绝不能包含PII) | 仅开发环境 |
| Information | 业务事件:订单已下单、任务已完成 | 开发 + 预发布环境 |
| Warning | 可恢复的异常:触发重试、使用降级方案 | 所有环境 —— 生产环境默认 |
| Error | 需要关注的失败操作 | 所有环境 |
| Fatal/Critical | 应用无法继续运行 | 所有环境 |
为何生产环境默认使用Warning级别:大规模下的Information级请求日志会占用大量日志存储成本,且会掩盖关键信号。通过命名空间覆盖仅保留真正的业务事件(详见serilog Skill的模式)。
MinimumLevel.OverrideAnti-patterns
反模式
Don't Log Sensitive Data
请勿记录敏感数据
csharp
// 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);csharp
// 错误示例 —— 记录凭证
logger.LogInformation("User logged in: {Email} with password {Password}", email, password);
// 正确示例 —— 记录标识符,绝不记录机密信息或PII(Information级别)
logger.LogInformation("User {UserId} logged in", userId);Don't Skip Health Check Tags
请勿省略健康检查标签
csharp
// 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") });csharp
// 错误示例 —— 所有检查同时用于存活检查和就绪检查
app.MapHealthChecks("/health");
// 正确示例 —— 将存活检查(我是否在运行?)与就绪检查(我能否处理流量?)分开
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
请勿重复实现已有Skill提供的功能
csharp
// 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)...);csharp
// 错误示例 —— 凭记忆手动实现Serilog启动逻辑
builder.Host.UseSerilog(...); // 旧版API —— serilog Skill禁止使用该方式
// 正确示例 —— 加载serilog Skill并使用其两阶段AddSerilog()启动逻辑
builder.Services.AddSerilog((services, lc) => lc.ReadFrom.Configuration(builder.Configuration)...);Decision Guide
决策指南
| Scenario | Recommendation |
|---|---|
| Application logging setup | Load |
| Distributed tracing / metrics | Load |
| Custom business metrics | |
| Request tracing | Correlation ID middleware (this skill) |
| Container health | |
| Log storage | Seq (development), Elastic/Grafana/OTLP backend (production) |
| Log levels | Debug in dev, Information in staging, Warning default in production |
| 场景 | 推荐方案 |
|---|---|
| 应用日志配置 | 加载 |
| 分布式追踪 / 指标 | 加载 |
| 自定义业务指标 | |
| 请求追踪 | 关联ID中间件(本Skill) |
| 容器健康检查 | |
| 日志存储 | Seq(开发环境)、Elastic/Grafana/OTLP后端(生产环境) |
| 日志级别 | 开发环境用Debug,预发布环境用Information,生产环境默认用Warning |