日志
约 1008 字大约 3 分钟
2025-05-08
在请求分析中, 我们说过, 日志是次核心功能, 重要程度仅次于核心能力, 前面的内容, 已经阐述了核心能力如何实现, 下面介绍日志如何处理.
日志选型
resty 库本身是提供了日志接口约束的, 可以在各个环节记录日志, 其约束如下:
// Logger interface is to abstract the logging from Resty. Gives control to
// the Resty users, choice of the logger.
type Logger interface {
Errorf(format string, v ...any)
Warnf(format string, v ...any)
Debugf(format string, v ...any)
}
通过 client.SetLogger()
, 可以设置一个日志实例, 但是这个日志的格式信息承载能力太弱, 用于开发阶段debug没有问题, 但是若用于生产信息的记录就显得过于单薄。
因此, 我们不使用内置的接口实现, 而是使用自定义的日志实例, 日志库使用: uber开源的zap
日志记录
日志库本身没有太多可讲的内容, 根据官方文档, 或者网络上海量教程, 很容易便知道如何使用. 重要的是, 我们该怎么记录日志.
日志记录,不是简简单单的将一段文本写入文件就可以的, 日志中必须包含足够我们分析问题的有效信息
那么, 在http 请求场景下, 什么是有效信息呢? 其实,可以参照记叙文六要素: 时间、地点、人物、起因、经过、结果
时间
: 请求是在哪一刻发生的, 用 毫秒时间戳表示地点
: 请求在哪台服务器上触发, server_ip / host_name 等信息人物
: 请求从哪里发出来? 要请求哪个接口? front-server信息 / api信息 / trace_id 等起因
: 为什么会发起接口调用?在网关场景下, 即为: 因为调用了某一个网关接口, 所以触发了某一个服务接口调用经过
: 调用接口时, 请求配置是什么样的(前面讲到的接口配置)? 执行了哪些校验逻辑(参数验证、流控等)?结果
: 执行结果成功还是失败? 最终的请求结果是什么?
上面就是http请求需要包含的信息, 包含上述信息足够日常监控与问题排查.
日志格式
上面讲了日志内容需要包含哪些信息, 在包含上述信息的前提下, 不同的团队可能会存在不同的日志打印规范, 因此, 做了两处处理:
- 日志实例由调用方传入, 只要是zap日志实例即可.
- 需要记录的数据, 字段名称、格式等, 不做强制要求, 由外部传入实现的format方法, 方法约束如下:
// BuildHttpLogDataFunc 构建日志记录的字段列表
// isResponseLog = false时, response 可能为 nil, 朱一处理 npe问题
// businessData 中是一些记录此日志时的一些上下文信息, 也可能为nil
type BuildHttpLogDataFunc func(ctx context.Context, isResponseLog bool, businessData map[string]any, reqCfg *define.Request, response *define.Response) []zap.Field
通过 httpclient.SetBuildDataFunc()
方法可以设置日志格式化的方法, 若未设置, 则不会记录日志信息.
日志实例
日志实例存在两个:
- 全局日志实例: 可以通过
log.Set()
方法进行设置 - 请求配置挂载的日志实例: 挂载在 RequestConfig 下的日志实例, 若未配置, 则会使用全局日志实例
为什么日志实例分为全局实例与请求实例?
大多数情况下, 仅有全局日志实例, 所有请求均复用此实例便足够. 但是网关代理了很多服务接口, 如果有极其敏感或者重要度极高的服务, 其日志策略、敏感程度与常规服务均不同又该如何处理?此时便可在请求配置下挂载独立的日志实例, 相当于个性化的日志记录实现。