Episode ini membuat service Rust bisa diamati: logging terstruktur dengan tracing dan ekosistem subscriber, distributed tracing dengan tracing-opentelemetry, serta metrics Prometheus dengan crate prometheus atau opentelemetry.

Aplikasi yang berjalan di production hampir tidak mungkin dipahami tanpa observability: logging, tracing, dan metrics. Saat error terjadi di jam 3 pagi, log yang terstruktur dan trace yang lengkap adalah alat pertama yang menyelamatkan kalian.
Episode 14 membangun tiga pilar observability di Rust: logging terstruktur dengan tracing, distributed tracing dengan tracing-opentelemetry, dan metrics dengan Prometheus. Di akhir episode, kalian bisa menjawab tiga pertanyaan: apa yang terjadi, di mana terjadinya, dan seberapa sering.
tracing adalah framework logging modern: ia membedakan event (sesaat terjadi, misalnya "request selesai") dan span (periode, misalnya "memproses request id 42"). Span bisa bersarang, sehingga seluruh request bisa dihubungkan dalam satu konteks.
cat > src/main.rs <<'EOF'
use tracing::{debug, error, info, span, Level};
use tracing_subscriber;
fn proses_request(id: u64) {
let span = span!(Level::INFO, "request", id);
let _entered = span.enter();
info!("memulai proses");
debug!("membaca data dari cache");
error!("timeout saat menghubungi upstream");
}
fn main() {
tracing_subscriber::fmt()
.with_max_level(Level::DEBUG)
.init();
proses_request(42);
}
EOF
cargo runspan!(Level::INFO, "request", id) membuka span berkonteks, dan _entered menutupnya di akhir scope. info!, debug!, dan error! mencatat event di dalam span. cargo add tracing-subscriber --features fmt,json menambah subscriber yang mencetak ke terminal.
Subscriber adalah bagian yang menerima event dan span. Untuk produksi, ganti formatter ke JSON agar log bisa diparse kolektor seperti Loki atau OpenSearch: tracing_subscriber::fmt().json().with_current_span(true).init() menghasilkan satu objek JSON per baris dengan timestamp, level, field, dan span aktif. tracing::info!(user = "budi", ...) menyisipkan field terstruktur — bukan sekadar teks yang digabung.
Menambahkan span di setiap fungsi secara manual itu membosankan. Macro #[tracing::instrument] melakukannya otomatis:
cat > src/main.rs <<'EOF'
use tracing::instrument;
#[instrument]
fn hitung_biaya(items: u32, harga: f64) -> f64 {
items as f64 * harga
}
fn main() {
tracing_subscriber::fmt().init();
let total = hitung_biaya(10, 5.5);
println!("total: {}", total);
}
EOF
cargo run#[instrument] membuat span yang mengikuti fungsi, mencatat parameter, dan menutup saat fungsi selesai. Untuk handler axum, #[instrument(skip(state))] mengecualikan state besar dari log. Ini cara paling cepat membuat aplikasi bisa dilacak.
Di arsitektur microservices, satu request melewati banyak service. Distributed tracing menghubungkan semua segmen lewat trace ID yang disebarkan antar service. Ekosistem standarnya adalah OpenTelemetry.
cat > src/main.rs <<'EOF'
use opentelemetry::global::set_tracer_provider;
use opentelemetry_otlp::new_exporter;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let exporter = new_exporter()
.tonic()
.with_endpoint("http://otel-collector:4317")
.build_export_pipeline()?;
let provider = exporter.setup();
set_tracer_provider(provider);
tracing_subscriber::registry()
.with(tracing_opentelemetry::layer())
.init();
tracing::info!("layanan observability siap");
Ok(())
}
EOF
cargo runExporter OTLP mengirim trace ke OpenTelemetry Collector yang kemudian meneruskannya ke backend seperti Jaeger atau Tempo. Dengan ini, satu trace ID menghubungkan request yang melintasi banyak service — sumber daya utama debugging.
Metrics adalah angka: jumlah request, durasi, error rate. Prometheus menarik angka ini lewat endpoint HTTP yang dibuka aplikasi. Mulai dari Registry dan IntCounter:
cat > src/main.rs <<'EOF'
use prometheus::{Encoder, IntCounter, Opts, Registry, TextEncoder};
fn main() {
let registry = Registry::new();
let counter = IntCounter::new(
Opts::new("http_request_total", "total request http"),
)
.unwrap();
registry.register(counter).unwrap();
let mut buffer = Vec::new();
let encoder = TextEncoder::new();
encoder.encode(®istry.gather(), &mut buffer).unwrap();
println!("{}", String::from_utf8(buffer).unwrap());
}
EOF
cargo runRegistry menampung seluruh metrics, dan TextEncoder menghasilkan format yang dipahami Prometheus. Di aplikasi server, pasang handler yang mengembalikan teks ini di endpoint /metrics seperti handler axum di episode 10 — Prometheus menariknya secara berkala, lalu Grafana memvisualisasikan. IntCounter menghitung event; untuk durasi, gunakan Histogram atau Summary.
Jika stack kalian sudah memakai OpenTelemetry, opentelemetry juga menyediakan metrics API yang bisa diekspor via OTLP. Pilihannya konsisten: pakai prometheus langsung untuk endpoint scrape, atau opentelemetry untuk pipeline terpadu dengan trace.
Beberapa kebiasaan yang membuat observability berguna, bukan sekadar berisik:
error untuk kegagalan, warn untuk degradasi, info untuk lifecycle.#[instrument] menangani sisanya.Inti yang harus dibawa pulang:
tracing membedakan event dan span; subscriber menentukan format dan tujuan.#[tracing::instrument] meng-instrument fungsi secara otomatis./metrics.Di episode 15 selanjutnya kita akan membahas resilience, graceful shutdown, dan health check — mematikan server dengan bersih lewat signal handling, menyediakan health check dan readiness probes untuk Kubernetes, serta menerapkan retry, timeout, dan circuit breaker dengan tower dan tower-http. Service kalian akan tahan banting.