一个好的日志记录器能够提供下面这些功能:

  • 能够将事件记录到文件中,而不是应用程序控制台。
  • 日志切割-能够根据文件大小、时间或间隔等来切割日志文件。
  • 支持不同的日志级别。例如INFO,DEBUG,ERROR等。
  • 能够打印基本信息,如调用文件/函数名和行号,日志时间等。

目录

  • log
    • 使用
    • 配置logger
    • flag选项
    • 配置日志前缀
    • 配置日志输出位置
    • 创建logger
    • Go Logger的优势和劣势
      • 优势
      • 劣势
  • logrus
    • 安装
    • 基本示例
    • 进阶示例
    • 日志级别
      • 设置日志级别
    • 字段
    • 默认字段
    • 日志条目
    • Hooks
    • 格式化
    • 记录函数名
    • 线程安全
    • gin框架使用logrus
  • Uber-go Zap
    • why zap
    • 安装
    • 配置Zap Logger
      • Logger
      • Sugared Logger
    • 定制logger
      • 将日志写入文件而不是终端
      • 将JSON Encoder更改为普通的Log Encoder
      • 格式化时间
      • AddCallerSkip
      • 将日志输出到多个位置
      • 将err日志单独输出到文件
    • 使用Lumberjack进行日志切割归档
    • 安装
    • zap logger中加入Lumberjack
    • 完整代码
    • zap接收gin框架默认的日志
      • gin默认的中间件
      • 基于zap的中间件
      • 在gin项目中使用zap

log

Go语言内置的log包实现了简单的日志服务。本文介绍了标准库log的基本使用。

使用

  1. log包定义了Logger类型,该类型提供了一些格式化输出的方法。
  2. 可以通过调用函数Print系列(Print|Printf|Println)、Fatal系列(Fatal|Fatalf|Fatalln)、和Panic系列(Panic|Panicf|Panicln)来使用,比自行创建一个logger对象更容易使用。
  3. logger会打印每条日志信息的日期、时间,默认输出到系统的标准错误。Fatal系列函数会在写入日志信息后调用os.Exit(1)。Panic系列函数会在写入日志信息后panic。

配置logger

func Flags() int     //返回配置
func SetFlags(flag int) //设置配置

flag选项

const (Ldate         = 1 << iota     // the date in the local time zone: 2009/01/23Ltime                         // the time in the local time zone: 01:23:23Lmicroseconds                 // 微妙级别的时间: 01:23:23.123123.  assumes Ltime.Llongfile                     // full file name and line number: /a/b/c/d.go:23Lshortfile                    // 文件名+行号: d.go:23. overrides LlongfileLUTC                          // 使用utcLmsgprefix                    // 添加前缀LstdFlags     = Ldate | Ltime // 标准logger配置
)

使用前配置

func main() {log.SetFlags(log.Llongfile | log.Lmicroseconds | log.Ldate)log.Println("这是一条很普通的日志。")
}

配置日志前缀

func Prefix() string
func SetPrefix(prefix string)

配置日志输出位置

func SetOutput(w io.Writer)

创建logger

log标准库中还提供了一个创建新logger对象的构造函数–New,支持子定制logger:

func New(out io.Writer, prefix string, flag int) *Logger

Go Logger的优势和劣势

优势

使用非常简单,可以设置任何io.Writer作为日志记录输出并向其发送要写入的日志。

劣势

  1. 仅限基本的日志级别
  2. 只有一个Print选项,不支持INFO/DEBUG等多个级别。
  3. 对于错误日志,它有Fatal和Panic
    1. Fatal日志通过调用os.Exit(1)来结束程序
    2. Panic日志在写入日志消息之后抛出一个panic
  4. 缺少一个ERROR日志级别,这个级别可以在不抛出panic或退出程序的情况下记录错误
  5. 缺乏日志格式化的能力——例如记录调用者的函数名和行号,格式化日期和时间格式。等等。
  6. 不提供日志切割的能力

logrus

Logrus是Go(golang)的结构化logger,与标准库logger完全API兼容。

