dotnet-structured-logging
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesedotnet-structured-logging
.NET结构化日志处理
Log pipeline design and operations for .NET distributed systems. Covers log aggregation architecture (ELK, Seq, Grafana Loki), structured query patterns for each platform, log sampling and volume management strategies, PII scrubbing and destructuring policies, and cross-service correlation beyond single-service log scopes. This skill addresses what happens after log emission -- the pipeline, query, and operations layer.
Out of scope: Log emission mechanics (Serilog/NLog/MEL configuration, source-generated LoggerMessage, enrichers, single-service log scopes, sink registration, OTel logging export) -- see [skill:dotnet-observability]. Application configuration and options pattern -- see [skill:dotnet-csharp-configuration]. Distributed tracing setup and trace context propagation -- see [skill:dotnet-observability].
Cross-references: [skill:dotnet-observability] for log emission, Serilog/MEL configuration, and OpenTelemetry logging export, [skill:dotnet-csharp-configuration] for appsettings.json configuration patterns used in log pipeline setup.
适用于.NET分布式系统的日志管道设计与运维。涵盖日志聚合架构(ELK、Seq、Grafana Loki)、各平台的结构化查询模式、日志采样与容量管理策略、PII清理与解构规则,以及超越单服务日志范围的跨服务关联分析。本技能聚焦日志生成之后的环节——管道、查询与运维层。
超出范围内容:日志生成机制(Serilog/NLog/MEL配置、源生成LoggerMessage、增强器、单服务日志范围、接收器注册、OTel日志导出)——详见 [skill:dotnet-observability]。应用配置与选项模式——详见 [skill:dotnet-csharp-configuration]。分布式追踪设置与追踪上下文传播——详见 [skill:dotnet-observability]。
交叉引用:日志生成、Serilog/MEL配置及OpenTelemetry日志导出请参考 [skill:dotnet-observability],日志管道设置中用到的appsettings.json配置模式请参考 [skill:dotnet-csharp-configuration]。
Log Aggregation Architecture
日志聚合架构
Architecture Options
架构选项
| Platform | Ingest | Storage | Query | Best for |
|---|---|---|---|---|
| ELK (Elasticsearch, Logstash, Kibana) | Logstash / Filebeat | Elasticsearch | KQL in Kibana | Large-scale, flexible schema, full-text search |
| Seq | HTTP API / Serilog sink | Built-in | Seq signal expressions | .NET-native, developer-friendly, structured queries |
| Grafana Loki | Promtail / OTel Collector | Loki (label-indexed) | LogQL | Cost-effective, Grafana ecosystem, label-based queries |
| Azure Monitor | OTel Collector / Application Insights SDK | Log Analytics workspace | KQL (Kusto) | Azure-native, integrated alerting, cost management |
| 平台 | 数据摄入 | 存储 | 查询 | 适用场景 |
|---|---|---|---|---|
| ELK (Elasticsearch, Logstash, Kibana) | Logstash / Filebeat | Elasticsearch | Kibana中的KQL | 大规模场景、灵活schema、全文检索 |
| Seq | HTTP API / Serilog接收器 | 内置存储 | Seq信号表达式 | .NET原生、开发者友好、结构化查询 |
| Grafana Loki | Promtail / OTel Collector | Loki(标签索引) | LogQL | 成本可控、Grafana生态、基于标签的查询 |
| Azure Monitor | OTel Collector / Application Insights SDK | Log Analytics工作区 | KQL (Kusto) | Azure原生、集成告警、成本管理 |
Recommended Pipeline Patterns
推荐管道模式
Pattern 1: OTel Collector as central router
App (OTLP) --> OTel Collector --> Elasticsearch / Loki / Azure Monitor
|
+--> Sampling / filtering / PII scrubThe OpenTelemetry Collector acts as a vendor-neutral log router. Applications emit logs via OTLP; the collector handles filtering, sampling, enrichment, and routing to one or more backends. This decouples applications from backend choice.
yaml
undefined模式1:OTel Collector作为中央路由器
App (OTLP) --> OTel Collector --> Elasticsearch / Loki / Azure Monitor
|
+--> Sampling / filtering / PII scrubOpenTelemetry Collector作为厂商中立的日志路由器。应用通过OTLP发送日志;Collector负责过滤、采样、增强,并路由至一个或多个后端。这种方式实现了应用与后端选择的解耦。
yaml
undefinedotel-collector-config.yaml
otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
processors:
batch:
timeout: 5s
send_batch_size: 1024
filter:
logs:
exclude:
match_type: strict
bodies:
- "Health check endpoint hit"
exporters:
elasticsearch:
endpoints: ["https://es-cluster:9200"]
logs_index: "app-logs"
loki:
endpoint: "http://loki:3100/loki/api/v1/push"
service:
pipelines:
logs:
receivers: [otlp]
processors: [batch, filter]
exporters: [elasticsearch, loki]
**Pattern 2: Direct sink (smaller deployments)**
App (Serilog) --> Seq / Elasticsearch sink
For smaller systems or development environments, Serilog sinks write directly to the aggregation platform. This avoids the OTel Collector but couples the application to the backend.receivers:
otlp:
protocols:
grpc:
endpoint: "0.0.0.0:4317"
http:
endpoint: "0.0.0.0:4318"
processors:
batch:
timeout: 5s
send_batch_size: 1024
filter:
logs:
exclude:
match_type: strict
bodies:
- "Health check endpoint hit"
exporters:
elasticsearch:
endpoints: ["https://es-cluster:9200"]
logs_index: "app-logs"
loki:
endpoint: "http://loki:3100/loki/api/v1/push"
service:
pipelines:
logs:
receivers: [otlp]
processors: [batch, filter]
exporters: [elasticsearch, loki]
**模式2:直接接收器(小型部署)**
App (Serilog) --> Seq / Elasticsearch sink
针对小型系统或开发环境,Serilog接收器直接写入聚合平台。这种方式无需OTel Collector,但会将应用与后端绑定。.NET Application OTLP Configuration
.NET应用OTLP配置
For .NET application-side OTLP log export configuration (), see [skill:dotnet-observability]. The OTLP endpoint is configured via environment variables (), keeping application code backend-agnostic.
builder.Logging.AddOpenTelemetry()OTEL_EXPORTER_OTLP_ENDPOINT.NET应用端的OTLP日志导出配置()请参考 [skill:dotnet-observability]。OTLP端点通过环境变量()配置,保持应用代码与后端无关。
builder.Logging.AddOpenTelemetry()OTEL_EXPORTER_OTLP_ENDPOINTStructured Query Patterns
结构化查询模式
Structured logs store each property as a queryable field. The query syntax differs by platform but the concepts are consistent: filter by property name, value, severity, and time range.
结构化日志将每个属性存储为可查询字段。不同平台的查询语法有所差异,但核心概念一致:按属性名、值、级别和时间范围过滤。
Kibana KQL (Elasticsearch / ELK)
Kibana KQL(Elasticsearch / ELK)
undefinedundefinedFind errors for a specific order
查找特定订单的错误日志
level: "Error" AND OrderId: "abc-123"
level: "Error" AND OrderId: "abc-123"
Find slow operations (custom Duration property)
查找慢操作(自定义Duration属性)
Duration > 5000 AND ServiceName: "order-api"
Duration > 5000 AND ServiceName: "order-api"
Wildcard on message template
消息模板通配符匹配
message: "Failed to process*" AND NOT level: "Debug"
message: "Failed to process*" AND NOT level: "Debug"
Time-scoped with correlation
带关联的时间范围查询
TraceId: "0af7651916cd43dd8448eb211c80319c" AND @timestamp >= "2025-01-15T10:00:00"
undefinedTraceId: "0af7651916cd43dd8448eb211c80319c" AND @timestamp >= "2025-01-15T10:00:00"
undefinedSeq Signal Expressions
Seq信号表达式
undefinedundefinedFind errors for a specific order
查找特定订单的错误日志
@Level = 'Error' and OrderId = 'abc-123'
@Level = 'Error' and OrderId = 'abc-123'
Find slow operations
查找慢操作
Duration > 5000 and Application = 'order-api'
Duration > 5000 and Application = 'order-api'
Free-text search combined with structured filter
自由文本搜索结合结构化过滤
@Message like '%timeout%' and @Level in ['Warning', 'Error']
@Message like '%timeout%' and @Level in ['Warning', 'Error']
Correlation across services
跨服务关联查询
TraceId = '0af7651916cd43dd8448eb211c80319c'
Seq signals are saved queries that trigger alerts. Define signals for recurring patterns (e.g., "Payment failures > 10/min") and attach notification channels.TraceId = '0af7651916cd43dd8448eb211c80319c'
Seq信号是可触发告警的保存查询。为重复出现的模式定义信号(例如“支付失败 > 10次/分钟”)并绑定通知渠道。Grafana LogQL (Loki)
Grafana LogQL(Loki)
undefinedundefinedFilter by labels then regex on log line
按标签过滤后对日志行进行正则匹配
{service_name="order-api"} |= "Error" | json | OrderId="abc-123"
{service_name="order-api"} |= "Error" | json | OrderId="abc-123"
Structured field extraction and filtering
结构化字段提取与过滤
{service_name="order-api"} | json | Duration > 5000
{service_name="order-api"} | json | Duration > 5000
Count errors per service over time (for dashboards)
按时间统计各服务错误数(用于仪表盘)
sum(rate({service_name=~".+"} |= "Error" [5m])) by (service_name)
undefinedsum(rate({service_name=~".+"} |= "Error" [5m])) by (service_name)
undefinedAzure Monitor KQL (Kusto)
Azure Monitor KQL(Kusto)
kusto
// Find errors for a specific order
traces
| where severityLevel >= 3
| where customDimensions.OrderId == "abc-123"
| order by timestamp desc
// Slow operations
traces
| where toint(customDimensions.Duration) > 5000
| where cloud_RoleName == "order-api"
// Cross-service correlation
union traces, exceptions
| where operation_Id == "0af7651916cd43dd8448eb211c80319c"
| order by timestamp asckusto
// 查找特定订单的错误日志
traces
| where severityLevel >= 3
| where customDimensions.OrderId == "abc-123"
| order by timestamp desc
// 慢操作查询
traces
| where toint(customDimensions.Duration) > 5000
| where cloud_RoleName == "order-api"
// 跨服务关联查询
union traces, exceptions
| where operation_Id == "0af7651916cd43dd8448eb211c80319c"
| order by timestamp ascLog Sampling and Volume Management
日志采样与容量管理
High-throughput systems can generate millions of log events per minute. Without sampling, storage costs and query performance degrade rapidly.
高吞吐量系统每分钟可生成数百万条日志事件。若不进行采样,存储成本和查询性能会迅速下降。
Sampling Strategies
采样策略
| Strategy | How it works | Use when |
|---|---|---|
| Head-based | Decide to sample before processing | Consistent per-request; simple to implement |
| Tail-based | Decide to sample after processing | Keep all errors/slow requests, drop routine logs |
| Level-based | Sample by severity | Always keep Warning+, sample Debug/Info |
| Dynamic | Adjust rate based on volume | Handle traffic spikes without config changes |
| 策略 | 工作原理 | 适用场景 |
|---|---|---|
| 头部采样 | 在处理前决定是否采样 | 每个请求保持一致;实现简单 |
| 尾部采样 | 在处理后决定是否采样 | 保留所有错误/慢请求,丢弃常规日志 |
| 基于级别采样 | 按日志级别采样 | 始终保留Warning及以上级别日志,对Debug/Info级别日志采样 |
| 动态采样 | 根据日志量调整采样率 | 无需修改配置即可应对流量峰值 |
OTel Collector Log Filtering
OTel Collector日志过滤
The processor in the OTel Collector drops log records at the pipeline level before they reach exporters. Use it to exclude noisy low-severity logs and reduce storage volume.
filterNote: The processor operates on traces (spans), not logs. For log volume management, use the and processors instead.
tail_samplingfiltertransformyaml
processors:
filter:
logs:
exclude:
match_type: regexp
# Drop Debug and Trace logs at the collector level
severity_texts: ["DEBUG", "TRACE"]
exclude:
match_type: strict
# Exclude health check noise
bodies:
- "Health check endpoint hit"
transform:
log_statements:
- context: log
conditions:
# Keep all Warning+ logs unconditionally
- severity_number >= SEVERITY_NUMBER_WARN
statements: []OTel Collector中的处理器会在日志到达导出器前,在管道层面丢弃日志记录。使用它排除低级别噪音日志,减少存储容量。
filter注意:处理器针对**追踪(span)**操作,而非日志。日志容量管理请使用和处理器。
tail_samplingfiltertransformyaml
processors:
filter:
logs:
exclude:
match_type: regexp
# 在Collector层面丢弃Debug和Trace日志
severity_texts: ["DEBUG", "TRACE"]
exclude:
match_type: strict
# 排除健康检查噪音
bodies:
- "Health check endpoint hit"
transform:
log_statements:
- context: log
conditions:
# 无条件保留所有Warning及以上级别日志
- severity_number >= SEVERITY_NUMBER_WARN
statements: []Application-Level Sampling with Serilog
基于Serilog的应用层采样
csharp
// Serilog.Expressions package for conditional log filtering
builder.Host.UseSerilog((context, loggerConfiguration) =>
{
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
// Drop health check logs entirely
.Filter.ByExcluding("RequestPath = '/health/ready'")
// Sample Debug logs at 10%
.Filter.ByExcluding(
"@Level = 'Debug' and Hash(@i) % 10 != 0");
});Key packages:
xml
<PackageReference Include="Serilog.Expressions" Version="5.*" />csharp
// 使用Serilog.Expressions包实现条件日志过滤
builder.Host.UseSerilog((context, loggerConfiguration) =>
{
loggerConfiguration
.ReadFrom.Configuration(context.Configuration)
// 完全丢弃健康检查日志
.Filter.ByExcluding("RequestPath = '/health/ready'")
// 对Debug日志进行10%采样
.Filter.ByExcluding(
"@Level = 'Debug' and Hash(@i) % 10 != 0");
});关键包:
xml
<PackageReference Include="Serilog.Expressions" Version="5.*" />Volume Management Checklist
容量管理检查清单
- Set retention policies per index/stream (e.g., 30 days for Info, 90 days for Error)
- Use log level filtering to suppress noisy framework categories at the source
- Exclude health check endpoints from request logging
- Apply index lifecycle management (ILM in Elasticsearch, retention policies in Loki)
- Monitor ingestion rates and set budget alerts on storage costs
- 设置保留策略:按索引/流设置(例如Info级别保留30天,Error级别保留90天)
- 使用日志级别过滤:在源头抑制噪音框架类别的日志
- 排除健康检查端点:不记录健康检查请求日志
- 应用索引生命周期管理(Elasticsearch中的ILM、Loki中的保留策略)
- 监控摄入速率:设置存储成本预算告警
PII Scrubbing and Destructuring Policies
PII清理与解构规则
Logs must not contain personally identifiable information (PII) in production. GDPR, HIPAA, and SOC 2 require that sensitive data is masked or excluded from log storage.
生产环境日志不得包含个人身份信息(PII)。GDPR、HIPAA和SOC 2要求敏感数据必须被掩码或排除在日志存储之外。
Property-Level Masking with Enrichers
基于增强器的属性级掩码
csharp
// Enricher that masks known-sensitive properties on every log event
public sealed class PiiMaskingEnricher : ILogEventEnricher
{
private static readonly HashSet<string> s_sensitiveKeys = new(
StringComparer.OrdinalIgnoreCase)
{
"Email", "PhoneNumber", "IpAddress",
"CreditCard", "SSN", "Password"
};
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory factory)
{
var propertiesToMask = logEvent.Properties
.Where(p => s_sensitiveKeys.Contains(p.Key))
.Select(p => p.Key)
.ToList();
foreach (var key in propertiesToMask)
{
logEvent.AddOrUpdateProperty(
factory.CreateProperty(key, "***REDACTED***"));
}
}
}
// Registration
loggerConfiguration.Enrich.With<PiiMaskingEnricher>();csharp
// 对每条日志事件中的已知敏感属性进行掩码的增强器
public sealed class PiiMaskingEnricher : ILogEventEnricher
{
private static readonly HashSet<string> s_sensitiveKeys = new(
StringComparer.OrdinalIgnoreCase)
{
"Email", "PhoneNumber", "IpAddress",
"CreditCard", "SSN", "Password"
};
public void Enrich(LogEvent logEvent, ILogEventPropertyFactory factory)
{
var propertiesToMask = logEvent.Properties
.Where(p => s_sensitiveKeys.Contains(p.Key))
.Select(p => p.Key)
.ToList();
foreach (var key in propertiesToMask)
{
logEvent.AddOrUpdateProperty(
factory.CreateProperty(key, "***REDACTED***"));
}
}
}
// 注册增强器
loggerConfiguration.Enrich.With<PiiMaskingEnricher>();OTel Collector Attribute Processing
OTel Collector属性处理
yaml
processors:
attributes:
actions:
# Mask email addresses using regex
- key: user.email
action: update
value: "***@redacted.com"
# Remove sensitive attributes entirely
- key: http.request.header.authorization
action: delete
- key: user.password
action: deleteyaml
processors:
attributes:
actions:
# 使用正则表达式掩码邮箱地址
- key: user.email
action: update
value: "***@redacted.com"
# 完全移除敏感属性
- key: http.request.header.authorization
action: delete
- key: user.password
action: deletePII Scrubbing Checklist
PII清理检查清单
- Identify PII fields -- email, phone, IP, SSN, credit card, auth tokens, cookies
- Apply at the earliest point -- enricher or OTel processor, not at query time
- Audit log templates -- ensure structured log templates do not capture PII as named properties
- Test with compliance team -- validate scrubbing rules against regulatory requirements
- Use separate retention for audit logs that legitimately require PII (with encryption at rest)
- 识别PII字段:邮箱、电话、IP地址、社保号、信用卡、认证令牌、Cookie
- 尽早应用清理规则:在增强器或OTel处理器层面处理,而非查询时
- 审核日志模板:确保结构化日志模板不会将PII捕获为命名属性
- 与合规团队协作测试:验证清理规则符合监管要求
- 为合法需要PII的审计日志设置单独保留策略(启用静态加密)
Cross-Service Correlation
跨服务关联分析
In distributed systems, a single user request may traverse multiple services. Correlation enables tracing a request across all services and reconstructing the full event timeline.
在分布式系统中,单个用户请求可能会流经多个服务。关联分析可追踪请求在所有服务中的路径,重建完整事件时间线。
W3C Trace Context Correlation
W3C追踪上下文关联
The primary correlation mechanism is the W3C header, which propagates automatically through when OpenTelemetry instrumentation is configured (see [skill:dotnet-observability]). All log events emitted within a traced request include and properties.
traceparentHttpClientTraceIdSpanIdcsharp
// Query all logs for a distributed operation across services
// In Seq:
TraceId = '0af7651916cd43dd8448eb211c80319c'
// In Kibana:
TraceId: "0af7651916cd43dd8448eb211c80319c"
// In Azure Monitor:
traces | where operation_Id == "0af7651916cd43dd8448eb211c80319c"主要的关联机制是W3C 头,当配置OpenTelemetry instrumentation后,它会通过自动传播(详见 [skill:dotnet-observability])。追踪请求中生成的所有日志事件都会包含和属性。
traceparentHttpClientTraceIdSpanIdcsharp
// 查询分布式操作跨服务的所有日志
// 在Seq中:
TraceId = '0af7651916cd43dd8448eb211c80319c'
// 在Kibana中:
TraceId: "0af7651916cd43dd8448eb211c80319c"
// 在Azure Monitor中:
traces | where operation_Id == "0af7651916cd43dd8448eb211c80319c"Custom Correlation IDs
自定义关联ID
When trace context is insufficient (e.g., async workflows spanning message queues, batch jobs, or external system callbacks), add custom correlation IDs:
csharp
// Propagate a business correlation ID through Serilog LogContext
public sealed class CorrelationIdMiddleware(RequestDelegate next)
{
private const string CorrelationHeader = "X-Correlation-Id";
public async Task InvokeAsync(HttpContext context)
{
var correlationId = context.Request.Headers[CorrelationHeader]
.FirstOrDefault() ?? Guid.NewGuid().ToString("N");
context.Response.Headers[CorrelationHeader] = correlationId;
using (LogContext.PushProperty("CorrelationId", correlationId))
{
await next(context);
}
}
}
// Registration
app.UseMiddleware<CorrelationIdMiddleware>();当追踪上下文不足时(例如跨消息队列、批处理作业或外部系统回调的异步工作流),添加自定义关联ID:
csharp
// 通过Serilog LogContext传播业务关联ID
public sealed class CorrelationIdMiddleware(RequestDelegate next)
{
private const string CorrelationHeader = "X-Correlation-Id";
public async Task InvokeAsync(HttpContext context)
{
var correlationId = context.Request.Headers[CorrelationHeader]
.FirstOrDefault() ?? Guid.NewGuid().ToString("N");
context.Response.Headers[CorrelationHeader] = correlationId;
using (LogContext.PushProperty("CorrelationId", correlationId))
{
await next(context);
}
}
}
// 注册中间件
app.UseMiddleware<CorrelationIdMiddleware>();Message Queue Correlation
消息队列关联
For asynchronous messaging (Azure Service Bus, RabbitMQ), propagate correlation through message properties:
csharp
// Producer -- attach correlation to message
var message = new ServiceBusMessage(payload)
{
CorrelationId = Activity.Current?.TraceId.ToString()
?? Guid.NewGuid().ToString("N"),
ApplicationProperties =
{
["BusinessCorrelationId"] = orderId.ToString()
}
};
// Consumer -- restore correlation in log scope
processor.ProcessMessageAsync += async args =>
{
using var scope = logger.BeginScope(new Dictionary<string, object>
{
["CorrelationId"] = args.Message.CorrelationId,
["BusinessCorrelationId"] =
args.Message.ApplicationProperties["BusinessCorrelationId"]
});
logger.LogInformation("Processing message {MessageId}", args.Message.MessageId);
await ProcessAsync(args.Message, args.CancellationToken);
};针对异步消息传递(Azure Service Bus、RabbitMQ),通过消息属性传播关联信息:
csharp
// 生产者——将关联信息附加到消息
var message = new ServiceBusMessage(payload)
{
CorrelationId = Activity.Current?.TraceId.ToString()
?? Guid.NewGuid().ToString("N"),
ApplicationProperties =
{
["BusinessCorrelationId"] = orderId.ToString()
}
};
// 消费者——在日志范围中恢复关联信息
processor.ProcessMessageAsync += async args =>
{
using var scope = logger.BeginScope(new Dictionary<string, object>
{
["CorrelationId"] = args.Message.CorrelationId,
["BusinessCorrelationId"] =
args.Message.ApplicationProperties["BusinessCorrelationId"]
});
logger.LogInformation("Processing message {MessageId}", args.Message.MessageId);
await ProcessAsync(args.Message, args.CancellationToken);
};Correlation Best Practices
关联分析最佳实践
| Practice | Rationale |
|---|---|
Always include | Enables log-to-trace joins in observability platforms |
Use | Survives async gaps where trace context resets |
| Store correlation IDs in message headers | Enables end-to-end tracing through queues |
| Include correlation in error responses | Enables support teams to look up the full trace |
Use Serilog | Automatically attaches to all log events in scope |
| 实践 | 理由 |
|---|---|
始终在日志输出中包含 | 支持在可观测平台中实现日志与追踪的关联 |
为业务流使用 | 在追踪上下文重置的异步场景中依然有效 |
| 将关联ID存储在消息头中 | 支持跨队列的端到端追踪 |
| 在错误响应中包含关联信息 | 支持运维团队查找完整追踪链路 |
使用Serilog | 自动附加到范围内的所有日志事件 |
Agent Gotchas
注意事项
- Do not conflate log emission with log pipeline -- this skill covers pipeline, query, and operations. For Serilog/MEL configuration, enrichers, sink registration, and source-generated LoggerMessage, see [skill:dotnet-observability].
- Do not store PII in production logs -- apply masking enrichers or OTel processor rules at the pipeline level. Redacting after storage is insufficient for compliance.
- Do not skip log sampling for high-throughput services -- unsampled Debug/Info logs in a service handling thousands of requests per second will overwhelm storage and degrade query performance. Use tail-based sampling to keep all errors and slow requests.
- Do not hardcode aggregation platform endpoints in application code -- use environment variables () or configuration so the same image works across environments.
OTEL_EXPORTER_OTLP_ENDPOINT - Do not rely solely on TraceId for business correlation -- trace context resets at async boundaries (message queues, scheduled jobs). Add explicit business correlation IDs for workflows that span these boundaries.
- Do not forget retention policies -- logs without retention policies accumulate indefinitely, increasing costs and slowing queries. Set per-severity retention (e.g., 30 days for Info, 90 days for Error).
- 不要混淆日志生成与日志管道:本技能涵盖管道、查询与运维。Serilog/MEL配置、增强器、接收器注册及源生成LoggerMessage请参考 [skill:dotnet-observability]。
- 不要在生产日志中存储PII:在管道层面应用掩码增强器或OTel处理器规则。存储后再脱敏不符合合规要求。
- 不要为高吞吐量服务跳过日志采样:未采样的Debug/Info日志在每秒处理数千请求的服务中会耗尽存储并降低查询性能。使用尾部采样保留所有错误和慢请求。
- 不要在应用代码中硬编码聚合平台端点:使用环境变量()或配置,确保同一镜像可在多环境中运行。
OTEL_EXPORTER_OTLP_ENDPOINT - 不要仅依赖TraceId进行业务关联:追踪上下文在异步边界(消息队列、定时任务)会重置。为跨这些边界的工作流添加显式业务关联ID。
- 不要忘记设置保留策略:无保留策略的日志会无限累积,增加成本并降低查询速度。按日志级别设置保留期(例如Info级别30天,Error级别90天)。