135dika revised this gist 4 days ago. Go to revision
1 file changed, 185 insertions
Sentry_nestjs_integration_guide.md(file created)
| @@ -0,0 +1,185 @@ | |||
| 1 | + | # Setup Error Tracking (Sentry SDK) di NestJS | |
| 2 | + | ||
| 3 | + | Dokumentasi ini buat masang error tracking (capture exception, request context, | |
| 4 | + | stack trace) ke instance Sentry-compatible (GlitchTip self-hosted atau Sentry Cloud) | |
| 5 | + | di aplikasi NestJS. | |
| 6 | + | ||
| 7 | + | ## 1. Prasyarat | |
| 8 | + | ||
| 9 | + | - Sudah punya DSN dari project di dashboard GlitchTip/Sentry | |
| 10 | + | (format: `https://<key>@<domain>/<project_id>`) | |
| 11 | + | - Project NestJS pakai `.env` buat konfigurasi | |
| 12 | + | ||
| 13 | + | ## 2. Instalasi | |
| 14 | + | ||
| 15 | + | ```bash | |
| 16 | + | npm install @sentry/nestjs --save | |
| 17 | + | ``` | |
| 18 | + | ||
| 19 | + | ## 3. Environment variable | |
| 20 | + | ||
| 21 | + | Tambahin di `.env`: | |
| 22 | + | ||
| 23 | + | ``` | |
| 24 | + | SENTRY_DSN="https://<key>@<domain>/<project_id>" | |
| 25 | + | ``` | |
| 26 | + | ||
| 27 | + | ## 4. Buat file `src/instrument.ts` | |
| 28 | + | ||
| 29 | + | File ini **harus terpisah** dari `main.ts`, dan berisi `Sentry.init()`. | |
| 30 | + | ||
| 31 | + | ```ts | |
| 32 | + | import 'dotenv/config'; // wajib, load .env sebelum Sentry.init() dipanggil | |
| 33 | + | import * as Sentry from '@sentry/nestjs'; | |
| 34 | + | ||
| 35 | + | Sentry.init({ | |
| 36 | + | dsn: process.env.SENTRY_DSN, | |
| 37 | + | sendDefaultPii: true, // biar IP, headers, user info ikut kecapture | |
| 38 | + | environment: process.env.NODE_ENV || 'production', | |
| 39 | + | ||
| 40 | + | // Konfigurasi eksplisit biar request body & query params PASTI kecapture | |
| 41 | + | // (default behaviour beda-beda antar versi SDK, jadi jangan andelin default) | |
| 42 | + | integrations: (defaultIntegrations) => [ | |
| 43 | + | ...defaultIntegrations.filter((i) => i.name !== 'RequestData'), | |
| 44 | + | Sentry.requestDataIntegration({ | |
| 45 | + | include: { | |
| 46 | + | ip: true, | |
| 47 | + | data: true, // request body | |
| 48 | + | query_string: true, // query params | |
| 49 | + | headers: true, | |
| 50 | + | cookies: true, | |
| 51 | + | user: true, | |
| 52 | + | }, | |
| 53 | + | }), | |
| 54 | + | ], | |
| 55 | + | }); | |
| 56 | + | ``` | |
| 57 | + | ||
| 58 | + | > Kenapa perlu `dotenv/config` manual: `instrument.ts` diimport paling awal, | |
| 59 | + | > sebelum NestJS `ConfigModule` sempat jalan dan load `.env`. Tanpa ini, | |
| 60 | + | > `process.env.SENTRY_DSN` masih `undefined` pas `Sentry.init()` dipanggil, | |
| 61 | + | > dan SDK diam-diam gak akan pernah ngirim event (cuma warning | |
| 62 | + | > "No DSN provided" di log kalau `debug: true`). | |
| 63 | + | ||
| 64 | + | ## 5. Import di baris paling atas `main.ts` | |
| 65 | + | ||
| 66 | + | ```ts | |
| 67 | + | import './instrument'; // HARUS baris pertama, sebelum import apapun lain | |
| 68 | + | ||
| 69 | + | import { NestFactory } from '@nestjs/core'; | |
| 70 | + | // ...import lain seperti biasa | |
| 71 | + | ``` | |
| 72 | + | ||
| 73 | + | Penting: | |
| 74 | + | - **Tanpa** ekstensi `.ts` di akhir (`'./instrument'`, bukan `'./instrument.ts'`) | |
| 75 | + | - File-nya harus ada di `src/` (folder yang sama dengan `main.ts`) | |
| 76 | + | ||
| 77 | + | ## 6. Register `SentryModule` & exception filter global di `app.module.ts` | |
| 78 | + | ||
| 79 | + | ```ts | |
| 80 | + | import { APP_FILTER } from '@nestjs/core'; | |
| 81 | + | import { SentryModule, SentryGlobalFilter } from '@sentry/nestjs/setup'; | |
| 82 | + | ||
| 83 | + | @Module({ | |
| 84 | + | imports: [ | |
| 85 | + | SentryModule.forRoot(), // HARUS import PERTAMA di array imports | |
| 86 | + | // ...module lain | |
| 87 | + | ], | |
| 88 | + | providers: [ | |
| 89 | + | { provide: APP_FILTER, useClass: SentryGlobalFilter }, // HARUS provider PERTAMA | |
| 90 | + | // ...provider lain | |
| 91 | + | ], | |
| 92 | + | }) | |
| 93 | + | export class AppModule {} | |
| 94 | + | ``` | |
| 95 | + | ||
| 96 | + | ## 7. Kalau project sudah punya custom exception filter | |
| 97 | + | ||
| 98 | + | Ini kasus yang sering kelewat. `SentryGlobalFilter` cuma nangkep exception yang | |
| 99 | + | **gak** ketangkep filter lain duluan. Kalau ada filter spesifik seperti: | |
| 100 | + | ||
| 101 | + | ```ts | |
| 102 | + | @Catch(PrismaClientKnownRequestError) | |
| 103 | + | export class SomeCustomFilter implements ExceptionFilter { ... } | |
| 104 | + | ``` | |
| 105 | + | ||
| 106 | + | ...exception yang match `@Catch(...)` itu **tidak akan pernah** sampai ke | |
| 107 | + | `SentryGlobalFilter`, walaupun `SentryModule` sudah ke-setup benar. Solusinya: | |
| 108 | + | tambahin `Sentry.captureException(exception)` manual di filter itu. | |
| 109 | + | ||
| 110 | + | Aturan yang dipakai: **status 5xx / error tak terduga → capture. Status 4xx yang | |
| 111 | + | memang disengaja (validasi, not found, unauthorized) → boleh skip**, karena itu | |
| 112 | + | flow normal, bukan bug. | |
| 113 | + | ||
| 114 | + | Helper biar konsisten di semua filter: | |
| 115 | + | ||
| 116 | + | ```ts | |
| 117 | + | // src/common/sentry-report.util.ts | |
| 118 | + | import * as Sentry from '@sentry/nestjs'; | |
| 119 | + | ||
| 120 | + | export function reportIfUnexpected(exception: unknown, httpStatus: number) { | |
| 121 | + | if (httpStatus >= 500) { | |
| 122 | + | Sentry.captureException(exception); | |
| 123 | + | } | |
| 124 | + | } | |
| 125 | + | ``` | |
| 126 | + | ||
| 127 | + | Panggil di ujung `catch()` tiap custom filter: | |
| 128 | + | ||
| 129 | + | ```ts | |
| 130 | + | reportIfUnexpected(exception, status); | |
| 131 | + | ``` | |
| 132 | + | ||
| 133 | + | **Checklist:** cari semua custom filter di project sebelum anggap setup selesai: | |
| 134 | + | ||
| 135 | + | ```bash | |
| 136 | + | grep -rln "@Catch(" src/ --include="*.ts" | grep -v node_modules | |
| 137 | + | ``` | |
| 138 | + | ||
| 139 | + | ## 8. Scrub data sensitif (opsional tapi disarankan) | |
| 140 | + | ||
| 141 | + | Karena `sendDefaultPii: true` bikin request body ikut terkirim penuh, tambahin | |
| 142 | + | `beforeSend` di `instrument.ts` buat mask field sensitif: | |
| 143 | + | ||
| 144 | + | ```ts | |
| 145 | + | Sentry.init({ | |
| 146 | + | // ...config di atas | |
| 147 | + | beforeSend(event) { | |
| 148 | + | const sensitiveFields = ['password', 'token', 'secret']; | |
| 149 | + | if (event.request?.data) { | |
| 150 | + | for (const field of sensitiveFields) { | |
| 151 | + | if (event.request.data[field]) event.request.data[field] = '[FILTERED]'; | |
| 152 | + | } | |
| 153 | + | } | |
| 154 | + | return event; | |
| 155 | + | }, | |
| 156 | + | }); | |
| 157 | + | ``` | |
| 158 | + | ||
| 159 | + | ## 9. Testing | |
| 160 | + | ||
| 161 | + | Penting: `SentryGlobalFilter` **tidak** mengirim `HttpException` bawaan NestJS | |
| 162 | + | (`NotFoundException`, `BadRequestException`, dll) secara default — itu dianggap | |
| 163 | + | flow kontrol normal, bukan bug. Buat testing, trigger error yang beneran | |
| 164 | + | unhandled, misal lewat constraint DB (contoh Prisma `create()` dengan data yang | |
| 165 | + | melanggar unique constraint), bukan `throw new HttpException(...)`. | |
| 166 | + | ||
| 167 | + | Cara cepat verifikasi: | |
| 168 | + | 1. Trigger error via endpoint yang ada request body/query params | |
| 169 | + | 2. Cek dashboard → tab **Issues** → buka issue-nya | |
| 170 | + | 3. Pastikan section **Request** ada: Body, Query String, Headers, IP | |
| 171 | + | ||
| 172 | + | ## 10. Troubleshooting | |
| 173 | + | ||
| 174 | + | | Gejala | Kemungkinan penyebab | | |
| 175 | + | |---|---| | |
| 176 | + | | `Cannot find module './instrument.ts'` | Ada `.ts` literal di baris import `main.ts`, atau `dist/` lama ke-cache — hapus `dist` dan build ulang | | |
| 177 | + | | `Cannot find module './instrument'` padahal sudah tanpa `.ts` | File `instrument.ts` gak ada di `src/`, atau salah folder | | |
| 178 | + | | `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` | | |
| 179 | + | | 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 | | |
| 180 | + | | Request body / query params kosong di dashboard | Konfigurasi `requestDataIntegration` secara eksplisit (lihat bagian 4), jangan andelin default SDK | | |
| 181 | + | | Error yang "seharusnya" muncul tapi gak ada | Kemungkinan ketangkep custom exception filter duluan — lihat bagian 7 | | |
| 182 | + | ||
| 183 | + | ## Referensi | |
| 184 | + | ||
| 185 | + | - Dokumentasi resmi Sentry untuk NestJS: https://docs.sentry.io/platforms/javascript/guides/nestjs/ | |
Newer
Older