装饰器 · logging

标准库没有内置 @log 装饰器;工程里通常自定义 logging 装饰器。本文给出可复用模式,并说明与 00.概念-装饰器机制function_timer 的关系。

0. 一句话定位

维度 内容
作用对象 函数
使用场景 日志
来源 自定义(基于 logging + functools.wraps
语法形式 @log_call / @log_exceptions(logger=...)

1. 做什么

在函数调用前/后写日志:记录函数名、参数(可脱敏)、返回值或异常、耗时;统一格式与 logger 名称,避免业务代码散落 print

2. 重点参数(以自定义 log_call 为例)

参数 类型 默认值 作用 配置建议
logger logging.Logger logging.getLogger(__name__) 输出目标 模块级 logger = logging.getLogger(__name__)
level int logging.INFO 日志级别 调试路径用 DEBUG
log_args bool True 是否记录参数 生产环境敏感参数设 False
log_result bool False 是否记录返回值 大对象勿开
slow_threshold float None 超过秒数打 WARNING 与计时结合

3. 最小可运行示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import logging
import time
from functools import wraps

logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
logger = logging.getLogger(__name__)

def log_call(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
logger.info("call %s args=%s kwargs=%s", fn.__name__, args, kwargs)
t0 = time.perf_counter()
try:
result = fn(*args, **kwargs)
logger.info("done %s in %.4fs", fn.__name__, time.perf_counter() - t0)
return result
except Exception:
logger.exception("error in %s", fn.__name__)
raise
return wrapper

@log_call
def divide(a: float, b: float) -> float:
return a / b

divide(10, 2)
try:
divide(1, 0)
except ZeroDivisionError:
pass

4. 常见变体

带配置的工厂装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def log_call(logger=None, level=logging.INFO):
log = logger or logging.getLogger(__name__)

def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
log.log(level, "enter %s", fn.__name__)
return fn(*args, **kwargs)
return wrapper
return decorator

@log_call(level=logging.DEBUG)
def work():
return 1

仅记录异常

1
2
3
4
5
6
7
8
9
10
11
12
13
def log_exceptions(logger=None):
log = logger or logging.getLogger(__name__)

def decorator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
try:
return fn(*args, **kwargs)
except Exception:
log.exception("%s failed", fn.__name__)
raise
return wrapper
return decorator

异步版本

1
2
3
4
5
6
def log_call_async(fn):
@wraps(fn)
async def wrapper(*args, **kwargs):
logger.info("call %s", fn.__name__)
return await fn(*args, **kwargs)
return wrapper

类方法注意:装饰实例方法时 args[0]self,日志中可省略或缩短。

5. 适用 / 不适用

适用

  • 服务层关键函数、集成调试、慢调用告警
  • 统一异常栈记录(配合 logger.exception

不适用

  • 生产高频路径全量 INFO + 大对象 → 磁盘与性能压力
  • 替代专业 APM(OpenTelemetry、Datadog)→ 装饰器可作补充

6. 易踩坑

  • 必须 @wraps,否则日志里函数名全是 wrapper
  • 记录密码、token、PII 须脱敏或关闭 log_args
  • logging 默认只输出 WARNING+ 到 stderr,需 basicConfig 或 handler 配置
  • 多进程/多 worker 各自写文件需 RotatingFileHandler 或集中日志
  • function_timer 重复时合并为一个装饰器,避免双层包装

7. 近邻替代

替代 何时用
标准库 @contextmanager + with 块级日志
OpenTelemetry span 分布式追踪
FastAPI @app.middleware HTTP 请求级访问日志

8. 参考

-------------本文结束感谢您的阅读-------------