type
status
date
slug
summary
tags
category
icon
password
📌
前言
学习孔云飞大佬的 miniblog 项目笔记,项目地址:https://github.com/onexstack/miniblog
具体知识详参引用文章,文章不做过多赘述,只概后续开发用到的知识,方便开发快速参考
 

1. 日志记录

 
记录日志通常涉及到以下几个方面:
  • 日志记录方式
  • 日志记录规范
  • 日志保存方式
 

1.1 日志记录方式

 
在 Go 项目开发中,通过日志包来记录日志。所以,在项目开发之前,需要准备一个易用、满足需求的 Go 日志包。准备日志包的方式有以下三种:
  1. 使用开源日志包:使用开源的日志包,例如 log、glog、logrus、zap 等。Docker、 ilium、Tyk 等项目使用了 logrus,etcd 则使用了 log 和 zap;
  1. 定制化开源日志包:基于开源日志包封装一个满足特定需求的日志包。例如,Kubernetes 使用的 klog 是基于 glog 开发的。有些项目封装的日志包还会兼容多种类别的 Logger;
  1. 自研日志包:根据需求,从零开发一个日志包。
 
目前已有许多开源日志包,社区比较受欢迎的开源日志包有 logrus、zap、zerolog、apex/log、log15 等。其中最受欢迎的两个日志包是 logrus 和 zap。k8s 使用的时 klog 进行记录。
 
一般项目中基于 logrus 或者 zap 两个包进行使用基本足够:
  • logrus 功能强大、使用简单,不仅实现了日志包的基本功能,还有很多高级特性,适合一些大型项目,尤其是需要结构化日志记录的项目。因为 logrus 封装了很多能力性能一般
  • zap 提供了很强大的日志功能,性能高,内存分配次数少,适合对日志性能要求很高的项目。另外,zap 包中的子包 zapcore,提供了很多底层的日志接口,适合用来做二次封装
 
logrus、 zap、 klog 对比:
特性对比
logrus (1)
zap (2)
klog (3)
日志级别
支持 Debug、Info、Warn、Error、Fatal、Panic
支持 Debug、Info、Warn、Error、DPanic、Panic、Fatal
支持 Info、Warning、Error、Fatal、Panic
结构化日志
支持,通过 Fields 添加结构化字段
支持,性能优化,可直接将复杂类型作为字段
支持,但不如 logrus 和 zap 灵活
日志格式
默认文本格式,可自定义为 JSON
提供 Console 和 JSON 编码器,可灵活配置
默认文本格式,可自定义为 JSON
日志输出
支持控制台、文件等
支持控制台、文件、网络等
支持控制台、文件
调用堆栈
支持,可输出堆栈信息
支持,可在特定级别输出堆栈
支持,可在日志中输出堆栈信息
插件支持
支持,可通过插件扩展功能
支持,可通过 Hooks 机制扩展
不支持
性能对比
logrus (3)
zap (1)
klog (2)
性能表现
性能一般,适合中小规模项目
性能极高,适合对性能要求极高的项目
性能较好,但不如 zap
内存分配
内存分配较多,性能瓶颈
使用 sync.Pool,内存分配少
内存分配适中
易用性
logrus (1)
zap (3)
klog (2)
学习成本
较低,适合新手
较高,功能丰富
较低,基于 glog 封装
使用复杂度
使用简单,适合快速开发
功能强大但配置复杂
使用简单,适合 Kubernetes
适用场景
logrus
zap
klog
适用项目
中小型项目、对结构化日志有需求的项目
高性能要求、大规模分布式系统
Kubernetes 生态
二次封装
不太适合,功能封装较多
非常适合,底层接口丰富
不适合,主要用于 Kubernetes
综上:
  • logrus:功能强大且灵活,适合中小规模项目,尤其是对结构化日志有需求的场景。
  • zap:性能卓越,适合对性能要求极高的项目,尤其是分布式系统。
  • klog:适合 Kubernetes 生态,使用简单,但功能相对有限。
 
如果是 Kubernetes 生态,应该选用 klog,平时开发直接选用 zap 即可。
 
定制化开源日志包以及自研日志包大概都接触不上,所以暂时不赘述。
 

1.2 日志记录规范

 
miniblog 也制定了相应的日志规范,具体规范内容见 docs/devel/zh-CN/conversions/logging.md。该日志规范可以在后续的开发过程中根据需求不断更新和迭代。
 
在 miniblog 的日志规范中,有以下两点规范需要注意:
  1. 错误日志应在最初发生错误的位置打印。这样做一方面可以避免上层代码缺失关键的日志信息(因为上层代码可能无法获取错误发生处的详细信息),另一方面可以减少日志漏打的情况(距离错误发生位置越远,越容易忽略错误的存在,从而导致日志未被打印);
  1. 当调用第三方报函数或放发报错时,需要在错误处打印日志,例如:
 
