# Node.js (SDK tương thích Sentry)

> Dùng SDK Sentry chính thức cho Node.js với DSN Belvador và hàm lọc dữ liệu nhạy cảm do Belvador cung cấp.

Với máy chủ Node.js, Belvador dùng **SDK Sentry chính thức cho Node.js** với DSN Belvador của bạn. Belvador bổ sung các tuỳ chọn khởi tạo ở dưới và bộ lọc dữ liệu của chính Belvador từ `@belvador/core/sentry` — đúng đoạn mã SDK của Belvador chạy — để xoá body request, cookie và thông tin đăng nhập trước khi bất cứ thứ gì rời máy chủ của bạn.

> [!NOTE]
> `@sentry/node` là SDK của Sentry, không phải gói của Belvador; Belvador không chứng nhận các tích hợp framework của nó. Belvador chạy đúng công thức này với `@sentry/node` 11.9.0 tới một node thật: lỗi chưa bắt làm sập tiến trình, kết quả `{ data, error }`, và các lỗi được `@belvador/spool` giữ lại khi node không liên lạc được — qua một lần `SIGKILL` và qua một lần sập vì hết bộ nhớ — đều tới đúng một lần. Node của bạn lọc thêm một lần trước khi lưu — xem [Bảo vệ dữ liệu](https://belvador.dev/vi/docs/privacy).

## Trước khi bắt đầu

- Một project có nền tảng **Node.js** và DSN của nó.
- Phiên bản Node.js được bản `@sentry/node` hiện tại hỗ trợ (xem [hướng dẫn Node.js của Sentry](https://docs.sentry.io/platforms/javascript/guides/node/)).

## 1. Cài đặt

```bash
npm install @sentry/node@11.9.0 @belvador/core@0.6.1
```

`@belvador/core` chỉ được cài để dùng lối vào ổn định `@belvador/core/sentry`: bộ lọc của Belvador và `captureResult` cho SDK Sentry chính thức. Nó không tải mã Sentry riêng nào.

## 2. Tạo `instrument.mjs`

```js title="instrument.mjs"
import * as Sentry from "@sentry/node";
import { scrubBreadcrumb, scrubEvent } from "@belvador/core/sentry";

Sentry.init({
  dsn: process.env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>",
  release: process.env.APP_VERSION,
  environment: process.env.NODE_ENV,
  sendDefaultPii: false,
  tracesSampleRate: 0,
  maxBreadcrumbs: 50,
  maxValueLength: 8192,
  // Belvador's scrubbing: request bodies, cookies and credentials never leave the app.
  beforeSend: scrubEvent,
  beforeBreadcrumb: scrubBreadcrumb,
});
```

Ý nghĩa các tuỳ chọn:

| Tuỳ chọn | Vì sao |
|---|---|
| `dsn` | Đọc `BELVADOR_DSN` từ biến môi trường, nếu không có thì dùng DSN của project |
| `release`, `environment` | Phiên bản và môi trường của ứng dụng, lấy từ `APP_VERSION` và `NODE_ENV` |
| `sendDefaultPii: false` | SDK không tự thêm địa chỉ IP, cookie hay thông tin người dùng |
| `tracesSampleRate: 0` | SDK Sentry không gửi span; muốn có traces thì dùng [OpenTelemetry](https://belvador.dev/vi/docs/otel/node) |
| `maxBreadcrumbs: 50`, `maxValueLength: 8192` | Cùng giới hạn với SDK của Belvador |
| `beforeSend: scrubEvent` | Xoá body request, cookie, header xác thực và header IP người dùng; che giá trị nằm dưới tên nhạy cảm và thông tin đăng nhập trong văn bản |
| `beforeBreadcrumb: scrubBreadcrumb` | Cùng bộ lọc cho breadcrumb (URL chứa token, giá trị được ghi log) |

## 3. Nạp trước ứng dụng

```bash
node --import ./instrument.mjs server.js
```

Cờ `--import` nạp phần theo dõi lỗi trước tiên trong ứng dụng ES module. Với ứng dụng CommonJS, thay vào đó gọi `require("./instrument")` ở dòng đầu tiên của file entry (đổi tên file thành `instrument.js` và dùng `require` trong đó).

## Framework web

`@sentry/node` tự theo dõi các framework phổ biến. Express, Fastify, Koa và các framework khác cần thêm một dòng để báo lỗi từ error handler của chúng — làm theo phần framework trong [hướng dẫn Node.js của Sentry](https://docs.sentry.io/platforms/javascript/guides/node/) và giữ nguyên các tuỳ chọn `init` ở trên. Belvador chưa kiểm chứng từng framework.

## Báo những lỗi bạn đã xử lý

```js
import * as Sentry from "@sentry/node";

try {
  await chargeCard(order);
} catch (error) {
  Sentry.captureException(error);
  throw new PaymentFailed(order.id);
}
```

## Lỗi cơ sở dữ liệu không ném

Supabase và PostgREST trả `{ data, error }` thay vì ném lỗi. Gắn `captureResult` vào SDK Sentry của bạn một lần:

```js title="db.mjs"
// captureResult on the app's own Sentry SDK (@sentry/node, @sentry/bun or @sentry/deno).
import * as Sentry from "@sentry/node";
import { captureResultWith } from "@belvador/core/sentry";

export const captureResult = captureResultWith(Sentry);
```

Rồi bọc từng lời gọi; kết quả trả về nguyên vẹn và lỗi được báo với kiểu theo mã Postgres. Các kiểu và tuỳ chọn: [Lỗi cơ sở dữ liệu](https://belvador.dev/vi/docs/sdk/database-errors).

## Trên VPS: giữ lỗi qua các lần sập

Mặc định `@sentry/node` giữ lỗi chưa gửi trong bộ nhớ: một lần sập, khởi động lại hay deploy đúng lúc node không liên lạc được sẽ làm mất chúng. Thêm `@belvador/spool` và dùng `instrument.mjs` này thay thế:

```js title="instrument.mjs"
import * as Sentry from "@sentry/node";
import { scrubBreadcrumb, scrubEvent } from "@belvador/core/sentry";
import { withSpool } from "@belvador/spool";

Sentry.init({
  dsn: process.env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>",
  release: process.env.APP_VERSION,
  environment: process.env.NODE_ENV,
  sendDefaultPii: false,
  tracesSampleRate: 0,
  maxBreadcrumbs: 50,
  maxValueLength: 8192,
  // Belvador's scrubbing: request bodies, cookies and credentials never leave the app.
  beforeSend: scrubEvent,
  beforeBreadcrumb: scrubBreadcrumb,
  // Errors that cannot be sent wait on disk and go out after a restart.
  transport: withSpool(Sentry.makeNodeTransport, {
    dir: process.env.BELVADOR_SPOOL_DIR ?? "/var/lib/my-app/belvador",
  }),
});
```

- Trỏ `BELVADOR_SPOOL_DIR` tới một thư mục còn nguyên sau khi khởi động lại và chỉ user của app đọc được — với systemd, `StateDirectory=my-app` cho bạn `/var/lib/my-app`.
- Lỗi chờ ở đó (tối đa 64 MiB, 72 giờ) và được tiến trình sau gửi khi node trả lời. Đã kiểm: các lỗi xảy ra khi node không liên lạc được đều tới sau khi tiến trình bị kill bằng `SIGKILL`, và sau khi nó chết vì hết bộ nhớ.
- Bản thân lần sập vì hết bộ nhớ không được báo — tiến trình chết trước khi bất kỳ code nào kịp chạy — nhưng mọi lỗi nó đã giữ trước đó thì có.

Chi tiết: [Offline và gửi bền](https://belvador.dev/vi/docs/sdk/offline).

## Cài bằng AI agent

Dán đoạn này vào Claude Code, Cursor hay một coding agent khác. Nó chứa các tệp ở trên cho máy chủ giữ lỗi qua các lần sập, cùng các quy tắc agent phải theo:

````text
Belvador errors: Node.js (@sentry/node)

Tested with: @sentry/* 11.9.0
Guide: https://belvador.dev/vi/docs/sdk/node

Mục tiêu
Báo lỗi của máy chủ này về Belvador bằng SDK Sentry chính thức và bộ lọc của Belvador, đúng như các tệp dưới đây. Không thêm SDK lỗi nào khác: mỗi tiến trình một Sentry.init.

Quy tắc
Chép nguyên các tệp và giữ mọi tuỳ chọn của Sentry.init: sendDefaultPii: false, tracesSampleRate: 0, maxBreadcrumbs: 50, beforeSend: scrubEvent, beforeBreadcrumb: scrubBreadcrumb. Đọc DSN từ BELVADOR_DSN; không tự đặt thêm biến môi trường khác. Không đổi xác thực, định tuyến hay logic nghiệp vụ. Báo những lỗi code đã tự xử lý bằng captureException (report() trên Supabase Edge Functions) và bọc bằng captureResult các kết quả Supabase hay PostgREST trả lỗi thay vì ném, không đổi cách code dùng kết quả đó.

```bash
npm install @sentry/node@11.9.0 @belvador/core@0.6.1
```

```bash
npm install @belvador/spool@0.6.1
```

instrument.mjs
```javascript
import * as Sentry from "@sentry/node";
import { scrubBreadcrumb, scrubEvent } from "@belvador/core/sentry";
import { withSpool } from "@belvador/spool";

Sentry.init({
  dsn: process.env.BELVADOR_DSN ?? "https://<public-key>@ingest.belvador.dev/<project-id>",
  release: process.env.APP_VERSION,
  environment: process.env.NODE_ENV,
  sendDefaultPii: false,
  tracesSampleRate: 0,
  maxBreadcrumbs: 50,
  maxValueLength: 8192,
  // Belvador's scrubbing: request bodies, cookies and credentials never leave the app.
  beforeSend: scrubEvent,
  beforeBreadcrumb: scrubBreadcrumb,
  // Errors that cannot be sent wait on disk and go out after a restart.
  transport: withSpool(Sentry.makeNodeTransport, {
    dir: process.env.BELVADOR_SPOOL_DIR ?? "/var/lib/my-app/belvador",
  }),
});
```

db.mjs
```javascript
// captureResult on the app's own Sentry SDK (@sentry/node, @sentry/bun or @sentry/deno).
import * as Sentry from "@sentry/node";
import { captureResultWith } from "@belvador/core/sentry";

export const captureResult = captureResultWith(Sentry);
```

Kiểm tra
Chạy app trên máy với BELVADOR_DSN đã đặt và gây một lỗi thử (một route hay script ngắn ném lỗi). Xác nhận nó hiện thành issue trong project Belvador. Không gửi lỗi thử lên production nếu người dùng chưa đồng ý.

Báo cáo
Liệt kê các tệp đã đổi, gói đã thêm kèm đúng phiên bản, biến môi trường app giờ cần, lỗi thử bạn thấy đã tới, và những gì chưa kiểm được.
````

## Kiểm tra

Chạy một script ngắn với cùng phần theo dõi:

```js
import * as Sentry from "@sentry/node";

Sentry.captureException(new Error("Belvador Node.js test"));
await Sentry.flush(2000);
```

Issue hiện trong project sau vài giây.

## Giới hạn cần biết

- Bộ lọc của Belvador dựa trên mẫu ký tự. Bí mật nằm dưới tên lạ, hoặc nằm trong một câu thông báo tự do, có thể lọt qua.
- Node lưu event lỗi, tệp đính kèm, check-in và log của Sentry, và đếm báo cáo gửi. Giữ `tracesSampleRate: 0`; dữ liệu tracing và session theo dõi release sẽ bị đếm vào **Chưa hỗ trợ** trong số liệu nhận của project và bị bỏ. `@sentry/node` 11 mặc định gửi session cho từng request và các lệnh `Sentry.logger`, nên bạn sẽ thấy một số nhỏ ở mục **Chưa hỗ trợ**.
- Job định kỳ và worker báo được lượt chạy bằng `Sentry.withMonitor` — xem [Monitor bằng SDK Sentry chính thức](https://belvador.dev/vi/docs/monitors/sentry-sdks).
- Windows service: dùng cùng công thức, nhưng Belvador chưa chạy nó trên Windows.
