Python 日志管理完整指南(层级→配置→实践全链路)从 logging 基础到生产级日志管理,彻底理解日志级别与最佳实践
日志层级深度解析、logging 模块配置、Handler 与 Formatter、文件管理策略、结构化日志、性能优化、不同环境配置全覆盖
目录
一、为什么需要日志管理
日志是程序运行时的”黑匣子”,记录系统状态、用户行为、异常信息和性能数据。没有日志,生产环境出问题时如同盲人摸象。
❌ 无日志(问题排查困难)
# 程序崩溃,只能看报错堆栈 # 不知道请求参数是什么 # 不知道数据流转到哪一步 # 不知道性能瓶颈在哪 # 复现 Bug 靠猜测
线上问题无法定位,只能加 print 重新部署排查,效率极低。
✅ 规范日志(问题一目了然)
2026-07-16 14:32:10 [INFO] 收到请求: /api/calculate, params={"a":10,"b":0}
2026-07-16 14:32:10 [DEBUG] 执行计算逻辑: divide(10, 0)
2026-07-16 14:32:10 [ERROR] 计算异常: ZeroDivisionError: division by zero
2026-07-16 14:32:10 [INFO] 返回响应: 400, {"error":"除数不能为零"}
请求参数、执行流程、异常信息、响应结果完整可追溯。
日志五大核心价值
故障排查 生产环境出问题,通过日志定位根因
行为审计 记录用户操作、数据变更、安全事件
性能分析 记录耗时,发现性能瓶颈
业务监控 统计接口调用量、错误率、吞吐量
调试辅助 开发阶段追踪程序执行流程
二、日志层级(Level)深度解析
2.1 Python 标准日志级别
| 级别 | 数值 | 含义 | 使用场景 | 生产环境 |
|---|---|---|---|---|
| DEBUG | 10 | 调试信息 | 变量值、函数入参、执行流程 | 关闭 |
| INFO | 20 | 常规信息 | 服务启动、请求处理、状态变更 | 开启 |
| WARNING | 30 | 警告信息 | 资源不足、配置降级、非致命异常 | 开启 |
| ERROR | 40 | 错误信息 | 功能异常、业务失败、需人工介入 | 开启 |
| CRITICAL | 50 | 严重错误 | 系统崩溃、数据丢失、安全事件 | 开启 |
2.2 如何正确选择日志级别
import logging
logger = logging.getLogger(__name__)
# DEBUG:开发调试,生产不输出
logger.debug(f"函数 divide 被调用,参数: a={a}, b={b}")
logger.debug(f"数据库查询 SQL: {sql}")
# INFO:关键业务节点,生产需要
logger.info(f"用户 {user_id} 登录成功,IP: {client_ip}")
logger.info(f"订单 {order_id} 创建成功,金额: {amount}")
logger.info("服务启动完成,监听端口 8000")
# WARNING:不影响主流程但需要关注
logger.warning(f"数据库连接池使用率超过 80%: {usage}%")
logger.warning(f"请求重试第 {retry_count} 次,URL: {url}")
logger.warning("使用默认配置,未找到 .env 文件")
# ERROR:功能异常,需要排查修复
logger.error(f"支付接口调用失败: {e}", exc_info=True)
logger.error(f"订单 {order_id} 状态更新失败,当前状态: {status}")
# CRITICAL:系统级灾难
logger.critical("数据库连接全部断开,服务不可用")
logger.critical("磁盘空间不足,写入操作已中止")
2.3 日志级别过滤原理
Logger 和 Handler 各自有级别设置,消息只有同时满足两者级别才会输出。
# 示例:Logger 级别为 INFO,Handler 级别为 ERROR
# INFO 级别的日志会被 Logger 接收,但被 Handler 过滤
# ERROR 级别的日志会通过所有过滤
logger = logging.getLogger("myapp")
logger.setLevel(logging.INFO) # 接收 INFO 及以上
handler = logging.StreamHandler()
handler.setLevel(logging.ERROR) # 只输出 ERROR 及以上
logger.addHandler(handler)
logger.info("这条不会输出") # 被 Handler 过滤
logger.error("这条会输出") # 通过所有过滤
三、Python logging 模块架构
3.1 核心组件关系
Logger(记录器)
├── 层级命名:"myapp.api.auth" 继承 "myapp.api"
├── 级别过滤:setLevel()
├── 传播开关:propagate = True/False
└── Handler(处理器)
├── StreamHandler → 输出到控制台
├── FileHandler → 输出到文件
├── RotatingFileHandler → 按大小轮转
├── TimedRotatingFileHandler → 按时间轮转
└── 级别过滤 + Formatter(格式器)
└── "%(asctime)s [%(levelname)s] %(message)s"
3.2 Logger 层级与继承
Logger 采用点号分隔的命名空间,形成树形结构,子 Logger 继承父 Logger 的配置。
import logging
# 根 Logger
root = logging.getLogger() # ""
# 子 Logger
app = logging.getLogger("myapp") # 继承 root
api = logging.getLogger("myapp.api") # 继承 myapp
auth = logging.getLogger("myapp.api.auth") # 继承 myapp.api
# 配置 root,所有子 Logger 默认继承
root.setLevel(logging.WARNING)
handler = logging.StreamHandler()
root.addHandler(handler)
# myapp.api.auth 会继承 root 的 Handler 和 Level
# 除非自己 addHandler 或 setLevel
四、配置日志系统(代码/字典/文件)
4.1 代码方式配置(最基础)
import logging
import sys
def setup_logging(level: int = logging.INFO) -> None:
"""基础日志配置。"""
formatter = logging.Formatter(
fmt="%(asctime)s [%(levelname)-8s] %(name)s:%(lineno)d - %(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
# 控制台 Handler
console = logging.StreamHandler(sys.stdout)
console.setLevel(level)
console.setFormatter(formatter)
# 文件 Handler
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
# 根 Logger
root = logging.getLogger()
root.setLevel(logging.DEBUG)
root.addHandler(console)
root.addHandler(file_handler)
# 使用
setup_logging()
logger = logging.getLogger(__name__)
logger.info("应用启动")
4.2 dictConfig 方式(推荐)
import logging.config
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"standard": {
"format": "%(asctime)s [%(levelname)-8s] %(name)s:%(lineno)d - %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S"
},
"detailed": {
"format": "%(asctime)s [%(levelname)-8s] %(name)s %(filename)s:%(lineno)d %(funcName)s() - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "standard",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"formatter": "detailed",
"filename": "logs/app.log",
"maxBytes": 10485760, # 10MB
"backupCount": 5,
"encoding": "utf-8"
},
"error_file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "ERROR",
"formatter": "detailed",
"filename": "logs/error.log",
"maxBytes": 10485760,
"backupCount": 10,
"encoding": "utf-8"
}
},
"loggers": {
"": { # root logger
"level": "DEBUG",
"handlers": ["console", "file", "error_file"],
"propagate": False
},
"uvicorn": {
"level": "INFO",
"handlers": ["console"],
"propagate": False
},
"sqlalchemy.engine": {
"level": "WARNING",
"handlers": ["file"],
"propagate": False
}
}
}
logging.config.dictConfig(LOGGING_CONFIG)
4.3 从文件加载配置
import logging.config
import json
# JSON 配置文件
with open("logging.json", "r", encoding="utf-8") as f:
config = json.load(f)
logging.config.dictConfig(config)
# 或通过环境变量选择配置
import os
env = os.getenv("ENV", "dev")
config_file = f"config/logging.{env}.json"
with open(config_file, "r", encoding="utf-8") as f:
logging.config.dictConfig(json.load(f))
五、日志文件管理策略
5.1 文件轮转策略对比
| Handler | 轮转条件 | 适用场景 | 注意点 |
|---|---|---|---|
| RotatingFileHandler | 文件大小达到 maxBytes | 流量稳定的应用 | 多进程同时写入可能丢失 |
| TimedRotatingFileHandler | 时间到达(天/小时/分钟) | 定时归档需求 | 午夜切换时注意日志归属 |
| WatchedFileHandler | 外部工具轮转信号 | 配合 logrotate 使用 | Linux 系统级日志管理 |
| QueueHandler | 异步队列 | 高并发性能场景 | 需要配合 QueueListener |
5.2 按大小轮转配置
from logging.handlers import RotatingFileHandler
# 10MB 一个文件,保留 5 个备份
handler = RotatingFileHandler(
"app.log",
maxBytes=10 * 1024 * 1024, # 10MB
backupCount=5,
encoding="utf-8"
)
# 生成: app.log, app.log.1, app.log.2, ..., app.log.5
5.3 按时间轮转配置
from logging.handlers import TimedRotatingFileHandler
# 每天午夜轮转,保留 30 天
handler = TimedRotatingFileHandler(
"app.log",
when="midnight", # 可选: S(秒), M(分), H(小时), D(天), W0-W6(周几)
interval=1,
backupCount=30,
encoding="utf-8"
)
# 生成: app.log, app.log.2026-07-15, app.log.2026-07-14, ...
5.4 配合 Linux logrotate
# /etc/logrotate.d/myapp
/var/log/myapp/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
create 0644 www-data www-data
sharedscripts
postrotate
# 通知应用重新打开日志文件
kill -USR1 $(cat /var/run/myapp.pid)
endscript
}
六、不同环境的日志配置
6.1 环境配置对照
开发环境(dev)
level: DEBUG handlers: [console] format: 简洁,带行号 propagate: True
输出到控制台,DEBUG 级别全开,方便调试追踪。
测试环境(test)
level: DEBUG handlers: [console, file] format: 详细,带函数名 propagate: False
同时输出到控制台和文件,方便 CI 收集日志。
预发布环境(staging)
level: INFO handlers: [file, error_file] format: JSON 结构化 propagate: False
接近生产配置,使用 JSON 格式对接日志收集系统。
生产环境(prod)
level: INFO handlers: [file] format: JSON 结构化 propagate: False
INFO 及以上写入文件,ERROR 单独归档,结构化输出。
6.2 环境切换实现
import logging.config
import os
def get_logging_config(env: str) -> dict:
"""根据环境返回日志配置。"""
base = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"simple": {"format": "%(levelname)s: %(message)s"},
"standard": {"format": "%(asctime)s [%(levelname)s] %(name)s - %(message)s"},
"json": {"class": "pythonjsonlogger.jsonlogger.JsonFormatter",
"format": "%(asctime)s %(levelname)s %(name)s %(message)s"}
},
"handlers": {},
"loggers": {}
}
if env == "dev":
base["handlers"]["console"] = {
"class": "logging.StreamHandler",
"level": "DEBUG",
"formatter": "standard"
}
base["loggers"][""] = {"level": "DEBUG", "handlers": ["console"]}
elif env == "prod":
base["handlers"]["file"] = {
"class": "logging.handlers.TimedRotatingFileHandler",
"level": "INFO",
"formatter": "json",
"filename": "/var/log/myapp/app.log",
"when": "midnight",
"backupCount": 30
}
base["handlers"]["error"] = {
"class": "logging.handlers.RotatingFileHandler",
"level": "ERROR",
"formatter": "json",
"filename": "/var/log/myapp/error.log",
"maxBytes": 10485760,
"backupCount": 10
}
base["loggers"][""] = {"level": "INFO", "handlers": ["file", "error"]}
return base
env = os.getenv("ENV", "dev")
logging.config.dictConfig(get_logging_config(env))
七、结构化日志与 JSON 输出
7.1 普通文本 vs 结构化 JSON
❌ 普通文本日志
2026-07-16 14:32:10 [INFO] myapp:231 - 用户 12345 登录成功,IP: 192.168.1.1,耗时: 45ms
解析困难,需要正则提取字段,搜索效率低。
✅ 结构化 JSON 日志
{"timestamp":"2026-07-16T14:32:10Z",
"level":"INFO",
"logger":"myapp.auth",
"event":"user_login",
"user_id":"12345",
"client_ip":"192.168.1.1",
"duration_ms":45}
字段明确,可直接被 Elasticsearch 等系统索引。
7.2 python-json-logger 配置
# pip install python-json-logger
from pythonjsonlogger import jsonlogger
import logging
logHandler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter(
"%(asctime)s %(levelname)s %(name)s %(message)s",
rename_fields={"levelname": "level", "asctime": "timestamp"}
)
logHandler.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(logHandler)
logger.setLevel(logging.INFO)
# 输出结构化日志
logger.info("用户登录", extra={"user_id": "12345", "ip": "192.168.1.1"})
# {"timestamp": "2026-07-16 14:32:10", "level": "INFO", "name": "root",
# "message": "用户登录", "user_id": "12345", "ip": "192.168.1.1"}
7.3 统一日志上下文(Context)
import logging
import contextvars
import uuid
# 上下文变量
request_id = contextvars.ContextVar("request_id", default="")
class ContextFilter(logging.Filter):
"""自动注入请求 ID 到日志记录。"""
def filter(self, record):
record.request_id = request_id.get()
return True
# 配置
logger = logging.getLogger("myapp")
logger.addFilter(ContextFilter())
formatter = logging.Formatter(
"%(asctime)s [%(levelname)s] [%(request_id)s] %(message)s"
)
# 使用
request_id.set(str(uuid.uuid4())[:8])
logger.info("处理请求开始")
logger.info("查询数据库")
logger.info("返回响应")
# 所有日志自动携带同一个 request_id,便于链路追踪
八、性能优化与异步日志
8.1 日志性能要点
性能优化原则
1. 延迟格式化 使用 logger.debug("value: %s", value) 而非 f"value: {value}",避免不必要的字符串拼接
2. 级别检查 DEBUG 日志密集场景使用 if logger.isEnabledFor(logging.DEBUG) 提前判断
3. 异步写入 高并发场景使用 QueueHandler 避免阻塞主线程
4. 批量刷新 文件 Handler 设置合理的 buffer size,减少 IO 次数
8.2 QueueHandler 异步配置
import logging
import logging.handlers
import queue
# 创建日志队列
log_queue = queue.Queue(-1) # 无界队列
# 队列 Handler(生产者)
queue_handler = logging.handlers.QueueHandler(log_queue)
# 实际写入 Handler(消费者)
file_handler = logging.FileHandler("app.log", encoding="utf-8")
file_handler.setFormatter(logging.Formatter(
"%(asctime)s [%(levelname)s] %(message)s"
))
# 队列监听器,在独立线程中消费日志
listener = logging.handlers.QueueListener(
log_queue, file_handler, respect_handler_level=True
)
# 配置根 Logger
root = logging.getLogger()
root.addHandler(queue_handler)
root.setLevel(logging.DEBUG)
# 启动监听器
listener.start()
# ... 应用运行 ...
# 关闭时停止监听器
listener.stop()
九、日志安全与敏感信息处理
9.1 敏感信息红线
9.2 敏感信息脱敏
import logging
import re
class SensitiveDataFilter(logging.Filter):
"""日志敏感信息脱敏过滤器。"""
PATTERNS = {
"password": re.compile(r'("password"\s*:\s*")[^"]*("', re.I),
"token": re.compile(r'(Bearer\s+)[\w\-\.]+', re.I),
"phone": re.compile(r'(1\d{2})\d{4}(\d{4})'),
"id_card": re.compile(r'(\d{6})\d{8}(\d{4})'),
}
def filter(self, record):
msg = record.getMessage()
for name, pattern in self.PATTERNS.items():
if name in ("password", "token"):
msg = pattern.sub(r'\1***\2', msg)
else:
msg = pattern.sub(r'\1****\2', msg)
record.msg = msg
record.args = ()
return True
# 应用过滤器
logger = logging.getLogger("myapp")
logger.addFilter(SensitiveDataFilter())
# 测试
logger.info('登录请求: {"username":"alice","password":"Secret123"}')
# 输出: 登录请求: {"username":"alice","password":"***"}
9.3 安全的日志记录实践
# ❌ 错误:记录敏感信息
logger.info(f"用户登录: {username}, 密码: {password}")
logger.debug(f"API 请求头: {headers}") # 可能包含 Token
# ✅ 正确:脱敏后记录
logger.info(f"用户登录: {username}")
logger.debug(f"API 请求头 keys: {list(headers.keys())}")
# ✅ 正确:只记录必要的标识
logger.info(f"支付请求: order_id={order_id}, amount={amount}")
# 不要记录: card_number, cvv, 持卡人姓名
十、常见问题与排查模板
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
| 日志不输出 | Logger/Handler 级别设置过高,或 propagate=False 且未配置 Handler | 检查 logger.level、handler.level、确认有 Handler 附加 |
| 日志重复输出 | 子 Logger 和根 Logger 都有 Handler,propagate=True | 子 Logger 设置 propagate=False,或移除重复的 Handler |
| 中文日志乱码 | FileHandler 未设置 encoding=”utf-8″ | 所有 FileHandler 显式设置 encoding=”utf-8″ |
| 日志文件不轮转 | 多进程下 RotatingFileHandler 竞争写入 | 使用 concurrent-log-handler,或改用 TimedRotatingFileHandler |
| 日志丢失 | 程序崩溃前日志未 flush 到磁盘 | 关键位置调用 logging.shutdown(),或设置 stream.flush() |
| 第三方库日志太多 | urllib3、sqlalchemy 等库默认级别低 | 单独配置第三方 Logger: logging.getLogger(“urllib3”).setLevel(logging.WARNING) |
| 日志阻塞主线程 | 同步文件 IO,高并发下性能瓶颈 | 使用 QueueHandler + QueueListener 实现异步日志 |
排查检查清单
# 1. 检查 Logger 层级和级别
for name in ["", "myapp", "myapp.api"]:
logger = logging.getLogger(name)
print(f"{name}: level={logger.level}, handlers={logger.handlers}, propagate={logger.propagate}")
# 2. 检查所有 Handler
for handler in logging.getLogger().handlers:
print(f"Handler: {handler}, level={handler.level}, formatter={handler.formatter}")
# 3. 强制输出测试
logging.getLogger().debug("debug test")
logging.getLogger().info("info test")
logging.getLogger().warning("warning test")
Python Logging Complete GuideLevels → Configuration → Production Best Practices
Logging levels deep dive, logging module architecture, Handler & Formatter, file management, structured logs, performance, environment-specific configs
Table of Contents
1 Why Logging Matters
Five Core Values of Logging
Troubleshooting Root cause analysis in production
Audit Trail Record user actions and data changes
Performance Identify bottlenecks via timing logs
Monitoring Track API calls, error rates, throughput
Debugging Trace execution flow during development
2 Logging Levels Deep Dive
| Level | Value | Meaning | Use Case | Production |
|---|---|---|---|---|
| DEBUG | 10 | Debug info | Variables, function params | Off |
| INFO | 20 | General info | Service start, requests | On |
| WARNING | 30 | Warning | Resource low, retries | On |
| ERROR | 40 | Error | Failures, exceptions | On |
| CRITICAL | 50 | Critical | System crash, data loss | On |
Level Filtering Flow
3 Python logging Architecture
Logger (hierarchical namespace)
├── "myapp.api.auth" inherits "myapp.api"
├── setLevel() — minimum accepted level
├── propagate — pass to parent logger
└── Handlers
├── StreamHandler → console
├── FileHandler → file
├── RotatingFileHandler → size-based rotation
└── TimedRotatingFileHandler → time-based rotation
└── Formatter: "%(asctime)s [%(levelname)s] %(message)s"
4 Configuring Logging
dictConfig (Recommended)
LOGGING_CONFIG = {
"version": 1,
"formatters": {
"standard": {
"format": "%(asctime)s [%(levelname)-8s] %(name)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"level": "INFO",
"formatter": "standard"
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"level": "DEBUG",
"formatter": "standard",
"filename": "app.log",
"maxBytes": 10485760,
"backupCount": 5
}
},
"loggers": {
"": {"level": "DEBUG", "handlers": ["console", "file"]}
}
}
logging.config.dictConfig(LOGGING_CONFIG)
5 Log File Management
Rotation Strategies
| Handler | Trigger | Use Case |
|---|---|---|
| RotatingFileHandler | File size | Stable traffic apps |
| TimedRotatingFileHandler | Time (day/hour) | Scheduled archiving |
| WatchedFileHandler | External signal | Linux logrotate |
| QueueHandler | Async queue | High concurrency |
# Size-based rotation: 10MB per file, keep 5 backups
RotatingFileHandler("app.log", maxBytes=10*1024*1024, backupCount=5)
# Time-based rotation: daily at midnight, keep 30 days
TimedRotatingFileHandler("app.log", when="midnight", backupCount=30)
6 Environment-Specific Configs
Development
level: DEBUG handlers: [console] format: simple with line numbers
Production
level: INFO handlers: [file] format: JSON structured rotation: daily
7 Structured & JSON Logging
# pip install python-json-logger
from pythonjsonlogger import jsonlogger
formatter = jsonlogger.JsonFormatter(
"%(asctime)s %(levelname)s %(name)s %(message)s",
rename_fields={"levelname": "level"}
)
# Output:
# {"timestamp": "2026-07-16T14:32:10", "level": "INFO",
# "name": "myapp", "message": "user login", "user_id": "12345"}
8 Performance & Async Logging
Performance Tips
1. Lazy formatting Use logger.debug("val: %s", val) not f"val: {val}"
2. Level guard Use logger.isEnabledFor(logging.DEBUG) for heavy debug logs
3. Async write Use QueueHandler to avoid blocking main thread
9 Security & Sensitive Data
# ❌ Wrong
logger.info(f"Login: {username}, password: {password}")
# ✅ Correct
logger.info(f"Login: {username}")
logger.debug(f"Header keys: {list(headers.keys())}")
10 FAQ & Troubleshooting
| Issue | Solution |
|---|---|
| No output | Check logger.level, handler.level, ensure handler attached |
| Duplicate output | Set propagate=False on child loggers |
| Chinese garbled | Set encoding=”utf-8″ on all FileHandlers |
| Rotation not working | Use concurrent-log-handler for multi-process |
| Logs lost on crash | Call logging.shutdown() or flush handlers |
| Too many 3rd-party logs | Set urllib3/sqlalchemy loggers to WARNING |
