参考资料

  1. https://www.liwenzhou.com/posts/Go/use_zap_in_gin/ -李文周博客

gin默认日志中间件

  1. // gin.go
  2. func Default() *Engine {
  3. debugPrintWARNINGDefault()
  4. engine := New()
  5. engine.Use(Logger(), Recovery())
  6. return engine
  7. }
  8. func Logger() HandlerFunc {
  9. return LoggerWithConfig(LoggerConfig{})
  10. }
  11. // 配置logger中间件
  12. func LoggerWithConfig(conf LoggerConfig) HandlerFunc {
  13. ...
  14. ...
  15. return func(c *Context) {
  16. // Start timer
  17. start := time.Now()
  18. path := c.Request.URL.Path
  19. raw := c.Request.URL.RawQuery
  20. // Process request
  21. c.Next()
  22. // Log only when path is not being skipped
  23. if _, ok := skip[path]; !ok {
  24. param := LogFormatterParams{
  25. Request: c.Request,
  26. isTerm: isTerm,
  27. Keys: c.Keys,
  28. }
  29. // Stop timer
  30. param.TimeStamp = time.Now()
  31. param.Latency = param.TimeStamp.Sub(start)
  32. param.ClientIP = c.ClientIP()
  33. param.Method = c.Request.Method
  34. param.StatusCode = c.Writer.Status()
  35. param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
  36. param.BodySize = c.Writer.Size()
  37. if raw != "" {
  38. path = path + "?" + raw
  39. }
  40. param.Path = path
  41. fmt.Fprint(out, formatter(param))
  42. }
  43. }
  44. }

配置传递给zap的中间件

基本是参考gin源码,只不过将默认的logger换成了zap。

  1. // GinLogger 接收gin框架默认的日志
  2. func GinLogger(logger *zap.Logger) gin.HandlerFunc {
  3. return func(c *gin.Context) {
  4. start := time.Now()
  5. path := c.Request.URL.Path
  6. query := c.Request.URL.RawQuery
  7. c.Next()
  8. cost := time.Since(start)
  9. logger.Info(path,
  10. zap.Int("status", c.Writer.Status()),
  11. zap.String("method", c.Request.Method),
  12. zap.String("path", path),
  13. zap.String("query", query),
  14. zap.String("ip", c.ClientIP()),
  15. zap.String("user-agent", c.Request.UserAgent()),
  16. zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
  17. zap.Duration("cost", cost),
  18. )
  19. }
  20. }
  21. // GinRecovery recover掉项目可能出现的panic
  22. func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
  23. return func(c *gin.Context) {
  24. defer func() {
  25. if err := recover(); err != nil {
  26. // Check for a broken connection, as it is not really a
  27. // condition that warrants a panic stack trace.
  28. var brokenPipe bool
  29. if ne, ok := err.(*net.OpError); ok {
  30. if se, ok := ne.Err.(*os.SyscallError); ok {
  31. if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
  32. brokenPipe = true
  33. }
  34. }
  35. }
  36. httpRequest, _ := httputil.DumpRequest(c.Request, false)
  37. if brokenPipe {
  38. logger.Error(c.Request.URL.Path,
  39. zap.Any("error", err),
  40. zap.String("request", string(httpRequest)),
  41. )
  42. // If the connection is dead, we can't write a status to it.
  43. c.Error(err.(error)) // nolint: errcheck
  44. c.Abort()
  45. return
  46. }
  47. if stack {
  48. logger.Error("[Recovery from panic]",
  49. zap.Any("error", err),
  50. zap.String("request", string(httpRequest)),
  51. zap.String("stack", string(debug.Stack())),
  52. )
  53. } else {
  54. logger.Error("[Recovery from panic]",
  55. zap.Any("error", err),
  56. zap.String("request", string(httpRequest)),
  57. )
  58. }
  59. c.AbortWithStatus(http.StatusInternalServerError)
  60. }
  61. }()
  62. c.Next()
  63. }
  64. }

如果不想自己实现,可以使用github上有别人封装好的https://github.com/gin-contrib/zap

gin中使用实例