它有以下特点:

  • 完全兼容标准日志库,拥有七种日志级别:Trace, Debug, Info, Warning, Error, Fataland Panic。
  • 可扩展的Hook机制,允许使用者通过Hook的方式将日志分发到任意地方,如本地文件系统,logstash,- elasticsearch或者mq等,或者通过Hook定义日志内容和格式等
  • 可选的日志输出格式,内置了两种日志格式JSONFormater和TextFormatter,还可以自定义日志格式
  • Field机制,通过Filed机制进行结构化的日志记录
  • 线程安全

安装

go get github.com/sirupsen/logrus

基本示例

使用Logrus最简单的方法是简单的包级导出日志程序:

package mainimport (log "github.com/sirupsen/logrus"
)func main() {log.WithFields(log.Fields{"animal": "dog",}).Info("一条舔狗出现了。")
}

进阶示例

对于更高级的用法,例如在同一应用程序记录到多个位置,你还可以创建logrus Logger的实例:

package mainimport ("os""github.com/sirupsen/logrus"
)// 创建一个新的logger实例。可以创建任意多个。
var log = logrus.New()func main() {// 设置日志输出为os.Stdoutlog.Out = os.Stdout// 可以设置像文件等任意`io.Writer`类型作为日志输出// file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)// if err == nil {//  log.Out = file// } else {//  log.Info("Failed to log to file, using default stderr")// }log.WithFields(logrus.Fields{"animal": "dog","size":   10,}).Info("一群舔狗出现了。")
}

日志级别

Logrus有七个日志级别:Trace, Debug, Info, Warning, Error, Fataland Panic。

log.Trace("Something very low level.")
log.Debug("Useful debugging information.")
log.Info("Something noteworthy happened!")
log.Warn("You should probably take a look at this.")
log.Error("Something failed but I'm not quitting.")
// 记完日志后会调用os.Exit(1)
log.Fatal("Bye.")
// 记完日志后会调用 panic()
log.Panic("I'm bailing.")

设置日志级别

如果你的程序支持debug或环境变量模式,设置log.Level = logrus.DebugLevel会很有帮助。

// 会记录info及以上级别 (warn, error, fatal, panic)
log.SetLevel(log.InfoLevel)

字段

Logrus鼓励通过日志字段进行谨慎的结构化日志记录,而不是冗长的、不可解析的错误消息。

例如,区别于使用log.Fatalf("Failed to send event %s to topic %s with key %d"),你应该使用如下方式记录更容易发现的内容:

log.WithFields(log.Fields{"event": event,"topic": topic,"key": key,
}).Fatal("Failed to send event")

WithFields的调用是可选的。

默认字段

通常,将一些字段始终附加到应用程序的全部或部分的日志语句中会很有帮助。例如,你可能希望始终在请求的上下文中记录request_id和user_ip。

区别于在每一行日志中写上log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip}),你可以向下面的示例代码一样创建一个logrus.Entry去传递这些字段。

requestLogger := log.WithFields(log.Fields{"request_id": request_id, "user_ip": user_ip})
requestLogger.Info("something happened on that request") # will log request_id and user_ip
requestLogger.Warn("something not great happened")

日志条目

除了使用WithField或WithFields添加的字段外,一些字段会自动添加到所有日志记录事中:

  1. time:记录日志时的时间戳
  2. msg:记录的日志信息
  3. level:记录的日志级别

Hooks

可以添加日志级别的钩子(Hook)。例如,向异常跟踪服务发送Error、Fatal和Panic、信息到StatsD或同时将日志发送到多个位置,例如syslog。

Logrus配有内置钩子。在init中添加这些内置钩子或你自定义的钩子:

import (log "github.com/sirupsen/logrus""gopkg.in/gemnasium/logrus-airbrake-hook.v2" // the package is named "airbrake"logrus_syslog "github.com/sirupsen/logrus/hooks/syslog""log/syslog"
)func init() {// Use the Airbrake hook to report errors that have Error severity or above to// an exception tracker. You can create custom hooks, see the Hooks section.log.AddHook(airbrake.NewHook(123, "xyz", "production"))hook, err := logrus_syslog.NewSyslogHook("udp", "localhost:514", syslog.LOG_INFO, "")if err != nil {log.Error("Unable to connect to local syslog daemon")} else {log.AddHook(hook)}
}

