日志是排查问题的重要依据,Gin 内置了日志中间件,也支持自定义日志格式。
gin.Default() 默认包含 Logger 中间件:
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/hello", func(c *gin.Context) {
c.String(200, "Hello")
})
r.Run(":8080")
}
输出格式:
[GIN] 2024/01/15 - 10:30:00 | 200 | 1.234ms | 127.0.0.1 | GET "/hello"
使用 gin.LoggerWithFormatter 自定义格式:
func main() {
r := gin.New()
r.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {
return fmt.Sprintf("[%s] %s %s %d %s %s\n",
param.TimeStamp.Format("2006-01-02 15:04:05"),
param.ClientIP,
param.Method,
param.StatusCode,
param.Path,
param.Latency,
)
}))
r.Use(gin.Recovery())
r.GET("/hello", func(c *gin.Context) {
c.String(200, "Hello")
})
r.Run(":8080")
}
将日志写入文件:
func main() {
r := gin.New()
file, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
gin.DefaultWriter = io.MultiWriter(file, os.Stdout)
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.GET("/hello", func(c *gin.Context) {
c.String(200, "Hello")
})
r.Run(":8080")
}
更灵活的日志中间件:
func Logger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
path := c.Request.URL.Path
query := c.Request.URL.RawQuery
c.Next()
end := time.Now()
latency := end.Sub(start)
logEntry := fmt.Sprintf(
"[%s] %s %s %s %d %v %s",
end.Format("2006-01-02 15:04:05"),
c.ClientIP(),
c.Request.Method,
path,
c.Writer.Status(),
latency,
query,
)
if len(c.Errors) > 0 {
logEntry += " errors: " + c.Errors.String()
}
if c.Writer.Status() >= 400 {
log.Printf("[ERROR] %s", logEntry)
} else {
log.Printf("[INFO] %s", logEntry)
}
}
}
r.Use(Logger())
使用 JSON 格式的结构化日志:
type LogEntry struct {
Time string `json:"time"`
Method string `json:"method"`
Path string `json:"path"`
Status int `json:"status"`
Latency string `json:"latency"`
ClientIP string `json:"client_ip"`
UserAgent string `json:"user_agent"`
}
func JSONLogger() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
entry := LogEntry{
Time: time.Now().Format(time.RFC3339),
Method: c.Request.Method,
Path: c.Request.URL.Path,
Status: c.Writer.Status(),
Latency: time.Since(start).String(),
ClientIP: c.ClientIP(),
UserAgent: c.GetHeader("User-Agent"),
}
data, _ := json.Marshal(entry)
log.Println(string(data))
}
}
r.Use(JSONLogger())
集成 zap 日志库:
import "go.uber.org/zap"
func ZapLogger(logger *zap.Logger) 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.GetHeader("User-Agent")),
)
}
}
func main() {
logger, _ := zap.NewProduction()
defer logger.Sync()
r := gin.New()
r.Use(ZapLogger(logger))
r.Use(gin.Recovery())
r.Run(":8080")
}
某些路径不需要记录日志:
func SkipLogger(skipPaths []string) gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
for _, skip := range skipPaths {
if path == skip {
c.Next()
return
}
}
start := time.Now()
c.Next()
log.Printf("[%s] %s %s - %d - %v",
start.Format("2006-01-02 15:04:05"),
c.Request.Method,
path,
c.Writer.Status(),
time.Since(start),
)
}
}
r.Use(SkipLogger([]string{"/health", "/metrics"}))
日志中间件是每个项目必备的。Gin 内置的 Logger 基本够用,但生产环境通常需要自定义格式或集成第三方日志库。记得设置合适的日志级别,避免日志过多影响性能。