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

Belajar Echo - Error Handling & Logging

Episode ini merapikan sisi respons gagal: HTTPError dan custom error handler, format RFC 9457 Problem Details untuk API error yang konsisten, logging terstruktur dengan slog melalui RequestLogger, serta integrasi log dengan aggregator seperti Loki dan ELK.

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

Pendahuluan

API yang sehat tidak hanya menangani keberhasilan — dia merancang kegagalan. Konsumen API perlu tahu apa yang salah, mengapa, dan bagaimana memperbaikinya, dengan format yang konsisten. Di sisi lain, developer butuh log yang bisa dicari dan dianalisis mesin.

Episode 11 ini merapikan kedua sisi tersebut: HTTPError dan custom error handler, format RFC 9457 Problem Details untuk API error, logging terstruktur dengan slog via RequestLogger, dan integrasi dengan log aggregator.

Memahami HTTPError

Error Terpusat dengan Status Code

Echo mewakili error sebagai echo.HTTPError — sebuah error yang membawa status code, pesan, dan payload tambahan:

Membuat HTTPError
if user == nil {
	return echo.NewHTTPError(http.StatusNotFound, "user tidak ditemukan")
}

Saat handler mengembalikan echo.HTTPError, Echo otomatis menulis status code dan pesan ke response. Handler tidak perlu tahu detail HTTP — cukup kembalikan error.

Mengisi Detail Tambahan

HTTPError punya field Details untuk data kontekstual, dan Message bisa diisi struktur kompleks:

HTTPError dengan detail
err := echo.NewHTTPError(http.StatusBadRequest)
err.Message = "validasi gagal"
err.Details = []string{"email harus format valid", "nama minimal 3 karakter"}
return err

Field Details diserialisasi ke response, memberi klien informasi yang bisa dipakai untuk memperbaiki request.

Custom Error Handler

e.HTTPErrorHandler

Semua error — dari handler, binding, validator, maupun router — akhirnya melewati satu titik: e.HTTPErrorHandler. Ganti dengan implementasi sendiri untuk kontrol penuh:

Custom error handler
e.HTTPErrorHandler = func(err error, c echo.Context) {
	he, ok := err.(*echo.HTTPError)
	if !ok {
		he = echo.NewHTTPError(http.StatusInternalServerError, "internal server error")
	}
	if err := c.JSON(he.Code, he.Message); err != nil {
		e.Logger.Error("gagal menulis respons error", "err", err)
	}
}

Error non-HTTP dibungkus menjadi 500 agar informasi internal tidak bocor ke klien. Logging detail error asli dilakukan terpisah, tidak dikirim ke response.

RFC 9457 Problem Details

Format Standar API Error

RFC 9457 mendefinisikan format konsisten untuk error HTTP: satu objek JSON dengan member standar seperti type, title, status, detail, dan instance:

Respons Problem Details
{
  "type": "https://example.com/problems/validation",
  "title": "Validasi gagal",
  "status": 400,
  "detail": "Satu atau lebih field tidak valid",
  "instance": "/api/v1/users",
  "errors": ["email harus format valid"]
}

Implementasikan dalam custom error handler dengan memetakan HTTPError ke struktur Problem Details:

Peta HTTPError ke Problem Details
type Problem struct {
	Type     string `json:"type"`
	Title    string `json:"title"`
	Status   int    `json:"status"`
	Detail   string `json:"detail"`
	Instance string `json:"instance"`
}
 
e.HTTPErrorHandler = func(err error, c echo.Context) {
	he, ok := err.(*echo.HTTPError)
	if !ok {
		he = echo.NewHTTPError(http.StatusInternalServerError)
	}
	problem := Problem{
		Type:     "https://example.com/problems/" + http.StatusText(he.Code),
		Title:    http.StatusText(he.Code),
		Status:   he.Code,
		Detail:   fmt.Sprint(he.Message),
		Instance: c.Path(),
	}
	if err := c.JSON(he.Code, problem); err != nil {
		e.Logger.Error("gagal menulis problem", "err", err)
	}
}

Dengan format ini, seluruh error API kalian memiliki skema yang sama — mudah dipahami klien dan mudah dipetakan ke dokumentasi.

Logging Terstruktur dengan slog

Konfigurasi RequestLogger

Di episode 3 kalian sudah memakai RequestLogger. Di production, atur level dan handler output slog sesuai environment:

Mengatur slog untuk produksi
level := slog.LevelInfo
if cfg.Environment == "development" {
	level = slog.LevelDebug
}
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
	Level: level,
})))

NewJSONHandler menghasilkan log JSON yang mudah di-parse mesin. Di development, LevelDebug memberi visibilitas lebih.

Format Log dan Konteks Request

Manfaatkan slog dalam handler untuk mencatat konteks spesifik domain:

Log konteks dalam handler
slog.Info("user dibuat",
	"user_id", user.ID,
	"by", c.Get("user_id"),
	"trace_id", c.Response().Header().Get(echo.HeaderXRequestID),
)

Biasakan menyertakan request_id di setiap log agar satu request bisa dilacak dari ujung ke ujung — fondasi observability di episode 18.

Integrasi dengan Log Aggregator

Mengalirkan Log ke Aggregator

Log JSON di stdout siap diarahkan ke aggregator seperti Loki atau ELK tanpa kode tambahan:

Docker Compose: penerusan log ke Loki
services:
  api:
    image: belajar-echo:latest
    logging:
      driver: json-file
  promtail:
    image: grafana/promtail:latest
    volumes:
      - /var/log:/var/log
    command: -config.file=/etc/promtail/config.yaml

Karena log sudah terstruktur, aggregator bisa langsung membuat label dari field seperti status dan latency tanpa parsing manual.

Penutup

Episode 11 merapikan respons gagal dan jejak digital aplikasi: HTTPError menjadi bahasa error terpusat, custom error handler mengontrol respons final, RFC 9457 Problem Details memberi skema error konsisten, slog menghasilkan log terstruktur, dan aggregator menerima log tersebut tanpa perubahan kode.

Inti yang harus dibawa pulang:

  • Handler mengembalikan error; HTTPError membawa status code dan pesan.
  • e.HTTPErrorHandler adalah satu titik kontrol seluruh error.
  • RFC 9457 memberi skema standar untuk API error.
  • Jangan bocorkan detail internal; bungkus error tak dikenal menjadi 500.
  • slog dengan JSON handler menghasilkan log siap mesin.
  • Sertakan request_id di setiap log untuk pelacakan.
  • Log JSON langsung bisa diarahkan ke Loki atau ELK.

Di episode 12 selanjutnya kita akan membahas context, timeout & concurrencyc.Request().Context() untuk nilai dan pembatalan, goroutine di dalam handler, context timeout, sinkronisasi state dengan mutex, dan pola aman saat berbagi data antar request.

Belajar Echo - Error Handling & Logging | Belajar Echo