Syslog钩子还支持连接到本地syslog(例如. “/dev/log” or “/var/run/syslog” or “/var/run/log”)

格式化

logrus内置以下两种日志格式化程序:

logrus.TextFormatter logrus.JSONFormatter

记录函数名

如果你希望将调用的函数名添加为字段,请通过以下方式设置:

log.SetReportCaller(true)

这会将调用者添加为”method”,如下所示:

{"animal":"penguin","level":"fatal","method":"github.com/sirupsen/arcticcreatures.migrate","msg":"a penguin swims by",
"time":"2014-03-10 19:57:38.562543129 -0400 EDT"}

开启这个模式会增加性能开销。

线程安全

logrus 默认的logger在并发写的时候是被mutex保护的,比如当同时调用hook和写log时mutex就会被请求,有另外一种情况,文件是以appending mode打开的, 此时的并发操作就是安全的,可以用logger.SetNoLock()来关闭它。

gin框架使用logrus

// a gin with logrus demovar log = logrus.New()func init() {// Log as JSON instead of the default ASCII formatter.log.Formatter = &logrus.JSONFormatter{}// Output to stdout instead of the default stderr// Can be any io.Writer, see below for File examplef, _ := os.Create("./gin.log")log.Out = fgin.SetMode(gin.ReleaseMode)gin.DefaultWriter = log.Out// Only log the warning severity or above.log.Level = logrus.InfoLevel
}func main() {// 创建一个默认的路由引擎r := gin.Default()// GET:请求方式;/hello:请求的路径// 当客户端以GET方法请求/hello路径时,会执行后面的匿名函数r.GET("/hello", func(c *gin.Context) {log.WithFields(logrus.Fields{"animal": "walrus","size":   10,}).Warn("A group of walrus emerges from the ocean")// c.JSON:返回JSON格式的数据c.JSON(200, gin.H{"message": "Hello world!",})})// 启动HTTP服务,默认在0.0.0.0:8080启动服务r.Run()

Uber-go Zap

Zap是非常快的、结构化的,分日志级别的Go日志库。

why zap

  1. 它同时提供了结构化日志记录和printf风格的日志记录
  2. 它非常的快
  3. 根据Uber-go Zap的文档,它的性能比类似的结构化日志包更好——也比标准库更快。
Package Time Time % to zap Objects Allocated
⚡️ zap 862 ns/op +0% 5 allocs/op
⚡️ zap (sugared) 1250 ns/op +45% 11 allocs/op
zerolog 4021 ns/op +366% 76 allocs/op
go-kit 4542 ns/op +427% 105 allocs/op
apex/log 26785 ns/op +3007% 115 allocs/op
logrus 29501 ns/op +3322% 125 allocs/op
log15 29906 ns/op +3369% 122 allocs/op

安装

go get -u go.uber.org/zap

配置Zap Logger

Zap提供了两种类型的日志记录器—Sugared LoggerLogger

  1. 在性能很好但不是很关键的上下文中,使用SugaredLogger。它比其他结构化日志记录包快4-10倍,并且支持结构化和printf风格的日志记录。

  2. 在每一微秒和每一次内存分配都很重要的上下文中,使用Logger。它甚至比SugaredLogger更快,内存分配次数也更少,但它只支持强类型的结构化日志记录。

Logger

  1. 通过调用zap.NewProduction()/zap.NewDevelopment()或者zap.Example()创建一个Logger。
  2. 上面的每一个函数都将创建一个logger。唯一的区别在于它将记录的信息不同。例如production logger默认记录调用函数信息、日期和时间等。
  3. 通过Logger调用Info/Error等。
  4. 默认情况下日志都会打印到应用程序的console界面。
var logger *zap.Loggerfunc main() {InitLogger()defer logger.Sync()simpleHttpGet("www.google.com")simpleHttpGet("http://www.google.com")
}func InitLogger() {logger, _ = zap.NewProduction()
}func simpleHttpGet(url string) {resp, err := http.Get(url)if err != nil {logger.Error("Error fetching url..",zap.String("url", url),zap.Error(err))} else {logger.Info("Success..",zap.String("statusCode", resp.Status),zap.String("url", url))resp.Body.Close()}
}

在上面的代码中,我们首先创建了一个Logger,然后使用Info/ Error等Logger方法记录消息。

日志记录器方法的语法是这样的:

func (log *Logger) MethodXXX(msg string, fields ...Field)

其中MethodXXX是一个可变参数函数,可以是Info / Error/ Debug / Panic等。每个方法都接受一个消息字符串和任意数量的zapcore.Field参数。

每个zapcore.Field其实就是一组键值对参数。

执行上面的代码会得到如下输出结果:

{"level":"error","ts":1572159218.912792,"caller":"zap_demo/temp.go:25","msg":"Error fetching url..","url":"www.sogo.com","error":"Get www.sogo.com: unsupported protocol scheme \"\"","stacktrace":"main.simpleHttpGet\n\t/Users/q1mi/zap_demo/temp.go:25\nmain.main\n\t/Users/q1mi/zap_demo/temp.go:14\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:203"}
{"level":"info","ts":1572159219.1227388,"caller":"zap_demo/temp.go:30","msg":"Success..","statusCode":"200 OK","url":"http://www.sogo.com"}

Sugared Logger

使用Sugared Logger来实现相同的功能:

  1. 大部分的实现基本都相同。
  2. 惟一的区别是,我们通过调用主logger的. Sugar()方法来获取一个SugaredLogger。
  3. 然后使用SugaredLogger以printf格式记录语句

下面是修改过后使用SugaredLogger代替Logger的代码:

var sugarLogger *zap.SugaredLoggerfunc main() {InitLogger()defer sugarLogger.Sync()simpleHttpGet("www.google.com")simpleHttpGet("http://www.google.com")
}func InitLogger() {logger, _ := zap.NewProduction()sugarLogger = logger.Sugar()
}func simpleHttpGet(url string) {sugarLogger.Debugf("Trying to hit GET request for %s", url)resp, err := http.Get(url)if err != nil {sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)} else {sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)resp.Body.Close()}
}

