功能说明
该示例自定义日志格式化器,把每条日志记录序列化为单行 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])