Gin实战(3):系统日志管理


📌Gin实战(3):系统日志管理

引言


二、Gin日志系统核心解析

  1. 内置日志中间件剖析

    • gin.Default()自带的Logger和Recovery中间件
    • 默认日志格式:[GIN] 2023/10/01 - 15:04:05 | 200 | 1.002539ms | 127.0.0.1 | GET "/api/v1/ping"
  2. 定制你的专属日志中间件

func LoggerMiddleware(logger logger.Logger) gin.HandlerFunc {
	return func(c *gin.Context) {
		t := time.Now()
		c.Next()
		latency := time.Now().Sub(t)
		logger.Info(
			"latency", latency,
			"status", c.Writer.Status(),
			"path", c.Request.URL.Path,
			"method", c.Request.Method,
			"ip", c.ClientIP(),
			"user-agent", c.Request.UserAgent(),
		)
	}
}
  1. 实现 logger.Logger
    • 按场景划分:Debug/Info/Warn/Error
    • 接口定义:
type Logger interface {
	Info(...interface{})
	Error(...interface{})
	Debug(...interface{})
	Warn(...interface{})
	Fatal(...interface{})
}
  • zap+file-rotatelogs 实现按日期切割的文件日志;
  • WithPrefix 用来设置日志的一些公共字段,如服务名、版本号等
  • zapcore.NewCore 设置 同时输出到控制台和日志文件 主要代码
var _ Logger = (*FileLogger)(nil)

type FileLogger struct {
	log    *zap.Logger
	prefix []interface{}
}

var DefaultLoggerSavePath = "./logs"

func NewFileLogger(level Level) *FileLogger {
	logSavePath := os.Getenv("GIN_LOG_PATH")
	if logSavePath == "" {
		logSavePath = DefaultLoggerSavePath
	}
	rotateLogger, err := rotatelogs.New(logSavePath+"/%Y%m%d.log", rotatelogs.WithMaxAge(time.Hour*24*30))
	if err != nil {
		log.Fatalf("logger.Setup err: %v", err)
	}
	encoder := zapcore.EncoderConfig{
		TimeKey:        "t",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		MessageKey:     "msg",
		StacktraceKey:  "stack",
		EncodeTime:     zapcore.ISO8601TimeEncoder,
		LineEnding:     zapcore.DefaultLineEnding,
		EncodeLevel:    zapcore.LowercaseLevelEncoder,
		EncodeDuration: zapcore.SecondsDurationEncoder,
		EncodeCaller:   zapcore.FullCallerEncoder,
	}
	zipLevel := levelToZipLevel(level)
	core := zapcore.NewCore(
		zapcore.NewConsoleEncoder(encoder),
		zapcore.NewMultiWriteSyncer(
			zapcore.AddSync(os.Stdout),    //输出到控制台上
			zapcore.AddSync(rotateLogger), //输出到文本中
		), zipLevel)
	zapLogger := zap.New(core,
		zap.AddStacktrace(zap.NewAtomicLevelAt(zapcore.ErrorLevel)),
		zap.AddCaller(),
		zap.AddCallerSkip(2),
		zap.Development())
	return &FileLogger{
		log: zapLogger,
	}
}
func WithPrefix(logger *FileLogger, prefix ...interface{}) Logger {
	kvs := make([]interface{}, 0, len(logger.prefix)+len(prefix))
	kvs = append(kvs, logger.prefix...)
	kvs = append(kvs, prefix...)
	return &FileLogger{
		log:    logger.log,
		prefix: kvs,
	}
}
func levelToZipLevel(level Level) zapcore.Level {
	switch level {
	case Info:
		return zapcore.InfoLevel
	case Error:
		return zapcore.ErrorLevel
	case Debug:
		return zapcore.DebugLevel
	case Warn:
		return zapcore.WarnLevel
	case Fatal:
		return zapcore.FatalLevel
	default:
		return zapcore.InfoLevel
	}
}

func (f FileLogger) Info(i ...interface{}) {
	f.InfoW(Info, i...)
}
func (f FileLogger) Error(i ...interface{}) {
	f.InfoW(Error, i...)
}

func (f FileLogger) Debug(i ...interface{}) {
	f.InfoW(Debug, i...)
}

func (f FileLogger) Warn(i ...interface{}) {
	f.InfoW(Warn, i...)
}

func (f FileLogger) Fatal(i ...interface{}) {
	f.InfoW(Fatal, i...)
}

三、项目中使用logger

  1. 配置日志中间件 internal/routers/router.go
    func InitRouters(
	account *service.AccountServer,
	logger logger.Logger,
    ) *gin.Engine {
        mdls := []gin.HandlerFunc{middleware.LoggerMiddleware(logger)}
        mdls = append(mdls, middleware.AppMiddleware...)
        r := initGin(mdls...)

        // add api ping
        r.GET("ping", func(c *gin.Context) {
            c.JSON(200, gin.H{"msg": "this is ping", "timestamp": time.Now().Unix()})
        })

        r.POST("/login", account.Login)
        return r
    }
  1. 项目中使用logger main.go
	fileLogger := logger.WithPrefix(logger.NewFileLogger(logger.Info),
		"service.id", Id,
		"service.name", Name,
		"service.version", Version,
	)

	s, err := wireApp(config.Server, fileLogger)

service/account.go

type AccountServer struct {
	logger logger.Logger
}

func NewAccountServer(logger logger.Logger) *AccountServer {
	return &AccountServer{
		logger: logger,
	}
}

func (a *AccountServer) Login(ctx *gin.Context) {
	a.logger.Info("msg", "this is login api")

	ctx.JSON(200, gin.H{
		"msg": "this is login api",
	})
}
  • 增加依赖参数后需要重新构建:执行命令 make generate

四、避坑指南

  1. 高并发场景下的日志性能优化
  2. 避免日志导致的goroutine泄漏
  3. 日志安全三原则:
    • 不记录敏感信息
    • 控制日志级别避免磁盘爆满
    • 设置合理的日志保留策略

公众号后台回复「Gin实战」获取

wx

关注公众号

©2017-2023 鲁ICP备17023316号-1 Powered by Hugo