执行上面的代码会得到如下输出:

{"level":"error","ts":1572159149.923002,"caller":"logic/temp2.go:27","msg":"Error fetching URL www.sogo.com : Error = Get www.sogo.com: unsupported protocol scheme \"\"","stacktrace":"main.simpleHttpGet\n\t/Users/q1mi/zap_demo/logic/temp2.go:27\nmain.main\n\t/Users/q1mi/zap_demo/logic/temp2.go:14\nruntime.main\n\t/usr/local/go/src/runtime/proc.go:203"}
{"level":"info","ts":1572159150.192585,"caller":"logic/temp2.go:29","msg":"Success! statusCode = 200 OK for URL http://www.sogo.com"}

定制logger

将日志写入文件而不是终端

要做的第一个更改是把日志写入文件,而不是打印到应用程序控制台。

将使用zap.New(…)方法来手动传递所有配置,而不是使用像zap.NewProduction()这样的预置方法来创建logger。

func New(core zapcore.Core, options ...Option) *Logger

zapcore.Core需要三个配置——Encoder,WriteSyncer,LogLevel

  1. Encoder:编码器(如何写入日志)。使用开箱即用的NewJSONEncoder(),并使用预先设置的ProductionEncoderConfig()。

    zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
    
  2. WriterSyncer :指定日志将写到哪里去。使用zapcore.AddSync()函数并且将打开的文件句柄传进去。

      file, _ := os.Create("./test.log")writeSyncer := zapcore.AddSync(file)
    
  3. Log Level:哪种级别的日志将被写入。

    func InitLogger() {writeSyncer := getLogWriter()encoder := getEncoder()core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)logger := zap.New(core)sugarLogger = logger.Sugar()
    }func getEncoder() zapcore.Encoder {return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
    }func getLogWriter() zapcore.WriteSyncer {file, _ := os.Create("./test.log")return zapcore.AddSync(file)
    }
    

当使用这些修改过的logger配置调用上述部分的main()函数时,以下输出将打印在文件——test.log中

{"level":"debug","ts":1572160754.994731,"msg":"Trying to hit GET request for www.sogo.com"}
{"level":"error","ts":1572160754.994982,"msg":"Error fetching URL www.sogo.com : Error = Get www.sogo.com: unsupported protocol scheme \"\""}
{"level":"debug","ts":1572160754.994996,"msg":"Trying to hit GET request for http://www.sogo.com"}
{"level":"info","ts":1572160757.3755069,"msg":"Success! statusCode = 200 OK for URL http://www.sogo.com"}

