Logging Terstruktur
Logging terstruktur menghasilkan output log yang dapat dibaca mesin (biasanya JSON) alih-alih teks biasa. Ini membuat log lebih mudah dicari, difilter, dan dianalisis dalam sistem agregasi log seperti ELK Stack, Datadog, atau Grafana Loki.
Mengapa logging terstruktur?
Logger default Gin menghasilkan teks yang mudah dibaca manusia, yang bagus untuk pengembangan tetapi sulit di-parse dalam skala besar. Logging terstruktur memberi Anda:
- Field yang dapat di-query — filter log berdasarkan kode status, latensi, atau ID pengguna
- Format konsisten — setiap entri log memiliki bentuk yang sama
- Korelasi — lacak permintaan di seluruh layanan menggunakan request ID
Menggunakan slog (Go 1.21+)
Pustaka standar Go menyertakan log/slog untuk logging terstruktur. Berikut cara menggunakannya dengan Gin:
package main
import ( "log/slog" "os" "time"
"github.com/gin-gonic/gin")
func SlogMiddleware(logger *slog.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery
c.Next()
logger.Info("request", slog.String("method", c.Request.Method), slog.String("path", path), slog.String("query", query), slog.Int("status", c.Writer.Status()), slog.Duration("latency", time.Since(start)), slog.String("client_ip", c.ClientIP()), slog.Int("body_size", c.Writer.Size()), )
if len(c.Errors) > 0 { for _, err := range c.Errors { logger.Error("request error", slog.String("error", err.Error())) } } }}
func main() { logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
r := gin.New() r.Use(SlogMiddleware(logger)) r.Use(gin.Recovery())
r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{"message": "pong"}) })
r.Run(":8080")}Ini menghasilkan entri log seperti:
{"time":"2024-01-15T10:30:00Z","level":"INFO","msg":"request","method":"GET","path":"/ping","query":"","status":200,"latency":"125µs","client_ip":"127.0.0.1","body_size":18}Request ID / Correlation ID
Menambahkan request ID unik ke setiap entri log membantu melacak permintaan di seluruh layanan:
package main
import ( "log/slog" "os" "time"
"github.com/gin-gonic/gin" "github.com/google/uuid")
func RequestIDMiddleware() gin.HandlerFunc { return func(c *gin.Context) { requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = uuid.New().String() } c.Set("request_id", requestID) c.Header("X-Request-ID", requestID) c.Next() }}
func SlogMiddleware(logger *slog.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now()
c.Next()
requestID, _ := c.Get("request_id") logger.Info("request", slog.String("request_id", requestID.(string)), slog.String("method", c.Request.Method), slog.String("path", c.Request.URL.Path), slog.Int("status", c.Writer.Status()), slog.Duration("latency", time.Since(start)), ) }}
func main() { logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
r := gin.New() r.Use(RequestIDMiddleware()) r.Use(SlogMiddleware(logger)) r.Use(gin.Recovery())
r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{"message": "pong"}) })
r.Run(":8080")}Menggunakan zerolog
zerolog adalah logger JSON populer tanpa alokasi:
package main
import ( "os" "time"
"github.com/gin-gonic/gin" "github.com/rs/zerolog" "github.com/rs/zerolog/log")
func ZerologMiddleware() gin.HandlerFunc { return func(c *gin.Context) { start := time.Now()
c.Next()
log.Info(). Str("method", c.Request.Method). Str("path", c.Request.URL.Path). Int("status", c.Writer.Status()). Dur("latency", time.Since(start)). Str("client_ip", c.ClientIP()). Msg("request") }}
func main() { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
// Pretty logging for development if gin.Mode() == gin.DebugMode { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) }
r := gin.New() r.Use(ZerologMiddleware()) r.Use(gin.Recovery())
r.GET("/ping", func(c *gin.Context) { c.JSON(200, gin.H{"message": "pong"}) })
r.Run(":8080")}Praktik terbaik
- Gunakan format JSON di produksi — format yang mudah dibaca manusia untuk pengembangan, JSON untuk agregasi log produksi
- Sertakan request ID — propagasi header
X-Request-IDdi seluruh batas layanan untuk distributed tracing - Log pada level yang tepat — gunakan
Infountuk permintaan normal,Warnuntuk 4xx,Erroruntuk 5xx - Hindari logging data sensitif — kecualikan password, token, dan PII dari output log
- Atur
GIN_MODE=release— ini menonaktifkan logging debug default Gin di produksi