Last active 4 days ago

Sentry_nestjs_integration_guide.md Raw

Setup Error Tracking (Sentry SDK) di NestJS

Dokumentasi ini buat masang error tracking (capture exception, request context, stack trace) ke instance Sentry-compatible (GlitchTip self-hosted atau Sentry Cloud) di aplikasi NestJS.

1. Prasyarat

  • Sudah punya DSN dari project di dashboard GlitchTip/Sentry (format: https://<key>@<domain>/<project_id>)
  • Project NestJS pakai .env buat konfigurasi

2. Instalasi

npm install @sentry/nestjs --save

3. Environment variable

Tambahin di .env:

SENTRY_DSN="https://<key>@<domain>/<project_id>"

4. Buat file src/instrument.ts

File ini harus terpisah dari main.ts, dan berisi Sentry.init().

import 'dotenv/config'; // wajib, load .env sebelum Sentry.init() dipanggil
import * as Sentry from '@sentry/nestjs';

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  sendDefaultPii: true, // biar IP, headers, user info ikut kecapture
  environment: process.env.NODE_ENV || 'production',

  // Konfigurasi eksplisit biar request body & query params PASTI kecapture
  // (default behaviour beda-beda antar versi SDK, jadi jangan andelin default)
  integrations: (defaultIntegrations) => [
    ...defaultIntegrations.filter((i) => i.name !== 'RequestData'),
    Sentry.requestDataIntegration({
      include: {
        ip: true,
        data: true, // request body
        query_string: true, // query params
        headers: true,
        cookies: true,
        user: true,
      },
    }),
  ],
});

Kenapa perlu dotenv/config manual: instrument.ts diimport paling awal, sebelum NestJS ConfigModule sempat jalan dan load .env. Tanpa ini, process.env.SENTRY_DSN masih undefined pas Sentry.init() dipanggil, dan SDK diam-diam gak akan pernah ngirim event (cuma warning "No DSN provided" di log kalau debug: true).

5. Import di baris paling atas main.ts

import './instrument'; // HARUS baris pertama, sebelum import apapun lain

import { NestFactory } from '@nestjs/core';
// ...import lain seperti biasa

Penting:

  • Tanpa ekstensi .ts di akhir ('./instrument', bukan './instrument.ts')
  • File-nya harus ada di src/ (folder yang sama dengan main.ts)

6. Register SentryModule & exception filter global di app.module.ts

import { APP_FILTER } from '@nestjs/core';
import { SentryModule, SentryGlobalFilter } from '@sentry/nestjs/setup';

@Module({
  imports: [
    SentryModule.forRoot(), // HARUS import PERTAMA di array imports
    // ...module lain
  ],
  providers: [
    { provide: APP_FILTER, useClass: SentryGlobalFilter }, // HARUS provider PERTAMA
    // ...provider lain
  ],
})
export class AppModule {}

7. Kalau project sudah punya custom exception filter

Ini kasus yang sering kelewat. SentryGlobalFilter cuma nangkep exception yang gak ketangkep filter lain duluan. Kalau ada filter spesifik seperti:

@Catch(PrismaClientKnownRequestError)
export class SomeCustomFilter implements ExceptionFilter { ... }

...exception yang match @Catch(...) itu tidak akan pernah sampai ke SentryGlobalFilter, walaupun SentryModule sudah ke-setup benar. Solusinya: tambahin Sentry.captureException(exception) manual di filter itu.

Aturan yang dipakai: status 5xx / error tak terduga → capture. Status 4xx yang memang disengaja (validasi, not found, unauthorized) → boleh skip, karena itu flow normal, bukan bug.

Helper biar konsisten di semua filter:

// src/common/sentry-report.util.ts
import * as Sentry from '@sentry/nestjs';

export function reportIfUnexpected(exception: unknown, httpStatus: number) {
  if (httpStatus >= 500) {
    Sentry.captureException(exception);
  }
}

Panggil di ujung catch() tiap custom filter:

reportIfUnexpected(exception, status);

Checklist: cari semua custom filter di project sebelum anggap setup selesai:

grep -rln "@Catch(" src/ --include="*.ts" | grep -v node_modules

8. Scrub data sensitif (opsional tapi disarankan)

Karena sendDefaultPii: true bikin request body ikut terkirim penuh, tambahin beforeSend di instrument.ts buat mask field sensitif:

Sentry.init({
  // ...config di atas
  beforeSend(event) {
    const sensitiveFields = ['password', 'token', 'secret'];
    if (event.request?.data) {
      for (const field of sensitiveFields) {
        if (event.request.data[field]) event.request.data[field] = '[FILTERED]';
      }
    }
    return event;
  },
});

9. Testing

Penting: SentryGlobalFilter tidak mengirim HttpException bawaan NestJS (NotFoundException, BadRequestException, dll) secara default — itu dianggap flow kontrol normal, bukan bug. Buat testing, trigger error yang beneran unhandled, misal lewat constraint DB (contoh Prisma create() dengan data yang melanggar unique constraint), bukan throw new HttpException(...).

Cara cepat verifikasi:

  1. Trigger error via endpoint yang ada request body/query params
  2. Cek dashboard → tab Issues → buka issue-nya
  3. Pastikan section Request ada: Body, Query String, Headers, IP

10. Troubleshooting

Gejala Kemungkinan penyebab
Cannot find module './instrument.ts' Ada .ts literal di baris import main.ts, atau dist/ lama ke-cache — hapus dist dan build ulang
Cannot find module './instrument' padahal sudah tanpa .ts File instrument.ts gak ada di src/, atau salah folder
No DSN provided, client will not send events (pakai debug: true) .env belum ke-load pas Sentry.init() jalan — tambahin import 'dotenv/config' di baris pertama instrument.ts
Event gak muncul di dashboard padahal Sentry Logger bilang "Captured error event" Cek log container GlitchTip (docker compose logs glitchtip-web) barengan waktu trigger — kalau gak ada request masuk sama sekali, cek konektivitas network / DSN salah domain
Request body / query params kosong di dashboard Konfigurasi requestDataIntegration secara eksplisit (lihat bagian 4), jangan andelin default SDK
Error yang "seharusnya" muncul tapi gak ada Kemungkinan ketangkep custom exception filter duluan — lihat bagian 7

Referensi