Python技术迷

日志看不懂?这 6 个 Python 库来得太及时了!

接口没挂,数据库也没抖,报警却一条接一条。你去翻日志,满屏时间戳、线程名、模块名,真正有用的那句异常,被埋在三百多行里。更烦的是,几台机器日志格式还不一样,有的打 JSON,有的还是一坨纯文本,grep error 看着像在抽奖。

这种场面,我一般先不怀疑业务。先怀疑日志本身写废了。

Python 这几年在日志这块,真有几把趁手的刀。不是那种“功能很多”的库,而是你线上真能拿来救命的。下面这 6 个,我自己更偏向按“先把日志打明白,再把日志看明白”这个顺序用。

1)loguru:先把日志打得像人话

很多项目还在硬扛标准库 logging,不是不能用,是默认写法太容易越写越乱。尤其多人协作之后,格式一会儿一个样,查问题的时候眼睛都快看花了。

loguru 最大的好处不是“高级”,是省事。文件切割、异常栈、上下文变量,几行就能收住。

from loguru import logger
import sys
import time

logger.remove()
logger.add(
    sys.stdout,
    format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | trace={extra[trace_id]} | {message}",
    level="INFO"
)
logger.add(
"logs/order_{time:YYYYMMDD}.log",
    rotation="200 MB",
    retention="7 days",
    enqueue=True,
    encoding="utf-8"
)

req_logger = logger.bind(trace_id="9f3c2b7d")

defsubmit_order(order_id: str):
    req_logger.info("start submit order_id={}", order_id)
    time.sleep(0.08)
    req_logger.warning("inventory rpc slow, cost_ms={}", 312)

submit_order("O20260330001")

这段没什么花活,但线上很够用。至少你拿到一条日志时,先知道是谁、什么时候、哪条链路,不会连 trace_id 都靠字符串拼接。

2)rich:别再拿纯文本硬啃异常栈了

有些异常不是“看不见”,是“看不下去”。尤其多层调用、嵌套字典、返回体很长的时候,终端里全挤成一片,越看越烦。

rich 这种库,第一眼很多人以为只是“好看”。其实不是,它解决的是可读性。你排障的时候,眼睛少受罪,就是生产力。

from rich.console import Console
from rich.traceback import install
from rich.pretty import pprint

install(show_locals=True)
console = Console()

defload_profile():
    payload = {
"user_id": 1024,
"scene": "coupon_settle",
"items": [{"sku": "A18", "count": 2}, {"sku": "B99", "count": 1}],
    }
    pprint(payload)
    total = payload["amount"]  # 故意写错
return total

load_profile()

这个库很适合本地复现和测试环境看问题。尤其 show_locals=True,有时候你都不用再临时补日志,局部变量直接给你摊开了。

3)structlog:日志别只打一行字,要带上下文

我最烦那种日志:

logger.error("request failed")

失败了谁失败?哪个用户?哪个订单?哪个下游?第几次重试?全没有。你最后只能去上下文里猜,猜到一半火气就上来了。

structlog 适合把日志当“结构化事件”来打。不是为了装规范,是为了后面查的时候能按字段过滤。

import structlog
import time

structlog.configure(
    processors=[
        structlog.processors.TimeStamper(fmt="iso"),
        structlog.processors.JSONRenderer()
    ]
)

log = structlog.get_logger()

defcall_payment(order_id: str, user_id: int):
    start = time.time()
try:
raise TimeoutError("payment timeout")
except Exception as e:
        log.error(
"payment_call_failed",
            order_id=order_id,
            user_id=user_id,
            retry=2,
            cost_ms=int((time.time() - start) * 1000),
            error=str(e),
        )

call_payment("O20260330001", 20017)

到了 ELK、Loki 这类平台里,这种日志就很舒服。直接按 order_id、retry、user_id 去筛,不用在 message 里抠字眼。

4)python-json-logger:老项目不想大改,先把 logging 救回来

不是每个项目都适合直接换 loguru 或 structlog。有些老服务已经到处都是 logging.getLogger(__name__),你真让团队整体迁,成本不低。

这种时候,python-json-logger 很实用。它不颠覆原来的 logging 体系,只是把输出先改成机器能读懂的样子。

import logging
from pythonjsonlogger import jsonlogger

logger = logging.getLogger("billing")
logger.setLevel(logging.INFO)

handler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
"%(asctime)s %(levelname)s %(name)s %(message)s %(trace_id)s %(invoice_no)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)

extra = {
"trace_id": "7ac91e2f",
"invoice_no": "INV-20260330-88"
}

logger.info("invoice pushed to mq", extra=extra)

这个很适合改造期。先别追求“日志体系升级”,先把日志喂给采集系统时别再一团浆糊,已经能少很多事。

5)stackprinter:异常栈太长的时候,它比原生 traceback 顺眼不少

Python 原生异常栈不是不能看,是复杂一点就很容易看漏。特别是函数层级深、参数多的时候,你盯着 traceback 往下翻,关键值还得自己补脑。

stackprinter 的好处,是它会把调用现场展开得更完整一点。

import stackprinter

defparse_line(line: str):
return int(line.split("=")[1])

defload_metric():
    raw = "qps=NaN"
return parse_line(raw)

try:
    load_metric()
except Exception:
    print(stackprinter.format())

这个库我一般在本地和测试环境用得多,线上直接全量打印要克制一点,不然日志量会炸。该收的时候还是得收,别为了“看清异常”把磁盘先打满了。

6)Textualize 的工具链:用 rich-cli / textual 场景化看日志,比 tail 舒服

最后这个我不想吹得太满。很多人看日志还停留在 tail -f xxx.log,不是不行,但当你要盯多字段、长 JSON、彩色高亮输出时,体验确实差。

如果你已经在用 rich,那一套相关生态拿来做简易日志查看页面也很顺手。比如内部工具里,我会写个很薄的终端面板,把错误等级、trace_id、耗时直接铺出来,而不是盯原始文件发呆。

from rich.table import Table
from rich.console import Console

console = Console()
table = Table(title="recent slow logs")
table.add_column("time")
table.add_column("trace_id")
table.add_column("api")
table.add_column("cost_ms")

rows = [
    ("2026-03-30 10:21:03", "a91f", "/api/pay", "812"),
    ("2026-03-30 10:21:07", "b72c", "/api/refund", "1204"),
]

for row in rows:
    table.add_row(*row)

console.print(table)

这种代码很薄,但真到排查现场,值钱。因为你不是在“读文件”,你是在“筛问题”。

日志这事,很多时候不是量不够,是打得太随便,看得太硬扛。真正难受的不是没日志,而是日志写了一堆,关键字段没落下来;异常打了,但栈看不出重点;进了平台能搜,但搜出来还是一坨。

我自己的顺手搭配通常是这样:

开发阶段先上 loguru 或 rich,让人先看得下去; 进到联调和测试,补 structlog 或 python-json-logger,把字段打齐; 碰到复杂异常,再让 stackprinter 出来顶一把。

别一上来就想着“日志平台怎么建设”。先把代码里那句 logger.error("出错了") 改掉,比什么都实在。