Go日志管理库zap
一、zap介绍
在许多Go语言项目中,我们需要一个好的日志记录器能够提供下面这些功能:
1.能够将事件记录到文件中,而不是应用程序控制台。
2.日志切割-能够根据文件大小、时间或间隔等来切割日志文件。
3.支持不同的日志级别。例如INFO,DEBUG,ERROR等。
4.能够打印基本信息,如调用文件/函数名和行号,日志时间等。
二、安装及使用
2.1 安装
go get -u go.uber.org/zap
2.2 配置logger日志记录器
Zap提供了两种类型的日志记录器—Sugared Logger和Logger,一般使用Logger 。
2.2.1 初始化Logger
func InitLogger() *zap.Logger{
logger ,_ := zap.NewProduction()
return logger
}
2.2.2 初始化SugaredLogger
//在Logger基础上调用logger.Suger()
func InitLogger() *zap.SugaredLogger{
logger ,_ := zap.NewProduction()
return logger.Sugar()
}
而初始化logger调用的函数可以通过调用zap.NewProduction()/zap.NewDevelopment()
或者zap.Example()
创建一个Logger。区别就是一个是以json的格式返回,一个是以终端标准输出带有空格返回。
NewProducts()
NewDevelopment()
2.3 使用logger进行日志记录
使用logger的自带的方法进行日志记录,logger.info(),logger.error().logger.debug等等
这些方法的语法都是
func (log *Logger) MethodXXX(msg string, fields ...Field)
例如:
logger.info(
"msg",
zap.String("msg",v),
zap.Error(err),
)
完整代码
点击查看代码
package main
import (
"net/http"
"go.uber.org/zap"
)
var Logger *zap.SugaredLogger
func main() {
//初始化zap日志记录器
Logger = InitLogger()
defer Logger.Sync()
//模拟义务
Simplefunc("http://www.baidu.com")
Simplefunc("www.google.com")
}
func InitLogger() *zap.SugaredLogger{
logger ,_ := zap.NewDevelopment()
return logger.Sugar()
}
func Simplefunc(url string) {
res,err:=http.Get(url)
if err!=nil {
//记录错误日志
Logger.Error(
"http get failed..",
zap.String("url:",url),
zap.Error(err),
)
}else {
//使用info记录成功日志。
Logger.Info(
"get success",
zap.String("status:",res.Status),
zap.String("url:",url),
)
res.Body.Close()
}
}
2.4 自定义logger记录器
2.4.1 将日志写入文件而不是终端
上述的官方提供的logger生成功能不是那么强大,项目需要将日志记录到文件和分割 的时候就需要自定义。
zap自定义logger生成器使用zap.New():
func New(core zapcore.Core, options ...Option) *Logger
其中zapcore.Core需要设置三个参数Encoder,WriteSyncer,LogLevel
Encoder:编译器,通俗说就是输出日志是什么格式,json or 终端格式。
json格式就使用NewJSONEncoder(),并使用预先设置的ProductionEncoderConfig():
zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
终端格式就使用NewConsoleEncoder():
zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
WriteSyncer:将日志输出到哪。使用zapcore.AddSync()函数并且将打开的文件句柄传进去。
file, _ := os.Create("./test.log")
writeSyncer := zapcore.AddSync(file)
LogLevel:将什么样级别的日志输出.
代码实例:
点击查看代码
//使用自定义的zap logger
func InitLogger() *zap.SugaredLogger{
//日志文件
logfile, _ :=os.OpenFile("zap_log.log",os.O_APPEND | os.O_CREATE|os.O_RDWR,0666)
//编码器
encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
//输出位置
writeSyncer := zapcore.AddSync(logfile)
//定义core
core := zapcore.NewCore(
encoder,
writeSyncer,
zapcore.DebugLevel,
)
//创建logger
logger:= zap.New(core)
return logger.Sugar()
}
2.4.2 若输出到文件和终端,只需要更改WriteSyncer参数
点击查看代码
//使用自定义的zap logger
func InitLogger() *zap.SugaredLogger{
//日志文件
logfile, _ :=os.OpenFile("zap_log.log",os.O_APPEND | os.O_CREATE|os.O_RDWR,0666)
//编码器
encoder := zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
//输出位置
// writeSyncer := zapcore.AddSync(logfile)
//输出多个位置
wc := io.MultiWriter(logfile,os.Stdout)
writeSyncer:= zapcore.AddSync(wc)
//定义core
core := zapcore.NewCore(
encoder,
writeSyncer,
zapcore.DebugLevel,
)
//创建logger
logger:= zap.New(core)
return logger.Sugar()
}
2.4.3 将输出的时间转化
//设置日志编译器,什么类型的日志
func getEncoder() zapcore.Encoder{
//encoder配置
encoderConfig := zap.NewProductionEncoderConfig()
//设置时间格式为2024-9-1-12.32
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
//json格式
// jsonencoder := zapcore.NewJSONEncoder(encoderConfig)
//终端形式
ConsoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
return ConsoleEncoder
}
2.4.4 记录不同级别的日志
有时候日志可分为log.erro.log 记录错误级别日志,;log.success.log记录成功级别日志,zapcore.DebugLevel就全记录。
core1 := zapcore.NewCore(
encoder,
writeSyncer,
zapcore.DebugLevel,//全记录
)
//错误日志
core2 := zapcore.NewCore(
encoder,
getwriteSyncer("log.err.log"),
zapcore.ErrorLevel,
)
c:=zapcore.NewTee(core1,core2)
logger:= zap.New(c,zap.AddCaller())
return logger.Sugar()
2.4.5 AddCaller详细记录调用的代码行,AddCallerSkip(1)调用链很多时直接跳过
logger:= zap.New(core,zap.AddCaller(), zap.AddCallerSkip(1))
3. 记录全日志,错误日志文件,同步终端,标准时间,记录代码位置的自定义logger代码
点击查看代码
//使用自定义的zap logger
func InitLogger() *zap.SugaredLogger{
//编码器
encoder := getEncoder()
//输出位置
writeSyncer:= getwriteSyncer("log_all.log")
//定义core
core1 := zapcore.NewCore(
encoder,
writeSyncer,
zapcore.DebugLevel,//全记录
)
//错误日志
core2 := zapcore.NewCore(
encoder,
getwriteSyncer("log.err.log"),
zapcore.ErrorLevel,
)
//创建单个logger
// logger:= zap.New(core1,zap.AddCaller(), zap.AddCallerSkip(1)) //AddCaller详细记录调用的代码行,AddCallerSkip(1)调用链很多时直接跳过
// return logger.Sugar()
//创建双日志,全日志和错误日志
c:=zapcore.NewTee(core1,core2)
logger:= zap.New(c,zap.AddCaller())
return logger.Sugar()
}
//设置日志编译器,什么类型的日志
func getEncoder() zapcore.Encoder{
//encoder配置
encoderConfig := zap.NewProductionEncoderConfig()
//设置时间格式为2024-9-1-12.32
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
//json格式
// jsonencoder := zapcore.NewJSONEncoder(encoderConfig)
//终端形式
ConsoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
return ConsoleEncoder
}
//设置输出位置
func getwriteSyncer(logfilename string) zapcore.WriteSyncer {
//日志文件
logfile, _ :=os.OpenFile(logfilename,os.O_APPEND | os.O_CREATE|os.O_RDWR,0666)
//只输出到日志文件
// return zapcore.AddSync(logfile)
//也输出到终端
wc := io.MultiWriter(logfile,os.Stdout)
return zapcore.AddSync(wc)
}
[========]
[========]
4.使用Lumberjack进行日志切割归档
//设置输出位置
func getwriteSyncer(logfilename string) zapcore.WriteSyncer {
//日志文件
// logfile, _ :=os.OpenFile(logfilename,os.O_APPEND | os.O_CREATE|os.O_RDWR,0666)
//分割日志
l, _ := rotatelogs.New(
logfilename+".%Y%m%d%H%M.log",
rotatelogs.WithMaxAge(30*24*time.Hour), // 最长保存30天
rotatelogs.WithRotationTime(time.Hour*24), // 24小时切割一次
)
//也输出到终端
wc := io.MultiWriter(l,os.Stdout)
return zapcore.AddSync(wc)
}
5.上述完整代码
点击查看代码
package main
import (
"io"
"net/http"
"os"
"time"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
// "gopkg.in/natefinch/lumberjack.v2"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var Logger *zap.SugaredLogger
func main() {
//初始化zap日志记录器
Logger = InitLogger()
defer Logger.Sync()
//模拟义务
Simplefunc("http://www.baidu.com")
Simplefunc("http://www.google.com")
}
//使用自定义的zap logger
func InitLogger() *zap.SugaredLogger{
//编码器
encoder := getEncoder()
//输出位置
writeSyncer:= getwriteSyncer("log_all")
//定义core
core1 := zapcore.NewCore(
encoder,
writeSyncer,
zapcore.DebugLevel,//全记录
)
//错误日志
core2 := zapcore.NewCore(
encoder,
getwriteSyncer("log.err"),
zapcore.ErrorLevel,
)
//创建单个logger
// logger:= zap.New(core1,zap.AddCaller(), zap.AddCallerSkip(1)) //AddCaller详细记录调用的代码行,AddCallerSkip(1)调用链很多时直接跳过
// return logger.Sugar()
//创建双日志,全日志和错误日志
c:=zapcore.NewTee(core1,core2)
logger:= zap.New(c,zap.AddCaller())
return logger.Sugar()
}
//设置日志编译器,什么类型的日志
func getEncoder() zapcore.Encoder{
//encoder配置
encoderConfig := zap.NewProductionEncoderConfig()
//设置时间格式为2024-9-1-12.32
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
//json格式
// jsonencoder := zapcore.NewJSONEncoder(encoderConfig)
//终端形式
ConsoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
return ConsoleEncoder
}
//设置输出位置
func getwriteSyncer(logfilename string) zapcore.WriteSyncer {
//日志文件
// logfile, _ :=os.OpenFile(logfilename,os.O_APPEND | os.O_CREATE|os.O_RDWR,0666)
//分割日志
l, _ := rotatelogs.New(
logfilename+".%Y%m%d%H%M.log",
rotatelogs.WithMaxAge(30*24*time.Hour), // 最长保存30天
rotatelogs.WithRotationTime(time.Hour*24), // 24小时切割一次
)
//也输出到终端
wc := io.MultiWriter(l,os.Stdout)
return zapcore.AddSync(wc)
}
func Simplefunc(url string) {
res,err:=http.Get(url)
if err!=nil {
//记录错误日志
Logger.Error(
"http get failed..",
zap.String("url:",url),
zap.Error(err),
)
}else {
//使用info记录成功日志。
Logger.Info(
"get success",
zap.String("status:",res.Status),
zap.String("url:",url),
)
res.Body.Close()
}
}
在gin框架中使用zap日志记录器
gin默认路由使用了自带的两个中间件,如下:
func Default(opts ...OptionFunc) *Engine {
debugPrintWARNINGDefault()
engine := New()
engine.Use(Logger(), Recovery())
return engine.With(opts...)
}
要想将zap集成到gin中,就需要重写logger和recovery两个中间件。
//使用自定义的zap logger
func InitLogger() *zap.Logger{
//编码器
encoder := getEncoder()
//输出位置
writeSyncer:= getwriteSyncer("log_all")
//定义core
core1 := zapcore.NewCore(
encoder,
writeSyncer,
zapcore.DebugLevel,//全记录
)
//错误日志
core2 := zapcore.NewCore(
encoder,
getwriteSyncer("log.err"),
zapcore.ErrorLevel,
)
//创建单个logger
// logger:= zap.New(core1,zap.AddCaller(), zap.AddCallerSkip(1)) //AddCaller详细记录调用的代码行,AddCallerSkip(1)调用链很多时直接跳过
// return logger.Sugar()
//创建双日志,全日志和错误日志
c:=zapcore.NewTee(core1,core2)
logger:= zap.New(c,zap.AddCaller())
return logger
}
//设置日志编译器,什么类型的日志
func getEncoder() zapcore.Encoder{
//encoder配置
encoderConfig := zap.NewProductionEncoderConfig()
//设置时间格式为2024-9-1-12.32
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
//json格式
// jsonencoder := zapcore.NewJSONEncoder(encoderConfig)
//终端形式
ConsoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
return ConsoleEncoder
}
//设置输出位置
func getwriteSyncer(logfilename string) zapcore.WriteSyncer {
//日志文件
// logfile, _ :=os.OpenFile(logfilename,os.O_APPEND | os.O_CREATE|os.O_RDWR,0666)
//分割日志
l, _ := rotatelogs.New(
logfilename+".%Y%m%d%H%M.log",
rotatelogs.WithMaxAge(30*24*time.Hour), // 最长保存30天
rotatelogs.WithRotationTime(time.Hour*24), // 24小时切割一次
)
//也输出到终端
wc := io.MultiWriter(l,os.Stdout)
return zapcore.AddSync(wc)
}
//Ginlogger
func GinLogger(logger *zap.Logger) gin.HandlerFunc {
return func (c *gin.Context) {
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
func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if err := recover(); err != nil {
// Check for a broken connection, as it is not really a
// condition that warrants a panic stack trace.
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
}
}
}
httpRequest, _ := httputil.DumpRequest(c.Request, false)
if brokenPipe {
logger.Error(c.Request.URL.Path,
zap.Any("error", err),
zap.String("request", string(httpRequest)),
)
// If the connection is dead, we can't write a status to it.
c.Error(err.(error)) // nolint: errcheck
c.Abort()
return
}
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.Next()
}
}
使用
package main
import (
"io"
"net"
"net/http"
"net/http/httputil"
"os"
"runtime/debug"
"strings"
"time"
"github.com/gin-gonic/gin"
rotatelogs "github.com/lestrrat-go/file-rotatelogs"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var Logger *zap.Logger
func main() {
//初始化zap日志记录器
Logger = InitLogger()
defer Logger.Sync()
//模拟义务
// Simplefunc("http://www.baidu.com")
// Simplefunc("http://www.google.com")
//将zap自定义的logger嵌入到gin中。
r := gin.New()
r.Use(GinLogger(Logger),GinRecovery(Logger,true))
r.GET("/", func(ctx *gin.Context) {
ctx.JSON(ctx.Writer.Status(),gin.H{"ok": "11"})
})
r.Run(":8080")
}