Python技术迷

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

最烦的不是报错,是日志明明打了一屏,真出事时还是像没打。

一串时间、一坨线程名、再来一段糊成粥的异常栈,翻了十分钟,你只能确认一件事:这事昨天就埋下了,今天只是炸了。Python 自带 logging 不是不能用,但很多项目一上来就默认配置,最后日志既不好看,也不好查,更别提链路排障。

下面这 6 个库,我是按“真能救场”这个标准挑的,不是按 stars 排的。

1)loguru:先把日志打顺眼了再说

很多项目的第一步不是上链路追踪,是先把日志格式救回来。loguru 这点很顶,开箱就比原生 logging 省心,滚动、压缩、异常栈都能直接配。

from loguru import logger
import sys

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

trace_id = "9f3ab21c"
logger.bind(trace_id=trace_id).info("开始处理退款单 refund_id=82019")
try:
1 / 0
except Exception:
    logger.bind(trace_id=trace_id).exception("退款回调处理失败")

这种库最大的好处不是“高级”,是少写废配置。尤其是小团队,别一上来先和 dictConfig 死磕。

2)rich:终端里看异常,终于不像审刑具了

排查本地问题时,我第一眼先不信业务代码,先看异常栈是不是能一眼定位到自己的文件。rich 在这方面非常舒服,彩色高亮、变量上下文、表格展示都比纯文本强太多。

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

install(show_locals=True)
console = Console()

defparse_amount(row):
    amount = int(row["amount"])
return amount / row["count"]

row = {"amount": "12x", "count": 0}
console.log("准备解析", row)
parse_amount(row)

本地调试、命令行工具、临时脚本,装上它,异常一下就没那么难看了。尤其是那种数据清洗脚本,一天报八百次错,能少瞎一次眼都是赚的。

3)structlog:日志别写散文,写结构

线上最怕什么?同一条请求过了 6 个函数,日志打了 20 行,结果没有一个字段能串起来。structlog 就适合干这个,把日志从“句子”变成“字段”。

import structlog
import uuid

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

log = structlog.get_logger().bind(
    trace_id=str(uuid.uuid4()),
    service="invoice-worker"
)

log.info("job_start", file_name="invoice_20260418.csv", batch_size=500)

try:
raise ValueError("bad tax_no")
except Exception as e:
    log.error("row_failed", row_no=37, reason=str(e), retry=False)

后面进 ELK、Loki、Splunk,你就知道结构化字段有多香了。搜 trace_id、筛 row_no、聚合 reason,比肉眼翻文本快太多。

4)python-json-logger:准备接日志平台的,别手搓 JSON

很多人想把日志输出成 JSON,第一反应是自己拼字符串。这个路子我一般不太信,迟早把引号、换行、异常对象拼坏。python-json-logger 干这事更稳。

import logging
from pythonjsonlogger import jsonlogger

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

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

logger.info(
"payment_created",
    extra={"trace_id": "trc-8821", "user_id": 1024}
)

这类日志不是给人直接看的,是给机器吃的。你本地看着普通,到了采集平台里按字段筛选,差别一下就出来了。

5)concurrent-log-handler:多进程写日志,别把文件写炸了

Gunicorn、多进程任务、批处理脚本,这类场景下如果还用普通的文件轮转,日志文件很容易互相抢,轻则乱序,重则切分失败。concurrent-log-handler 就是补这个坑的。

import logging
from concurrent_log_handler import ConcurrentRotatingFileHandler

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

handler = ConcurrentRotatingFileHandler(
"logs/crawler.log",
    maxBytes=10 * 1024 * 1024,
    backupCount=5
)
formatter = logging.Formatter(
"%(asctime)s | %(process)d | %(levelname)s | %(message)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)

logger.info("开始抓取 page=1")

这个坑平时不明显,一上生产、多实例一跑,就开始出现“怎么日志少了一段”的鬼故事。

6)stackprinter:异常栈终于能看出点人话

有些异常栈特别长,尤其是装饰器、异步调用、框架层包多了以后,看到最后脑子都木了。stackprinter 适合在你想把异常打印得更清楚一点的时候用。

import stackprinter

defload_profile(user_id):
    profile = {"id": user_id, "age": "unknown"}
return18 + profile["age"]

try:
    load_profile(7)
except Exception:
    print(stackprinter.format())

它不是天天都要上,但碰到那种“栈很长、问题很蠢、定位很慢”的异常,真能省点时间。

别一口气全装。真正常用的组合,往往就两套:

本地开发:loguru + rich线上采集:structlog + python-json-logger多进程落盘再补一个 concurrent-log-handler

至于 stackprinter,属于抽屉里那把不常用、但丢了又不行的螺丝刀。

日志这东西,平时看着像成本,出事时就是证据。等线上真有一条超时、一条脏数据、一条重复回调混在一起的时候,你就知道“能不能看懂日志”这件事,真不是小优化。