dotnet-structured-logging

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

dotnet-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

架构选项

PlatformIngestStorageQueryBest for
ELK (Elasticsearch, Logstash, Kibana)Logstash / FilebeatElasticsearchKQL in KibanaLarge-scale, flexible schema, full-text search
SeqHTTP API / Serilog sinkBuilt-inSeq signal expressions.NET-native, developer-friendly, structured queries
Grafana LokiPromtail / OTel CollectorLoki (label-indexed)LogQLCost-effective, Grafana ecosystem, label-based queries
Azure MonitorOTel Collector / Application Insights SDKLog Analytics workspaceKQL (Kusto)Azure-native, integrated alerting, cost management
平台数据摄入存储查询适用场景
ELK (Elasticsearch, Logstash, Kibana)Logstash / FilebeatElasticsearchKibana中的KQL大规模场景、灵活schema、全文检索
SeqHTTP API / Serilog接收器内置存储Seq信号表达式.NET原生、开发者友好、结构化查询
Grafana LokiPromtail / OTel CollectorLoki(标签索引)LogQL成本可控、Grafana生态、基于标签的查询
Azure MonitorOTel Collector / Application Insights SDKLog 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 scrub
The 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 scrub
OpenTelemetry Collector作为厂商中立的日志路由器。应用通过OTLP发送日志;Collector负责过滤、采样、增强,并路由至一个或多个后端。这种方式实现了应用与后端选择的解耦。
yaml
undefined

otel-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 (
builder.Logging.AddOpenTelemetry()
), see [skill:dotnet-observability]. The OTLP endpoint is configured via environment variables (
OTEL_EXPORTER_OTLP_ENDPOINT
), keeping application code backend-agnostic.

.NET应用端的OTLP日志导出配置(
builder.Logging.AddOpenTelemetry()
)请参考 [skill:dotnet-observability]。OTLP端点通过环境变量(
OTEL_EXPORTER_OTLP_ENDPOINT
)配置,保持应用代码与后端无关。

Structured 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)

undefined
undefined

Find 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"
undefined
TraceId: "0af7651916cd43dd8448eb211c80319c" AND @timestamp >= "2025-01-15T10:00:00"
undefined

Seq Signal Expressions

Seq信号表达式

undefined
undefined

Find 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)

undefined
undefined

Filter 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)
undefined
sum(rate({service_name=~".+"} |= "Error" [5m])) by (service_name)
undefined

Azure 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 asc

kusto
// 查找特定订单的错误日志
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 asc

Log 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

采样策略

StrategyHow it worksUse when
Head-basedDecide to sample before processingConsistent per-request; simple to implement
Tail-basedDecide to sample after processingKeep all errors/slow requests, drop routine logs
Level-basedSample by severityAlways keep Warning+, sample Debug/Info
DynamicAdjust rate based on volumeHandle traffic spikes without config changes
策略工作原理适用场景
头部采样在处理前决定是否采样每个请求保持一致;实现简单
尾部采样在处理后决定是否采样保留所有错误/慢请求,丢弃常规日志
基于级别采样按日志级别采样始终保留Warning及以上级别日志,对Debug/Info级别日志采样
动态采样根据日志量调整采样率无需修改配置即可应对流量峰值

OTel Collector Log Filtering

OTel Collector日志过滤

The
filter
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.
Note: The
tail_sampling
processor operates on traces (spans), not logs. For log volume management, use the
filter
and
transform
processors instead.
yaml
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
处理器会在日志到达导出器前,在管道层面丢弃日志记录。使用它排除低级别噪音日志,减少存储容量。
注意:
tail_sampling
处理器针对**追踪(span)**操作,而非日志。日志容量管理请使用
filter
transform
处理器。
yaml
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

