多模块错误日志混在一起?三招理清源头

昨天帮同事看一台跑着微服务的开发机,系统一启动就报错,但翻遍 /var/log/app/ 下的日志文件,发现所有模块——用户服务、订单服务、支付网关——的日志全挤在同一个 app.log 里,时间戳乱序、进程ID穿插、错误堆栈断成几截。查了半小时,才发现是某个模块没配独立 logger,直接用了 root logger 输出。

为啥会混?常见几个坑

不是所有程序都天生懂‘分家’。比如 Python 的 logging 模块,默认 root logger 一开,所有没显式声明 logger 的模块都会往它身上塞日志;Java 里 Logback 如果只配了一个 <appender-ref ref="CONSOLE"/> 却没按 logger name 拆分,Spring Boot 启动时各 starter 的日志就全糊一块儿。

还有更隐蔽的:容器环境里,多个 sidecar 容器(比如 nginx + python app)都往 stdout 写,Kubernetes 日志采集器(如 fluentd)不加 tag 或 parser,最后在 ELK 里看到的就是一团带不同前缀的乱码。

动手拆开它

第一招:按模块隔离输出文件
以 Python 为例,在每个模块开头加:

import logging
logger = logging.getLogger(__name__)  # __name__ 自动带包路径,如 'order.service'
logger.setLevel(logging.INFO)
handler = logging.FileHandler(f'logs/{__name__.replace(".", "_")}.log')
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

这样 user.authuser_auth.logpayment.gatewaypayment_gateway.log,一眼分清谁在报错。

第二招:加结构化前缀

如果暂时不能改代码,至少让日志带上身份。Linux 下用 stdbuf + awk 临时打标:

./order-service | stdbuf -oL -eL awk '{print "[ORDER] " $0}' >> /var/log/all.log &
./user-service | stdbuf -oL -eL awk '{print "[USER] " $0}' >> /var/log/all.log &

再 grep 就方便多了:grep '\[ORDER\] ERROR' /var/log/all.log

第三招:用日志采集器过滤

Fluentd 配置片段示例(按进程名分离):

<filter **>
  @type record_transformer
  <record>
    module ${record["process"] || "unknown"}
  </record>
</filter>
<match order.**>
  @type file
  path /var/log/modules/order
</match>

日志进了 ES 后,Kibana 里直接按 module: "payment" 筛,比肉眼扫快十倍。

说白了,日志混着不是故障,是线索被藏起来了。把模块名、时间、错误等级这三样钉死,再复杂的系统也能一层层剥开看。