将JSON Encoder更改为普通的Log Encoder

将编码器从JSON Encoder更改为普通Encoder。需要将NewJSONEncoder()更改为NewConsoleEncoder()。

 return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
1.572161051846623e+09   debug   Trying to hit GET request for www.sogo.com
1.572161051846828e+09  error   Error fetching URL www.sogo.com : Error = Get www.sogo.com: unsupported protocol scheme ""
1.5721610518468401e+09 debug   Trying to hit GET request for http://www.sogo.com
1.572161052068744e+09  info    Success! statusCode = 200 OK for URL http://www.sogo.com

格式化时间

有下面两个问题:

  1. 时间是以非人类可读的方式展示,例如1.572161051846623e+09
  2. 调用方函数的详细信息没有显示在日志中

要做的第一件事是覆盖默认的ProductionConfig(),并进行以下更改:

  1. 修改时间编码器
  2. 在日志文件中使用大写字母记录日志级别
func getEncoder() zapcore.Encoder {encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderencoderConfig.EncodeLevel = zapcore.CapitalLevelEncoderreturn zapcore.NewConsoleEncoder(encoderConfig)
}

修改zap logger代码,添加将调用函数信息记录到日志中的功能。为此,我们将在zap.New(…)函数中添加一个Option。

logger := zap.New(core, zap.AddCaller())

日志结果:

2019-10-27T15:33:29.855+0800    DEBUG   logic/temp2.go:47   Trying to hit GET request for www.sogo.com
2019-10-27T15:33:29.855+0800   ERROR   logic/temp2.go:50   Error fetching URL www.sogo.com : Error = Get www.sogo.com: unsupported protocol scheme ""
2019-10-27T15:33:29.856+0800   DEBUG   logic/temp2.go:47   Trying to hit GET request for http://www.sogo.com
2019-10-27T15:33:30.125+0800   INFO    logic/temp2.go:52   Success! statusCode = 200 OK for URL http://www.sogo.com

AddCallerSkip

当我们不是直接使用初始化好的logger实例记录日志,而是将其包装成一个函数等,此时日录日志的函数调用链会增加,想要获得准确的调用信息就需要通过AddCallerSkip函数来跳过。

logger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))

将日志输出到多个位置

可以将日志同时输出到文件和终端。

func getLogWriter() zapcore.WriteSyncer {file, _ := os.Create("./test.log")// 利用io.MultiWriter支持文件和终端两个输出目标ws := io.MultiWriter(file, os.Stdout)return zapcore.AddSync(ws)
}

将err日志单独输出到文件

func InitLogger() {encoder := getEncoder()// test.log记录全量日志logF, _ := os.Create("./test.log")c1 := zapcore.NewCore(encoder, zapcore.AddSync(logF), zapcore.DebugLevel)// test.err.log记录ERROR级别的日志errF, _ := os.Create("./test.err.log")c2 := zapcore.NewCore(encoder, zapcore.AddSync(errF), zap.ErrorLevel)// 使用NewTee将c1和c2合并到corecore := zapcore.NewTee(c1, c2)logger = zap.New(core, zap.AddCaller())
}

使用Lumberjack进行日志切割归档

Zap本身不支持切割归档日志文件,这个日志程序中唯一缺少的就是日志切割归档功能。

目前只支持按文件大小切割,原因是按时间切割效率低且不能保证日志数据不被破坏。

想按日期切割可以使用github.com/lestrrat-go/file-rotatelogs这个库,目前不维护,但也够用了。

// 使用file-rotatelogs按天切割日志import rotatelogs "github.com/lestrrat-go/file-rotatelogs"l, _ := rotatelogs.New(filename+".%Y%m%d%H%M",rotatelogs.WithMaxAge(30*24*time.Hour),    // 最长保存30天rotatelogs.WithRotationTime(time.Hour*24), // 24小时切割一次
)
zapcore.AddSync(l)

安装

go get gopkg.in/natefinch/lumberjack.v2

