16-史上最快的日志库-zap.md
一 Zap介绍
Zap 是Uber推出,非常快的、结构化的,分日志级别的 Go 日志库
无反射,零分配的JSON编码器,基本记录器尽可能避免序列化开销和分配
项目地址:https://github.com/uber-go/zap
官方文档:https://pkg.go.dev/go.uber.org/zap
Stars数量:20.3k
速度比较

二 快速使用
zap提供了两种日志记录器
go get -u go.uber.org/zap
2.1 SugaredLogger
加了糖的 Logger
在性能很好但不是很关键的环境中,使用SugaredLogger。它比其他结构化日志包快4-10倍,并且包含结构化和printf风格的api。
package main
import (
"go.uber.org/zap"
"time"
)
func main() {
//1 创建一个Production(生产环境)的 logger
logger, _ := zap.NewProduction()
//2 flushes buffer, if any, 刷新缓冲区,存盘
defer logger.Sync()
// 3 创建Sugar的logger
sugar := logger.Sugar()
//4 输出日志
sugar.Info("info--普通格式日志")
sugar.Infof("info--格式化字符串格式日志: %s", "lqz")
sugar.Infow("info---松散类型的键值对格式日志",
// 结构化上下文为松散类型的键值对,随便写键值对
"name", "lqz",
"attempt", 3,
"backoff", time.Second,
)
}
2.2 Logger
当性能和类型安全至关重要时,使用Logger。它甚至比SugaredLogger还要快,并且分配的数量要少得多,但是它只支持结构化日志 。
package main
import (
"go.uber.org/zap"
"time"
)
func main() {
// 看源码,默认基本是InfoLevel,所有Debug不会打印
logger, _ := zap.NewProduction()
defer logger.Sync()
logger.Info("info--松散类型的键值对格式日志",
// 作为强类型字段值的结构化上下文.
zap.String("name", "lqz"),
zap.Int("age", 19),
zap.Duration("backoff", time.Second),
)
logger.Error("error--松散类型的键值对格式日志",
zap.String("name", "lqz"),
zap.Int("age", 19),
zap.Duration("backoff", time.Second),)
}
2.3 如何选择logger
在Logger和SugaredLogger之间进行选择不需要在应用程序范围内进行决定:在两者之间进行转换既简单又便宜。从上面就可以看出来,二者创建使用区别很小。
package main
import (
"go.uber.org/zap"
)
func main() {
logger := zap.NewExample()
defer logger.Sync()
sugar := logger.Sugar() // 通过logger得到Sugar
plain := sugar.Desugar()// 通过Sugar得到logger
sugar.Info("info-->sugar")
plain.Info("info-->logger")
}
2.4 日志级别
//const 文档下面有介绍日志级别的定义,7个日志级别
const (
// DebugLevel logs are typically voluminous, and are usually disabled in
// production.
DebugLevel = zapcore.DebugLevel
// InfoLevel is the default logging priority.
InfoLevel = zapcore.InfoLevel
// WarnLevel logs are more important than Info, but don't need individual
// human review.
WarnLevel = zapcore.WarnLevel
// ErrorLevel logs are high-priority. If an application is running smoothly,
// it shouldn't generate any error-level logs.
ErrorLevel = zapcore.ErrorLevel
// DPanicLevel logs are particularly important errors. In development the
// logger panics after writing the message.
DPanicLevel = zapcore.DPanicLevel
// PanicLevel logs a message, then panics.
PanicLevel = zapcore.PanicLevel
// FatalLevel logs a message, then calls os.Exit(1).
FatalLevel = zapcore.FatalLevel
)
三 高级使用
3.1 初始化 looger
在文档的 Configuring Zap中:
构建Logger最简单的方法是使用zap固有的预设:
NewExample,NewProduction和NewDevelopment
三者创建的 logger 是有区别的, 我们可以在官方文档的 type logger下面找到三个函数的介绍, 对应不同的场景。
NewExample
func NewExample(options ...Option) *Logger
NewExample构建了一个专门为zap的可测试示例设计的Logger。它将DebugLevel及以上的日志作为JSON写入标准输出,但省略了时间戳和调用函数,以保持示例输出的简短和确定性
NewProduction
func NewProduction(options ...Option) (*Logger, error)
NewProduction构建了一个合理的生产日志记录器,它将infollevel及以上的日志以JSON的形式写入标准错误。
它是NewProductionConfig(). build(…Option)的快捷方式。
NewDevelopment
func NewDevelopment(options ...Option) (*Logger, error)
NewDevelopment构建一个开发日志记录器,它以人类友好的格式将DebugLevel及以上级别的日志写入标准错误。
这是NewDevelopmentConfig().Build(…选项)的快捷方式
就好比去商店卖商品,初上自带了几个配置好的模式。 通过配置生成对应的 logger。 我们也可以自定义 配置,生成自己自定义的 logger
3.2 方法使用
在文档的 types/logger 和 types/SaguredLogger 里面记录了相关的looger记录消息的使用方法
以 logger为例:
//1 都接受一个 msg String, 后面是可选的一些字段。Field 类型,可以查看文档有很多的类型。
//2 写一个Get请求访问相应的网址,记录日志信息

