日志看不懂?这 6 个 Python 库来得太及时了!
线上报错最怕这种:
ERROR pay callback failed
Traceback (most recent call last):
File "worker.py", line 48, in run
handle_order(msg)
KeyError: 'order_id'
一眼看过去,好像知道错了。
再看两分钟,又感觉啥也没说。
KeyError 是谁传的?哪条消息?哪个用户?重试了几次?请求链路从哪来的?这些东西日志里没有,基本就只能靠猜。面试里问“你平时怎么处理日志”,我一般不太喜欢听“打印日志、定位问题”这种话,太空了。
Python 里这 6 个库,真能把日志从“看个热闹”变成“能追现场”。
第一个,logging。
别嫌它老。线上项目里,我第一反应还是先看有没有把标准库用明白。
很多人写日志是这样的:
print("order error", order_id)
开发环境看着挺顺手,一上服务器就麻烦了。没有级别,没有时间,没有文件,没有模块名,出了问题 grep 都不好 grep。
我一般会先把最基础的格式补上:
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s %(message)s"
)
log = logging.getLogger("pay.callback")
defhandle_callback(payload):
order_id = payload.get("order_id")
log.info("callback received order_id=%s channel=%s",
order_id, payload.get("channel"))
ifnot order_id:
log.warning("callback missing order_id payload=%s", payload)
return
别上来就整很复杂。先让日志能按时间、级别、模块搜出来。很多小项目,做到这一步已经够用了。
第二个,loguru。
这个库适合那种脚本多、定时任务多、又不想写一堆 logging 配置的场景。
比如每天跑一个订单对账脚本,凌晨出错,第二天早上一看日志文件 600M,头都大了。这个时候我会直接加滚动和保留时间:
from loguru import logger
logger.add(
"logs/reconcile_{time:YYYYMMDD}.log",
rotation="100 MB",
retention="14 days",
encoding="utf-8"
)
defcheck_bill(row):
try:
fee = int(row["fee"])
logger.info("bill ok order_no={} fee={}", row["order_no"], fee)
except Exception:
logger.exception("bill parse failed row={}", row)
logger.exception() 这个我挺喜欢,异常栈和上下文一起打出来。排查时不用再问“当时传的参数是什么”。
第三个,traceback。
面试里有人说“我会捕获异常”,然后代码写成:
except Exception as e:
print(e)
这地方我一般就不太信了。
只打印 e,很多时候只剩一句 invalid literal for int(),出错文件和行号都没了。traceback 至少要会用:
import traceback
defrun_job(job_id, fn):
try:
fn()
except Exception:
err = traceback.format_exc()
logging.error("job failed job_id=%s\n%s", job_id, err)
这东西不花哨,但救命。特别是一些老项目,日志系统没接好,先把完整异常栈打出来,比什么都强。
第四个,rich。
rich 我一般不会直接往生产日志里塞太多颜色,生产环境看文件,颜色没意义。
但本地调试、写命令行工具,它很舒服。尤其是那种嵌套 JSON、接口返回特别长的东西。
from rich.console import Console
from rich.traceback import install
install(show_locals=True)
console = Console()
defdebug_response(resp):
console.rule("gateway response")
console.print_json(data=resp)
show_locals=True 要注意,可能会把变量值打出来。里面如果有 token、手机号、身份证号,这就不是调试,是埋雷。这个习惯要有:日志越详细,越要防泄漏。
第五个,structlog。
微服务里日志最烦的是一条链路散在好几个服务里。
支付服务打一条,订单服务打一条,库存服务打一条。没有 trace_id,你只能靠时间凑。时间一密集,基本就废了。
structlog 比较适合结构化日志:
import structlog
log = structlog.get_logger()
defcreate_order(req):
trace_id = req.headers.get("X-Trace-Id", "-")
user_id = req.json.get("user_id")
log.info(
"order_create_start",
trace_id=trace_id,
user_id=user_id,
sku=req.json.get("sku")
)
这种日志进 ELK、Loki、ClickHouse 都好查。
我更关心的是字段稳定。别今天叫 traceId,明天叫 trace_id,后天又叫 tid。日志字段一乱,后面告警和查询都得跟着擦屁股。
第六个,python-json-logger。
如果项目还是标准 logging,但又想输出 JSON 日志,可以用它。
比如容器里跑服务,日志直接打到 stdout,再由采集器收走。这种场景下,JSON 比普通文本更好处理。
import logging
from pythonjsonlogger import jsonlogger
handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
"%(asctime)s %(levelname)s %(name)s %(message)s %(trace_id)s"
)
handler.setFormatter(formatter)
log = logging.getLogger("api")
log.addHandler(handler)
log.setLevel(logging.INFO)
log.info(
"user_login_failed",
extra={"trace_id": "t-20260424-001"}
)
这里有个坑,extra 里的字段别和 logging 自带字段冲突。比如你硬塞一个 message,可能就把自己绕晕了。
真到了线上,我看日志一般不是从代码开始看。
先看 ERROR 有没有集中爆发,再看有没有 trace_id,再看异常栈,再看关键入参。如果日志里只有一句“处理失败”,那基本等于没打。
日志不是写给机器看的,也不是写给领导看的。
是写给半夜被叫醒的那个倒霉开发看的。
能让他少猜一分钟,这日志就值了。