全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-18 8 分钟 ✍️ juanwangdev

Gin 日志分级与格式化

合理的日志分级和格式化是日志系统的基础,便于问题定位和日志分析。

日志级别

级别定义

级别说明使用场景
DEBUG调试信息开发调试
INFO常规信息正常流程
WARN警告信息潜在问题
ERROR错误信息业务错误
FATAL致命错误服务崩溃

logrus 基础配置

基础设置

Go
import (
    "github.com/sirupsen/logrus"
)

var logger = logrus.New()

func SetupLogger() {
    // 设置日志级别
    logger.SetLevel(logrus.InfoLevel)

    // 设置输出
    logger.SetOutput(os.Stdout)

    // 设置格式
    logger.SetFormatter(&logrus.TextFormatter{
        FullTimestamp:   true,
        TimestampFormat: "2006-01-02 15:04:05",
        DisableColors:   false,
    })
}

func main() {
    SetupLogger()

    logger.Debug("调试信息")   // 不输出(低于 INFO)
    logger.Info("正常信息")
    logger.Warn("警告信息")
    logger.Error("错误信息")
}

JSON 格式化

Go
func SetupJSONLogger() {
    logger.SetFormatter(&logrus.JSONFormatter{
        TimestampFormat: "2006-01-02T15:04:05Z07:00",
        FieldMap: logrus.FieldMap{
            logrus.FieldKeyTime:  "timestamp",
            logrus.FieldKeyLevel: "level",
            logrus.FieldKeyMsg:   "message",
            logrus.FieldKeyFunc:  "function",
        },
    })

    logger.SetLevel(logrus.InfoLevel)
}

// 输出示例
// {"timestamp":"2026-05-18T10:30:00+08:00","level":"info","message":"请求开始"}

自定义格式化

Go
type CustomFormatter struct{}

func (f *CustomFormatter) Format(entry *logrus.Entry) ([]byte, error) {
    timestamp := entry.Time.Format("2006-01-02 15:04:05")
    level := strings.ToUpper(entry.Level.String())

    var fieldsStr string
    if len(entry.Data) > 0 {
        fields := make([]string, 0)
        for k, v := range entry.Data {
            fields = append(fields, fmt.Sprintf("%s=%v", k, v))
        }
        fieldsStr = " [" + strings.Join(fields, " ") + "]"
    }

    output := fmt.Sprintf("[%s] [%s] %s%s\n",
        timestamp, level, entry.Message, fieldsStr)

    return []byte(output), nil
}

func SetupCustomLogger() {
    logger.SetFormatter(&CustomFormatter{})
    logger.SetLevel(logrus.DebugLevel)
}

// 输出示例
// [2026-05-18 10:30:00] [INFO] 请求开始 [request_id=abc123 path=/api/users]

Gin 日志中间件

替换默认日志

Go
func LoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        startTime := time.Now()

        // 处理请求
        c.Next()

        // 记录日志
        latency := time.Since(startTime)
        statusCode := c.Writer.Status()

        logger.WithFields(logrus.Fields{
            "method":      c.Request.Method,
            "path":        c.Request.URL.Path,
            "status":      statusCode,
            "latency_ms":  latency.Milliseconds(),
            "client_ip":   c.ClientIP(),
            "request_id":  c.GetString("request_id"),
        }).Info("请求完成")
    }
}

func main() {
    r := gin.New()
    r.Use(RequestIDMiddleware())
    r.Use(LoggerMiddleware())
    // 替换 gin.Default() 的默认日志中间件
}

分级日志中间件

Go
func LevelLoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        startTime := time.Now()
        path := c.Request.URL.Path

        c.Next()

        latency := time.Since(startTime)
        statusCode := c.Writer.Status()

        fields := logrus.Fields{
            "method":     c.Request.Method,
            "path":       path,
            "status":     statusCode,
            "latency_ms": latency.Milliseconds(),
            "client_ip":  c.ClientIP(),
        }

        // 根据状态码分级记录
        switch {
        case statusCode >= 500:
            logger.WithFields(fields).Error("服务错误")

        case statusCode >= 400:
            logger.WithFields(fields).Warn("客户端错误")

        case statusCode >= 300:
            logger.WithFields(fields).Info("重定向")

        default:
            logger.WithFields(fields).Info("请求成功")
        }
    }
}

动态调整日志级别

基于配置调整

Go
type LogConfig struct {
    Level string `json:"level"`
    Format string `json:"format"`
}

func LoadLogConfig(configPath string) (*LogConfig, error) {
    data, err := os.ReadFile(configPath)
    if err != nil {
        return nil, err
    }

    var config LogConfig
    json.Unmarshal(data, &config)
    return &config, nil
}

func ApplyLogConfig(config *LogConfig) {
    // 设置级别
    level, err := logrus.ParseLevel(config.Level)
    if err != nil {
        level = logrus.InfoLevel
    }
    logger.SetLevel(level)

    // 设置格式
    switch config.Format {
    case "json":
        logger.SetFormatter(&logrus.JSONFormatter{})
    case "text":
        logger.SetFormatter(&logrus.TextFormatter{
            FullTimestamp: true,
        })
    }
}