NewProduction
package main
import (
"go.uber.org/zap"
"net/http"
"time"
)
func main() {
// 创建一个 logger, 可以选择其他预置创建,会有不同的输出效果
logger, _ := zap.NewProduction()
defer logger.Sync() // flushes buffer, if any, 刷新缓冲区,存盘
url:="https://www.cnblogs.com/liuqingzheng"
res,err:=http.Get(url)
if err != nil {
logger.Error("访问博客失败",
zap.String("url",url), // 字符串形式field
zap.Error(err)) // 错误形式field
}else {
logger.Info("访问博客成功",
zap.String("url",url), // 字符串形式field
zap.Int("status",res.StatusCode),// int类型字段
zap.Duration("backoff",time.Second*3)) // Duration类型字段,只是用来做测试,无特殊含义
res.Body.Close()
}
}
// 成功
{"level":"info","ts":1651478232.032387,"caller":"go_test_learn/s20.go:20","msg":"访问博客成功","url":"https://www.cnblogs.com/liuqingzheng"us":200,"backoff":3}
// 失败
{"level":"error","ts":1651478317.474609,"caller":"go_test_learn/s20.go:16","msg":"访问博客失败","url":"https://www.cnblogs.cm/liuqingzheng"r":"Get \"https://www.cnblogs.cm/liuqingzheng\": dial tcp: lookup www.cnblogs.cm: no such host","stacktrace":"main.main\n\t/Users/liuqingzheng/go/src/go_test_learn/s20.go:16\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:255"}
注意
可以看到执行程序后终端提示相关的信心。 msg 就是自己设置的,时间, url等。 还有一个 caller 调用者信息,指明问题出现的行数
NewDevelopment()
创建的生成日志是这样的: 空格隔开, 缺少调用者
2022-05-02T16:01:00.202+0800 INFO go_test_learn/s20.go:21 访问博客成功 {"url": "https://www.cnblogs.com/liuqingzheng", "status": 200, "backoff": "3s"}
NewExample()
{"level":"info","msg":"访问博客成功","url":"https://www.cnblogs.com/liuqingzheng","status":200,"backoff":"3s"}
3.3 定制 logger
查看NewProduction 的源码,实际底层就是: NewProductionConfig().Build(options...)
func NewProduction(options ...Option) (*Logger, error) {
//调用了 NewProductionConfig()方法,内部初始化创建,返回了一个 Config 对象
//Build, 内部通过 Config对象的配置, 利用New方法生成相应的 logger对象,并返回
return NewProductionConfig().Build(options...)
}
// 这是 zap库给我们预置的 NewProduction()等方法,内部是按照指定的配置,生成相应的 logger 日志对象。 我们也可以自己调用内部的相关方法, 模仿 NewProductionConfig().Build(options…) 相关过程,自己创建,定制化 logger对象。
观察New方法 生成logger 所需要的东西。在Build 函数中:
// 返回一个 Core对象, 需要的是三个参数
func NewCore(enc Encoder, ws WriteSyncer, enab LevelEnabler) Core
// 返回一个Logger指针
func New(core zapcore.Core, options ...Option) *Logger
log := New(
zapcore.NewCore(enc, sink, cfg.Level),
cfg.buildOptions(errSink)...,
)
//Core是一个最小的、快速的记录器接口。它是为库作者设计的,用来封装更友好的API
关于 NewProductionConfig()函数, 返回对应的Config 对象,Build 函数根据这个配置,进行生成 logger对象。
我们可以自定义这个, 来实现生成自己的logger下面看下它的源码
// NewProductionConfig是一个合理的生产日志配置。
//在infollevel及以上级别启用日志记录。
//它使用JSON编码器,写入标准错误,并启用采样。
// stacktrace会自动包含在ErrorLevel及以上的日志中。
func NewProductionConfig() Config {
return Config{
// 日志级别
Level: NewAtomicLevelAt(InfoLevel),
Development: false,
Sampling: &SamplingConfig{
Initial: 100,
Thereafter: 100,
},
// 编码方式
Encoding: "json",
// EncoderCofig, 配置 encoder 编辑器的默认配置。
EncoderConfig: NewProductionEncoderConfig(),
// 打开的文件, 写入日志信息到这里。
OutputPaths: []string{"stderr"},
ErrorOutputPaths: []string{"stderr"},
}
}
写入文件
方式一
-
按照上面的自定义 logger, 创建核心Core需要三个参数,其中就有控制 写入文件的
-
Encoder: 编辑器。提供了两种信息的编辑方式
-
WriteSyncer:
指定日志写到哪里
。可以定义自己指定的文件路径-
通过func AddSync(w io.Writer) WriteSyncer 方法,返回一个。
AddSync用于转换io。Writer到WriteSyncer。它是智能的:Writer实现了WriteSyncer,我们将使用现有的Sync方法。如果没有,我们将添加一个无操作同步。// 创建文件对象 file, _ := os.Create("./getLog.log") // 或者是用 OpenFile函数,在原来基础上追加。 // file, _ := os.OpenFile("./getLog.log", os.O_APPEND | os.O_RDWR, 0744) // 生成 WriteSyncer wSy := zapcore.AddSync(file)
-
LevelEnabler:
设置哪种级别的日志将被写入
- 对应的就是前面介绍的日志级别;如:
zapcore.DebugLevel
- 对应的就是前面介绍的日志级别;如:
-
创建自定义logger:
-
根据上面三点参数的理解,就可以指定文件建立了
-
// 还剩一个后面的配置信息没有传入,但是已经可以了 // 默认我们调用 NewProduction()方法也是没有传递啥配置进去的。 log := New( zapcore.NewCore(传递编辑器(两种), 自定义文件输出, cfg.Level(级别)), )
-
按照这样的建立完成之后就可以使用了,往指定文件里打印日志。
func initLogger2() *zap.Logger { //1 日志输出路径 //file, _ := os.Create("./test2.log") file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744) // 把文件对象做成WriteSyncer类型 writeSyncer := zapcore.AddSync(file) // 2 encoder编码,就两种 //encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig()) encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig()) // 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别 core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel) // 4 创建logger对象 logger := zap.New(core) return logger }
方式二
还有 zap 预置的生成 logger的方式,都是通过 NewProductionConfig() 来生成相关配置的, 自定义一个 NewProductionConfig() 然后,按着相应的步骤就可以了。 Build方法 就是通过 配置的 Config 对象,来生成的 logger。
重写方法,只需要加个文件名就可以了
func initLogger() *zap.Logger { // 1 得到config对象 conf := zap.NewProductionConfig() // 2 修改config对象的属性,如编码,输出路径等 //conf.Encoding="console" conf.Encoding = "json" //conf.OutputPaths = append(conf.OutputPaths, "./test.log") conf.OutputPaths = []string{"./test.log"} //3 通过config对象得到logger对象指针 logger, _ := conf.Build() return logger }
-
更改时间编码
方式一
func initLogger() *zap.Logger {
// 1 得到config对象
conf := zap.NewProductionConfig()
// 2 修改config对象的属性,如编码,输出路径等
//conf.Encoding="console"
conf.Encoding = "json"
//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
conf.OutputPaths = []string{"./test.log"}
//3 更改时间 人类可读
conf.EncoderConfig.EncodeTime=zapcore.ISO8601TimeEncoder
//3 通过config对象得到logger对象指针
logger, _ := conf.Build()
return logger
}
方式二
func initLogger2() *zap.Logger {
//1 日志输出路径
//file, _ := os.Create("./test2.log")
file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744)
// 把文件对象做成WriteSyncer类型
writeSyncer := zapcore.AddSync(file)
// 2 encoder编码,就两种
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
//encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
encoder := zapcore.NewConsoleEncoder(encoderConfig)
// 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
// 4 创建logger对象
logger := zap.New(core)
return logger
}
增加调用者信息
根据时间的理解, 这里很容易想到: 配置项当中的 EncodeCaller, 也可以指定相关的函数,用来打印 调用者的信息。
方式一(该方式本身就有,不需要再加)
func initLogger() *zap.Logger {
// 1 得到config对象
conf := zap.NewProductionConfig()
// 2 修改config对象的属性,如编码,输出路径等
//conf.Encoding="console"
conf.Encoding = "json"
//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
conf.OutputPaths = []string{"./test.log"}
//3 更改时间 人类可读
conf.EncoderConfig.EncodeTime=zapcore.ISO8601TimeEncoder
//4 增加调用者信息(该方式本身就有)
//3 通过config对象得到logger对象指针
logger, _ := conf.Build()
return logger
}
还有一种方式是: 我们创建Core, zap.New()创建 logger, 分析第二个参数的 Options 类型, 文档中可以找到相关的方法,就有添加调试 显示调用人信息的方法。
方式二
func initLogger2() *zap.Logger {
//1 日志输出路径
//file, _ := os.Create("./test2.log")
file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744)
// 把文件对象做成WriteSyncer类型
writeSyncer := zapcore.AddSync(file)
// 2 encoder编码,就两种
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
//encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
encoder := zapcore.NewConsoleEncoder(encoderConfig)
// 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
// 4 创建logger对象
//logger := zap.New(core)
logger := zap.New(core, zap.AddCaller())
return logger
}
四 使用 Lumberjack 进行日志切割归档
Zap 本身不支持切割归档日志文件,为了添加日志切割归档功能,我们将使用第三方库 Lumberjack 来实现。
go get -u github.com/natefinch/lumberjack
lumberjack.Logger 实现了 io.writer 接口,可以作为参数。
func getLogWriter() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "./test.log", // Filename: 日志文件的位置
MaxSize: 10, // 在进行切割之前,日志文件的最大大小(以 MB 为单位)
MaxBackups: 5, // 保留旧文件的最大个数
MaxAge: 30, // 保留旧文件的最大天数
Compress: false, // 是否压缩 / 归档旧文件
}
return zapcore.AddSync(lumberJackLogger)
}
/*
Lumberjack Logger 采用以下属性作为输入:
● Filename: 日志文件的位置
● MaxSize:在进行切割之前,日志文件的最大大小(以 MB 为单位)
● MaxBackups:保留旧文件的最大个数
● MaxAges:保留旧文件的最大天数
● Compress:是否压缩 / 归档旧文件
*/
4.1 代码案例
package main
import (
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func initLogger2() *zap.Logger {
//1 日志输出路径
//file, _ := os.Create("./test2.log")
//file, _ := os.OpenFile("./test2.log", os.O_APPEND | os.O_RDWR, 0744)
// 把文件对象做成WriteSyncer类型
writeSyncer := zapcore.AddSync(getLogWriter())
// 2 encoder编码,就两种
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
//encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
//encoder := zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
encoder := zapcore.NewConsoleEncoder(encoderConfig)
// 3 创建core对象,指定encoder编码,WriteSyncer对象和日志级别
core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
// 4 创建logger对象
//logger := zap.New(core)
logger := zap.New(core, zap.AddCaller())
return logger
}
func getLogWriter() zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: "./test3.log", // 导入文件名
MaxSize: 1, // 大小M兆
MaxBackups: 5, // 最大备份数量
MaxAge: 30, // 最大备份天数
Compress: false, // 是否压缩
}
return zapcore.AddSync(lumberJackLogger)
}
func main() {
logger := initLogger2()
defer logger.Sync() // flushes buffer, if any, 刷新缓冲区,存盘
for i:=0;i<100000;i++{
logger.Info("测试日志分隔")
}
}