对于嵌套的 Error,可在 Error 产生的最初位置打印 Error 日志,上层如果不需要添加必要的信息,可以直接返回下层的 Error。例如:
 
在最初产生错误的位置打印日志,可以很方便地追踪到错误产生的根源,并且错误日志只打印一次,可以减少重复的日志打印,减少排障时重复日志干扰,也可以提高代码的简洁度。当然,在开发中也可以根据需要对错误补充一些有用的信息,以记录错误产生的其他影响。
 

1.3 日志保存方式

 
我们可以将日志保存到任意需要的位置,常见的保存位置包括以下几种:
  1. 标准输出:通常用于开发和测试阶段,主要目的是便于调试和查看;
  1. 日志文件:这是生产环境中最常见的日志保存方式。保存的日志通常会被 Filebeat、Fluentd 等日志采集组件收集,并存储到 Elasticsearch 等系统中;
  1. 消息中间件:例如 Kafka。日志包会调用 API 接口将日志保存到 Kafka 中。为了提高性能,通常会使用异步任务队列异步保存。然而,在这种情况下,需要开发异步上报逻辑,且服务重启时可能导致日志丢失,因此这种方式较少被采用。
 
当前比较受欢迎的日志包(如 zap、logrus 等)都支持将日志同时保存到多个位置。例如,miniblog 项目的日志包底层封装了 zap,zap 支持同时将日志输出到标准输出和日志文件中。
 
如果应用采用容器化部署,建议优先将日志输出到标准输出。容器平台通常具备采集容器日志的能力,采集日志时可以选择从标准输出采集或从容器内的日志文件中采集。如果选择从日志文件采集,则需要配置日志采集路径;而如果选择从标准输出采集,则无需额外配置,可以直接复用容器平台现有的能力,从而实现日志记录与日志采集的完全解耦。在 Kubernetes 最新的日志设计方案中,也建议应用直接将日志输出到标准输出。

2. miniblog 日志包

 

2.1 miniblog 日志包开发

 
这里不做过多说明,详情参考文章:10 | 基础 Go 包开发:日志包设计和实现
 

2.2 miniblog 日志包使用

 

2.2.1 开箱即用前需要知道的知识点

 
日志级别和记录方法:
  1. 日志级别:在记录日志时,按严重性由低到高通常包括 Debug、Info、Warn、Error、Panic、Fatal 级别。Warn 级别在有些日志包中也叫 Warning 级别;
  1. 日志记录方法:每个日志级别,根据记录方式,又包括非格式化记录、格式化记录和结构化记录三种方式。形如 Info(msg string) 的方法为非格式化记录方式。形如 Infof(format string, args ...any) 的方法为格式化记录方式。形如 Infow(msg string, kvs ...any) 的方法为结构化记录方式。Infow 方法名中的 w 代表“with”,即“带有”额外的上下文信息。这些方法与没有 w 的方法(如 Debug,Info 等)相比,允许你在日志消息后面附加额外的键值对(key-value),从而提供更详细的上下文信息。
 
miniblog 在设计时,为了满足项目不同日志级别的记录需求,实现了 Debug、Info、Warn、Error、Panic、Fatal 级别的记录方法。
 
在 Go 项目开发中,建议的日志记录方式为结构化记录方式(带有 w 后缀的,例如 Infow
 
格式化记录方式可以通过结构化记录方式来替代,例如:log.Infof("Failed to create user: %s", username) 可替换为 log.Infow("Failed to create user", "username", username)。
 
所以,miniblog 项目为了方便日志记录,降低开发者理解日志记录方法的负担,只实现了结构化记录方法。
 
 
将日志包 log 放置在 internal/pkg 目录下的原因在于,日志包封装了一些定制化的逻辑,不适合对外暴露,所以不适合放在 pkg/ 目录下。但是日志包又是项目内的共享包,所以需要放在 internal/pkg 目录下。
 
通过定义 Logger 接口,可以体现接口即规范的编程哲学。这意味着,通过 Logger 接口可以清晰地表明 zapLogger 需要实现哪些方法,并明确日志调用者应调用哪些方法。在 Go 项目中,通常将日志接口命名为 Logger。
 

2.3 miniblog 日志包开箱即用

2.3.1 初始化日志包

 
日志配置,实际开发项目中如有特殊配置项再更新改配置:
 
 
初始化日志包:
 
📌
在 run 函数中,添加了 log.Init(logOptions()) 函数调用,用来在应用运行时,初始化日志实例,并在 miniblog 应用退出时,调用 log.Sync() 将缓存中的日志写入磁盘中。

🤗 总结归纳

  • 整理日志记录打印以及保存方式
  • miniblog 日志包开箱即用说明方式

📎 参考文章