跳转到主内容
极星编程网:以代码为星,赴技术山海!

如何利用“函数装饰器”在不修改原函数签名的前提下注入逻辑日志

日志装饰器需严格保持原函数签名不变,预制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(...) }
关键路径(如支付、登录)默认全量;后台任务、健康检查等可设低频或关闭

相关文章