除了logger和recovery,再加上日志分割

  1. package logger
  2. import (
  3. "gin_zap_demo/config"
  4. "net"
  5. "net/http"
  6. "net/http/httputil"
  7. "os"
  8. "runtime/debug"
  9. "strings"
  10. "time"
  11. "github.com/gin-gonic/gin"
  12. "github.com/natefinch/lumberjack"
  13. "go.uber.org/zap"
  14. "go.uber.org/zap/zapcore"
  15. )
  16. var lg *zap.Logger
  17. // InitLogger 初始化Logger
  18. func InitLogger(cfg *config.LogConfig) (err error) {
  19. writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge)
  20. encoder := getEncoder()
  21. var l = new(zapcore.Level)
  22. err = l.UnmarshalText([]byte(cfg.Level))
  23. if err != nil {
  24. return
  25. }
  26. core := zapcore.NewCore(encoder, writeSyncer, l)
  27. lg = zap.New(core, zap.AddCaller())
  28. zap.ReplaceGlobals(lg) // 替换zap包中全局的logger实例,后续在其他包中只需使用zap.L()调用即可
  29. return
  30. }
  31. func getEncoder() zapcore.Encoder {
  32. encoderConfig := zap.NewProductionEncoderConfig()
  33. encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
  34. encoderConfig.TimeKey = "time"
  35. encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
  36. encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder
  37. encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder
  38. return zapcore.NewJSONEncoder(encoderConfig)
  39. }
  40. func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer {
  41. lumberJackLogger := &lumberjack.Logger{
  42. Filename: filename,
  43. MaxSize: maxSize,
  44. MaxBackups: maxBackup,
  45. MaxAge: maxAge,
  46. }
  47. return zapcore.AddSync(lumberJackLogger)
  48. }
  49. // GinLogger 接收gin框架默认的日志
  50. func GinLogger() gin.HandlerFunc {
  51. return func(c *gin.Context) {
  52. start := time.Now()
  53. path := c.Request.URL.Path
  54. query := c.Request.URL.RawQuery
  55. c.Next()
  56. cost := time.Since(start)
  57. lg.Info(path,
  58. zap.Int("status", c.Writer.Status()),
  59. zap.String("method", c.Request.Method),
  60. zap.String("path", path),
  61. zap.String("query", query),
  62. zap.String("ip", c.ClientIP()),
  63. zap.String("user-agent", c.Request.UserAgent()),
  64. zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
  65. zap.Duration("cost", cost),
  66. )
  67. }
  68. }
  69. // GinRecovery recover掉项目可能出现的panic,并使用zap记录相关日志
  70. func GinRecovery(stack bool) gin.HandlerFunc {
  71. return func(c *gin.Context) {
  72. defer func() {
  73. if err := recover(); err != nil {
  74. // Check for a broken connection, as it is not really a
  75. // condition that warrants a panic stack trace.
  76. var brokenPipe bool
  77. if ne, ok := err.(*net.OpError); ok {
  78. if se, ok := ne.Err.(*os.SyscallError); ok {
  79. if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
  80. brokenPipe = true
  81. }
  82. }
  83. }
  84. httpRequest, _ := httputil.DumpRequest(c.Request, false)
  85. if brokenPipe {
  86. lg.Error(c.Request.URL.Path,
  87. zap.Any("error", err),
  88. zap.String("request", string(httpRequest)),
  89. )
  90. // If the connection is dead, we can't write a status to it.
  91. c.Error(err.(error)) // nolint: errcheck
  92. c.Abort()
  93. return
  94. }
  95. if stack {
  96. lg.Error("[Recovery from panic]",
  97. zap.Any("error", err),
  98. zap.String("request", string(httpRequest)),
  99. zap.String("stack", string(debug.Stack())),
  100. )
  101. } else {
  102. lg.Error("[Recovery from panic]",
  103. zap.Any("error", err),
  104. zap.String("request", string(httpRequest)),
  105. )
  106. }
  107. c.AbortWithStatus(http.StatusInternalServerError)
  108. }
  109. }()
  110. c.Next()
  111. }
  112. }