开源日志包 标准库 log 包
标准库自带,无需安装
只提供 Print 、Panic 、Fatal 函数用于日志输出
Go 标准库大量使用了该 log 包
glog
Kubernetes 使用的 klog 是基于 glog 进行封装
Google 推出的轻量级 日志包
特性
支持 4 种日志级别: Info 、Warning 、Error 、Fatal
支持命令行 选项
支持根据文件大小切割 日志文件
支持日志按级别分类输出
支持 V level – 开发者自定义日志级别
支持 vmodule – 开发者对不同的文件 使用不同的日志级别
支持 traceLocation – 打印指定位置的栈信息
logrus
Github star 数量最多的日志包,Docker 和 Prometheus 也在使用 logrus
支持常用的日志级别
可扩展 :允许使用者通过 Hook 的方式,将日志分发到任意地方
支持自定义的日志格式 :内置支持 JSON 和 TEXT
结构化 日志记录:Field 机制 允许使用者自定义字段
预设 日志字段:Default Field 机制 ,可以给一部分或者全部日志统一添加共同的日志字段
Fatal handlers :允许注册一个或多个 Handler,当产生 Fatal 级别的日志时调用,常用于优雅关闭
zap
Uber 开源,以高性能 著称,子包 zapcore 提供很多底层的日志接口,适合二次封装
支持常用的日志级别
性能非常高
支持针对特定的日志级别 ,输出调用堆栈
与 logrus 类似:结构化日志、预设日志字段、支持 Hook
设计实现
源码:https://github.com/marmotedu/gopractise-demo/tree/master/log/cuslog
功能需求
支持自定义配置
支持文件名 和行号
支持日志级别 :Debug、Info、Warn、Error、Panic、Fatal
支持输出到本地文件 和标准输出
支持 JSON 和 TEXT 的日志输出,支持自定义日志格式
支持选项模式
级别 + 选项
为了方便比较 ,几乎所有的日志包都用常量计数器 iota 来定义日志级别
options.go 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 type Level uint8 const ( DebugLevel Level = iota InfoLevel WarnLevel ErrorLevel PanicLevel FatalLevel ) var LevelNameMapping = map [Level]string { DebugLevel: "DEBUG" , InfoLevel: "INFO" , WarnLevel: "WARN" , ErrorLevel: "ERROR" , PanicLevel: "PANIC" , FatalLevel: "FATAL" , }
常见的日志选项:日志级别 、输出位置 (标准输出 or 文件)、输出格式 (JSON or Text)、是否开启文件名 和行号
options.go 1 2 3 4 5 6 7 type options struct { output io.Writer level Level stdLevel Level formatter Formatter disableCaller bool }
formatter.go 1 2 3 type Formatter interface { Format(entry *Entry) error }
通过选项 模式,可以灵活 地设置日志选项
options.go 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 type Option func (*options) func initOptions (opts ...Option) (o *options) { o = &options{} for _, opt := range opts { opt(o) } if o.output == nil { o.output = os.Stderr } if o.formatter == nil { o.formatter = &TextFormatter{} } return } func WithLevel (level Level) Option { return func (o *options) { o.level = level } } func WithDisableCaller (caller bool ) Option { return func (o *options) { o.disableCaller = caller } }
logger.go 1 2 3 4 5 6 7 8 9 10 11 12 func SetOptions (opts ...Option) { std.SetOptions(opts...) } func (l *logger) SetOptions(opts ...Option) { l.mu.Lock() defer l.mu.Unlock() for _, opt := range opts { opt(l.opt) } }
具有选项模式的日志包,可以动态 地修改日志选项
main.go 1 cuslog.SetOptions(cuslog.WithLevel(cuslog.DebugLevel), cuslog.WithDisableCaller(true ))
Logger + 打印
创建 Logger,日志包都会有一个默认的全局 Logger
logger.go 1 2 3 4 5 6 7 8 9 10 11 12 13 var std = New()type logger struct { opt *options mu sync.Mutex entryPool *sync.Pool } func New (opts ...Option) *logger { logger := &logger{opt: initOptions(opts...)} logger.entryPool = &sync.Pool{New: func () interface {} { return entry(logger) }} return logger }
非格式化打印 + 格式化打印
logger.go 1 2 3 4 5 6 7 func (l *logger) Debug(args ...interface {}) { l.entry().write(DebugLevel, FmtEmptySeparate, args...) } func (l *logger) Debugf(format string , args ...interface {}) { l.entry().write(DebugLevel, format, args...) }
Panic、Panicf 要调用 panic()
函数;Fatal、Fatalf 要调用 os.Exit(1)
函数
写入输出
Entry 用来保存所有的日志信息:日志配置 + 日志内容
entry.go 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 type Entry struct { logger *logger Buffer *bytes.Buffer Map map [string ]interface {} Level Level Time time.Time File string Line int Func string Format string Args []interface {} } func (e *Entry) write(level Level, format string , args ...interface {}) { if e.logger.opt.level > level { return } e.Time = time.Now() e.Level = level e.Format = format e.Args = args if !e.logger.opt.disableCaller { if pc, file, line, ok := runtime.Caller(2 ); !ok { e.File = "???" e.Func = "???" } else { e.File, e.Line, e.Func = file, line, runtime.FuncForPC(pc).Name() e.Func = e.Func[strings.LastIndex(e.Func, "/" )+1 :] } } e.format() e.writer() e.release() } func (e *Entry) format() { _ = e.logger.opt.formatter.Format(e) } func (e *Entry) writer() { e.logger.mu.Lock() _, _ = e.logger.opt.output.Write(e.Buffer.Bytes()) e.logger.mu.Unlock() } func (e *Entry) release() { e.Args, e.Line, e.File, e.Format, e.Func = nil , 0 , "" , "" , "" e.Buffer.Reset() e.logger.entryPool.Put(e) }
io.go 1 2 3 type Writer interface { Write(p []byte ) (n int , err error ) }
自定义输出格式
测试 example.go 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 package mainimport ( "log" "os" "github.com/marmotedu/gopractise-demo/log/cuslog" ) func main () { cuslog.Info("std log" ) cuslog.SetOptions(cuslog.WithLevel(cuslog.DebugLevel)) cuslog.Debug("change std log to debug level" ) cuslog.SetOptions(cuslog.WithFormatter(&cuslog.JsonFormatter{IgnoreBasicFields: false })) cuslog.Debug("log in json format" ) cuslog.Info("another log in json format" ) fd, err := os.OpenFile("test.log" , os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644 ) if err != nil { log.Fatalln("create file test.log failed" ) } defer fd.Close() l := cuslog.New(cuslog.WithLevel(cuslog.InfoLevel), cuslog.WithOutput(fd), cuslog.WithFormatter(&cuslog.JsonFormatter{IgnoreBasicFields: false }), ) l.Info("custom log with json formatter" ) }
1 2 3 4 5 $ go run example.go 2022-05-02T20:38:54+08:00 INFO example.go:11 std log 2022-05-02T20:38:54+08:00 DEBUG example.go:13 change std log to debug level {"message":"log in json format","level":"DEBUG","time":"2022-05-02T20:38:54+08:00","file":"/Users/zhongmingmao/workspace/go/src/github.com/marmotedu/gopractise-demo/log/cuslog/example/example.go:15","func":"main.main"} {"file":"/Users/zhongmingmao/workspace/go/src/github.com/marmotedu/gopractise-demo/log/cuslog/example/example.go:16","func":"main.main","message":"another log in json format","level":"INFO","time":"2022-05-02T20:38:54+08:00"}