日志系统 #

一、日志概述 #

1.1 日志级别 #

级别 说明 使用场景
DEBUG 调试信息 开发调试
INFO 一般信息 正常流程
WARN 警告信息 潜在问题
ERROR 错误信息 错误处理
FATAL 致命错误 程序终止

1.2 日志内容 #

text
时间戳 | 日志级别 | 请求信息 | 响应信息 | 错误信息

二、Gin默认日志 #

2.1 默认日志中间件 #

go
func main() {
    // gin.Default() 包含Logger中间件
    r := gin.Default()
    
    r.GET("/", func(c *gin.Context) {
        c.String(200, "Hello")
    })
    
    r.Run()
}

2.2 日志输出示例 #

text
[GIN] 2024/01/01 - 10:00:00 | 200 |      12.345µs | 127.0.0.1 | GET      "/"

2.3 自定义日志格式 #

go
func main() {
    r := gin.New()
    
    r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
        return fmt.Sprintf("[GIN] %s | %s | %d | %v | %s | %s %s\n",
            param.TimeStamp.Format("2006/01/02 - 15:04:05"),
            param.ClientIP,
            param.StatusCode,
            param.Latency,
            param.Method,
            param.Path,
            param.ErrorMessage,
        )
    }))
    
    r.Run()
}

三、日志文件输出 #

3.1 输出到文件 #

go
func main() {
    r := gin.New()
    
    // 创建日志文件
    file, err := os.Create("gin.log")
    if err != nil {
        panic(err)
    }
    
    // 同时输出到文件和控制台
    gin.DefaultWriter = io.MultiWriter(file, os.Stdout)
    
    r.Use(gin.Logger())
    r.Run()
}

3.2 按日期分割日志 #

go
func setupLogOutput() *os.File {
    logDir := "./logs"
    if err := os.MkdirAll(logDir, 0755); err != nil {
        panic(err)
    }
    
    // 按日期创建日志文件
    logFile := fmt.Sprintf("%s/gin_%s.log", logDir, time.Now().Format("2006-01-02"))
    file, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
    if err != nil {
        panic(err)
    }
    
    return file
}

func main() {
    r := gin.New()
    
    file := setupLogOutput()
    gin.DefaultWriter = io.MultiWriter(file, os.Stdout)
    
    r.Use(gin.Logger())
    r.Run()
}

3.3 使用lumberjack #

bash
go get gopkg.in/natefinch/lumberjack.v2
go
import "gopkg.in/natefinch/lumberjack.v2"

func setupLogger() {
    logger := &lumberjack.Logger{
        Filename:   "./logs/gin.log",
        MaxSize:    100, // MB
        MaxBackups: 3,
        MaxAge:     28,   // days
        Compress:   true,
    }
    
    gin.DefaultWriter = io.MultiWriter(logger, os.Stdout)
}

func main() {
    setupLogger()
    
    r := gin.Default()
    r.Run()
}

四、自定义日志中间件 #

4.1 请求日志中间件 #

go
func LoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path
        method := c.Request.Method
        clientIP := c.ClientIP()
        
        // 处理请求
        c.Next()
        
        // 计算耗时
        latency := time.Since(start)
        statusCode := c.Writer.Status()
        
        // 记录日志
        log.Printf("[REQUEST] %s | %s | %s | %d | %v | %s",
            time.Now().Format("2006-01-02 15:04:05"),
            method,
            path,
            statusCode,
            latency,
            clientIP,
        )
    }
}

func main() {
    r := gin.New()
    r.Use(LoggerMiddleware())
    r.Run()
}

4.2 详细日志中间件 #

go
func DetailedLoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        
        // 请求信息
        logData := map[string]interface{}{
            "time":       start.Format(time.RFC3339),
            "method":     c.Request.Method,
            "path":       c.Request.URL.Path,
            "query":      c.Request.URL.RawQuery,
            "client_ip":  c.ClientIP(),
            "user_agent": c.Request.UserAgent(),
            "headers":    c.Request.Header,
        }
        
        c.Next()
        
        // 响应信息
        logData["status"] = c.Writer.Status()
        logData["latency"] = time.Since(start).String()
        logData["size"] = c.Writer.Size()
        
        if len(c.Errors) > 0 {
            logData["errors"] = c.Errors.String()
        }
        
        // 输出JSON格式日志
        jsonData, _ := json.Marshal(logData)
        log.Println(string(jsonData))
    }
}

4.3 错误日志中间件 #

go
func ErrorLoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
        
        if len(c.Errors) > 0 {
            for _, err := range c.Errors {
                log.Printf("[ERROR] %s | %s | %s | %v",
                    time.Now().Format("2006-01-02 15:04:05"),
                    c.Request.Method,
                    c.Request.URL.Path,
                    err,
                )
            }
        }
    }
}

