Melihat apa yang sebenarnya terjadi di production: tiga pilar observability (logs, metrics, traces), integrasi error tracking dengan Sentry, trace request dari browser hingga database, dan membangun dashboard pemantauan.

Di development, error muncul di layar kalian. Di production, error terjadi pada pengguna — dan kalian tidak ada di sana. Episode ini membahas observability fullstack: kemampuan untuk menjawab pertanyaan tentang sistem yang sedang berjalan — apa yang lambat, apa yang gagal, dan mengapa.
Di episode 11 kita membangun fondasi (structured logging). Sekarang kita lengkapi menjadi sistem penuh dengan tiga pilar observability — logs, metrics, traces — ditambah error tracking dan dashboard. Inilah yang memisahkan aplikasi "yang jalan" dari aplikasi "yang dipahami".
| Pilar | Menjawab | Contoh |
|---|---|---|
| Logs | "Apa yang terjadi?" | gagal query note 123: koneksi timeout |
| Metrics | "Berapa banyak?" | 3% error rate, p99 latency 800 ms |
| Traces | "Bagaimana alurnya?" | browser → route → DB → LLM, 1,2 detik |
Logs memberi detail, metrics memberi angka, traces memberi hubungan antar langkah — termasuk bagaimana satu request mengalir melewati banyak lapisan. Observability penuh berarti ketiganya.
Alat error tracking mengubah "error acak" menjadi data terorganisir: ringkasan, pengelompokan (issue), stack trace asli, dan konteks pengguna. Sentry adalah pilihan paling umum untuk Next.js:
pnpm add @sentry/nextjs
pnpm sentry/wizardWizard mengonfigurasi otomatis: Sentry.init di instrumentation.ts, Sentry.ErrorBoundary untuk UI, dan wrapper route handler. Setelah itu, setiap error — di server atau browser — muncul sebagai issue:
import * as Sentry from "@sentry/nextjs"
export async function GET(request: Request) {
const session = await auth()
if (session) {
Sentry.setUser({ id: session.user.id, email: session.user.email })
}
try {
// ...
} catch (error) {
Sentry.captureException(error)
return Response.json({ error: "Terjadi kesalahan" }, { status: 500 })
}
}Tip
Error tracking bukan pengganti logging — keduanya saling melengkapi. Sentry menjawab "error apa yang paling sering dan siapa terkena"; log detail menjawab "apa yang terjadi tepat sebelum error itu". Sambungkan keduanya: bawa requestId di log dan kirim ke Sentry lewat Sentry.setContext, sehingga dari issue Sentry bisa melompat ke log terkait.
Tracing (OpenTelemetry standar) menangkap perjalanan satu request: waktu di browser, di route handler, di query database, di panggilan LLM. Next.js mendukung OpenTelemetry secara bawaan:
export default NextConfig({
experimental: {
instrumentationHook: true,
},
})export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { initNodeTracing } = await import("@/lib/tracing")
await initNodeTracing()
}
}Dengan tracing aktif, suatu laporan di dashboard bisa menjawab pertanyaan seperti: "kenapa halaman catatan lambat?" → trace menunjukkan 1,1 detik di query database, bukan di render. Tanpa trace, kalian hanya menebak.
Metrics adalah "detak jantung" — angka yang diagregasi dan di-alert. Tiga set yang selalu berguna:
| Set | Contoh | Alert ketika |
|---|---|---|
| Traffic | request/s, pengguna aktif | Lonjakan abnormal |
| Latency | p50, p95, p99 | p99 > target |
| Errors | error rate, 5xx | Rate > 1% |
Untuk aplikasi fullstack yang di-host Vercel, metrik platform (request count, latency, error) sudah otomatis. Tambahkan metrik aplikasi yang bermakna bisnis — misal job queue melonjak (episode 13) atau upload gagal (episode 12):
const start = Date.now()
try {
await processJob(job)
recordCounter("jobs.processed", { queue: job.queue })
} catch (error) {
recordCounter("jobs.failed", { queue: job.queue })
}
recordHistogram("jobs.duration_ms", Date.now() - start)Structured log di episode 11 (Pino) sekarang dialirkan ke sistem terpusat — jangan biarkan log tinggal di stdout container (hilang saat restart). Gunakan penyedia log terkelola atau ekspor sendiri. Kunci yang membuat log berguna di production:
info untuk kejadian normal, warn untuk anomali, error untuk kegagalan — jangan semua info.import { randomUUID } from "node:crypto"
export async function GET(request: Request) {
const requestId = request.headers.get("x-request-id") ?? randomUUID()
logger = logger.child({ requestId })
logger.info("request dimulai")
// ...
}Warning
Satu peringatan praktis: jangan log seluruh response body — bisa berisi data pribadi pengguna (melanggar episode 20), dan membengkakkan volume log (melanggar anggaran). Log id, status code, dan durasi; simpan body detail hanya saat error dan hanya untuk skema yang sudah disaring.
Observability tanpa dashboard tidak berguna — kalian tidak akan membuka konsol tiap menit. Buat dashboard minimal (di penyedia pilihan kalian) dengan satu layar yang menjawab tiga pertanyaan:
Mulai dari satu dashboard ini; tambah panel sesuai kebutuhan — bukan sebaliknya.
Selesaikan dengan urutan berikut:
Sentry.setUser dan requestId di log pada route handler penting.curl -s http://localhost:3000/notes/nonexistent -o /dev/nullInti yang harus dibawa pulang:
Di episode 25, episode penutup seri ini, kita membahas performance & optimization lanjutan — profiling, bundle size, dan tuning database untuk produksi. Sampai jumpa di episode 25!