zap logger中加入Lumberjack

要在zap中加入Lumberjack支持,我们需要修改WriteSyncer代码。我们将按照下面的代码修改getLogWriter()函数:

func getLogWriter() zapcore.WriteSyncer {lumberJackLogger := &lumberjack.Logger{Filename:   "./test.log",MaxSize:    10,MaxBackups: 5,MaxAge:     30,Compress:   false,}return zapcore.AddSync(lumberJackLogger)
}

Lumberjack Logger采用以下属性作为输入:

  1. Filename: 日志文件的位置
  2. MaxSize:在进行切割之前,日志文件的最大大小(以MB为单位)
  3. MaxBackups:保留旧文件的最大个数
  4. MaxAges:保留旧文件的最大天数
  5. Compress:是否压缩/归档旧文件

完整代码

package mainimport ("net/http""gopkg.in/natefinch/lumberjack.v2""go.uber.org/zap""go.uber.org/zap/zapcore"
)var sugarLogger *zap.SugaredLoggerfunc main() {InitLogger()defer sugarLogger.Sync()simpleHttpGet("www.sogo.com")simpleHttpGet("http://www.sogo.com")
}func InitLogger() {writeSyncer := getLogWriter()encoder := getEncoder()core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)logger := zap.New(core, zap.AddCaller())sugarLogger = logger.Sugar()
}func getEncoder() zapcore.Encoder {encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderencoderConfig.EncodeLevel = zapcore.CapitalLevelEncoderreturn zapcore.NewConsoleEncoder(encoderConfig)
}func getLogWriter() zapcore.WriteSyncer {lumberJackLogger := &lumberjack.Logger{Filename:   "./test.log",MaxSize:    1,MaxBackups: 5,MaxAge:     30,Compress:   false,}return zapcore.AddSync(lumberJackLogger)
}func simpleHttpGet(url string) {sugarLogger.Debugf("Trying to hit GET request for %s", url)resp, err := http.Get(url)if err != nil {sugarLogger.Errorf("Error fetching URL %s : Error = %s", url, err)} else {sugarLogger.Infof("Success! statusCode = %s for URL %s", resp.Status, url)resp.Body.Close()}
}

执行上述代码,下面的内容会输出到文件——test.log中:

2019-10-27T15:50:32.944+0800    DEBUG   logic/temp2.go:48   Trying to hit GET request for www.sogo.com
2019-10-27T15:50:32.944+0800   ERROR   logic/temp2.go:51   Error fetching URL www.sogo.com : Error = Get www.sogo.com: unsupported protocol scheme ""
2019-10-27T15:50:32.944+0800   DEBUG   logic/temp2.go:48   Trying to hit GET request for http://www.sogo.com
2019-10-27T15:50:33.165+0800   INFO    logic/temp2.go:53   Success! statusCode = 200 OK for URL http://www.sogo.com

zap接收gin框架默认的日志

gin默认的中间件

func Default() *Engine {debugPrintWARNINGDefault()engine := New()engine.Use(Logger(), Recovery())return engine
}

在使用gin.Default()的同时是用到了gin框架内的两个默认中间件Logger()和Recovery()。

其中Logger()是把gin框架本身的日志输出到标准输出,而Recovery()是在程序出现panic的时候恢复现场并写入500响应的。

基于zap的中间件

// GinLogger 接收gin框架默认的日志
func GinLogger(logger *zap.Logger) gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()path := c.Request.URL.Pathquery := c.Request.URL.RawQueryc.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 recover掉项目可能出现的panic
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 boolif 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: errcheckc.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()}
}

可以使用github上有别人封装好的:https://github.com/gin-contrib/zap。

这样就可以在gin框架中使用我们上面定义好的两个中间件来代替gin框架默认的Logger()和Recovery()了。

r := gin.New()
r.Use(GinLogger(), GinRecovery())

在gin项目中使用zap

最后再加入项目中常用的日志切割,完整版的logger.go代码如下:

