79. gRPC Logging Terstruktur dengan Zap
Logging merupakan aspek esensial dalam membangun microservices berbasis gRPC. Tanpa logging terstruktur, proses troubleshooting, monitoring, hingga alerting akan menjadi hal yang sangat menantang. Dalam praktik modern, engineer profesional beralih dari plain logging (misalnya dengan fmt.Println) menuju logging terstruktur menggunakan libraries seperti Uber Zap: sebuah high-performance, structured, leveled logging library untuk Golang.
Pada artikel ini saya akan membahas praktik terbaik implementasi structured logging dengan Zap pada layanan gRPC, dilengkapi contoh kode, diagram, hingga simulasi output log yang relevan untuk kebutuhan observabilitas produksi.
Mengapa Structured Logging?
Sebelum masuk ke implementasi, mari kita pahami kenapa structured logging itu krusial:
- Pencarian Lebih Mudah: Data log dalam format JSON atau key-value mudah diproses oleh tools seperti ElasticSearch, Grafana Loki, atau Datadog.
- Konteks Lebih Kaya: Kita bisa menambahkan metadata kontekstual seperti traceID, userID, status code; sangat membantu untuk tracing dan audit trail.
- Query Log yang Powerful: Karakterisme structured log membuka jalan untuk filter-dan-aggregate berdasarkan fields tertentu tanpa harus parsing secara manual.
Konteks gRPC & Logging
Layanan gRPC, berbeda dengan HTTP konvensional, menangani request dalam bentuk protokol biner dan biasanya memanfaatkan context propagation secara lebih intensif. Maka error handling dan logging context sangat krusial.
Sayangnya, log yang tidak terstruktur tidak dapat menangkap detail penting seperti latency, request metadata, maupun status secara konsisten.
Memulai Zap pada Layanan gRPC
Mari kita beralih ke contoh implementasi. Kita akan membangun interceptor gRPC yang menggabungkan Zap Logger secara terstruktur ke setiap lifecycle request. Interceptor di gRPC mirip dengan middleware pada HTTP, mereka mengeksekusi fungsi sebelum dan sesudah request handler dijalankan.
Instalasi
go get go.uber.org/zap
go get google.golang.org/grpc
Setup Zap Logger
package main
import (
"go.uber.org/zap"
)
func NewZapLogger() *zap.Logger {
logger, err := zap.NewProduction() // Output: JSON, cocok untuk produksi
if err != nil {
panic(err)
}
return logger
}
Zap mendukung dua mode utama:
| Mode | Keterangan |
|---|---|
| Production | Output JSON, cocok untuk deployment |
| Development | Output friendly untuk local development |
Zap Interceptor untuk gRPC
Berikut contoh implementasi unary interceptor yang melakukan structured logging pada setiap request:
package main
import (
"context"
"time"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/status"
)
func UnaryZapLogger(logger *zap.Logger) grpc.UnaryServerInterceptor {
return func(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (resp interface{}, err error) {
start := time.Now()
resp, err = handler(ctx, req)
duration := time.Since(start)
grpcStatus, _ := status.FromError(err)
logger.Info("gRPC Request",
zap.String("method", info.FullMethod),
zap.Any("request", req),
zap.Duration("latency_ms", duration),
zap.String("code", grpcStatus.Code().String()),
zap.Error(err),
)
return resp, err
}
}
Penjelasan:
info.FullMethod: Nama method yang diakses (misal:/helloworld.Greeter/SayHello)zap.Any("request", req): Melog request dalam bentuk serializable (hati-hati sensitive info)zap.Duration("latency_ms", duration): Latensi proseszap.String("code", grpcStatus.Code().String()): Status code dari gRPC
Integrasi Interceptor ke Server gRPC
func main() {
logger := NewZapLogger()
grpcServer := grpc.NewServer(
grpc.UnaryInterceptor(UnaryZapLogger(logger)),
)
// Register service & listen...
}
Simulasi Output Log
Dengan setup di atas, log Anda akan terekam dengan output JSON rapi seperti ini:
{
"level": "info",
"ts": 1719491811.966,
"caller": "main.go:24",
"msg": "gRPC Request",
"method": "/helloworld.Greeter/SayHello",
"request": {"name": "Bob"},
"latency_ms": 7.424,
"code": "OK",
"error": ""
}
Struktur log seperti tersebut sangat mudah dicari dan dianalisis di backend observabilitas.
Menambah Context Metadata (TraceID dsb)
gRPC Context dapat membawa metadata seperti traceID, userID, dsb yang dapat diteruskan ke log Zap.
Berikut contoh mengakses traceID dari context dan inject ke log:
func UnaryZapLogger(logger *zap.Logger) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// Simulasikan extract traceID dari context/metadata
traceID, _ := ctx.Value("trace_id").(string)
start := time.Now()
resp, err := handler(ctx, req)
duration := time.Since(start)
grpcStatus, _ := status.FromError(err)
logger.Info("gRPC Request",
zap.String("trace_id", traceID),
zap.String("method", info.FullMethod),
zap.Duration("latency_ms", duration),
zap.String("code", grpcStatus.Code().String()),
zap.Error(err),
)
return resp, err
}
}
Tips: Untuk otomatisasi, gunakan grpc-metadata atau library tracing seperti OpenTelemetry untuk inject context metadata.
Diagram Alur Logging dengan Interceptor (Mermaid)
sequenceDiagram
participant Client as gRPC Client
participant Server as gRPC Server
participant Logger as Zap Logger
Client->>Server: Send gRPC Request
Server->>Logger: Log context, request, dsb
Server->>Server: Handler process
Server->>Logger: Log status, duration, error
Server-->>Client: Return gRPC Response
Good Practices
Jangan Log Sensitive Data
Hindari melog data rahasia seperti password.Level Logging
Gunakanlogger.Erroruntuk error,logger.Infountuk event normal.Field Baku
Biasakan log dengan field tetap: method, userID, code, duration.
Berikut tabel contoh fields esensial yang disarankan:Field Fungsi method Nama API/method code Status code gRPC latency_ms Durasi eksekusi (ms) trace_id Trace untuk tracing error Error detail (jika ada) user_id User (jika user-facing) Integrasikan Logger di Context
Untuk handler yang kompleks, pattern menyimpan logger pada context bisa diaplikasikan, sehingga tiap sub-fungsi bisa akses logger yang sama dengan kontekstual info.
Kesimpulan
Structured logging dengan Zap pada layanan gRPC memberikan kemudahan dalam hal monitoring, troubleshooting, dan post-mortem. Ketersediaan context-rich log akan memangkas waktu MTTR (Mean Time To Recovery) serta meningkatkan observabilitas secara signifikan.
Jika Anda serius membangun produk berbasis gRPC, jangan abaikan logging terstruktur—jadikan Zap dan interceptor sebagai default template proyek Anda.
Selamat bereksperimen dan jangan lupa refactor log Anda ke arah yang lebih terstruktur!
Referensi: