Tracing
Tracing?
Tracing adalah metode untuk memantau perjalanan sebuah permintaan melalui berbagai komponen atau layanan dalam sistem terdistribusi. Dengan tracing, Anda dapat:
Melacak Satu Permintaan: Mengikuti alur permintaan dari awal hingga akhir, misalnya, dari browser pengguna ke API, database, dan layanan eksternal.
Menganalisis Data Agregat: Memahami perilaku sistem secara keseluruhan, seperti latensi rata-rata atau titik kegagalan.
Mendiagnosis Masalah: Mengidentifikasi di mana masalah terjadi, seperti layanan mana yang lambat atau gagal.
Tracing sangat berguna untuk:
Troubleshooting: Menemukan akar masalah, misalnya, mengapa sebuah permintaan lambat.
Pemantauan Performa: Mengukur latensi dan ketergantungan antar layanan.
Analisis Perilaku Sistem: Memahami bagaimana layanan berinteraksi dalam arsitektur mikro.
Tracing menggunakan dua konsep utama: traces dan spans.
Konsep Traces dan Spans
Traces
Trace adalah objek induk yang merepresentasikan alur data atau jalur eksekusi melalui sistem. Trace mencakup semua operasi yang terkait dengan satu permintaan.
Atribut Trace:
Identifier: ID unik untuk mengidentifikasi trace (misalnya,
trace_id).Name: Deskripsi pekerjaan yang dilakukan (misalnya, "Process HTTP Request").
Timing Details: Timestamp awal dan akhir trace secara keseluruhan.
Spans
Span adalah unit kerja logis dalam trace, yang merepresentasikan operasi spesifik (misalnya, panggilan API, query database).
Atribut Span:
Trace Identifier: Menghubungkan span ke trace induk.
Identifier: ID unik untuk span (misalnya,
span_id).Parent Span Identifier: Menunjukkan hubungan dengan span induk.
Name: Deskripsi operasi (misalnya, "GET /api/users").
Timing Details: Timestamp awal dan akhir span.
Hubungan Traces dan Spans:
Satu trace terdiri dari beberapa span, membentuk directed acyclic graph (DAG).
Setiap span mencatat operasi spesifik dan waktu yang dibutuhkan, membantu mengidentifikasi bagian sistem yang lambat.
Ilustrasi: Berikut adalah visualisasi hubungan traces dan spans:
Penjelasan Ilustrasi:
Trace mencakup seluruh alur permintaan HTTP.
Span mencatat operasi seperti pemrosesan HTTP, query database, dan panggilan API eksternal.
Struktur DAG menunjukkan hubungan hierarkis, misalnya, span "Query Database" memiliki sub-span seperti "Fetch User Data".
Protokol Tracing
Ada beberapa protokol tracing yang umum digunakan, masing-masing dengan fitur spesifik:
OTLP (OpenTelemetry Protocol):
Fitur: Mendukung atribut span, propagasi konteks, event span, link span, dan jenis span (span kind).
Kegunaan: Standar modern yang vendor-agnostic, cocok untuk sistem terdistribusi kompleks.
Zipkin:
Fitur: Mendukung tag span, anotasi, propagasi konteks, dan jenis span.
Kegunaan: Populer untuk aplikasi berbasis mikro yang membutuhkan tracing sederhana.
Jaeger:
Fitur: Mendukung tag span, log span, referensi span, dan propagasi konteks (tergantung format).
Catatan: Format Jaeger Proto telah dihentikan demi OTLP.
Rekomendasi: OpenTelemetry (OTLP) adalah pilihan terbaik saat ini karena standar terbuka, dukungan luas, dan fleksibilitas untuk berbagai bahasa pemrograman, termasuk Go.
Praktik Terbaik untuk Tracing
Untuk mengimplementasikan tracing dengan efektif, pertimbangkan praktik terbaik berikut:
Perhatikan Performa:
Tracing dapat menambah overhead (latensi, memori, waktu startup).
Gunakan sampling untuk mengurangi jumlah span yang dikirim (misalnya, 10% dari semua trace).
Contoh: OpenTelemetry Collector mendukung konfigurasi sampling dari 0% hingga 100%.
Kelola Biaya:
Tracing menghasilkan banyak data, meningkatkan biaya jaringan dan penyimpanan.
Terapkan sampling (hanya kirim sebagian trace), filtering (batasi trace yang disimpan), dan retention (atur durasi penyimpanan data).
Pastikan Propagasi Konteks:
Propagasi konteks (misalnya,
trace_id) antar layanan sangat penting untuk menjaga kesinambungan trace.Tanpa propagasi yang benar, trace akan terpecah, mengurangi kegunaan.
Gunakan Instrumentasi yang Efisien:
Automatic Instrumentation: Cepat diimplementasikan, tetapi kurang kontrol dan dapat menyebabkan overhead.
Manual Instrumentation: Lebih kompleks, tetapi memungkinkan kontrol penuh atas data yang dihasilkan.
Gunakan library seperti OpenTelemetry untuk menyederhanakan instrumentasi.
Contoh Implementasi Tracing di Go dengan OpenTelemetry
Berikut adalah contoh aplikasi RESTful sederhana menggunakan Go 1.22 (dengan routing baru dari net/http) yang mengimplementasikan tracing dengan OpenTelemetry. Aplikasi ini memiliki dua endpoint (/api/users dan /api/orders) dan mencatat trace untuk setiap permintaan.
Struktur Proyek
go-opentelemetry-tracing/
├── main.go
├── Dockerfile
├── docker-compose.yml
├── jaeger-config.ymlKode Go
package main
import (
"context"
"fmt"
"log"
"math/rand"
"net/http"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
"go.opentelemetry.io/otel/trace"
)
// Nama tracer
const tracerName = "go-tracing-example"
// Inisialisasi OpenTelemetry
func initTracer() func(context.Context) error {
// Buat exporter untuk Jaeger (menggunakan OTLP over HTTP)
exporter, err := otlptracehttp.New(context.Background(),
otlptracehttp.WithEndpoint("jaeger:4318"),
otlptracehttp.WithInsecure(),
)
if err != nil {
log.Fatalf("Gagal membuat exporter: %v", err)
}
// Buat resource untuk mengidentifikasi aplikasi
resource, err := resource.New(context.Background(),
resource.WithAttributes(
semconv.ServiceNameKey.String("go-tracing-example"),
),
)
if err != nil {
log.Fatalf("Gagal membuat resource: %v", err)
}
// Buat tracer provider dengan sampling 100%
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource),
sdktrace.WithSampler(sdktrace.AlwaysSample()),
)
// Set tracer provider global
otel.SetTracerProvider(tracerProvider)
// Set propagator untuk konteks
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
))
// Fungsi untuk shutdown tracer
return func(ctx context.Context) error {
return tracerProvider.Shutdown(ctx)
}
}
func main() {
// Inisialisasi tracer
shutdown := initTracer()
defer func() {
if err := shutdown(context.Background()); err != nil {
log.Printf("Gagal shutdown tracer: %v", err)
}
}()
// Seed random untuk simulasi latensi
rand.Seed(time.Now().UnixNano())
// Buat router baru
mux := http.NewServeMux()
// Handler untuk /api/users
mux.HandleFunc("GET /api/users", func(w http.ResponseWriter, r *http.Request) {
// Dapatkan tracer
tracer := otel.Tracer(tracerName)
// Mulai span untuk permintaan
ctx, span := tracer.Start(r.Context(), "GET /api/users")
defer span.End()
// Simulasi latensi (50ms hingga 500ms)
time.Sleep(time.Duration(rand.Intn(450)+50) * time.Millisecond)
// Simulasi operasi database
_, dbSpan := tracer.Start(ctx, "Query Database")
time.Sleep(time.Duration(rand.Intn(100)+20) * time.Millisecond)
dbSpan.End()
// Tulis respons
fmt.Fprintln(w, `[{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]`)
})
// Handler untuk /api/orders
mux.HandleFunc("GET /api/orders", func(w http.ResponseWriter, r *http.Request) {
// Dapatkan tracer
tracer := otel.Tracer(tracerName)
// Mulai span untuk permintaan
ctx, span := tracer.Start(r.Context(), "GET /api/orders")
defer span.End()
// Simulasi latensi (50ms hingga 500ms)
time.Sleep(time.Duration(rand.Intn(450)+50) * time.Millisecond)
// Simulasi panggilan API eksternal
_, apiSpan := tracer.Start(ctx, "Call External API")
time.Sleep(time.Duration(rand.Intn(150)+30) * time.Millisecond)
apiSpan.End()
// Tulis respons
fmt.Fprintln(w, `[{"order_id": 101, "item": "Book"}, {"order_id": 102, "item": "Pen"}]`)
})
// Start server
log.Println("Starting server on :8080")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatalf("Server failed: %v", err)
}
}Penjelasan Kode:
OpenTelemetry Setup:
Menggunakan
otlptracehttpuntuk mengirim trace ke Jaeger melalui OTLP over HTTP.Mengatur
TracerProviderdengan sampling 100% (AlwaysSample) untuk mencatat semua trace.Mengatur propagasi konteks untuk memastikan
trace_idditeruskan antar layanan.
Tracing:
Endpoint
/api/usersmencatat span untuk permintaan HTTP dan simulasi query database.Endpoint
/api/ordersmencatat span untuk permintaan HTTP dan simulasi panggilan API eksternal.Setiap span mencatat waktu mulai dan selesai, membantu mengidentifikasi latensi.
Simulasi Latensi: Menggunakan
randuntuk mensimulasikan latensi realistis.
Konfigurasi Docker
Dockerfile
# Use official Go 1.22 image
FROM golang:1.22-alpine
# Set working directory
WORKDIR /app
# Copy source code
COPY main.go .
# Install dependencies
RUN go mod init go-opentelemetry-tracing
RUN go get go.opentelemetry.io/otel
RUN go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
RUN go get go.opentelemetry.io/otel/sdk/trace
RUN go get go.opentelemetry.io/otel/semconv/v1.17.0
RUN go get go.opentelemetry.io/otel/propagation
# Build the application
RUN go build -o app main.go
# Expose port
EXPOSE 8080
# Run the application
CMD ["./app"]docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
networks:
- tracing
jaeger:
image: jaegertracing/all-in-one:1.58
ports:
- "16686:16686" # Jaeger UI
- "4318:4318" # OTLP HTTP
environment:
- COLLECTOR_OTLP_ENABLED=true
networks:
- tracing
networks:
tracing:
driver: bridgeCara Menjalankan
Buat Direktori Proyek:
mkdir go-opentelemetry-tracing cd go-opentelemetry-tracingSimpan file
main.go,Dockerfile, dandocker-compose.yml.Jalankan dengan Docker Compose:
docker-compose up --buildAplikasi Go berjalan di
http://localhost:8080.Jaeger UI berjalan di
http://localhost:16686.
Uji Endpoint:
curl http://localhost:8080/api/users curl http://localhost:8080/api/ordersLihat Trace:
Buka
http://localhost:16686di browser.Pilih layanan
go-tracing-exampledan cari trace berdasarkantrace_id.Anda akan melihat DAG dari span, misalnya:
Span "GET /api/users" dengan sub-span "Query Database".
Span "GET /api/orders" dengan sub-span "Call External API".
Contoh Visualisasi di Jaeger:
Trace untuk
/api/usersakan menunjukkan waktu total dan kontribusi latensi dari "Query Database".Anda dapat mengidentifikasi jika "Query Database" memakan waktu lama, menunjukkan potensi bottleneck.
Praktik Terbaik dalam Kode
Gunakan Nama Span yang Deskriptif: Misalnya, "GET /api/users" atau "Query Database".
Terapkan Sampling: Untuk produksi, gunakan
ParentBased(TraceIDRatioBased(0.1))untuk mengurangi overhead.Propagasi Konteks: Pastikan
trace_idditeruskan antar layanan menggunakan header HTTP (otomatis dengan OpenTelemetry).Tambahkan Atribut: Tambahkan metadata seperti
http.status_codeatauuser.idke span untuk analisis lebih mendalam.
Kesimpulan
Tracing adalah alat penting dalam observability untuk melacak permintaan dan memahami perilaku sistem terdistribusi. Dengan konsep traces dan spans, protokol seperti OpenTelemetry, dan praktik terbaik seperti sampling dan propagasi konteks, Anda dapat mendiagnosis masalah dengan cepat dan meningkatkan performa sistem. Contoh implementasi di Go dengan OpenTelemetry menunjukkan bagaimana tracing dapat diintegrasikan dalam aplikasi RESTful sederhana, dengan visualisasi di Jaeger untuk analisis. Dengan mengikuti praktik terbaik, tracing dapat memberikan wawasan berharga tanpa mengorbankan performa atau biaya.
Last updated