监控与日志在 DevOps 中的角色:可观测性实践

监控与日志是 DevOps 可观测性的基石。本文从日志记录规范、OpenTelemetry 集成、失败条件与常见误区入手,提供一套可落地的实践指南。

监控与日志在 DevOps 中的角色:可观测性实践
封面图:ZuCDN · ZuCDN 原创

在 DevOps 实践中,监控与日志常被混为一谈,但它们的角色和边界其实不同。可观测性(Observability)是比监控更广的概念:监控告诉你系统“现在是否正常”,而可观测性让你能通过日志、指标、链路追踪等信号,回答“为什么异常”。本文聚焦日志这一信号,结合 OpenTelemetry 和 Python 标准库,给出可直接落地的实践方案。先明确边界,再给操作步骤。

日志在可观测性中的定位:不只是“记录”

日志是应用运行状态的直接输出,但很多团队把日志当成“事后排查”的工具,忽略了它在安全审计、业务分析和性能调优中的作用。OWASP 日志安全速查表指出,应用日志应包含安全事件,且日志格式要一致,以便被多种系统消费、关联和分析。这提醒我们:日志的设计要从“能被谁用”出发,而不是“记了什么”。

在 DevOps 流水线中,日志的消费方包括:开发人员(调试)、运维(告警)、安全(审计)、业务(分析)。因此,日志的结构化、级别控制、敏感信息脱敏,都是可观测性实践的一部分。

先明确边界:日志、指标与追踪的分工

可观测性的三大支柱是日志、指标(Metrics)和追踪(Traces)。它们各有侧重:

  • 日志:记录离散事件,适合排查具体问题,但难以聚合统计。
  • 指标:聚合数值,适合告警和趋势分析,但丢失上下文。
  • 追踪:记录请求在分布式系统中的传播路径,适合定位延迟瓶颈。

OpenTelemetry 官方文档强调,日志是三大信号中“历史包袱”最重的:大多数语言已有成熟的日志库,OpenTelemetry 的日志方案不是推倒重来,而是兼容现有日志体系,并提供与指标、追踪的集成能力。这意味着,你不需要替换现有日志库,而是通过标准化的方式让日志能被统一采集和关联。

实操:基于 Python logging 构建结构化日志

Python 的 logging 模块是标准库,几乎所有 Python 应用都在用。它的核心是 层级日志器(hierarchical logging):每个模块通过 getLogger(__name__) 获取日志器,消息会向上传播到根日志器。这种设计的好处是:下游代码可以按模块控制日志级别和输出,而无需改动业务代码。

但默认的 logging 输出是纯文本,不利于机器解析。可观测性实践的第一步,是让日志变成 结构化 JSON。以下是一个最小配置:

import logging, json
class JsonFormatter(logging.Formatter):
    def format(self, record):
        log_entry = {
            "time": self.formatTime(record),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
        }
        if hasattr(record, "extra_data"):
            log_entry.update(record.extra_data)
        return json.dumps(log_entry)

handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())
root = logging.getLogger()
root.addHandler(handler)
root.setLevel(logging.INFO)

这个配置把日志输出为 JSON,方便后续接入日志采集系统(如 ELK、Loki)。关键点是:不要在生产代码中直接 print,而是使用模块级 logger,并传递额外字段(如 request_id)作为上下文。

日志级别与安全:避免常见误区

很多团队把日志级别当作摆设,要么全部用 INFO,要么 DEBUG 打满。OWASP 建议:安全事件必须记录,且日志应包含足够的上下文(如用户 ID、IP、时间戳),但不能包含敏感数据(如密码、令牌)。

实操建议:

  • 使用 logger.exception() 记录异常,它会附带堆栈信息。
  • 在日志中注入 trace_idspan_id,与追踪关联。
  • 对敏感字段(如 email、手机号)进行脱敏,避免合规风险。

一个常见的失败条件是:日志写入阻塞应用。如果日志处理是同步的,且输出到慢速磁盘,会拖垮性能。解决方法是使用异步 handler 或队列,但要注意日志丢失的取舍。

集成 OpenTelemetry:让日志与追踪关联

OpenTelemetry 的日志方案并不强制你重写日志代码,而是提供 日志与追踪的关联机制。例如,在 Python 中,你可以通过 opentelemetry-pythonLoggingHandler 将日志记录与当前 trace context 绑定:

from opentelemetry import trace
from opentelemetry.sdk._logs import LoggerProvider
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.grpc._log_exporter import OTLPLogExporter

# 初始化日志提供者并添加处理器
logger_provider = LoggerProvider()
logger_provider.add_log_record_processor(BatchLogRecordProcessor(OTLPLogExporter()))
# 设置全局日志提供者(需要与 trace 关联)

这样,日志会自动携带 trace_id,当你通过追踪看到一个慢请求时,可以一键跳转到该请求的日志,而不是靠时间戳猜测。这是可观测性实践的关键收益。

失败条件与取舍:日志采集的常见坑

即使有了结构化日志,采集环节也可能失败。常见的坑包括:

  • 日志轮转配置不当:导致磁盘写满,应用崩溃。
  • 采集器(如 Filebeat)与日志格式不匹配:解析失败,数据丢失。
  • 日志量过大:成本飙升,需要采样或降级。

取舍在于:全量日志 vs 采样。对于高流量系统,全量存储成本高,可对 DEBUG 级别采样,但 ERROR 必须全量。同时,设置日志保留策略(如 30 天),并定期归档。

与 CI/CD 流水线的集成

日志和监控不应是事后部署的,而应嵌入到 DevOps 流水线中。例如,在 CI 阶段生成测试日志,在 CD 阶段收集部署日志,并在发布后自动检查错误率。可参考 DevOps 流水线搭建指南 了解更多流水线设计。同时,监控指标(如错误率、延迟)可作为发布的门禁,这部分在 CI/CD 与 DevOps 的关系及实践要点 中有展开。

参考资料

延伸阅读