Belajar Gin - Error Handling & Logging
Series/Belajar Gin/Episode 11
Episode 11 of 23

Belajar Gin - Error Handling & Logging

Episode ini menyatukan penanganan error dan logging Gin: mencatat error dengan c.Error, membuat custom error type, middleware error handler yang mengembalikan response JSON terpadu, serta integrasi slog untuk structured logging di middleware Gin.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Pendahuluan

Aplikasi yang berjalan mulus tanpa error hanyalah angan-angan. Yang membedakan aplikasi matang adalah cara ia menangani error dan mencatat kejadian. Episode 11 ini membedah error handling terpusat di Gin: menampung error dengan c.Error, mendefinisikan custom error type yang membawa status HTTP, middleware yang mengubahnya menjadi response JSON terpadu, dan structured logging dengan slog dari pustaka standar Go.

Dengan pendekatan ini, handler tidak perlu mengulang pola if err != nil yang menulis JSON serampangan. Satu middleware menjadi penjaga terakhir: menerjemahkan semua error menjadi format konsisten dan mencatatnya ke log untuk debugging.

Menampung Error dengan c.Error

Kenapa Bukan Langsung Menulis Response

Handler sering harus meneruskan error ke lapisan atas. Gin menyediakan c.Error(err) untuk menyimpan error ke dalam context, sementara penulisan response bisa diserahkan ke middleware:

Menyimpan error di context
func getUserHandler(c *gin.Context) {
    user, err := h.svc.GetUser(c.Request.Context(), id)
    if err != nil {
        c.Error(err)
        return
    }
    c.JSON(200, user)
}

c.Error(err) menambahkan error ke slice c.Errors dan menandai response belum ditulis. Handler berhenti, dan middleware yang terdaftar nanti membaca c.Errors untuk menentukan response. Perhatikan bahwa c.Error tidak secara otomatis mengembalikan status code — itu tugas middleware.

Custom Error Type

Error yang Membawa Status HTTP

Agar middleware tahu status code dan pesan yang aman dikirim ke klien, definisikan tipe error sendiri:

Custom error type
type AppError struct {
    Code     string
    Message  string
    HTTPCode int
    Err      error
}
 
func (e *AppError) Error() string {
    if e.Err != nil {
        return e.Err.Error()
    }
    return e.Message
}
 
func NotFound(msg string) *AppError {
    return &AppError{Code: "not_found", Message: msg, HTTPCode: 404}
}

AppError mengimplementasikan interface error lewat method Error(). Field HTTPCode membawa status HTTP, Code membawa kode mesin yang stabil untuk klien, dan Err membungkus error asli untuk logging. Constructor seperti NotFound(msg) mempermudah pembuatan error yang konsisten.

Menggabungkan dengan Error Standar

Error bawaan Go tetap bisa dibungkus dengan fmt.Errorf dan ditelusuri dengan errors.As:

Membungkus dan mengekstrak
if err := repo.FindByID(ctx, id); err != nil {
    return nil, fmt.Errorf("repo: %w", err)
}
Deteksi tipe di middleware
var appErr *AppError
if errors.As(err, &appErr) {
    c.JSON(appErr.HTTPCode, gin.H{
        "code":    appErr.Code,
        "message": appErr.Message,
    })
    return
}
c.JSON(500, gin.H{"code": "internal", "message": "terjadi kesalahan"})

errors.As(err, &appErr) memeriksa apakah error (atau bungkusannya) bertipe *AppError. Jika ya, response memakai kode dan status dari AppError; jika bukan, fallback ke 500 dengan pesan generik agar detail internal tidak bocor ke klien.

Middleware Error Handler dan Response Terpadu

Middleware Lengkap

Gabungkan semua potongan menjadi satu middleware utuh:

Middleware error terpusat
func ErrorHandler() gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Next()
 
        if len(c.Errors) == 0 {
            return
        }
 
        err := c.Errors.Last().Err
        if c.Writer.Written() {
            return
        }
 
        var appErr *AppError
        if errors.As(err, &appErr) {
            c.JSON(appErr.HTTPCode, gin.H{
                "code":    appErr.Code,
                "message": appErr.Message,
            })
            return
        }
        slog.Error("unhandled error", "path", c.Request.URL.Path, "error", err)
        c.JSON(500, gin.H{"code": "internal", "message": "terjadi kesalahan"})
    }
}

Penjaga c.Writer.Written() mencegah middleware menimpa response yang sudah ditulis handler. Error yang tidak dikenal dicatat dengan slog.Error sebelum response 500 generik dikirim — pola ini menjaga log tetap informatif dan response tetap aman.

Structured Logging dengan slog

Membuat Logger JSON

Go 1.21+ menyediakan log/slog untuk structured logging. Buat logger JSON di main:

Logger slog JSON
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    Level: slog.LevelInfo,
}))
slog.SetDefault(logger)
 
slog.Info("server started", "port", cfg.Port)
slog.Error("database unreachable", "addr", cfg.DatabaseURL)

slog.NewJSONHandler(os.Stdout, nil) menghasilkan log dalam format JSON — satu baris per kejadian, mudah dibaca mesin seperti Loki, CloudWatch, atau ELK. Level bisa dinaikkan ke slog.LevelDebug saat development melalui LOG_LEVEL dari episode 10.

Middleware Logging dengan slog

Integrasikan slog ke alur request:

Middleware slog
func slogMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        c.Next()
 
        slog.Info("request",
            "method", c.Request.Method,
            "path", c.Request.URL.Path,
            "status", c.Writer.Status(),
            "duration_ms", time.Since(start).Milliseconds(),
        )
    }
}

c.Writer.Status() diambil setelah handler selesai sehingga status final tercatat. Field duration_ms berguna untuk memantau endpoint lambat. Kombinasikan dengan middleware error handler di atas: satu log untuk request, satu log untuk error yang tidak tertangani.

Penutup

Inti yang harus dibawa pulang:

  • c.Error(err) menampung error di context, bukan menulis response langsung.
  • c.Errors.Last().Err membaca error terakhir di middleware.
  • Custom AppError membawa kode, pesan, dan status HTTP.
  • errors.As membedakan error yang dikenal dari error tak dikenal.
  • Satu middleware error handler menghasilkan response JSON terpadu.
  • slog memberikan structured logging JSON yang siap dipakai production.

Di episode 12 selanjutnya kita akan membedah context, timeout & concurrency — memakai c.Request.Context, c.Copy untuk goroutine, context timeout, rate limiting sederhana, serta sinkronisasi akses data dengan mutex.

Belajar Gin - Error Handling & Logging | Belajar Gin