Error monitoring adalah tool yang menangkap error di aplikasi yang sedang dipakai user (production), lalu mengirimkannya otomatis ke satu dashboard, lengkap dengan stack trace dan konteksnya. Contohnya Sentry, Rollbar, Bugsnag, dan Datadog. Bayangin pusat pengaduan ojek online. Ratusan penumpang melapor "aplikasi error waktu bayar", tapi petugasnya gak bikin ratusan tiket: laporan yang sama digabung jadi satu tiket bertuliskan "dilaporkan 47 kali", dan tiap laporan membawa catatan apa yang dilakukan penumpang sebelum masalahnya muncul. Bedanya, di sini laporannya dikirim aplikasi sendiri, tanpa menunggu user mengeluh.
🧪 Tebakan cepat: tiga error tertangkap. Dua kali
Cannot read propertydarilogin.jsbaris 45 (useru1laluu2), dan sekaliNetwork errordariapi.js. Di dashboard ada berapa issue, dan berapacountissue pertama? Jawabannya ada di bagian "Fingerprint: error yang sama jadi satu issue".
Kenapa butuh error monitoring
console.log dan DevTools cuma jalan di laptopmu. Error yang terjadi di HP user gak pernah kamu lihat, dan kebanyakan user gak melapor: mereka cuma menutup aplikasinya. Error monitoring memasang sebuah SDK (library kecil dari tool-nya) di aplikasimu. Setiap ada error yang gak ditangkap, SDK itu mengirim satu event, yaitu satu laporan error, ke server tool-nya.
Isi satu event kira-kira begini:
- message dan stack trace: error apa, dan baris mana yang memanggil baris mana.
- release, user, dan tags: versi aplikasi (
v1.2.3), siapa yang kena (userId: "u1"), dan label bebas untuk menyaring (env: "prod"atauenv: "staging"). Ditambah url, browser, dan sistem operasinya. - breadcrumbs: jejak kejadian sebelum error, dari yang paling lama.
Breadcrumb dicatat SDK Sentry otomatis: klik, pindah halaman, request fetch, dan isi console. Contohnya, sebelum login gagal:
navigation / → /login
ui.click button#masuk
fetch POST /api/login 200
error TypeError: Cannot read property
Dari jejak ini kamu tahu: request login-nya berhasil (200), jadi masalahnya ada di kode yang membaca jawabannya, bukan di server.
Fingerprint: error yang sama jadi satu issue
Kalau 500 user kena error yang sama, kamu gak mau 500 notifikasi. Tool-nya menghitung fingerprint (sidik jari) tiap event. Event dengan fingerprint yang sama digabung jadi satu issue, dan angka count-nya naik. Sentry memakai stack trace sebagai fingerprint, dan kalau stack trace-nya gak ada, dia memakai jenis dan pesan error-nya. Di latihan, fingerprint-nya disederhanakan: baris pertama error.stack, atau error.message kalau stack-nya kosong.
Ini tiga error dari tebakan tadi, ditangkap satu per satu:
| # | Error | Context | Fingerprint | Hasil |
|---|---|---|---|---|
| 1 | Cannot read property, di login.js:45 |
u1, env: prod |
TypeError: Cannot read property |
issue baru, count 1 |
| 2 | Cannot read property, di login.js:45 |
u2, env: prod |
TypeError: Cannot read property |
sama, count jadi 2 |
| 3 | Network error, di api.js:12 |
u1, env: staging |
Error: Network error |
issue baru, count 1 |
Tiga laporan, dua issue, dan issue pertama punya count 2. Itu jawaban tebakannya.
Bikin monitor seperti Sentry
function createMonitor(release) {
const events = [];
let nextId = 1;
function generateFingerprint(error) {
// baris pertama stack trace, atau pesannya kalau stack kosong
return error.stack ? error.stack.split('\n')[0] : error.message;
}
return {
capture(error, context) {
const fingerprint = generateFingerprint(error);
const sudahAda = events.find((e) => e.fingerprint === fingerprint);
if (sudahAda) {
sudahAda.count++; // digabung, bukan event baru
return sudahAda;
}
const event = {
id: nextId++, message: error.message, fingerprint, release,
userId: context.userId, url: context.url, tags: context.tags || {},
timestamp: new Date().toISOString(), count: 1,
};
events.push(event);
return event;
},
getEvents: () => events,
getEventsByTag: (key, value) => events.filter((e) => e.tags[key] === value),
getTopErrors: (n) => [...events].sort((a, b) => b.count - a.count).slice(0, n),
};
}
const mon = createMonitor('v1.2.3');
const errLogin = { message: 'Cannot read property', stack: 'TypeError: Cannot read property\n at login.js:45' };
mon.capture(errLogin, { userId: 'u1', url: '/login', tags: { env: 'prod' } });
mon.capture(errLogin, { userId: 'u2', url: '/login', tags: { env: 'prod' } });
mon.capture(
{ message: 'Network error', stack: 'Error: Network error\n at api.js:12' },
{ userId: 'u1', tags: { env: 'staging' } }
);
console.log(mon.getEvents().length); // 2
console.log(mon.getEvents()[0].count); // 2
console.log(mon.getEventsByTag('env', 'prod').length); // 1
console.log(mon.getTopErrors(1)[0].message); // Cannot read property
createMonitor(release)menyimpaneventsdanreleasedi dalam closure. Semua event yang dibuat monitor ini dicap release yang sama.generateFingerprintmembuang\n at login.js:45dan menyisakanTypeError: Cannot read property.events.find(...)mencari issue yang fingerprint-nya sama. Ketemu:count++lalu selesai. Gak ketemu: event baru dengancount: 1.tags: context.tags || {}memastikan event tanpa tags tetap punya objek kosong, jadie.tags[key]gak error.getEventsByTag('env', 'prod')cuma mengembalikan 1, karena issueNetwork errorber-tagstaging.getTopErrors(1)mengurutkan salinan ([...events]) dari count terbesar, lalu mengambilnteratas. Array aslinya gak ikut teracak.
Pasang Sentry sungguhan
import * as Sentry from '@sentry/browser';
Sentry.init({
dsn: 'https://[email protected]/456', // alamat proyekmu di Sentry
release: '[email protected]',
environment: 'production',
});
Sentry.setUser({ id: 'u1' });
Sentry.setTag('env', 'prod');
Sentry.initdipanggil sekali, paling awal. SDK lalu memasang listenererrordanunhandledrejectiondiwindow, jadi error yang gak ditangkap dan promise yang ditolak tanpa.catchterkirim otomatis.releasemenempel di tiap event. Dari situ kamu tahu versi mana yang pertama kali memunculkan sebuah issue. Kalau issue yang sudah kamu tandai resolved muncul lagi di release berikutnya, Sentry membukanya lagi sebagai regression.environmentmemisahkan error production dari staging, supaya error waktu testing gak bikin panik.setUserdansetTagmenempel ke semua event sesudahnya.
Error yang sudah ditangkap try/catch
Error yang kamu tangkap sendiri gak pernah sampai ke SDK. Laporkan manual dengan Sentry.captureException(error):
try {
await bayar(pesanan);
} catch (error) {
Sentry.captureException(error); // tetap terkirim, lengkap dengan stack trace
tampilkanPesan('Pembayaran gagal, coba lagi ya.');
}
Untuk kejadian yang bukan error, pakai Sentry.captureMessage('kupon kedaluwarsa dipakai'). Untuk menambah jejak sendiri, pakai Sentry.addBreadcrumb({ category: 'checkout', message: 'pilih QRIS', level: 'info' }).
⚠️ Jebakan yang sering ketemu
- Error ditelan
try/catch.catchyang cumaconsole.log(error)bikin dashboard tetap bersih padahal user gagal bayar. PanggilSentry.captureException(error)di dalamcatch. - Menghitung laporan, bukan issue. Di latihan,
getEvents().lengthharus2, bukan3. Laporan dengan fingerprint yang sama menaikkancountdi event yang sudah ada, bukanpushevent baru. - Mengurutkan array aslinya.
events.sort(...)mengubah urutangetEvents(). Urutkan salinannya,[...events], dan potong dengan.slice(0, n)supayagetTopErrors(1)cuma berisi satu. - Lupa
releasedanenvironment. Tanpa itu kamu gak bisa bilang bug ini muncul sejak versi berapa, dan error staging bercampur dengan error production.
TL;DR: Error monitoring (misalnya Sentry) mengirim tiap error di production sebagai event: message, stack trace, release, user, tags, dan breadcrumbs (jejak klik, navigasi, fetch sebelum error). Event dengan fingerprint sama digabung jadi satu issue dan count-nya naik, jadi 3 laporan bisa jadi 2 issue. Di latihan, fingerprint = baris pertama error.stack atau error.message, getEventsByTag menyaring tags[key] === value, dan getTopErrors(n) mengurutkan salinan dari count terbesar. Pasang Sentry.init dengan release dan environment, dan error yang kamu tangkap sendiri laporkan dengan Sentry.captureException(error).