func main() {
    config, _ := LoadLogConfig("config/log.json")
    ApplyLogConfig(config)
}

运行时动态调整

Go
// 动态调整日志级别接口
func SetLogLevelHandler(c *gin.Context) {
    var req struct {
        Level string `json:"level" binding:"required"`
    }

    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(400, gin.H{"error": "参数错误"})
        return
    }

    level, err := logrus.ParseLevel(req.Level)
    if err != nil {
        c.JSON(400, gin.H{"error": "无效的日志级别"})
        return
    }

    logger.SetLevel(level)

    c.JSON(200, gin.H{
        "message": "日志级别已更新",
        "level":   level.String(),
    })
}

func main() {
    r := gin.New()

    // 管理接口(需要权限控制)
    admin := r.Group("/admin")
    admin.Use(AuthMiddleware())
    {
        admin.POST("/log/level", SetLogLevelHandler)
    }

    r.Run(":8080")
}

不同环境配置

Go
func SetupLoggerByEnv() {
    env := os.Getenv("APP_ENV")

    switch env {
    case "development":
        // 开发环境:详细日志、彩色输出
        logger.SetLevel(logrus.DebugLevel)
        logger.SetFormatter(&logrus.TextFormatter{
            FullTimestamp:   true,
            TimestampFormat: "2006-01-02 15:04:05",
            DisableColors:   false,
        })

    case "production":
        // 生产环境:JSON格式、Info级别
        logger.SetLevel(logrus.InfoLevel)
        logger.SetFormatter(&logrus.JSONFormatter{
            TimestampFormat: "2006-01-02T15:04:05Z",
        })

    case "test":
        // 测试环境:最简日志
        logger.SetLevel(logrus.WarnLevel)
        logger.SetFormatter(&logrus.TextFormatter{
            DisableTimestamp: true,
        })

    default:
        // 默认配置
        logger.SetLevel(logrus.InfoLevel)
    }
}

结构化字段注入

Go
// 请求上下文字段注入
func ContextFieldsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // 基础字段
        fields := logrus.Fields{
            "request_id": c.GetString("request_id"),
            "client_ip":  c.ClientIP(),
            "user_agent": c.Request.UserAgent(),
        }

        // 用户信息(如果已认证)
        if userID, exists := c.Get("user_id"); exists {
            fields["user_id"] = userID
        }

        // 存储带字段的 logger
        ctxLogger := logger.WithFields(fields)
        c.Set("logger", ctxLogger)

        c.Next()
    }
}

// 在 Handler 中使用
func UserHandler(c *gin.Context) {
    ctxLogger := c.MustGet("logger").(*logrus.Entry)

    ctxLogger.Info("处理用户请求")

    user, err := getUser()
    if err != nil {
        ctxLogger.WithError(err).Error("获取用户失败")
        c.JSON(500, gin.H{"error": "获取用户失败"})
        return
    }

    ctxLogger.WithField("user_name", user.Name).Info("获取用户成功")
    c.JSON(200, user)
}

日志输出控制

控制不同级别输出位置

Go
type LevelHook struct {
    DebugWriter io.Writer
    InfoWriter  io.Writer
    WarnWriter  io.Writer
    ErrorWriter io.Writer
}

func (hook *LevelHook) Fire(entry *logrus.Entry) error {
    switch entry.Level {
    case logrus.DebugLevel:
        if hook.DebugWriter != nil {
            entry.Writer = hook.DebugWriter
        }
    case logrus.InfoLevel:
        if hook.InfoWriter != nil {
            entry.Writer = hook.InfoWriter
        }
    case logrus.WarnLevel:
        if hook.WarnWriter != nil {
            entry.Writer = hook.WarnWriter
        }
    case logrus.ErrorLevel:
        if hook.ErrorWriter != nil {
            entry.Writer = hook.ErrorWriter
        }
    }
    return nil
}

func (hook *LevelHook) Levels() []logrus.Level {
    return logrus.AllLevels
}

func SetupMultiOutputLogger() {
    // Error 日志单独文件
    errorFile, _ := os.OpenFile("logs/error.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)

    // Info 日志单独文件
    infoFile, _ := os.OpenFile("logs/info.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)

    logger.AddHook(&LevelHook{
        DebugWriter: os.Stdout,
        InfoWriter:  io.MultiWriter(os.Stdout, infoFile),
        WarnWriter:  os.Stdout,
        ErrorWriter: io.MultiWriter(os.Stdout, errorFile),
    })
}

日志格式对比

格式适用场景优点缺点
Text开发调试可读性好不易解析
JSON生产环境易解析、结构化可读性差
Custom特定需求灵活定制需额外开发

注意:生产环境推荐 JSON 格式,便于日志系统收集分析。

要点总结

  1. 日志级别:Debug/Info/Warn/Error/Fatal,按场景使用
  2. 格式化:Text(开发)、JSON(生产)、自定义
  3. 动态调整:支持运行时修改日志级别
  4. 环境区分:开发、测试、生产不同配置
  5. 字段注入:请求 ID、用户 ID 等上下文信息
  6. 多输出:不同级别日志输出到不同位置

📝 发现内容有误?点击此处直接编辑

← 上一篇 自定义中间件编写
下一篇 → Gin 日志输出到文件与轮转
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库