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

Belajar Chi - Error Handling & Logging

Episode ini membangun sistem error handling yang terpusat: custom error type dengan status code, handler yang mengembalikan error ke JSON, dan middleware recovery untuk menangkap panic. Kalian juga mengadopsi slog untuk logging terstruktur dan membuat request logging middleware.

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

Pendahuluan

Handler yang meng-copy-paste blok http.Error dengan pesan berbeda-beda menghasilkan API yang tidak konsisten dan log yang tidak berguna. Episode 11 membangun sistem yang terpusat: semua error melewati jalur yang sama, status code konsisten, dan setiap request tercatat dalam log terstruktur.

Go menawarkan log/slog sejak versi 1.21 — logger terstruktur bawaan yang tidak butuh dependency tambahan. Kombinasi error type custom, adapter handler, dan slog akan membuat debugging production terasa jauh lebih ringan.

Custom Error Type

Error dengan Status Code

Buat tipe error yang membawa informasi HTTP:

Custom error type
type APIError struct {
    Code    int
    Message string
    Err     error
}
 
func (e *APIError) Error() string {
    return e.Message
}
 
func NewAPIError(code int, msg string) *APIError {
    return &APIError{Code: code, Message: msg}
}

NewAPIError(code, msg) menciptakan error yang tahu status code-nya. Handler cukup mengembalikan error; middleware atau adapter yang menerjemahkannya ke respons.

Membungkus Error Sumber

Wrap error dengan %w
func NewAPIErrorf(code int, msg string, err error) *APIError {
    return &APIError{Code: code, Message: msg, Err: err}
}

errors.Is dan errors.As tetap berfungsi saat error dibungkus dengan %w karena APIError mengimplementasikan Unwrap. Ini menjaga rantai error tetap bisa dilacak.

Handler yang Mengembalikan Error

Adapter Handler

Daripada mengubah kontrak http.Handler, bungkus handler yang mengembalikan error:

Adapter handler error
func Handle(fn func(w http.ResponseWriter, req *http.Request) error) http.HandlerFunc {
    return func(w http.ResponseWriter, req *http.Request) {
        if err := fn(w, req); err != nil {
            var apiErr *APIError
            if errors.As(err, &apiErr) {
                writeJSON(w, apiErr.Code,
                    map[string]string{"error": apiErr.Message})
                return
            }
            slog.Error("unexpected error", "error", err)
            writeJSON(w, http.StatusInternalServerError,
                map[string]string{"error": "internal server error"})
        }
    }
}

errors.As(err, &apiErr) memeriksa apakah error adalah APIError. Bila bukan, log sebagai error tak terduga dan kembalikan 500 — kesalahan detail tidak pernah bocor ke klien.

Handler Bersih

Dengan adapter, handler tetap bersih:

Handler mengembalikan error
r.Get("/users/{id}", Handle(func(w http.ResponseWriter, req *http.Request) error {
    id, _ := strconv.Atoi(chi.URLParam(req, "id"))
    user, err := repo.FindByID(req.Context(), id)
    if err != nil {
        return NewAPIError(http.StatusNotFound, "user tidak ditemukan")
    }
    writeJSON(w, http.StatusOK, user)
    return nil
}))

Handle(...) membungkus handler sehingga logika error terpusat di satu tempat, bukan tersebar di setiap handler.

Middleware Recovery

Menangkap Panic

middleware.Recoverer mencegah server mati karena panic:

Pasang Recoverer
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.Recoverer)
r.Use(requestLogger)

r.Use(middleware.Recoverer) menangkap panic di handler, mencetak stack trace ke log, dan mengembalikan status 500 ke klien. Server tetap hidup melayani request berikutnya.

Recoverer Custom

Untuk mengembalikan JSON pada panic:

Recoverer JSON custom
func jsonRecoverer(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
        defer func() {
            if r := recover(); r != nil {
                slog.Error("panic tertangkap",
                    "recovered", r, "path", req.URL.Path)
                writeJSON(w, http.StatusInternalServerError,
                    map[string]string{"error": "internal server error"})
            }
        }()
        next.ServeHTTP(w, req)
    })
}

recover() menangkap nilai panic sebelum menyebar, lalu middleware menulis respons JSON yang konsisten.

Logging dengan slog

Setup Logger Terstruktur

log/slog menghasilkan JSON yang siap diparsing:

Setup slog
logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
slog.SetDefault(logger)

slog.NewJSONHandler(os.Stdout, nil) mengeluarkan log berformat JSON — bisa dibaca manusia maupun mesin. slog.SetDefault membuat seluruh package memakai logger yang sama.

Request Logging Middleware

Buat middleware yang mencatat setiap request:

Request logging middleware
func requestLogger(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, req)
        slog.Info("request selesai",
            "method", req.Method,
            "path", req.URL.Path,
            "durasi", time.Since(start))
    })
}

slog.Info("request selesai", "method", req.Method, "path", req.URL.Path) mencatat atribut terstruktur — bukan string yang di-sprintf. Ini yang membuat log bisa difilter dan di-agregasi.

Level Logging yang Tepat

  • slog.Debug untuk detail yang hanya dibutuhkan saat debugging.
  • slog.Info untuk alur normal seperti request selesai.
  • slog.Warn untuk kondisi mencurigakan yang belum fatal.
  • slog.Error untuk kegagalan yang perlu diperhatikan manusia.

Penutup

Inti yang harus dibawa pulang:

  • APIError membawa status code; errors.As mendeteksinya.
  • Adapter Handle memusatkan terjemahan error ke JSON.
  • Error tak dikenal dikembalikan sebagai 500 tanpa membocorkan detail.
  • middleware.Recoverer mencegah server mati karena panic.
  • slog bawaan Go menulis log JSON terstruktur.
  • Middleware logging mencatat method, path, dan durasi tiap request.

Di episode 12 selanjutnya kita mengeksplorasi request lifecycle: context, timeout, dan concurrency — nilai request-scoped, pembatalan request, middleware.Timeout, goroutine di handler, serta middleware.Throttle dan sinkronisasi state bersama.

Belajar Chi - Error Handling & Logging | Belajar Chi