tutorial

14 Menambahkan Logging Sederhana di Server gRPC

Ketika membangun aplikasi backend modern, salah satu elemen penting namun sering diabaikan adalah logging. Logging bukan hanya tentang menuliskan error ke file log — ia adalah jendela kehidupan aplikasi kita: Apakah ada yang error? Endpoint mana yang sering diakses? Berapa lama waktu eksekusi tiap permintaan? Apalagi, jika kita menggunakan gRPC yang komunikasinya berbasis protokol biner dan streaming, log yang baik dapat sangat membantu debugging dan pemantauan.

Pada artikel ini, kita akan membahas bagaimana cara menambahkan logging sederhana pada server gRPC menggunakan bahasa pemrograman Go. Kita juga akan membahas kapan waktu yang tepat untuk melakukan logging, tipe informasi yang sebaiknya dituliskan, serta contoh kode lengkap dengan simulasi, diagram alur, dan tabel.


Kenapa Logging di gRPC itu Penting?

Sebelum menuju kode, mari kita jabarkan beberapa alasan berikut:

  • gRPC menggunakan protokol biner, sehingga pesan request/response tidak dapat diinspeksi dengan mudah via tools HTTP.
  • Dengan pipeline intersep (interceptors) di gRPC, developer dapat menambahkan logging tanpa banyak mengubah kode business logic.
  • Logging mempermudah troubleshooting dan monitoring, terutama pada microservices yang diskalakan secara horizontal.

Skema Logging di Server gRPC

gRPC menyediakan interceptor yang dapat digunakan untuk mengintersepsi (menyisipkan logika middleware) pada setiap permintaan (request) yang masuk.

Secara sederhana, alurnya adalah sebagai berikut:

flowchart LR
    A[Request dari Client] --> B[Interceptor Logging]
    B --> C[Method/Handler gRPC]
    C --> D[Response Kembali ke Client]

Jadi, logging terjadi sebelum (atau setelah) handler gRPC dijalankan.


Implementasi gRPC Logging di Go

Mari ke daging utama artikel: implementasi sederhana logging pada gRPC server.

1. Setup Proyek Sederhana

Kita asumsikan Anda sudah punya service sederhana, misal layanan HelloService.

// hello.proto
syntax = "proto3";

package helloworld;