package loggerimport ("gin_zap_demo/config""net""net/http""net/http/httputil""os""runtime/debug""strings""time""github.com/gin-gonic/gin""github.com/natefinch/lumberjack""go.uber.org/zap""go.uber.org/zap/zapcore"
)var lg *zap.Logger// InitLogger 初始化Logger
func InitLogger(cfg *config.LogConfig) (err error) {writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)encoder := getEncoder()var l = new(zapcore.Level)err = l.UnmarshalText([]byte(cfg.Level))if err != nil {return}core := zapcore.NewCore(encoder, writeSyncer, l)lg = zap.New(core, zap.AddCaller())zap.ReplaceGlobals(lg) // 替换zap包中全局的logger实例,后续在其他包中只需使用zap.L()调用即可return
}func getEncoder() zapcore.Encoder {encoderConfig := zap.NewProductionEncoderConfig()encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoderencoderConfig.TimeKey = "time"encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoderencoderConfig.EncodeDuration = zapcore.SecondsDurationEncoderencoderConfig.EncodeCaller = zapcore.ShortCallerEncoderreturn zapcore.NewJSONEncoder(encoderConfig)
}func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {lumberJackLogger := &lumberjack.Logger{Filename:   filename,MaxSize:    maxSize,MaxBackups: maxBackup,MaxAge:     maxAge,}return zapcore.AddSync(lumberJackLogger)
}// GinLogger 接收gin框架默认的日志
func GinLogger() gin.HandlerFunc {return func(c *gin.Context) {start := time.Now()path := c.Request.URL.Pathquery := c.Request.URL.RawQueryc.Next()cost := time.Since(start)lg.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 recover掉项目可能出现的panic,并使用zap记录相关日志
func GinRecovery(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 boolif 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 {lg.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: errcheckc.Abort()return}if stack {lg.Error("[Recovery from panic]",zap.Any("error", err),zap.String("request", string(httpRequest)),zap.String("stack", string(debug.Stack())),)} else {lg.Error("[Recovery from panic]",zap.Any("error", err),zap.String("request", string(httpRequest)),)}c.AbortWithStatus(http.StatusInternalServerError)}}()c.Next()}
}

然后定义日志相关配置:

type LogConfig struct {Level string `json:"level"`Filename string `json:"filename"`MaxSize int `json:"maxsize"`MaxAge int `json:"max_age"`MaxBackups int `json:"max_backups"`
}

在项目中先从配置文件加载配置信息,再调用logger.InitLogger(config.Conf.LogConfig)即可完成logger实例的初识化。

其中,通过r.Use(logger.GinLogger(), logger.GinRecovery(true))注册中间件来使用zap接收gin框架自身的日志,在项目中需要的地方通过使用zap.L().Xxx()方法来记录自定义日志信息。

package mainimport ("fmt""gin_zap_demo/config""gin_zap_demo/logger""net/http""os""go.uber.org/zap""github.com/gin-gonic/gin"
)func main() {// load config from config.jsonif len(os.Args) < 1 {return}if err := config.Init(os.Args[1]); err != nil {panic(err)}// init loggerif err := logger.InitLogger(config.Conf.LogConfig); err != nil {fmt.Printf("init logger failed, err:%v\n", err)return}gin.SetMode(config.Conf.Mode)r := gin.Default()// 注册zap相关中间件r.Use(logger.GinLogger(), logger.GinRecovery(true))r.GET("/hello", func(c *gin.Context) {// 假设你有一些数据需要记录到日志中var (name = "q1mi"age  = 18)// 记录日志并使用zap.Xxx(key, val)记录相关字段zap.L().Debug("this is hello func", zap.String("user", name), zap.Int("age", age))c.String(http.StatusOK, "hello liwenzhou.com!")})addr := fmt.Sprintf(":%v", config.Conf.Port)r.Run(addr)
}

