Django 的 LOGGING 字典无法原生输出 JSON,因其内置 Formatter 不支持 JSON 序列化;需自定义继承 logging.Formatter 的 JSONFormatter 类,安全处理 exc_info、bytes 等非 JSON 可序列化字段,并在 settings.py 中正确配置 formatters、handlers 和 loggers。

为什么直接用 Django 的 LOGGING 字典无法原生输出 JSON
Django 自带的 logging 模块不提供 JSON 格式化器(JSONFormatter),它只内置了 Formatter 和 BufferingFormatter。想输出 JSON,必须自己写一个继承自 logging.Formatter 的类,并重写 format() 方法——否则日志永远是纯文本,字段无法被 ELK、Loki 或 Datadog 正确解析。
如何写一个安全可用的 JSONFormatter
关键点不是“能转成 JSON”,而是“不崩、不丢字段、兼容日志上下文”。常见错误包括:直接 json.dumps(record.__dict__) 导致 TypeError: Object of type bytes is not JSON serializable,或把 exc_info、stack_info 当普通字符串处理。
- 用
record.exc_text or self.formatException(record)提取异常堆栈,再手动清理换行符 - 过滤掉不能 JSON 序列化的字段(如
record.args是元组或含 bytes 时,先尝试str(),失败则用repr() - 固定添加
timestamp(用self.formatTime(record))、level、logger_name、message等必需字段 - 不要在 formatter 中做 I/O 或调用外部服务——它在每条日志输出时同步执行
示例片段:
import json
import logging
from datetime import datetime
<p>class JSONFormatter(logging.Formatter):
def format(self, record):
log_entry = {
"timestamp": self.formatTime(record),
"level": record.levelname,
"logger": record.name,
"message": record.getMessage(),
"module": record.module,
"funcName": record.funcName,
"lineno": record.lineno,
}
if record.exc_info:
log_entry["exception"] = self.formatException(record.exc_info)
if hasattr(record, "request"):
log_entry["request_id"] = getattr(record.request, "id", "unknown")</p><h1>安全序列化额外字段</h1><pre class="brush:php;toolbar:false;"> for key, value in record.__dict__.items():
if key not in log_entry and not key.startswith("_") and key not in ("args", "exc_info", "exc_text", "stack_info"):
try:
json.dumps(value) # 浅验证
log_entry[key] = value
except (TypeError, ValueError):
log_entry[key] = repr(value)
return json.dumps(log_entry, ensure_ascii=False)
Django settings.py 中怎么配置 LOGGING 字典启用它
重点不是加个 formatters,而是确保 handler 的 class 和 formatter 名称匹配,且 handlers 被实际挂到 loggers 上——很多人配完了却还在看 plain text,就是因为没把 django 或 myapp logger 的 handlers 列表指向你定义的 JSON handler。
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
- 在
LOGGING字典中注册 formatter:"json": {"()": "myproject.log.JSONFormatter"}(注意路径要可 import) - handler 必须指定
"formatter": "json",且 class 推荐用"logging.StreamHandler"(开发)或"logging.handlers.RotatingFileHandler"(生产) - root logger 或具体 logger(如
"django")的"handlers"列表里必须包含该 handler 名 - 避免重复记录:设
"propagate": False在子 logger 上,防止被 root 再刷一遍 plain text
最小可用配置节选:
LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"json": {"()": "myproject.log.JSONFormatter"},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "json",
},
},
"loggers": {
"django": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
},
}
怎么让 request_id、user_id 这类上下文字段自动进 JSON 日志
Django 请求生命周期里,request 对象不会自动塞进 logging record;靠 middleware + LoggerAdapter 或 Filter 才行。别信“只要加 extra 就能全局生效”——那是单次 logger.info(..., extra={...}) 的行为,没法覆盖中间件、信号、异步任务等场景。
- 写一个
ContextFilter类,重写filter(self, record),从threading.local或asgiref.local.Local(Django 4.0+)中取当前请求上下文 - 在 middleware 中,于
process_request设置上下文(如local.request_id = str(uuid4())),process_response清理 - 在 LOGGING 的 handler 里加
"filters": ["context"],并注册该 filter - 注意异步视图:
threading.local在 async scope 下失效,必须用asgiref.local.Local
真正容易被忽略的是:filter 返回 False 会丢弃整条日志。调试时先加 record.context_debug = "ok" 确认 filter 被调用,再逐步注入字段。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










