package log import ( "fmt" rotatelogs "github.com/lestrrat-go/file-rotatelogs" "gitlab.wodcloud.com/smart-operation/so-operation-api/src/common/conf" "io" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strings" "time" "github.com/gin-gonic/gin" "go.uber.org/zap" "go.uber.org/zap/zapcore" "gopkg.in/natefinch/lumberjack.v2" ) func NewSugaredLogger(config zapcore.EncoderConfig, logLevel string) *zap.SugaredLogger { return initLog(config, logLevel).Sugar() } func NewLogger(config zapcore.EncoderConfig, logLevel string) *zap.Logger { return initLog(config, logLevel) } func initLog(config zapcore.EncoderConfig, logLevel string) *zap.Logger { atom := zap.NewAtomicLevel() // zapcore.Lock(os.Stdout), // 1.标准输出 // zapcore.Lock(zapcore.AddSync(getWriter("syslog"))), // 2.写入日志文件(使用file-rotatelogs) // zapcore.AddSync(WriteSyncer(conf.Options.LogDirName)) // 3.写入日志文件(使用lumberjack) var writeSyncer zapcore.WriteSyncer switch conf.Options.LogMode { case 1: writeSyncer = zapcore.Lock(os.Stdout) case 2: writeSyncer = zapcore.AddSync(WriteSyncer(conf.Options.LogDirName)) } lg := zap.New(zapcore.NewCore( zapcore.NewJSONEncoder(config), writeSyncer, atom, ), zap.AddCaller(), // 打印日志调用位置和行号 //zap.AddCallerSkip(1), // 上级调用 ) // 根据上面的配置创建logger zap.ReplaceGlobals(lg) // 替换zap库里全局的logger defer lg.Sync() atom.UnmarshalText([]byte(logLevel)) return lg } // WriteSyncer lumberjack 日志分割工具 func WriteSyncer(filename string) (writeSyncer zapcore.WriteSyncer) { // 使用 lumberjack 实现 log rotate fileName := fmt.Sprintf("%s/%s-%s.log", conf.Options.LogDirPrefix, filename, time.Now().Format("2006-01-02")) lumberJackLogger := &lumberjack.Logger{ Filename: fileName, // 日志文件的位置 MaxSize: 100, // 单个文件最大100MB MaxBackups: 5, // 多于 5 个日志文件后,清理较旧的日志 MaxAge: 1, // 一天一切割 Compress: false, // 是否压缩/归档旧文件 } return zapcore.AddSync(lumberJackLogger) } // WriteSyncer2 file-rotatelogs 日志分割工具 func WriteSyncer2(filename string) io.Writer { // 生成rotatelogs的Logger 实际生成的文件名 demo.log.YYmmddHH // demo.log是指向最新日志的链接 // 保存7天内的日志,每1小时(整点)分割一次日志 hook, err := rotatelogs.New( conf.Options.LogDirPrefix+"/"+filename+"-%Y-%m-%d.log", // 没有使用go风格反人类的format格式 //rotatelogs.WithLinkName(filename), rotatelogs.WithMaxAge(time.Duration(conf.Options.LogSaveDays)*24*time.Hour), rotatelogs.WithRotationTime(time.Hour*24), ) if err != nil { panic(err) } return hook } // GinLogger 接收gin框架默认的日志 func GinLogger() 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) zap.L().Info("visit", 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 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 { zap.L().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 { zap.L().Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { zap.L().Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } }