package lxzap import ( "fmt" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strconv" "strings" "time" "github.com/gin-gonic/gin" "github.com/natefinch/lumberjack" "go.uber.org/zap" "go.uber.org/zap/zapcore" ) var Lg *zap.Logger // Level = "info" // Filename = "./log/a.log" // MaxSize = 2 // MaxAge = 1000 // MaxBackups = 1000 type ZapLogConfig struct { Level string `json:"level"` Filename string `json:"filename"` MaxSize int `json:"maxsize"` MaxAge int `json:"max_age"` MaxBackups int `json:"max_backups"` } // InitZapLogger 初始化Logger func InitZapLogger(cfg ZapLogConfig, env string) (err error) { writeSyncer := getLogWriter(cfg.Filename, cfg.MaxSize, cfg.MaxBackups, cfg.MaxAge) encoder := getEncoder(env) var l = new(zapcore.Level) err = l.UnmarshalText([]byte(cfg.Level)) if err != nil { return } // 创建 zap 核心 core := zapcore.NewCore( encoder, zapcore.NewMultiWriteSyncer(zapcore.AddSync(os.Stdout), writeSyncer), l, ) if env != "dev" { core = zapcore.NewCore(encoder, writeSyncer, l) } // 创建 zap 日志对象 Lg = zap.New(core, zap.AddCaller()) zap.ReplaceGlobals(Lg) // 替换zap包中全局的logger实例,后续在其他包中只需使用zap.L()调用即可 return } func getEncoder(env string) zapcore.Encoder { encoderConfig := zap.NewProductionEncoderConfig() encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder encoderConfig.TimeKey = "time" encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder encoderConfig.EncodeDuration = zapcore.SecondsDurationEncoder encoderConfig.EncodeCaller = zapcore.ShortCallerEncoder l := zapcore.NewConsoleEncoder(encoderConfig) if env != "dev" { l = zapcore.NewJSONEncoder(encoderConfig) } return l } func getLogWriter(filename string, maxSize, maxBackup, maxAge int) zapcore.WriteSyncer { lumberJackLogger := &lumberjack.Logger{ Filename: filename, MaxSize: maxSize, MaxBackups: maxBackup, MaxAge: maxAge, LocalTime: true, } return zapcore.AddSync(lumberJackLogger) } // GinLogger 接收gin框架默认的日志 func GinLogger() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery spanID := NewUUID() c.Set("X-Span-ID", spanID) cost := time.Since(start) Lg.Info(path, zap.Int("status", c.Writer.Status()), zap.String("spanId", strconv.Itoa(int(spanID))), 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), ) c.Next() } } // 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 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 { 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: errcheck c.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() } } func Log(msg string, c *gin.Context) { v, _ := c.Get("X-Span-ID") spanId := fmt.Sprintf("%v", v) zap.L().Info(`spanId:"` + spanId + `"log:` + msg) }