日志装饰器需严格保持原函数签名不变,预制LogFunc1/LogFunc2E等通用包装器;用logrus/zap结构化打日志,带上下文字段、起止时间、参数快照与耗时;不recover panic,仅记录不兜底;支持开关与采样控制。

直接用装饰器包一层,保持原函数签名完全不变,就能注入日志逻辑。核心不是加功能,而是不惊动调用方——别人还在写 process(u),你不能让它突然报错说“找不到这个函数”。
必须严格匹配原函数签名
装饰器返回的包装函数,参数类型、数量、顺序和返回值类型,要和原函数一模一样。比如原函数是 func(id int, name string) (bool, error),包装后也得是这个签名。否则无法赋值给同类型变量,也无法传给期望该类型的接口或回调。
- 别用
reflect动态适配所有签名:运行慢、无编译检查、调试困难 - 推荐按常用模式预制几个通用包装器,如
LogFunc1(1入1出)、LogFunc2E(2入+error返回)等,靠 IDE 补全快速复用 - Python 中用
*args, **kwargs可覆盖多数情况;Go 中需为每类签名单独写,这是权衡后的稳态方案
用结构化日志替代 print 或 log.Printf
只打一句 "calling GetUser 123" 很快会失效。真实场景需要字段可查、上下文可追溯、错误可定位。
- 优先选用
logrus或zap,用WithField("user_id", u.ID).WithField("trace_id", tid)带上下文打日志 - Go 中注意
zap.Logger非并发安全,包装器里应一次性构造子 logger,不要反复With()后丢弃 - 记录起止时间、参数快照、返回值(或错误)、耗时,这些信息要作为结构体字段输出,而非拼接字符串
不处理 panic,也不静默吞异常
日志装饰器只负责“记发生了什么”,不是兜底层。加 recover() 看似健壮,实则埋雷。
- 避免在包装器里写
defer func() { recover() }—— 会吃掉 panic,上层收不到错误,HTTP 返回 200 却没做事 - 真要捕获 panic,至少打印完整 stack trace:
debug.PrintStack(),否则recover()返回的interface{}几乎没法排查 - panic 处理应该放在真正能响应的位置,比如 HTTP handler 中间件、goroutine 启动入口,而不是每个业务函数包装器里
支持开关与采样,尤其在线上环境
日志不是越多越好。高频函数全量打日志,可能压垮磁盘或日志服务。
- 提供全局开关,比如环境变量
LOG_ENABLED=false或配置项一键关闭 - 对高频率函数启用采样,例如每 100 次调用只记录 1 次:
if rand.Intn(100) == 0 { logger.Info(...) } - 关键路径(如支付、登录)默认全量;后台任务、健康检查等可设低频或关闭

















