为什么需要结构化日志

传统日志是一行行拼接的字符串:时间、级别、消息堆在一起,出问题时靠肉眼和正则去抠字段。结构化日志把每条记录拆成「键值对」:用户标识、订单号、耗时、状态码各自独立成字段,查询、聚合、告警都建立在字段之上。

.NET 生态里最常用的结构化日志库是 Serilog:它把日志当作事件而非文本,每个事件可以附带任意数量的属性。配合控制台、文件、数据库、云日志服务等接收器,一套代码多处投递。

日志不是给人看的,是给机器分析的;结构化是分析的前提。
结构化四要素
  • 时间精确到毫秒。
  • 级别Trace 到 Fatal。
  • 字段业务键值对。
  • 链路TraceId 贯穿。
.NET 日志与监控配图:一叠空白的发光文档纸页,旁边是上升折线仪表盘与放大镜轮廓
日志结构化,问题可检索。

Serilog 接入与配置

接入 Serilog 分三步:安装包、配置接收器、替换日志工厂。最小化配置只需要 NuGet 三个包:Serilog、Serilog.AspNetCore、Serilog.Sinks.Console。

// Program.cs 最小接入
using Serilog;
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.Console()
    .WriteTo.File("logs/app-.log",
        rollingInterval: RollingInterval.Day)
    .CreateLogger();

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog();

UseSerilog 会把框架自身日志、第三方库日志统一汇入 Serilog 管道,一次配置全站生效。配置文件里也能通过 appsettings.json 的 Serilog 节管理接收器,运维改配置不用重新编译。

结构化字段与过滤

写日志时用模板字符串占位符,Serilog 会把参数变成字段,而不是拼进消息文本。这样既能保持消息可读,又能让字段参与筛选。

// 用命名占位符,参数自动成为结构化字段
_logger.LogInformation(
    "用户 {UserId} 完成订单 {OrderId},耗时 {ElapsedMs}ms",
    userId, orderId, sw.ElapsedMilliseconds);

过滤靠 MinimumLevel 与子级别:开发环境开 Debug,生产只留 Information 以上;某个命名空间可以单独降噪,比如把框架自带的 HealthCheck 日志压到 Warning,避免每分钟一次的心跳刷屏。

OpenTelemetry 与指标链路

日志解决「发生了什么」,指标回答「有多少、有多快」,链路追踪解释「一次请求经过了谁」。OpenTelemetry 是三者统一的标准:同一套 SDK 采集 Trace、Metric、Log,统一导出到后端。

// OpenTelemetry 注册:链路 + 指标 + 日志
builder.Services.AddOpenTelemetry()
    .WithTracing(t => t
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddOtlpExporter())
    .WithMetrics(m => m
        .AddAspNetCoreInstrumentation()
        .AddOtlpExporter());

OTLP 导出器可以把数据送到 Prometheus、Jaeger、SkyWalking 等后端。关键是把 TraceId 注入日志,让日志与链路能在同一把钥匙下互相跳转。

健康检查与告警

应用有没有活着,靠健康检查探针说了算。ASP.NET Core 内置 HealthChecks,可以检查数据库连通、外部依赖可用、磁盘空间等,输出 JSON 供负载均衡与监控平台消费。

builder.Services.AddHealthChecks()
    .AddDbContextCheck<AppDbContext>()
    .AddCheck("disk", () =>
        DriveInfo.GetDrives().All(d => d.AvailableFreeSpace > 1L << 30)
            ? HealthCheckResult.Healthy()
            : HealthCheckResult.Unhealthy("磁盘空间不足"));

告警规则一般落在监控平台上:连续三次探针失败、错误日志五分钟内超过阈值、接口 P95 超时,各配一条通知渠道。告警要可执行,每条都指向一份处理手册,否则只是噪音。