Observability: Mata untuk Production Systems
Sebagai developer, kita sering fokus membangun fitur. Tapi di production, pertanyaan yang lebih penting adalah: 'Apa yang terjadi dengan sistem saya?' Observability memberikan jawaban melalui tiga pilar: logging, metrics, dan tracing. Dari pengalaman saya handle production systems di Bandung, ini adalah aspek yang paling sering di-skip tapi paling kritis saat ada masalah.
Structured Logging dengan Zerolog
Logging yang baik harus structured (bukan text biasa) dan contextual. Saya menggunakan zerolog karena performanya yang excellent:
import "github.com/rs/zerolog"
// Setup logger
func SetupLogger(level string) zerolog.Logger {
l, _ := zerolog.ParseLevel(level)
return zerolog.New(
zerolog.ConsoleWriter{Out: os.Stderr},
).Level(l).With().
Timestamp().
Str("service", "myapp").
Str("version", os.Getenv("APP_VERSION")).
Logger()
}
// Usage dalam code
func handleRequest(ctx context.Context, req Request) error {
logger := zerolog.Ctx(ctx)
logger.Info().
Str("user_id", req.UserID).
Str("action", req.Action).
Int("payload_size", len(req.Body)).
Msg("processing request")
// ... process request
logger.Info().
Str("user_id", req.UserID).
Dur("duration", time.Since(start)).
Int("status", 200).
Msg("request completed")
return nil
}Setiap log entry harus punya context yang cukup untuk debugging: user ID, request ID, duration, dan status code. Ini memudahkan kita mencari logs spesifik di Elasticsearch atau Loki.
Prometheus Metrics
Metrics memberikan gambaran quantitative tentang kesehatan sistem. Berikut pattern metrics yang saya gunakan di semua service:
import "github.com/prometheus/client_golang/prometheus"
var (
requestDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "Duration of HTTP requests",
Buckets: []float64{0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5},
},
[]string{"method", "path", "status"},
)
requestTotal = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status"},
)
activeConnections = prometheus.NewGauge(
prometheus.GaugeOpts{
Name: "active_connections",
Help: "Number of active connections",
},
)
)
// Middleware untuk auto-record metrics
func MetricsMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
start := time.Now()
err := next(c)
duration := time.Since(start).Seconds()
status := strconv.Itoa(c.Response().Status)
requestDuration.WithLabelValues(c.Request().Method, c.Path(), status).Observe(duration)
requestTotal.WithLabelValues(c.Request().Method, c.Path(), status).Inc()
return err
}
}Distributed Tracing dengan OpenTelemetry
Di microservices, tracing memungkinkan kita melihat bagaimana request mengalir dari satu service ke service lain:
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/trace"
)
func InitTracing(serviceName string) (*trace.TracerProvider, error) {
exporter, err := otlptracehttp.New(
context.Background(),
otlptracehttp.WithEndpoint("localhost:4318"),
otlptracehttp.WithInsecure(),
)
if err != nil {
return nil, err
}
tp := trace.NewTracerProvider(
trace.WithBatcher(exporter),
trace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String(serviceName),
)),
)
otel.SetTracerProvider(tp)
return tp, nil
}Alerting yang Meaningful
Metrics tanpa alerting tidak ada artinya. Saya menyarankan untuk alert hanya pada hal yang benar-benar actionable: error rate > 1%, latency p99 > 2s, atau disk usage > 80%. Jangan alert untuk setiap anomali - alert fatigue adalah musuh dari good monitoring.
Kesimpulan
Observability bukan optional untuk production systems. Investasi di logging, monitoring, dan tracing akan terbayar berkali-kali saat ada incident di production. Dari pengalaman saya, 80% debugging time bisa dipotong dengan observability yang baik.