1. 日志分析基础

1.1 日志的定义

日志(Log)是系统、应用程序或设备在运行过程中按时间顺序记录的、带有时间戳的离散事件集合。每一条日志记录通常包含事件发生的时间、来源、级别以及描述信息,是系统运行状态的“黑匣子”。

核心定义:日志是系统行为的客观、有序、可追溯的数字化记录,是故障定位的第一手证据。

1.2 日志的重要性

在 SRE 和运维工作中,日志承担着不可替代的角色,其重要性体现在以下四个维度:

维度 说明 典型场景
故障定位 通过回溯日志时间线,还原故障发生前后的系统状态 服务宕机、请求超时、数据不一致
性能分析 统计请求耗时、资源使用率、慢查询等指标 接口响应变慢、CPU 飙高、内存泄漏
安全审计 记录用户操作、权限变更、异常登录等行为 越权访问、暴力破解、数据泄露
业务监控 跟踪业务转化率、订单状态、支付回调等关键链路 订单丢失、支付失败、用户注册异常
SRE 经验:在故障定位中,日志是唯一能提供“发生了什么”和“什么时候发生”双重信息的可靠数据源。指标(Metrics)告诉你系统“病了”,日志告诉你“病因”。

1.3 日志的级别(Level)

日志级别用于标识事件的严重程度和紧急程度,帮助运维人员快速过滤和聚焦关键信息。绝大多数日志框架(如 Log4j、Python logging、rsyslog)都遵循以下五级标准:

级别 数值 含义 典型内容 运维响应
DEBUG 10 调试信息,开发阶段使用 变量值、函数入口/出口、SQL 语句 生产环境通常关闭
INFO 20 正常运行时的重要事件 服务启动、请求到达、任务完成 日常监控关注
WARN 30 潜在问题,不影响当前功能 磁盘使用率 > 80%、重试请求、配置降级 需要关注并排查
ERROR 40 功能异常,部分请求失败 数据库连接失败、API 返回 500、超时 立即响应,触发告警
FATAL 50 严重错误,系统可能崩溃 OOM、主进程退出、关键服务不可用 紧急介入,P0 级故障
常见误区:不要将所有日志都设为 ERROR 级别,否则 ERROR 日志会淹没在噪音中,失去告警区分度。合理的做法是:ERROR 只记录“需要人工介入”的异常,WARN 记录“需要关注但不紧急”的异常。

1.4 日志级别选择原则

在实际开发与运维中,日志级别的选择应遵循以下原则:

  • DEBUG:仅在开发和测试环境开启,生产环境默认关闭,避免磁盘 I/O 和存储压力。
  • INFO:记录关键业务节点和系统状态变更,如服务启动、配置加载、定时任务执行。
  • WARN:记录非预期的但可自动恢复的情况,如重试、降级、限流触发。
  • ERROR:记录业务或系统层面的失败,需要人工介入排查,如数据库连接失败、第三方接口异常。
  • FATAL:记录导致进程或服务无法继续运行的灾难性错误,如内存溢出、关键依赖不可用。

1.5 日志级别过滤与告警策略

在日志收集与告警系统中,通常按级别设置不同的处理策略:

日志级别 存储策略 告警方式 保留周期
DEBUG 不存储或短期存储 不告警 1 天
INFO 全量存储 不告警 7-30 天
WARN 全量存储 聚合告警(如 5 分钟内出现 10 次) 30 天
ERROR 全量存储 + 索引 即时告警(电话/短信/IM) 30-90 天
FATAL 全量存储 + 持久化 紧急告警(电话 + 值班组长) 90 天以上

1.6 日志级别关系图

下图展示了日志级别从低到高的递进关系,以及各级别对应的典型事件和响应方式:

日志级别递进关系与响应策略 DEBUG INFO WARN ERROR FATAL 开发调试 生产关闭 日常监控 全量存储 潜在风险 聚合告警 功能异常 即时告警 系统崩溃 紧急介入 严重程度递增 → 级别越高,响应优先级越高,存储策略越严格

1.7 日志级别配置示例

以下是一个 Python 日志配置示例,展示了如何按级别设置不同的处理器和格式:

import logging

# 创建日志器
logger = logging.getLogger('sre_course')
logger.setLevel(logging.DEBUG)

# 控制台处理器:输出 INFO 及以上级别
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_format = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
console_handler.setFormatter(console_format)

# 文件处理器:输出 DEBUG 及以上级别,用于详细排查
file_handler = logging.FileHandler('app.log')
file_handler.setLevel(logging.DEBUG)
file_format = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s - %(message)s')
file_handler.setFormatter(file_format)

# 错误文件处理器:只记录 ERROR 和 FATAL
error_handler = logging.FileHandler('error.log')
error_handler.setLevel(logging.ERROR)
error_format = logging.Formatter('%(asctime)s [%(levelname)s] %(name)s:%(lineno)d - %(message)s')
error_handler.setFormatter(error_format)

# 添加处理器
logger.addHandler(console_handler)
logger.addHandler(file_handler)
logger.addHandler(error_handler)

# 使用示例
logger.debug('数据库查询 SQL: SELECT * FROM users')
logger.info('用户登录成功: user_id=12345')
logger.warning('磁盘使用率已达 85%')
logger.error('数据库连接超时,重试第 3 次')
logger.fatal('内存溢出,进程即将退出')
最佳实践:生产环境中建议将 DEBUG 日志写入独立文件,并设置日志轮转(log rotation)避免磁盘写满。ERROR 和 FATAL 日志应同时输出到标准错误流(stderr),便于容器化环境下的日志采集。

1.8 日志级别与故障定位的关联

在故障定位过程中,日志级别直接决定了排查效率:

  • 快速过滤:先查看 ERROR 和 FATAL 日志,确定故障范围和影响面。
  • 上下文回溯:根据 ERROR 时间点,向前查看 WARN 和 INFO 日志,寻找前置异常信号。
  • 根因分析:在 DEBUG 日志中定位具体的代码路径、参数值和异常堆栈。
  • 趋势判断:通过 WARN 日志的频率变化,预判系统是否正在恶化。
核心结论:日志级别是故障定位的“导航仪”——ERROR 告诉你“哪里出了问题”,WARN 告诉你“哪里可能出问题”,DEBUG 告诉你“为什么出问题”。合理使用日志级别,能将故障定位时间从小时级缩短到分钟级。