日志看不懂?这 6 个 Python 库来得太及时了!
最烦的不是报错,是日志明明打了一屏,真出事时还是像没打。
一串时间、一坨线程名、再来一段糊成粥的异常栈,翻了十分钟,你只能确认一件事:这事昨天就埋下了,今天只是炸了。Python 自带 logging 不是不能用,但很多项目一上来就默认配置,最后日志既不好看,也不好查,更别提链路排障。
下面这 6 个库,我是按“真能救场”这个标准挑的,不是按 stars 排的。
1)loguru:先把日志打顺眼了再说
很多项目的第一步不是上链路追踪,是先把日志格式救回来。loguru 这点很顶,开箱就比原生 logging 省心,滚动、压缩、异常栈都能直接配。
from loguru import logger
import syslogger.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 installinstall(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 uuidstructlog.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 jsonloggerlogger = 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 ConcurrentRotatingFileHandlerlogger = 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 stackprinterdefload_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,属于抽屉里那把不常用、但丢了又不行的螺丝刀。
日志这东西,平时看着像成本,出事时就是证据。等线上真有一条超时、一条脏数据、一条重复回调混在一起的时候,你就知道“能不能看懂日志”这件事,真不是小优化。