Go 语言之在 gin 框架中使用 zap 日志库

打印 上一主题 下一主题

主题 944|帖子 944|积分 2832

Go 语言之在 gin 框架中使用 zap 日志库

gin 框架默认使用的是自带的日志

gin.Default()的源码 Logger(), Recovery()
  1. func Default() *Engine {
  2.         debugPrintWARNINGDefault()
  3.         engine := New()
  4.         engine.Use(Logger(), Recovery())
  5.         return engine
  6. }
  7. // Logger instances a Logger middleware that will write the logs to gin.DefaultWriter.
  8. // By default, gin.DefaultWriter = os.Stdout.
  9. func Logger() HandlerFunc {
  10.         return LoggerWithConfig(LoggerConfig{})
  11. }
  12. // Recovery returns a middleware that recovers from any panics and writes a 500 if there was one.
  13. func Recovery() HandlerFunc {
  14.         return RecoveryWithWriter(DefaultErrorWriter)
  15. }
  16. // RecoveryWithWriter returns a middleware for a given writer that recovers from any panics and writes a 500 if there was one.
  17. func RecoveryWithWriter(out io.Writer, recovery ...RecoveryFunc) HandlerFunc {
  18.         if len(recovery) > 0 {
  19.                 return CustomRecoveryWithWriter(out, recovery[0])
  20.         }
  21.         return CustomRecoveryWithWriter(out, defaultHandleRecovery)
  22. }
  23. // CustomRecoveryWithWriter returns a middleware for a given writer that recovers from any panics and calls the provided handle func to handle it.
  24. func CustomRecoveryWithWriter(out io.Writer, handle RecoveryFunc) HandlerFunc {
  25.         var logger *log.Logger
  26.         if out != nil {
  27.                 logger = log.New(out, "\n\n\x1b[31m", log.LstdFlags)
  28.         }
  29.         return func(c *Context) {
  30.                 defer func() {
  31.                         if err := recover(); err != nil {
  32.                                 // Check for a broken connection, as it is not really a
  33.                                 // condition that warrants a panic stack trace.
  34.                                 var brokenPipe bool
  35.                                 if ne, ok := err.(*net.OpError); ok {
  36.                                         var se *os.SyscallError
  37.                                         if errors.As(ne, &se) {
  38.                                                 seStr := strings.ToLower(se.Error())
  39.                                                 if strings.Contains(seStr, "broken pipe") ||
  40.                                                         strings.Contains(seStr, "connection reset by peer") {
  41.                                                         brokenPipe = true
  42.                                                 }
  43.                                         }
  44.                                 }
  45.                                 if logger != nil {
  46.                                         stack := stack(3)
  47.                                         httpRequest, _ := httputil.DumpRequest(c.Request, false)
  48.                                         headers := strings.Split(string(httpRequest), "\r\n")
  49.                                         for idx, header := range headers {
  50.                                                 current := strings.Split(header, ":")
  51.                                                 if current[0] == "Authorization" {
  52.                                                         headers[idx] = current[0] + ": *"
  53.                                                 }
  54.                                         }
  55.                                         headersToStr := strings.Join(headers, "\r\n")
  56.                                         if brokenPipe {
  57.                                                 logger.Printf("%s\n%s%s", err, headersToStr, reset)
  58.                                         } else if IsDebugging() {
  59.                                                 logger.Printf("[Recovery] %s panic recovered:\n%s\n%s\n%s%s",
  60.                                                         timeFormat(time.Now()), headersToStr, err, stack, reset)
  61.                                         } else {
  62.                                                 logger.Printf("[Recovery] %s panic recovered:\n%s\n%s%s",
  63.                                                         timeFormat(time.Now()), err, stack, reset)
  64.                                         }
  65.                                 }
  66.                                 if brokenPipe {
  67.                                         // If the connection is dead, we can't write a status to it.
  68.                                         c.Error(err.(error)) //nolint: errcheck
  69.                                         c.Abort()
  70.                                 } else {
  71.                                         handle(c, err)
  72.                                 }
  73.                         }
  74.                 }()
  75.                 c.Next()
  76.         }
  77. }
复制代码
自定义 Logger(), Recovery()