service HelloService {
    rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

Setelah meng-generate kode hasil dari file proto di atas (misal: protoc ...), mari kita lanjutkan ke implementasi logging.


2. Membuat Interceptor Logging

Interceptor gRPC

Interceptor gRPC di Go adalah fungsi middleware yang mengikuti signature tertentu. UnaryServerInterceptor umum digunakan untuk RPC biasa (non-streaming). Berikut contoh interceptor logging sederhana:

// logging_interceptor.go
package main

import (
	"context"
	"log"
	"time"

	"google.golang.org/grpc"
)

// Unary interceptor untuk logging request dan response time
func LoggingInterceptor(
	ctx context.Context,
	req interface{},
	info *grpc.UnaryServerInfo,
	handler grpc.UnaryHandler,
) (resp interface{}, err error) {
	start := time.Now()
	log.Printf("Received request: %s at %s", info.FullMethod, start.Format(time.RFC3339))

	// Eksekusi RPC handler (bisnis logic)
	resp, err = handler(ctx, req)

	duration := time.Since(start)
	if err != nil {
		log.Printf("Error on %s: %v (duration: %s)", info.FullMethod, err, duration)
	} else {
		log.Printf("Completed %s (duration: %s)", info.FullMethod, duration)
	}

	return resp, err
}

Apa yang dilakukan kode ini?

  • Sebelum handler dijalankan, ia mencatat waktu mulai dan nama endpoint RPC yang diakses.
  • Setelah handler selesai, ia mencatat waktu selesai (duration) dan status sukses atau error.

3. Mengaktifkan Interceptor pada Server

Pada inisialisasi server gRPC, tambahkan interceptor tersebut:

// main.go
import (
	"google.golang.org/grpc"
	"log"
	"net"
)

func main() {
	lis, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	grpcServer := grpc.NewServer(
		grpc.UnaryInterceptor(LoggingInterceptor),
	)
	// Register service anda di sini
	helloworld.RegisterHelloServiceServer(grpcServer, &HelloServiceImpl{})

	log.Println("Server listening at :50051")
	if err := grpcServer.Serve(lis); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

Dengan setup seperti ini, setiap request yang datang ke server gRPC Anda akan melewati LoggingInterceptor, dan tercatat ke log Anda.


Contoh Output Log

Jika Anda menjalankan server dan melakukan request, log yang dihasilkan bisa berupa:

2024/07/01 10:15:23 Received request: /helloworld.HelloService/SayHello at 2024-07-01T10:15:23+07:00
2024/07/01 10:15:23 Completed /helloworld.HelloService/SayHello (duration: 102.324µs)

Jika terjadi error pada handler:

2024/07/01 10:15:25 Received request: /helloworld.HelloService/SayHello at 2024-07-01T10:15:25+07:00
2024/07/01 10:15:25 Error on /helloworld.HelloService/SayHello: rpc error: code = NotFound desc = name not found (duration: 98.202µs)

Simulasi: Membandingkan Server dengan & tanpa Logging

SkenarioServer Tanpa LoggingServer Dengan Logging
Debugging error RPCHanya dapat melihat dari error clientBisa melihat log error di server, waktu error terjadi, request RPC
Menganalisis performaTidak tahu endpoint lambat/cepatDapat melihat durasi tiap request
Audit requestTidak tahu siapa yang request, kapanMuncul riwayat akses di log

Menambahkan Informasi Tambahan ke Log

Untuk produksi, umumnya info berikut sebaiknya ikut dicatat:

  • Request/response ID (trace id, jika ada)
  • Metadata user (jika authentication diimplementasi)
  • IP address peminta
  • Size request/response
  • Custom tags

Untuk hal tersebut, bisa dikembangkan misalnya menggunakan library zap atau logrus yang mendukung structured logging dan output JSON. Namun untuk artikel kali ini, kita fokus pada logging dasar terlebih dahulu.


Streaming RPC? Bisa, Tapi…

Untuk gRPC streaming, Anda perlu menggunakan StreamServerInterceptor yang memiliki signature berbeda. Konsepnya sama:

func LoggingStreamInterceptor(
    srv interface{},
    ss grpc.ServerStream,
    info *grpc.StreamServerInfo,
    handler grpc.StreamHandler,
) error {
    log.Printf("Streaming request: %s", info.FullMethod)
    start := time.Now()

    err := handler(srv, ss)

    if err != nil {
        log.Printf("Streaming error on %s: %v (in %s)", info.FullMethod, err, time.Since(start))
    } else {
        log.Printf("Finished streaming %s (in %s)", info.FullMethod, time.Since(start))
    }
    return err
}

Dan didaftarkan dengan:

grpc.NewServer(
	grpc.StreamInterceptor(LoggingStreamInterceptor),
)

Diagram Alur Logging di gRPC Server

sequenceDiagram
    participant C as gRPC Client
    participant I as Logging Interceptor
    participant S as Service Handler
    C->>I: Kirim request
    I->>I: Catat log permintaan (timestamp, method)
    I->>S: Teruskan ke handler service
    S-->>I: Jawab/Response dari handler
    I->>I: Catat log response/error (durasi eksekusi)
    I->>C: Kirim response

Kesimpulan

Logging di server gRPC itu esensial agar aplikasi bisa diobservasi dan di-debug dengan baik. Dengan memanfaatkan mekanisme interceptor, kita bisa menambah logging dengan cost yang rendah dan nyaris tanpa modifikasi di business logic.

Tentu, produksi membutuhkan logging lebih advanced — termasuk trace ID antar layanan, contextual log, export ke service observability (ELK Stack, Grafana Loki, dll). Namun, mulai dari logging sederhana seperti ini sudah sangat membantu.

Semoga artikel ini membantu Anda meningkatkan observability aplikasi gRPC Anda!
Pingin bahas logging JSON, multi-level logging, atau trace-id di gRPC Go? Drop komentar di bawah! 🚀

comments powered by Disqus

Topik Terhangat

programming
170
tips-and-trick
43
tutorial
36
jaringan
28
hardware
11
linux
4
kubernetes
1