← 中文资源

简体中文版本

结构化 JSON 日志格式化器

把 Python 日志记录格式化为一行的 JSON 并带上额外字段,使日志可被机器解析且不丢上下文。

阅读英文原文

功能说明

该示例自定义日志格式化器,把每条日志记录序列化为单行 JSON。除级别、消息、logger 名称与时间等固定字段外,它把调用方通过 extra 传入的自定义字段并入 JSON,并把异常堆栈作为结构化字段输出。单行 JSON 便于采集器逐行解析、按字段检索与聚合,而不是在自由文本上做正则。

关键代码

Reserved 集合列出 LogRecord 自带的内置属性,循环时跳过它们,只保留调用方附加的字段,避免把内部实现细节写进日志。对以下划线开头的键也跳过。异常在有 exc_info 时单独格式化为 exception 字段。dumps 使用 ensure_ascii=False 保留非 ASCII 文本,并用 default=str 兜底处理不可直接序列化的对象。

使用注意

extra 的键不能与内置字段冲突,否则 logging 会抛错,命名字段时应加业务前缀以降低碰撞概率。务必保证每条记录只有一行,若异常或消息含有换行,采集器可能按行解析出错,真实实现应对字段内的换行做转义。日志中不得写入密钥或个人信息,结构化输出便于检索的同时也更便于被误用,需在来源处就做好脱敏。

代码示例

import json
import logging

RESERVED = {'name', 'msg', 'args', 'levelname', 'levelno', 'pathname', 'filename',
            'module', 'exc_info', 'exc_text', 'stack_info', 'lineno', 'funcName',
            'created', 'msecs', 'relativeCreated', 'thread', 'threadName',
            'processName', 'process', 'taskName'}

class JsonFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        payload = {
            'level': record.levelname,
            'message': record.getMessage(),
            'logger': record.name,
            'time': self.formatTime(record, '%Y-%m-%dT%H:%M:%S'),
        }
        for key, value in record.__dict__.items():
            if key not in RESERVED and not key.startswith('_'):
                payload[key] = value
        if record.exc_info:
            payload['exception'] = self.formatException(record.exc_info)
        return json.dumps(payload, ensure_ascii=False, default=str)

def configure() -> None:
    handler = logging.StreamHandler()
    handler.setFormatter(JsonFormatter())
    logging.basicConfig(level=logging.INFO, handlers=[handler])