Golang 日志框架 Zap 入坑指南
文章目录
- 简介
- Cases
- case 1: Hello World
- case 2: SugaredLogger
- case 3: 定制化 SugaredLogger
- 使用 Console 格式
- 修改日期显示格式
- 增加 caller 信息
- 修改日志级别
- 添加自定义字段
简介
众所周知,Zap 是个很 nb 的日志框架,作为入门篇,本文主要用几个例子来直观地感受下 Zap 写出来的日志长什么样,符不符合我们的需求(主要是审美需求),性能什么的我们攒钱不 care。
以下栗子由浅入深,循序渐进,大部分栗子可以直接 copy 运行。
Cases
case 1: Hello World
Zap 的 Hello World 代码大概长下面这样:
package mainimport ("fmt""go.uber.org/zap"
)func main() {var logger *zap.Loggerlogger, _ = zap.NewProduction()logger.Debug("i am debug") // 这行不会打印,因为默认日志级别是 INFOlogger.Info("i am info") // INFO 级别日志,这个会正常打印logger.Warn("i am warn") // WARN 级别日志,这个会正常打印logger.Error("i am error") // ERROR 级别日志,这个会打印,并附带堆栈信息logger.Fatal("i am fatal") // FATAL 级别日志,这个会打印,附带堆栈信息,并调用 os.Exit 退出fmt.Println("can i be printed?") // 这行不会打印,呃...上面已经退出了
}
编译运行该程序,输出大概如下:
{"level":"info","ts":1608362501.672074,"caller":"zap2/main.go:11","msg":"i am info"}
{"level":"warn","ts":1608362501.672139,"caller":"zap2/main.go:12","msg":"i am warn"}
{"level":"error","ts":1608362501.672147,"caller":"zap2/main.go:13","msg":"i am error","stacktrace":"main.main\n\t/Users/jack/go/src/helloworld/zap2/main.go:13\nruntime.main\n\t/usr/local/Cellar/go/1.15.2/libexec/src/runtime/proc.go:204"}
{"level":"fatal","ts":1608362501.6721768,"caller":"zap2/main.go:14","msg":"i am fatal","stacktrace":"main.main\n\t/Users/jack/go/src/helloworld/zap2/main.go:14\nruntime.main\n\t/usr/local/Cellar/go/1.15.2/libexec/src/runtime/proc.go:204"}
以上代码应该是相当简洁易懂了,在这里需要再唠嗑一下的是关于输出的日志的几个字段的说明:
- level: 顾名思义,就是日志的级别了,默认的日志级别是 INFO,所以我们的第一行日志没有被打印出来
- ts: timestamp,时间戳的意思
- caller: 调用者,就是打印日志的那行代码的位置
- stacktrace: 调用堆栈,及打印日志的那行代码所在的堆栈信息,默认 ERROR 级别及以上的日志会附带堆栈信息,可以修改
case 2: SugaredLogger
Sugar 是糖的意思,顾名思义,zap.SugaredLogger
就是对 zap.Logger
进行了封装,提供了一些高级的语法糖特性,如支持使用类似 fmt.Printf
的形式进行格式化输出,使用 zap.SugaredLogger
改造一下上面的例子:
package mainimport ("fmt""go.uber.org/zap"
)func main() {var sugaredLogger *zap.SugaredLoggerlogger, _ := zap.NewProduction()sugaredLogger = logger.Sugar()sugaredLogger.Debugf("i am debug, using %s", "sugar") // 这行不会打印,因为默认日志级别是 INFOsugaredLogger.Infof("i am info, using %s", "sugar") // INFO 级别日志,这个会正常打印sugaredLogger.Warnf("i am warn, using %s", "sugar") // WARN 级别日志,这个会正常打印sugaredLogger.Errorf("i am error, using %s", "sugar") // ERROR 级别日志,这个会打印,并附带堆栈信息sugaredLogger.Fatalf("i am fatal, using %s", "sugar") // FATAL 级别日志,这个会打印,附带堆栈信息,并调用 os.Exit 退出fmt.Println("can i be printed?") // 这行不会打印,呃...上面已经退出了
}
编译运行该程序,输出类似如下:
{"level":"info","ts":1608365481.615607,"caller":"zap3/main.go:13","msg":"i am info, using sugar"}
{"level":"warn","ts":1608365481.615681,"caller":"zap3/main.go:14","msg":"i am warn, using sugar"}
{"level":"error","ts":1608365481.6156878,"caller":"zap3/main.go:15","msg":"i am error, using sugar","stacktrace":"main.main\n\t/Users/jack/go/src/helloworld/zap3/main.go:15\nruntime.main\n\t/usr/local/Cellar/go/1.15.2/libexec/src/runtime/proc.go:204"}
{"level":"fatal","ts":1608365481.6157131,"caller":"zap3/main.go:16","msg":"i am fatal, using sugar","stacktrace":"main.main\n\t/Users/jack/go/src/helloworld/zap3/main.go:16\nruntime.main\n\t/usr/local/Cellar/go/1.15.2/libexec/src/runtime/proc.go:204"}
case 3: 定制化 SugaredLogger
使用 zap.NewProduction()
实际上是创建了一个带了预置配置的 zap.Logger
,我们可以使用 zap.New(core zapcore.Core, options ...Option)
来手动传递所有配置项,zapcore.Core
需要 3 个配置:
- Encoder: 编码器,用于告诉
zap
以什么样的形式写入日志。zap
提供了jsonEncoder
和consoleEncoder
两种内置的编码器 - WriterSyncer: 告诉
zap
将日志写到哪里,比如文件(os.File
) 或标准输出(os.stdout
) - LevelEnabler: 用于设置
zap.Logger
的日志级别
我们用定制化的方式来改写上述栗子:
package mainimport ("fmt""go.uber.org/zap""go.uber.org/zap/zapcore""os"
)func main() {// 配置 sugaredLoggervar sugaredLogger *zap.SugaredLoggerwriter := zapcore.AddSync(os.Stdout) // 设置日志输出的设备,这里还是使用标准输出,也可以传一个 File 类型让它写入到文件encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) // 设置编码器,即日志输出的格式,默认提供了 json 和 console 两种编码器,这里我们还是使用 json 的编码器core := zapcore.NewCore(encoder, writer, zapcore.InfoLevel) // 设置日志的默认级别logger := zap.New(core)sugaredLogger = logger.Sugar()// 打印日志sugaredLogger.Debugf("i am debug, using %s", "sugar") // 这行不会打印,因为日志级别是 INFOsugaredLogger.Infof("i am info, using %s", "sugar") // INFO 级别日志,这个会正常打印sugaredLogger.Warnf("i am warn, using %s", "sugar") // WARN 级别日志,这个会正常打印sugaredLogger.Errorf("i am error, using %s", "sugar") // ERROR 级别日志,这个会打印,并附带堆栈信息sugaredLogger.Fatalf("i am fatal, using %s", "sugar") // FATAL 级别日志,这个会打印,附带堆栈信息,并调用 os.Exit 退出fmt.Println("can i be printed?") // 这行不会打印,呃...上面已经退出了
}
编译运行该程序,输出类似如下:
{"level":"info","ts":1608367820.9526541,"msg":"i am info, using sugar"}
{"level":"warn","ts":1608367820.952697,"msg":"i am warn, using sugar"}
{"level":"error","ts":1608367820.9527,"msg":"i am error, using sugar"}
{"level":"fatal","ts":1608367820.952702,"msg":"i am fatal, using sugar"}
和上面的相比,这里不会打印 caller
和 stackstace
,因为我们没有配置,需要我们显式去配置它们。
使用 Console 格式
默认的 json
格式非常方面用于专业的第三日志分析工具(如 elk)处理,但可读性就不是非常好,如果日志是用来给人看的话,设置成 console
格式的可读性会好一点,特别是存在换行的时候(json
格式的每条日志只会显示一行),将上述栗子改成 console
格式:
package mainimport ("fmt""go.uber.org/zap""go.uber.org/zap/zapcore""os"
)func main() {// 配置 sugaredLoggervar sugaredLogger *zap.SugaredLoggerwriter := zapcore.AddSync(os.Stdout)encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()) // 设置 console 编码器core := zapcore.NewCore(encoder, writer, zapcore.InfoLevel)logger := zap.New(core)sugaredLogger = logger.Sugar()// 打印日志sugaredLogger.Debugf("i am debug, using %s", "sugar") // 这行不会打印,因为日志级别是 INFOsugaredLogger.Infof("i am info, using %s", "sugar") // INFO 级别日志,这个会正常打印sugaredLogger.Warnf("i am warn, using %s", "sugar") // WARN 级别日志,这个会正常打印sugaredLogger.Errorf("i am error, using %s", "sugar") // ERROR 级别日志,这个会打印,并附带堆栈信息sugaredLogger.Fatalf("i am fatal, using %s", "sugar") // FATAL 级别日志,这个会打印,附带堆栈信息,并调用 os.Exit 退出fmt.Println("can i be printed?") // 这行不会打印,呃...上面已经退出了
}
编译运行该程序,输出类似如下:
1.60837067575392e+09 info i am info, using sugar
1.6083706757539682e+09 warn i am warn, using sugar
1.608370675753972e+09 error i am error, using sugar
1.6083706757539752e+09 fatal i am fatal, using sugar
修改日期显示格式
上面栗子的时间戳看起来不是人能看得懂的对不对?所有我们要修改成人能看懂的(human readable
),继续修改上述栗子:
package mainimport ("fmt""go.uber.org/zap""go.uber.org/zap/zapcore""os"
)func main() {// 配置 sugaredLoggervar sugaredLogger *zap.SugaredLoggerwriter := zapcore.AddSync(os.Stdout)// 格式相关的配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 修改时间戳的格式encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 日志级别使用大写显示encoder := zapcore.NewConsoleEncoder(encoderConfig)core := zapcore.NewCore(encoder, writer, zapcore.InfoLevel)logger := zap.New(core)sugaredLogger = logger.Sugar()// 打印日志sugaredLogger.Debugf("i am debug, using %s", "sugar") // 这行不会打印,因为日志级别是 INFOsugaredLogger.Infof("i am info, using %s", "sugar") // INFO 级别日志,这个会正常打印sugaredLogger.Warnf("i am warn, using %s", "sugar") // WARN 级别日志,这个会正常打印sugaredLogger.Errorf("i am error, using %s", "sugar") // ERROR 级别日志,这个会打印,并附带堆栈信息sugaredLogger.Fatalf("i am fatal, using %s", "sugar") // FATAL 级别日志,这个会打印,附带堆栈信息,并调用 os.Exit 退出fmt.Println("can i be printed?") // 这行不会打印,呃...上面已经退出了
}
编译运行该程序,输出类似如下:
2020-12-19T17:44:19.031+0800 INFO i am info, using sugar
2020-12-19T17:44:19.031+0800 WARN i am warn, using sugar
2020-12-19T17:44:19.031+0800 ERROR i am error, using sugar
2020-12-19T17:44:19.031+0800 FATAL i am fatal, using sugar
呃…看起来正常点了,上述栗子顺便把日志级别显示改成大写了,看起来更习惯一点~
增加 caller 信息
在最前面的栗子中我们可以看到,使用默认的配置打印出来的日志是带有 caller
(即打印日志的代码所在的位置信息)的,如果需要的话我们可以把它加上去:
package mainimport ("fmt""go.uber.org/zap""go.uber.org/zap/zapcore""os"
)func main() {// 配置 sugaredLoggervar sugaredLogger *zap.SugaredLoggerwriter := zapcore.AddSync(os.Stdout)// 格式相关的配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 修改时间戳的格式encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 日志级别使用大写显示encoder := zapcore.NewConsoleEncoder(encoderConfig)core := zapcore.NewCore(encoder, writer, zapcore.InfoLevel)logger := zap.New(core, zap.AddCaller()) // 增加 caller 信息sugaredLogger = logger.Sugar()// 打印日志sugaredLogger.Debugf("i am debug, using %s", "sugar") // 这行不会打印,因为日志级别是 INFOsugaredLogger.Infof("i am info, using %s", "sugar") // INFO 级别日志,这个会正常打印sugaredLogger.Warnf("i am warn, using %s", "sugar") // WARN 级别日志,这个会正常打印sugaredLogger.Errorf("i am error, using %s", "sugar") // ERROR 级别日志,这个会打印,并附带堆栈信息sugaredLogger.Fatalf("i am fatal, using %s", "sugar") // FATAL 级别日志,这个会打印,附带堆栈信息,并调用 os.Exit 退出fmt.Println("can i be printed?") // 这行不会打印,呃...上面已经退出了
}
编译运行该程序,输出类似如下:
2020-12-19T17:50:24.257+0800 INFO zap3/main.go:29 i am info, using sugar
2020-12-19T17:50:24.257+0800 WARN zap3/main.go:30 i am warn, using sugar
2020-12-19T17:50:24.257+0800 ERROR zap3/main.go:31 i am error, using sugar
2020-12-19T17:50:24.257+0800 FATAL zap3/main.go:32 i am fatal, using sugar
修改日志级别
默认的级别的 INFO
,我们把它改成 DEBUG
, 这个也简单:
package mainimport ("fmt""go.uber.org/zap""go.uber.org/zap/zapcore""os"
)func main() {// 配置 sugaredLoggervar sugaredLogger *zap.SugaredLoggerwriter := zapcore.AddSync(os.Stdout)// 格式相关的配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 修改时间戳的格式encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 日志级别使用大写显示encoder := zapcore.NewConsoleEncoder(encoderConfig)core := zapcore.NewCore(encoder, writer, zapcore.DebugLevel) // 将日志级别设置为 DEBUGlogger := zap.New(core, zap.AddCaller()) // 增加 caller 信息sugaredLogger = logger.Sugar()// 打印日志sugaredLogger.Debugf("i am debug, using %s", "sugar") // 这行现在可以打印出来了!sugaredLogger.Infof("i am info, using %s", "sugar") // INFO 级别日志,这个会正常打印sugaredLogger.Warnf("i am warn, using %s", "sugar") // WARN 级别日志,这个会正常打印sugaredLogger.Errorf("i am error, using %s", "sugar") // ERROR 级别日志,这个会打印,并附带堆栈信息sugaredLogger.Fatalf("i am fatal, using %s", "sugar") // FATAL 级别日志,这个会打印,附带堆栈信息,并调用 os.Exit 退出fmt.Println("can i be printed?") // 这行不会打印,呃...上面已经退出了
}
编译运行该程序,输出类似如下:
2020-12-19T17:52:29.438+0800 DEBUG zap3/main.go:28 i am debug, using sugar
2020-12-19T17:52:29.439+0800 INFO zap3/main.go:29 i am info, using sugar
2020-12-19T17:52:29.439+0800 WARN zap3/main.go:30 i am warn, using sugar
2020-12-19T17:52:29.439+0800 ERROR zap3/main.go:31 i am error, using sugar
2020-12-19T17:52:29.439+0800 FATAL zap3/main.go:32 i am fatal, using sugar
可以看到 DEBUG
级别的日志也打印出来了。
添加自定义字段
有时候我们需要在每行日志中都加入一些字段,用来标识这些日志,可以在调用 zap.New()
创建 logger
的时候把这些字段设置上去,如下:
package mainimport ("fmt""github.com/google/uuid""go.uber.org/zap""go.uber.org/zap/zapcore""os"
)func main() {// 配置 sugaredLoggervar sugaredLogger *zap.SugaredLoggerwriter := zapcore.AddSync(os.Stdout)// 格式相关的配置encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder // 修改时间戳的格式encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder // 日志级别使用大写显示encoder := zapcore.NewConsoleEncoder(encoderConfig)core := zapcore.NewCore(encoder, writer, zapcore.DebugLevel) // 将日志级别设置为 DEBUGlogger := zap.New(core, zap.AddCaller(), zap.Fields(zapcore.Field{ // 添加 uuid 字段Key: "uuid",Type: zapcore.StringType,String: uuid.New().String(),}))sugaredLogger = logger.Sugar()// 打印日志sugaredLogger.Debugf("i am debug, using %s", "sugar") // 这行现在可以打印出来了!sugaredLogger.Infof("i am info, using %s", "sugar") // INFO 级别日志,这个会正常打印sugaredLogger.Warnf("i am warn, using %s", "sugar") // WARN 级别日志,这个会正常打印sugaredLogger.Errorf("i am error, using %s", "sugar") // ERROR 级别日志,这个会打印,并附带堆栈信息sugaredLogger.Fatalf("i am fatal, using %s", "sugar") // FATAL 级别日志,这个会打印,附带堆栈信息,并调用 os.Exit 退出fmt.Println("can i be printed?") // 这行不会打印,呃...上面已经退出了
}
编译运行该程序,输出类似如下:
2020-12-19T18:02:15.093+0800 DEBUG zap3/main.go:32 i am debug, using sugar {"uuid": "79432609-3ae9-4728-bbd2-f368d404018d"}
2020-12-19T18:02:15.093+0800 INFO zap3/main.go:33 i am info, using sugar {"uuid": "79432609-3ae9-4728-bbd2-f368d404018d"}
2020-12-19T18:02:15.093+0800 WARN zap3/main.go:34 i am warn, using sugar {"uuid": "79432609-3ae9-4728-bbd2-f368d404018d"}
2020-12-19T18:02:15.093+0800 ERROR zap3/main.go:35 i am error, using sugar {"uuid": "79432609-3ae9-4728-bbd2-f368d404018d"}
2020-12-19T18:02:15.093+0800 FATAL zap3/main.go:36 i am fatal, using sugar {"uuid": "79432609-3ae9-4728-bbd2-f368d404018d"}
可以看到每行日志都带有 uuid
信息了。
Golang 日志框架 Zap 入坑指南相关推荐
- 发布开源框架到CocoaPods入坑指南
个人原文博客地址: 发布开源框架到CocoaPods入坑指南 在开发过程中一定会用到一些第三方框架, 只要安装了CocoaPods, 然后通过pod install命令, 就可以集成框架到项目中了 可 ...
- python web-python web入坑指南
原标题:python web入坑指南 Invest regularly in your knowledge portfolio. Make learning a habit. 自学python web ...
- api 微信内置浏览器js_多端开发框架uni-app入坑指南,一套代码适用微信、头条等小程序...
前言 hello 小伙伴们,现在我已经正式入坑 uni-app 了.uni-app 已经发布好几个月了,期间也是踩坑无数,但是官方秉承着不抛弃不放弃的精神,积极解决开发者的各种简单的.复杂的问题,在此 ...
- 微信小程序继续入坑指南
微信小程序继续入坑指南 wxml 类似于html 感觉和ejs灰常的相似 数据绑定 js Page({data: {message: "hello world"} }) wxml ...
- uniapp调用c语言方法,uni-app 入坑指南-web开发
编辑推荐: 本文重点介绍了 uni-app 入坑指南,方便大家更好的了解 uni-app 本篇只讲述 uni-app,不与其他类似框架进行对比 本文来自于博客园,由火龙果软件Alice编辑推荐. 什么 ...
- 小白深度学习入坑指南
小白深度学习入坑指南 小白深度学习入坑指南 写博客的初衷适合的人群 理论篇 说明 数学 CV ML DL 优化 实践篇 linux python 深度学习框架 框架学习 硬件配置 软件环境配置 常见的 ...
- 开发工具篇第九讲:菜鸟入坑指南
摘要:本文是开发工具篇第九讲:菜鸟入坑指南.针对新人上手慢的问题,写了这篇入坑指南,方便自己回顾,总结.本文分为四个部分,分别为jenkins使用技巧:常用软件操作命令:开发手册:调试手册.主要材料来 ...
- Phalcon在Windows上安装 《Phalcon入坑指南系列 一》
Phalcon入坑指南 本系列目录 前言 一.安装 二.Phalcon 开发工具安装 三.环境变量配置 四.Phalcon 开发工具的使用 生成项目框架 生成控制器 / 模型 夸一下phalcon 五 ...
- 一块GPU搞定ChatGPT;ML系统入坑指南;理解GPU底层架构
1. 跑ChatGPT体量模型,从此只需一块GPU 在发展技术,让大模型掌握更多能力的同时,也有人在尝试降低AI所需的算力资源.最近,一种名为FlexGen的技术因为「一块RTX 3090跑ChatG ...
最新文章
- Scrapy项目实战
- 【BZOJ2117】 [2010国家集训队]Crash的旅游计划
- Python PIP Install throws TypeError: unsupported operand type(s) for -=: 'Retry' and 'int'
- ubuntu中查找文件后高亮_ubuntu 中查找文件的命令
- IOS-组件化架构漫谈
- 在unity2d同屏显示9千人
- Windows Phone 7 Developer Tools amp; Training Kit 正式版发布!
- [javaSE] 网络编程(浏览器客户端-自定义服务端)
- 日志的log中如何输出变量_如何在kubernetes中优雅的输出日志
- 图像分类中数据增强的有效性
- iPhone开发知识和项目
- 创建显示特殊文档的视图:$FormulaClass的奥秘
- 电脑文件一键实时备份同步至云端(百度云盘)
- 舒尔特注意力训练表格_用微信小程序里训练提升你的专注力
- js获取ip本机地址
- 同比 数据模型 环比_同比环比累计
- 最易学和最难学编程语言排行榜!
- html做群聊通讯方法,一例完整的websocket实现群聊demo
- 云杰恒指:8.16恒指期货指导交易周小结
- jQuery源码解析(架构与依赖模块)第一章 理解架构
热门文章
- 2018python培训-2018python深度学习核心技术培训班
- python入门指南 小说-Python 入门指南
- python培训出来的有公司要吗-目前从事Python培训的机构有很多家
- python网上编程课程-什么是Python编程课程
- python输入任意多个成绩-Python 实现输入任意多个数,并计算其平均值的例子
- python怎么读文件后删去空格以行为单位进行排序-文件操作
- linux 上使用wc -l命令快速查看文件内容有多少行,以及如何匹配指定字符查看多少行
- c#和mysql之间的类型_【SqlServer数据类型、C#数据类型、SqlDbType】对应关系及转换...
- ubuntu20.04编译openjdk8
- 给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。