五、使用zap日志库 #

5.1 安装zap #

bash
go get go.uber.org/zap

5.2 配置zap #

go
import "go.uber.org/zap"

var logger *zap.Logger

func InitLogger() {
    var err error
    logger, err = zap.NewProduction()
    if err != nil {
        panic(err)
    }
}

func ZapLoggerMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        path := c.Request.URL.Path
        query := c.Request.URL.RawQuery
        
        c.Next()
        
        latency := time.Since(start)
        
        logger.Info("request",
            zap.String("method", c.Request.Method),
            zap.String("path", path),
            zap.String("query", query),
            zap.Int("status", c.Writer.Status()),
            zap.Duration("latency", latency),
            zap.String("client_ip", c.ClientIP()),
            zap.String("user_agent", c.Request.UserAgent()),
            zap.String("errors", c.Errors.String()),
        )
    }
}

func main() {
    InitLogger()
    defer logger.Sync()
    
    r := gin.New()
    r.Use(ZapLoggerMiddleware())
    r.Run()
}

5.3 zap配置选项 #

go
func InitLogger() {
    config := zap.Config{
        Level:       zap.NewAtomicLevelAt(zap.InfoLevel),
        Development: false,
        Encoding:    "json",
        EncoderConfig: zapcore.EncoderConfig{
            TimeKey:        "time",
            LevelKey:       "level",
            NameKey:        "logger",
            CallerKey:      "caller",
            MessageKey:     "msg",
            StacktraceKey:  "stacktrace",
            LineEnding:     zapcore.DefaultLineEnding,
            EncodeLevel:    zapcore.LowercaseLevelEncoder,
            EncodeTime:     zapcore.ISO8601TimeEncoder,
            EncodeDuration: zapcore.SecondsDurationEncoder,
            EncodeCaller:   zapcore.ShortCallerEncoder,
        },
        OutputPaths:      []string{"stdout", "./logs/app.log"},
        ErrorOutputPaths: []string{"stderr"},
    }
    
    var err error
    logger, err = config.Build()
    if err != nil {
        panic(err)
    }
}

六、使用logrus #

6.1 安装logrus #

bash
go get github.com/sirupsen/logrus

6.2 配置logrus #

go
import (
    "github.com/gin-gonic/gin"
    "github.com/sirupsen/logrus"
)

var log = logrus.New()

func InitLogger() {
    log.SetFormatter(&logrus.JSONFormatter{})
    log.SetOutput(os.Stdout)
    log.SetLevel(logrus.InfoLevel)
}

func LogrusMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        
        c.Next()
        
        log.WithFields(logrus.Fields{
            "method":     c.Request.Method,
            "path":       c.Request.URL.Path,
            "status":     c.Writer.Status(),
            "latency":    time.Since(start).String(),
            "client_ip":  c.ClientIP(),
            "user_agent": c.Request.UserAgent(),
        }).Info("request")
    }
}

func main() {
    InitLogger()
    
    r := gin.New()
    r.Use(LogrusMiddleware())
    r.Run()
}

七、日志最佳实践 #

7.1 结构化日志 #

go
log.WithFields(logrus.Fields{
    "user_id":    userId,
    "action":     "login",
    "ip":         c.ClientIP(),
    "user_agent": c.Request.UserAgent(),
}).Info("用户登录")

7.2 日志级别使用 #

go
// DEBUG: 调试信息
log.Debug("Processing request", "params", params)

// INFO: 正常流程
log.Info("User logged in", "user_id", userId)

// WARN: 警告信息
log.Warn("Rate limit approaching", "requests", count)

// ERROR: 错误信息
log.Error("Database error", "error", err)

// FATAL: 致命错误
log.Fatal("Failed to start server", "error", err)

7.3 敏感信息处理 #

go
func maskSensitive(data map[string]interface{}) map[string]interface{} {
    sensitiveFields := []string{"password", "token", "secret"}
    
    for _, field := range sensitiveFields {
        if _, exists := data[field]; exists {
            data[field] = "******"
        }
    }
    
    return data
}

八、总结 #

8.1 核心要点 #

要点 说明
日志格式 统一的日志格式
日志级别 合理使用日志级别
日志输出 文件和控制台输出
结构化日志 使用JSON格式

8.2 最佳实践 #

实践 说明
日志分级 使用合适的日志级别
结构化 使用结构化日志
敏感信息 不记录敏感信息
日志轮转 使用日志轮转工具

8.3 下一步 #

现在你已经掌握了日志系统,接下来让我们学习 优雅关闭,了解服务器的优雅关闭!

最后更新:2026-03-28