为什么需要Logger
一般在开发项目的时候我们都是需要一个存储日志的文件,因为在部署项目以后,我们只能通过去筛查日志进行检索问题,这时候日志是否可以呈现清晰这个对于我们进行排查工作是十分重要的,所以Logger能否展示出我们最想要的错误展示方式是很有必要的!本章节的案例是基于gin框架和viper
进行编写一个Logger的日志文件,日志会根据yaml
文件定义的 mode
进行判断是否是开发环境还是线上环境进行写的。
实现Logger.go
首先看一下yaml文件的配置
在yaml文件中定义了一个app,app下面有一个mode,这个mode就是用来识别我们是开发环境还是线上环境的。
app:
name: "web_app"
mode: "dev"
port: 8080
version: "v0.01"
log:
level: "debug"
filename: "web_app.log"
max_size: 200
max_age: 30
max_backups: 7
settings的定义
var Conf AppConfig
type AppConfig struct {
App App `mapstructure:"app" yaml:"app"`
Log LogConfig `mapstructure:"log"`
}
type App struct {
Name string `mapstructure:"name"`
Mode string `mapstructure:"mode"`
Version string `mapstructure:"version"`
Port int `mapstructure:"port" yaml:"port"`
}
type LogConfig struct {
Level string `mapstructure:"level"`
Filename string `mapstructure:"filename"`
MaxSize int `mapstructure:"max_size"`
MaxAge int `mapstructure:"max_age"`
MaxBackups int `mapstructure:"max_backups"`
}
Logger.go
这里需要结合自己的实际进行修改,如果复制了import中的依赖,需要执行一下:go mod tidy
package logger
import(
"github.com/gin-gonic/gin"
"github.com/spf13/viper"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
var logger *zap.Logger
// Init 的两个参数: cfg 和 mode
func Init(cfg *settings.LogConfig, mode string) (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 err
}
var core zapcore.Core
// 开发模式,日志输出终端
if mode == "dev" {
consoleEncoder := zapcore.NewConsoleEncoder(zap.NewDevelopmentEncoderConfig())
core = zapcore.NewTee(
zapcore.NewCore(encoder, writeSyncer, l), // 保存在日志中
// 错误信息展示在终端
zapcore.NewCore(consoleEncoder, zapcore.Lock(os.Stdout), zapcore.DebugLevel),
)
} else {
core = zapcore.NewCore(encoder, writeSyncer, l)
}
logger = zap.New(core, zap.AddCaller())
lg := zap.New(core, zap.AddCaller())
// 替换zap库中的全局
zap.ReplaceGlobals(lg)
return err
}
func getEncoder() zapcore.Encoder {
//return zapcore.NewJSONEncoder(zap.NewProductionEncoderConfig())
encodeConfig := zapcore.EncoderConfig{
LevelKey: "level",
TimeKey: "ts",
NameKey: "logger",
CallerKey: "caller",
StacktraceKey: "stacktrace",
LineEnding: zapcore.DefaultLineEnding,
EncodeLevel: zapcore.LowercaseLevelEncoder,
EncodeTime: zapcore.ISO8601TimeEncoder,
EncodeDuration: zapcore.SecondsDurationEncoder,
EncodeCaller: zapcore.ShortCallerEncoder,
}
//return zapcore.NewConsoleEncoder(zap.NewProductionEncoderConfig())
return zapcore.NewConsoleEncoder(encodeConfig)
}
// 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(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),
)
}
}
func getLogWriter(string, int, int, int) zapcore.WriteSyncer {
lumberJackLogger := &lumberjack.Logger{
Filename: viper.GetString("log.filename"),
MaxSize: viper.GetInt("log.max_size"), // 单位是M
MaxBackups: viper.GetInt("log.max_backups"), // 备份数量
MaxAge: viper.GetInt("log.max_age"), // 最大备份天数
Compress: false, // 是否压缩
}
return zapcore.AddSync(lumberJackLogger)
}
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()
}
}
最后在main.go中进行注册方法
func main() {
// 1、加载配置文件
if err := settings.Init(); err != nil {
fmt.Println("init setting failed", err)
return
}
// 2、初始化日志
if err := logger.Init(&settings.Conf.Log, settings.Conf.App.Mode); err != nil {
fmt.Printf("init logger failed, err:%v\n", err)
}
defer zap.L().Sync()
zap.L().Debug("logger init success...")
}
在开发环境(dev)中的测试结果
与自己想要的结果是一致的: