昨天帮同事看一台跑着微服务的开发机,系统一启动就报错,但翻遍 /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.auth 写 user_auth.log,payment.gateway 写 payment_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" 筛,比肉眼扫快十倍。
说白了,日志混着不是故障,是线索被藏起来了。把模块名、时间、错误等级这三样钉死,再复杂的系统也能一层层剥开看。