第10章:日志系统概述——为什么需要日志?

说实话,我见过太多团队把日志当成「可有可无」的东西。

项目初期,大家忙着写业务代码。日志?随便打几条就行。等到线上出了故障,一群人对着控制台干瞪眼。那种感觉,我经历过不止一次。

日志不是写给机器看的。它是写给「未来的你」看的。那个凌晨三点被电话叫醒的你,那个面对用户投诉却无从下手的你。

为什么需要日志?

简单说,日志就是系统的「黑匣子」。飞机出事要找黑匣子,系统出问题就要翻日志。

具体来说,日志有这几个核心作用:

  • 故障排查:系统崩了,你总得知道崩在哪吧?日志就是第一手线索。
  • 性能分析:哪个接口慢?哪个SQL耗时?日志里一清二楚。
  • 安全审计:谁在什么时候做了什么操作?日志是铁证。
  • 业务监控:订单量、用户活跃度,这些都能从日志里提炼出来。

我的经验:有一次线上服务频繁重启,排查了两小时没头绪。最后翻出GC日志,发现是内存泄漏。如果当时没开日志,这问题可能得折腾一整天。

日志的级别

日志级别不是随便定的。每个级别都有它的使用场景。我见过有人把所有日志都打成Info,结果生产环境一天几个G的日志,真正有用的信息全被淹没了。

级别 含义 使用场景 我建议
Trace 最详细的跟踪信息 开发调试,几乎不用在生产环境 开发环境开,生产环境关
Debug 调试信息 开发阶段,定位具体代码路径 测试环境可以开,生产慎用
Info 常规信息 记录系统正常运行状态 生产环境主力,但别滥用
Warn 警告 潜在问题,不影响当前运行 一定要关注,往往是故障前兆
Error 错误 功能不可用,需要人工介入 必须告警,不能忽略
Fatal 致命错误 系统即将崩溃或已崩溃 立刻通知值班人员

避坑指南:我曾经在一个项目里,把数据库连接失败打成了Warn级别。结果连接池耗尽,服务挂了,告警系统却没触发。从那以后,凡是影响核心功能的异常,我至少打Error。

日志框架选型

.NET生态里,主流的日志框架就三个:NLog、Serilog、Log4Net。选哪个?我个人的经验是:看场景。

NLog

老牌框架,稳定可靠。配置灵活,性能也不错。我最早用的就是它。

  • 优点:配置简单,上手快,文档齐全
  • 缺点:结构化日志支持一般,扩展性不如Serilog
  • 适合:中小型项目,传统架构

Serilog

现在最火的日志框架。它的核心思想是「结构化日志」。

  • 优点:原生支持结构化,和ELK、Seq等配合完美
  • 缺点:学习曲线稍陡,配置项多
  • 适合:微服务、云原生、需要集中式日志分析的场景

Log4Net

从Java移植过来的老将。现在用的人越来越少了。

  • 优点:稳定,配置方式和Log4j一致
  • 缺点:性能一般,社区活跃度低
  • 适合:遗留系统维护,或者团队有Java背景

我的选择:新项目我基本都用Serilog。为什么?因为现在做全链路监控,日志必须结构化。你想想看,如果日志只是一堆字符串,你怎么做关联分析?怎么做链路追踪?Serilog的@操作符能把对象序列化成结构化数据,这一点太香了。

一个简单的Serilog示例

光说不练假把式。我贴一段实际项目里的配置代码:

// 安装包:Serilog.AspNetCore, Serilog.Sinks.Console, Serilog.Sinks.File

using Serilog;

Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .WriteTo.Console()
    .WriteTo.File("logs/myapp-.log", rollingInterval: RollingInterval.Day)
    .Enrich.WithProperty("Application", "MyApp")
    .CreateLogger();

try
{
    Log.Information("应用启动");
    CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
    Log.Fatal(ex, "应用启动失败");
}
finally
{
    Log.CloseAndFlush();
}

这段代码做了几件事:

  • 只记录Info及以上级别的日志
  • 把微软自家的日志降到Warn,避免刷屏
  • 同时输出到控制台和文件
  • 每天滚动一个日志文件
  • 给每条日志打上应用名标签

注意Log.CloseAndFlush()一定要写在finally块里。我见过有人忘了这行,结果应用退出时日志没刷完,丢了几条关键信息。排查问题的时候,那叫一个抓狂。

小结

日志这事,说简单也简单,说复杂也复杂。简单的是API调用,复杂的是怎么用好它。

我的建议是:

  1. 先选一个框架用起来,别纠结
  2. 级别要分清楚,别什么都打Info
  3. 结构化日志是趋势,早点上手
  4. 日志不是写完了就完事,要有人看、有人分析

下一章,我会讲怎么把日志和全链路监控结合起来。到时候你就知道,日志不只是「记录」,更是「洞察」。