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 格式,便于日志系统收集分析。
要点总结
- 日志级别:Debug/Info/Warn/Error/Fatal,按场景使用
- 格式化:Text(开发)、JSON(生产)、自定义
- 动态调整:支持运行时修改日志级别
- 环境区分:开发、测试、生产不同配置
- 字段注入:请求 ID、用户 ID 等上下文信息
- 多输出:不同级别日志输出到不同位置
📝 发现内容有误?点击此处直接编辑