日志系统 #
一、日志概述 #
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