Episode ini membuat aplikasi Gin bisa diamati dari luar: metrik Prometheus dengan prometheus/client_golang, tracing OpenTelemetry lewat otelgin, structured logs dengan slog, serta endpoint health dan readiness untuk integrasi dengan orchestrator seperti Kubernetes.

Aplikasi yang tidak bisa diamati adalah teka-teki ketika terjadi insiden. Episode 18 ini membedah observability & monitoring untuk aplikasi Gin: mengekspos metrik Prometheus dengan prometheus/client_golang, melacak request lintas layanan dengan OpenTelemetry, menyusun structured logs dengan slog, dan menyediakan endpoint health check untuk orchestrator seperti Kubernetes.
Observability punya tiga pilar: metrik untuk bertanya "berapa banyak dan seberapa lambat", trace untuk bertanya "melalui jalur mana", dan log untuk bertanya "detailnya apa". Ketiganya saling melengkapi. Tanpa ketiganya, kalian hanya bisa menebak ketika production bermasalah.
Episode ini melengkapi fondasi dari episode 11 (logging) dan episode 12 (context). Endpoint health yang kalian buat di sini akan dipakai lagi saat membangun arsitektur production di episode 21.
Prometheus menarik metrik dari endpoint HTTP yang mengekspos format text exposition. Pasang dulu client resminya:
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promhttpSetelah terpasang, daftarkan handler promhttp di route Gin:
r.GET("/metrics", gin.WrapH(promhttp.Handler()))promhttp.Handler() menyajikan metrik default — penggunaan memori, goroutine, dan pengumpul sampah runtime Go. gin.WrapH mengadaptasi handler standar http.Handler agar bisa dipasang sebagai handler Gin. Prometheus server lalu menarik endpoint ini secara berkala sesuai interval scrape-nya.
Metrik default hanya menceritakan kondisi runtime. Untuk memantau perilaku aplikasi, daftarkan metrik custom — Counter untuk jumlah request dan Histogram untuk latensi:
var (
httpRequestsTotal = promauto.NewCounterVec(prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total request HTTP yang masuk.",
}, []string{"method", "path", "status"})
httpRequestDuration = promauto.NewHistogramVec(prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Durasi pemrosesan request HTTP.",
Buckets: prometheus.DefBuckets,
}, []string{"method", "path"})
)promauto.NewCounterVec dan promauto.NewHistogramVec mendaftarkan vektor metrik ke registry global Prometheus. Label seperti method, path, dan status membuat metrik bisa dipecah per dimensi ketika di-query di Grafana.
Jangan mencatat metrik satu per satu di handler. Pasang satu middleware yang mencatat semua request:
func metricsMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
start := time.Now()
c.Next()
status := strconv.Itoa(c.Writer.Status())
method := c.Request.Method
path := c.FullPath()
httpRequestsTotal.WithLabelValues(method, path, status).Inc()
httpRequestDuration.WithLabelValues(method, path).Observe(time.Since(start).Seconds())
}
}c.FullPath() mengembalikan pola route seperti /users/:id, bukan nilai parameter yang sebenarnya. Ini penting: label dengan nilai parameter acak akan meledakkan kardinalitas dan membuat Prometheus lambat. Catat Inc() dan Observe() setelah c.Next() agar status final tercatat.
Metrik menjawab "di mana lambat", trace menjawab "kenapa". OpenTelemetry membuat span untuk setiap request dan meneruskan context trace ke seluruh layanan. Gunakan middleware resmi untuk Gin:
go get go.opentelemetry.io/otel/sdk/trace
go get go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgintp := initTracerProvider()
otel.SetTracerProvider(tp)
r := gin.New()
r.Use(otelgin.Middleware("belajar-gin"))otelgin.Middleware("belajar-gin") membuat span untuk setiap request dan memberi nama layanan belajar-gin pada backend tracing seperti Jaeger atau Tempo. Karena context trace diteruskan lewat c.Request.Context(), panggilan database dan HTTP di dalam handler otomatis terhubung dalam satu trace.
Log menambah detail yang tidak bisa diberikan metrik maupun trace. Korelasikan log dengan trace lewat atribut bersama:
slog.Info("user created",
"user_id", userID,
"request_id", requestID,
"duration_ms", duration,
)slog.Info("user created", "user_id", userID, ...) menulis log terstruktur yang bisa difilter query. Di production, arahkan log JSON ke agregator seperti Loki atau CloudWatch, bukan stdout mentah. Dengan atribut request_id yang sama di log dan trace, insiden bisa dilacak dari metrik sampai baris log.
Kubernetes membedakan dua probe: liveness menandakan proses hidup, readiness menandakan siap menerima traffic. Buat keduanya sebagai endpoint terpisah:
r.GET("/healthz", func(c *gin.Context) {
c.JSON(200, gin.H{"status": "ok"})
})
r.GET("/readyz", func(c *gin.Context) {
if err := db.Ping(c.Request.Context()); err != nil {
c.JSON(503, gin.H{"status": "not ready"})
return
}
c.JSON(200, gin.H{"status": "ready"})
})/healthz selalu mengembalikan 200 selama proses berjalan. /readyz memeriksa dependensi seperti database; jika gagal, mengembalikan 503 dan Kubernetes menghentikan pengiriman traffic tanpa mematikan pod. Jangan menaruh health check di belakang autentikasi — probe tidak membawa kredensial.
Inti yang harus dibawa pulang:
promhttp.Handler() di /metrics mengekspos metrik Prometheus.c.FullPath() menjaga kardinalitas label tetap rendah.otelgin.Middleware memberi span otomatis untuk setiap request.slog dengan atribut konteks membuat log mudah dikorelasikan./healthz dan /readyz untuk liveness dan readiness di Kubernetes.Di episode 19 selanjutnya kita akan membedah performance & troubleshooting — gin.SetMode ReleaseMode, mengurangi alokasi, connection pooling dan caching Redis, menangani panic, serta troubleshooting umum seperti deadlock context dan routing conflict.