实操
  1. package main
  2. import (
  3.         "github.com/gin-gonic/gin"
  4.         "go.uber.org/zap"
  5.         "go.uber.org/zap/zapcore"
  6.         "gopkg.in/natefinch/lumberjack.v2"
  7.         "net"
  8.         "net/http"
  9.         "net/http/httputil"
  10.         "os"
  11.         "runtime/debug"
  12.         "strings"
  13.         "time"
  14. )
  15. // 定义一个全局 logger 实例
  16. // Logger提供快速、分级、结构化的日志记录。所有方法对于并发使用都是安全的。
  17. // Logger是为每一微秒和每一个分配都很重要的上下文设计的,
  18. // 因此它的API有意倾向于性能和类型安全,而不是简便性。
  19. // 对于大多数应用程序,SugaredLogger在性能和人体工程学之间取得了更好的平衡。
  20. var logger *zap.Logger
  21. // SugaredLogger将基本的Logger功能封装在一个较慢但不那么冗长的API中。任何Logger都可以通过其Sugar方法转换为sugardlogger。
  22. //与Logger不同,SugaredLogger并不坚持结构化日志记录。对于每个日志级别,它公开了四个方法:
  23. //   - methods named after the log level for log.Print-style logging
  24. //   - methods ending in "w" for loosely-typed structured logging
  25. //   - methods ending in "f" for log.Printf-style logging
  26. //   - methods ending in "ln" for log.Println-style logging
  27. // For example, the methods for InfoLevel are:
  28. //
  29. //        Info(...any)           Print-style logging
  30. //        Infow(...any)          Structured logging (read as "info with")
  31. //        Infof(string, ...any)  Printf-style logging
  32. //        Infoln(...any)         Println-style logging
  33. var sugarLogger *zap.SugaredLogger
  34. //func main() {
  35. //        // 初始化
  36. //        InitLogger()
  37. //        // Sync调用底层Core的Sync方法,刷新所有缓冲的日志条目。应用程序在退出之前应该注意调用Sync。
  38. //        // 在程序退出之前,把缓冲区里的日志刷到磁盘上
  39. //        defer logger.Sync()
  40. //        simpleHttpGet("www.baidu.com")
  41. //        simpleHttpGet("http://www.baidu.com")
  42. //
  43. //        for i := 0; i < 10000; i++ {
  44. //                logger.Info("test lumberjack for log rotate....")
  45. //        }
  46. //}
  47. func main() {
  48.         InitLogger()
  49.         //r := gin.Default()
  50.         r := gin.New()
  51.         r.Use(GinLogger(logger), GinRecovery(logger, true))
  52.         r.GET("/hello", func(c *gin.Context) {
  53.                 c.String(http.StatusOK, "hello xiaoqiao!")
  54.         })
  55.         r.Run()
  56. }
  57. // GinLogger
  58. func GinLogger(logger *zap.Logger) gin.HandlerFunc {
  59.         return func(c *gin.Context) {
  60.                 start := time.Now()
  61.                 path := c.Request.URL.Path
  62.                 query := c.Request.URL.RawQuery
  63.                 c.Next() // 执行后续中间件
  64.                 // Since returns the time elapsed since t.
  65.                 // It is shorthand for time.Now().Sub(t).
  66.                 cost := time.Since(start)
  67.                 logger.Info(path,
  68.                         zap.Int("status", c.Writer.Status()),
  69.                         zap.String("method", c.Request.Method),
  70.                         zap.String("path", path),
  71.                         zap.String("query", query),
  72.                         zap.String("ip", c.ClientIP()),
  73.                         zap.String("user-agent", c.Request.UserAgent()),
  74.                         zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()),
  75.                         zap.Duration("cost", cost), // 运行时间
  76.                 )
  77.         }
  78. }
  79. // GinRecovery
  80. func GinRecovery(logger *zap.Logger, stack bool) gin.HandlerFunc {
  81.         return func(c *gin.Context) {
  82.                 defer func() {
  83.                         if err := recover(); err != nil {
  84.                                 // Check for a broken connection, as it is not really a
  85.                                 // condition that warrants a panic stack trace.
  86.                                 var brokenPipe bool
  87.                                 if ne, ok := err.(*net.OpError); ok {
  88.                                         if se, ok := ne.Err.(*os.SyscallError); ok {
  89.                                                 if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") {
  90.                                                         brokenPipe = true
  91.                                                 }
  92.                                         }
  93.                                 }
  94.                                 httpRequest, _ := httputil.DumpRequest(c.Request, false)
  95.                                 if brokenPipe {
  96.                                         logger.Error(c.Request.URL.Path,
  97.                                                 zap.Any("error", err),
  98.                                                 zap.String("request", string(httpRequest)),
  99.                                         )
  100.                                         // If the connection is dead, we can't write a status to it.
  101.                                         c.Error(err.(error)) // nolint: errcheck
  102.                                         c.Abort()
  103.                                         return
  104.                                 }
  105.                                 if stack {
  106.                                         logger.Error("[Recovery from panic]",
  107.                                                 zap.Any("error", err),
  108.                                                 zap.String("request", string(httpRequest)),
  109.                                                 zap.String("stack", string(debug.Stack())),
  110.                                         )
  111.                                 } else {
  112.                                         logger.Error("[Recovery from panic]",
  113.                                                 zap.Any("error", err),
  114.                                                 zap.String("request", string(httpRequest)),
  115.                                         )
  116.                                 }
  117.                                 c.AbortWithStatus(http.StatusInternalServerError)
  118.                         }
  119.                 }()
  120.                 c.Next()
  121.         }
  122. }
  123. func InitLogger() {
  124.         writeSyncer := getLogWriter()
  125.         encoder := getEncoder()
  126.         // NewCore创建一个向WriteSyncer写入日志的Core。
  127.         // A WriteSyncer is an io.Writer that can also flush any buffered data. Note
  128.         // that *os.File (and thus, os.Stderr and os.Stdout) implement WriteSyncer.
  129.         // LevelEnabler决定在记录消息时是否启用给定的日志级别。
  130.         // Each concrete Level value implements a static LevelEnabler which returns
  131.         // true for itself and all higher logging levels. For example WarnLevel.Enabled()
  132.         // will return true for WarnLevel, ErrorLevel, DPanicLevel, PanicLevel, and
  133.         // FatalLevel, but return false for InfoLevel and DebugLevel.
  134.         core := zapcore.NewCore(encoder, writeSyncer, zapcore.DebugLevel)
  135.         // New constructs a new Logger from the provided zapcore.Core and Options. If
  136.         // the passed zapcore.Core is nil, it falls back to using a no-op
  137.         // implementation.
  138.         // AddCaller configures the Logger to annotate each message with the filename,
  139.         // line number, and function name of zap's caller. See also WithCaller.
  140.         logger = zap.New(core, zap.AddCaller())
  141.         // Sugar封装了Logger,以提供更符合人体工程学的API,但速度略慢。糖化一个Logger的成本非常低,
  142.         // 因此一个应用程序同时使用Loggers和SugaredLoggers是合理的,在性能敏感代码的边界上在它们之间进行转换。
  143.         sugarLogger = logger.Sugar()
  144. }
  145. func getEncoder() zapcore.Encoder {
  146.         // NewJSONEncoder创建了一个快速、低分配的JSON编码器。编码器适当地转义所有字段键和值。
  147.         // NewProductionEncoderConfig returns an opinionated EncoderConfig for
  148.         // production environments.
  149.         //return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
  150.         // NewConsoleEncoder创建一个编码器,其输出是为人类而不是机器设计的。
  151.         // 它以纯文本格式序列化核心日志条目数据(消息、级别、时间戳等),并将结构化上下文保留为JSON。
  152.         encoderConfig := zapcore.EncoderConfig{
  153.                 TimeKey:        "ts",
  154.                 LevelKey:       "level",
  155.                 NameKey:        "logger",
  156.                 CallerKey:      "caller",
  157.                 FunctionKey:    zapcore.OmitKey,
  158.                 MessageKey:     "msg",
  159.                 StacktraceKey:  "stacktrace",
  160.                 LineEnding:     zapcore.DefaultLineEnding,
  161.                 EncodeLevel:    zapcore.LowercaseLevelEncoder,
  162.                 EncodeTime:     zapcore.ISO8601TimeEncoder,
  163.                 EncodeDuration: zapcore.SecondsDurationEncoder,
  164.                 EncodeCaller:   zapcore.ShortCallerEncoder,
  165.         }
  166.         return zapcore.NewConsoleEncoder(encoderConfig)
  167. }
  168. //func getLogWriter() zapcore.WriteSyncer {
  169. //        // Create创建或截断指定文件。如果文件已经存在,它将被截断。如果该文件不存在,则以模式0666(在umask之前)创建。
  170. //        // 如果成功,返回的File上的方法可以用于IO;关联的文件描述符模式为O_RDWR。如果有一个错误,它的类型将是PathError。
  171. //        //file, _ := os.Create("./test.log")
  172. //        file, err := os.OpenFile("./test.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
  173. //        if err != nil {
  174. //                log.Fatalf("open log file failed with error: %v", err)
  175. //        }
  176. //        // AddSync converts an io.Writer to a WriteSyncer. It attempts to be
  177. //        // intelligent: if the concrete type of the io.Writer implements WriteSyncer,
  178. //        // we'll use the existing Sync method. If it doesn't, we'll add a no-op Sync.
  179. //        return zapcore.AddSync(file)
  180. //}
  181. func getLogWriter() zapcore.WriteSyncer {
  182.         // Logger is an io.WriteCloser that writes to the specified filename.
  183.         // 日志记录器在第一次写入时打开或创建日志文件。如果文件存在并且小于MaxSize兆字节,则lumberjack将打开并追加该文件。
  184.         // 如果该文件存在并且其大小为>= MaxSize兆字节,
  185.         // 则通过将当前时间放在文件扩展名(或者如果没有扩展名则放在文件名的末尾)的名称中的时间戳中来重命名该文件。
  186.         // 然后使用原始文件名创建一个新的日志文件。
  187.         // 每当写操作导致当前日志文件超过MaxSize兆字节时,将关闭当前文件,重新命名,并使用原始名称创建新的日志文件。
  188.         // 因此,您给Logger的文件名始终是“当前”日志文件。
  189.         // 如果MaxBackups和MaxAge均为0,则不会删除旧的日志文件。
  190.         lumberJackLogger := &lumberjack.Logger{
  191.                 // Filename是要写入日志的文件。备份日志文件将保留在同一目录下
  192.                 Filename: "./test.log",
  193.                 // MaxSize是日志文件旋转之前的最大大小(以兆字节为单位)。默认为100兆字节。
  194.                 MaxSize: 1, // M
  195.                 // MaxBackups是要保留的旧日志文件的最大数量。默认是保留所有旧的日志文件(尽管MaxAge仍然可能导致它们被删除)。
  196.                 MaxBackups: 5, // 备份数量
  197.                 // MaxAge是根据文件名中编码的时间戳保留旧日志文件的最大天数。
  198.                 // 请注意,一天被定义为24小时,由于夏令时、闰秒等原因,可能与日历日不完全对应。默认情况下,不根据时间删除旧的日志文件。
  199.                 MaxAge: 30, // 备份天数
  200.                 // Compress决定是否应该使用gzip压缩旋转的日志文件。默认情况下不执行压缩。
  201.                 Compress: false, // 是否压缩
  202.         }
  203.         return zapcore.AddSync(lumberJackLogger)
  204. }
  205. func simpleHttpGet(url string) {
  206.         // Get向指定的URL发出Get命令。如果响应是以下重定向代码之一,则Get跟随重定向,最多可重定向10个:
  207.         //        301 (Moved Permanently)
  208.         //        302 (Found)
  209.         //        303 (See Other)
  210.         //        307 (Temporary Redirect)
  211.         //        308 (Permanent Redirect)
  212.         // Get is a wrapper around DefaultClient.Get.
  213.         // 使用NewRequest和DefaultClient.Do来发出带有自定义头的请求。
  214.         resp, err := http.Get(url)
  215.         if err != nil {
  216.                 // Error在ErrorLevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
  217.                 //logger.Error(
  218.                 // 错误使用fmt。以Sprint方式构造和记录消息。
  219.                 sugarLogger.Error(
  220.                         "Error fetching url..",
  221.                         zap.String("url", url), // 字符串用给定的键和值构造一个字段。
  222.                         zap.Error(err))         // // Error is shorthand for the common idiom NamedError("error", err).
  223.         } else {
  224.                 // Info以infollevel记录消息。该消息包括在日志站点传递的任何字段,以及日志记录器上积累的任何字段。
  225.                 //logger.Info("Success..",
  226.                 // Info使用fmt。以Sprint方式构造和记录消息。
  227.                 sugarLogger.Info("Success..",
  228.                         zap.String("statusCode", resp.Status),
  229.                         zap.String("url", url))
  230.                 resp.Body.Close()
  231.         }
  232. }
复制代码
运行并访问:http://localhost:8080/hello
[code]Code/go/zap_demo via
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

去皮卡多

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表