五 Gin中使用zap日志库
5.1 使用第三方库
// 地址:https://github.com/gin-contrib/zap
// 下载
go get github.com/gin-contrib/zap
package main
import (
"fmt"
ginzap "github.com/gin-contrib/zap"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"time"
)
//func main() {
// // 初始化logger
// logger.InitLogger()
// r:=gin.New()
// // 两个中间件加入gin中
// r.Use(logger.GinLogger,logger.GinRecovery(true))
//
// r.GET("/", func(c *gin.Context) {
// // 以后直接使用zap.L()即可,并且有锁,不存在并发安全的问题
// // 1 正常访问
// //zap.L().Info("info-日志记录了")
// //c.String(200,"hello logger insert")
// // 2 抛异常
// panic("出错了宝贝")
// c.String(200,"hello logger insert")
// })
// r.Run(":8080")
//
//}
func main() {
r := gin.New()
//logger, _ := zap.NewProduction()
// 自定义写到文件中
// 1 得到config对象
conf := zap.NewProductionConfig()
// 2 修改config对象的属性,如编码,输出路径等
//conf.Encoding="console"
conf.Encoding = "json"
//conf.OutputPaths = append(conf.OutputPaths, "./test.log")
conf.OutputPaths = []string{"./web.log"}
//3 通过config对象得到logger对象指针
logger, _ := conf.Build()
//4 替换掉全局的logger,以后都使用zap.L()
zap.ReplaceGlobals(logger)
// Add a ginzap middleware, which:
// - Logs all requests, like a combined access and error log.
// - Logs to stdout.
// - RFC3339 with UTC time format.
r.Use(ginzap.Ginzap(logger, time.RFC3339, true))
// Logs all panic to error log
// - stack means whether output the stack info.
r.Use(ginzap.RecoveryWithZap(logger, true))
r.GET("/", func(c *gin.Context) {
zap.L().Info("info-test")
c.String(200, "pong "+fmt.Sprint(time.Now().Unix()))
})
// Example when panic happen.
r.GET("/panic", func(c *gin.Context) {
panic("An unexpected error happen!")
})
r.Run(":8080")
}
5.2 自己定制
讲解了 gin.Default 创建引擎, 默认的添加了两个中间件。 一个是 logger 日志,一个是 recover 恢复。 gin 自带的 logger 就是在这里实现起作用的
那么我们也需要将 zap封装为中间件-->GinLogger和GinRecovery
logger/logger.go
package logger
import (
"github.com/gin-gonic/gin"
"github.com/natefinch/lumberjack"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"net"
"net/http/httputil"
"os"
"runtime/debug"
"strings"
"time"
)
// 1 定义一下logger使用的常量
const (
mode = "dev" //开发模式
filename = "web_app.log" // 日志存放路径
//level = "debug" // 日志级别
level = zapcore.DebugLevel // 日志级别
max_size = 200 //最大存储大小
max_age = 30 //最大存储时间
max_backups = 7 //#备份数量
)
// 2 初始化Logger对象
func InitLogger() (err error) {
// 创建Core三大件,进行初始化
writeSyncer := getLogWriter(filename, max_size, max_backups, max_age)
encoder := getEncoder()
// 创建核心-->如果是dev模式,就在控制台和文件都打印,否则就只写到文件中
var core zapcore.Core
if mode == "dev" {
// 开发模式,日志输出到终端
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
// NewTee创建一个核心,将日志条目复制到两个或多个底层核心中。
core = zapcore.NewTee(
zapcore.NewCore(encoder, writeSyncer, level),
zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), level),
)
} else {
core = zapcore.NewCore(encoder, writeSyncer, level)
}
//core := zapcore.NewCore(encoder, writeSyncer, level)
// 创建 logger 对象
log := zap.New(core, zap.AddCaller())
// 替换全局的 logger, 后续在其他包中只需使用zap.L()调用即可
zap.ReplaceGlobals(log)
return
}
// 获取Encoder,给初始化logger使用的
func getEncoder() zapcore.Encoder {
// 使用zap提供的 NewProductionEncoderConfig
encoderConfig := zap.NewProductionEncoderConfig()
// 设置时间格式
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
// 时间的key
encoderConfig.TimeKey = "time"
// 级别
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
// 显示调用者信息
encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
// 返回json 格式的 日志编辑器
return zapcore.NewJSONEncoder(encoderConfig)
}
// 获取切割的问题,给初始化logger使用的
func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {
// 使用 lumberjack 归档切片日志
lumberJackLogger := &lumberjack.Logger{
Filename: filename,
MaxSize: maxSize,
MaxBackups: maxBackup,
MaxAge: maxAge,
}
return zapcore.AddSync(lumberJackLogger)
}
// GinLogger 用于替换gin框架的Logger中间件,不传参数,直接这样写
func GinLogger(c *gin.Context) {
logger := zap.L()
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next() // 执行视图函数
// 视图函数执行完成,统计时间,记录日志
cost := time.Since(start)
logger.Info(path,
zap.Int("status", c.Writer.Status()),
zap.String("method", c.Request.Method),
zap.String("path", path),
zap.String("query", query),
zap.String("ip", c.ClientIP()),
zap.String("user-agent", c.Request.UserAgent()),
zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
zap.Duration("cost", cost),
)
}
// GinRecovery 用于替换gin框架的Recovery中间件,因为传入参数,再包一层
func GinRecovery(stack bool) gin.HandlerFunc {
logger := zap.L()
return func(c *gin.Context) {
defer func() {
// defer 延迟调用,出了异常,处理并恢复异常,记录日志
if err := recover(); err != nil {
// 这个不必须,检查是否存在断开的连接(broken pipe或者connection reset by peer)---------开始--------
var brokenPipe bool
if ne, ok := err.(*net.OpError); ok {
if se, ok := ne.Err.(*os.SyscallError); ok {
if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
brokenPipe = true
}
}
}
//httputil包预先准备好的DumpRequest方法
httpRequest, _ := httputil.DumpRequest(c.Request, false)
if brokenPipe {
logger.Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
// 如果连接已断开,我们无法向其写入状态
c.Error(err.(error))
c.Abort()
return
}
// 这个不必须,检查是否存在断开的连接(broken pipe或者connection reset by peer)---------结束--------
// 是否打印堆栈信息,使用的是debug.Stack(),传入false,在日志中就没有堆栈信息
if stack {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
zap.String("stack", string(debug.Stack())),
)
} else {
logger.Error("[Recovery from panic]",
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
}
// 有错误,直接返回给前端错误,前端直接报错
//c.AbortWithStatus(http.StatusInternalServerError)
// 该方式前端不报错
c.String(200,"访问出错了")
}
}()
c.Next()
}
}
main.go
package main
import (
"github.com/gin-gonic/gin"
"go_test_learn/logger"
)
func main() {
// 初始化logger
logger.InitLogger()
r:=gin.New()
// 两个中间件加入gin中
r.Use(logger.GinLogger,logger.GinRecovery(true))
r.GET("/", func(c *gin.Context) {
// 以后直接使用zap.L()即可,并且有锁,不存在并发安全的问题
// 1 正常访问
//zap.L().Info("info-日志记录了")
//c.String(200,"hello logger insert")
// 2 抛异常
panic("出错了宝贝")
c.String(200,"hello logger insert")
})
r.Run(":8080")
}
作者:liuqingzheng
出处:https://www.cnblogs.com/liuqingzheng/p/16244546.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· 单线程的Redis速度为什么快?
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
2021-05-07 内网穿透方案大全
2018-05-07 Pycharm常用快捷键,以及设置