容量管理检查清单

  1. Set retention policies per index/stream (e.g., 30 days for Info, 90 days for Error)
  2. Use log level filtering to suppress noisy framework categories at the source
  3. Exclude health check endpoints from request logging
  4. Apply index lifecycle management (ILM in Elasticsearch, retention policies in Loki)
  5. Monitor ingestion rates and set budget alerts on storage costs

  1. 设置保留策略:按索引/流设置(例如Info级别保留30天,Error级别保留90天)
  2. 使用日志级别过滤:在源头抑制噪音框架类别的日志
  3. 排除健康检查端点:不记录健康检查请求日志
  4. 应用索引生命周期管理(Elasticsearch中的ILM、Loki中的保留策略)
  5. 监控摄入速率:设置存储成本预算告警

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: delete
yaml
processors:
  attributes:
    actions:
      # 使用正则表达式掩码邮箱地址
      - key: user.email
        action: update
        value: "***@redacted.com"
      # 完全移除敏感属性
      - key: http.request.header.authorization
        action: delete
      - key: user.password
        action: delete

PII Scrubbing Checklist

PII清理检查清单

  1. Identify PII fields -- email, phone, IP, SSN, credit card, auth tokens, cookies
  2. Apply at the earliest point -- enricher or OTel processor, not at query time
  3. Audit log templates -- ensure structured log templates do not capture PII as named properties
  4. Test with compliance team -- validate scrubbing rules against regulatory requirements
  5. Use separate retention for audit logs that legitimately require PII (with encryption at rest)

  1. 识别PII字段:邮箱、电话、IP地址、社保号、信用卡、认证令牌、Cookie
  2. 尽早应用清理规则:在增强器或OTel处理器层面处理,而非查询时
  3. 审核日志模板:确保结构化日志模板不会将PII捕获为命名属性
  4. 与合规团队协作测试:验证清理规则符合监管要求
  5. 为合法需要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
traceparent
header, which propagates automatically through
HttpClient
when OpenTelemetry instrumentation is configured (see [skill:dotnet-observability]). All log events emitted within a traced request include
TraceId
and
SpanId
properties.
csharp
// 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
traceparent
头,当配置OpenTelemetry instrumentation后,它会通过
HttpClient
自动传播(详见 [skill:dotnet-observability])。追踪请求中生成的所有日志事件都会包含
TraceId
SpanId
属性。
csharp
// 查询分布式操作跨服务的所有日志
// 在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

关联分析最佳实践

PracticeRationale
Always include
TraceId
in log output
Enables log-to-trace joins in observability platforms
Use
CorrelationId
for business flows
Survives async gaps where trace context resets
Store correlation IDs in message headersEnables end-to-end tracing through queues
Include correlation in error responsesEnables support teams to look up the full trace
Use Serilog
LogContext.PushProperty
or MEL
BeginScope
Automatically attaches to all log events in scope

实践理由
始终在日志输出中包含
TraceId
支持在可观测平台中实现日志与追踪的关联
为业务流使用
CorrelationId
在追踪上下文重置的异步场景中依然有效
将关联ID存储在消息头中支持跨队列的端到端追踪
在错误响应中包含关联信息支持运维团队查找完整追踪链路
使用Serilog
LogContext.PushProperty
或MEL
BeginScope
自动附加到范围内的所有日志事件

Agent Gotchas

注意事项

  1. 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].
  2. 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.
  3. 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.
  4. Do not hardcode aggregation platform endpoints in application code -- use environment variables (
    OTEL_EXPORTER_OTLP_ENDPOINT
    ) or configuration so the same image works across environments.
  5. 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.
  6. 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).

  1. 不要混淆日志生成与日志管道:本技能涵盖管道、查询与运维。Serilog/MEL配置、增强器、接收器注册及源生成LoggerMessage请参考 [skill:dotnet-observability]。
  2. 不要在生产日志中存储PII:在管道层面应用掩码增强器或OTel处理器规则。存储后再脱敏不符合合规要求。
  3. 不要为高吞吐量服务跳过日志采样:未采样的Debug/Info日志在每秒处理数千请求的服务中会耗尽存储并降低查询性能。使用尾部采样保留所有错误和慢请求。
  4. 不要在应用代码中硬编码聚合平台端点:使用环境变量(
    OTEL_EXPORTER_OTLP_ENDPOINT
    )或配置,确保同一镜像可在多环境中运行。
  5. 不要仅依赖TraceId进行业务关联:追踪上下文在异步边界(消息队列、定时任务)会重置。为跨这些边界的工作流添加显式业务关联ID。
  6. 不要忘记设置保留策略:无保留策略的日志会无限累积,增加成本并降低查询速度。按日志级别设置保留期(例如Info级别30天,Error级别90天)。

References

参考资料