go日志库log/zap/logrus相关推荐

  1. go第三方日志库uber-go/zap、lumberjack

    uber-go/zap.lumberjack zap是uber开源的go语言高性能日志库, lumberjack是zap官方推荐的日志分割库, 结合这两个库我们可以在项目中实现完整的日志机制, 例如: ...

  2. 【Rust】日志库log

    日志库一般会实现日志分级.日志过滤.日志输出格式化.日志回滚等功能.本文介绍了Rust log库的使用,并给出了几个常用日志库的使用示例. 一.Rust log 1.log库 这个log库给出了日志库 ...

  3. 高性能 Go 日志库 zap 设计与实现

    作者 | luozhiyun       责编 | 欧阳姝黎 最近在学习如何在开发中让代码运行更加高效时,在浏览各种优秀的日志设计的时候看到 uber 有一个叫 zap 的日志库引起了我的注意,它主要 ...

  4. go语言 gin框架中集成zap日志库

    在go语言gin框架中,日志是默认输出到终端的,但是我们在实际工作中,一般来说是需要记录服务器日志的.而最常用的日志库就是zap日志库,我们需要将gin在终端输出的内容通过zap日志库记录到文件中,首 ...

  5. Go 每日一库之 zap

    转载地址:Go 每日一库之 zap - SegmentFault 思否 简介 在很早之前的文章中,我们介绍过 Go 标准日志库log和结构化的日志库logrus.在热点函数中记录日志对日志库的执行性能 ...

  6. Go语言常用标准库——log、net_http、strconv、time包

    文章目录 log 使用Logger 配置logger 标准logger的配置 flag选项 配置日志前缀 配置日志输出位置 创建logger 总结 net_http net/http介绍 HTTP协议 ...

  7. Go语言日志库zerolog

    Go语言日志库zerolog 在开发大型项目时,将日志进行结构化以提高可读性.可查询性和速度是非常重要的. 为什么你选择不使用其他结构化日志库,如logrus或zap? Zerolog 是一款高性能且 ...

  8. 用go写一个docker(8)-介绍两个库cli和logrus

    有了前面知识就可以开始写一个简单的docker了,但为了让这个docker好看一点,我们先了解一下会用到的两个库:cli 和 logrus github.com/urfave/cli github.c ...

  9. 深度|从Go高性能日志库zap看如何实现高性能Go组件

    导语:zap是uber开源的Go高性能日志库.本文作者深入分析了zap的架构设计和具体实现,揭示了zap高效的原因.并且对如何构建高性能Go语言库给出自己的建议. 作者简介:李子昂,美图公司架构平台系 ...

最新文章

  1. 22.类对象和类指针
  2. 死磕算法第二弹——栈、队列、链表(5)
  3. android10手机众筹,最小Android 10手机?屏幕仅3英寸的Jelly 2开始众筹
  4. AI技术说:人工智能相关概念与发展简史
  5. canva怎么拼接图片_搭配图片:短款外套+纱裙的冬季搭配,可爱又青春!|周二搭配...
  6. 余承东:华为P50系列无5G版本,但依然流畅
  7. python简单代码-Python简单进程锁代码实例
  8. MVC仓储执行存储过程报错“未提供该参数”
  9. idea java常用手册
  10. python读取xps文件_Python操作PDF-文本和图片提取(使用PyPDF2和PyMuPDF)
  11. 《树莓派4B家庭服务器搭建指南》第六期:将RSSHub私有化部署到树莓派,并通过《嘎!RSS》订阅自己的信息流...
  12. 学生图书馆系统mysql数据库设计
  13. 暴力破解zip压缩密码
  14. 小白如何成长为一名黑客
  15. Linux鼠标断断续续,鼠标断断续续失灵该怎么办
  16. Android7.1.1新增Shortcuts快捷方式,让App更加快捷方便
  17. 2021年安全员-C证(陕西省)考试资料及安全员-C证(陕西省)免费试题
  18. win10电脑部分软件不能连接网络,只有QQ和微信可以连接网络,浏览器不能上网
  19. 优化代码中的“坏味道”
  20. Serializable的含义

热门文章

  1. windowsmobile软件_经验:CSPJ/S初赛知识点整理(NOIP硬件与软件基础)
  2. 逆向破解思路和获取app的代码,脱壳操作(一)
  3. Java - 什么是Session
  4. uni-app:使用uni.downloadFile下载文件并保存到手机
  5. Cannot serialize; nested exception is org.springframework.core.serializer
  6. CMOS图像传感器——深入ISO
  7. Android插件化-Activity篇,腾讯安卓开发面试
  8. 与其说项羽败给刘邦,还不如说他输给了人情
  9. 输出100-200之间素数,每行8个输出
  10. java 迷你dvd_java基础迷你DVD系统