Skip to content

Sentry: lỗi kèm stack, release và cảnh báo ​

Hai repo (16.10.2026)

Từ 16.10.2026 www (cả SSR) nằm ở repo conan-web; check-sentry.mjs ở repo này đếm bốn app (api, tool-plane, tokyo, admin). Phần về www dưới đây vẫn mô tả đúng mã bên đó. Xem Hai repo.

Trạng thái (14.10.2026): ĐÃ NỐI MÃ, CHƯA BẬT. Hai DSN trong packages/observability/dsn.ts còn rỗng, nên không Worker, không trang nào gửi gì cho Sentry. Gác scripts/check-sentry.mjs (chạy qua scripts/run-guards.mjs ở job changes của ci-checks.yml) in một dòng ::warning:: Sentry CHƯA BẬT: DSN rỗng ... ở mỗi lượt CI. Chừng nào dòng đó còn hiện, một dashboard Sentry trống không có nghĩa là không có lỗi. Gác tự kiểm nó đã được nối vào CI (luật m): đỏ nếu bước đó vắng, vì không có bước đó thì dòng cảnh báo không in ở đâu cả. Source map: chưa sinh (mục Release và source map), nên stack trace của trình duyệt sẽ là mã đã nén cho tới khi bước tải map lên Sentry được thêm. Cách bật: mục Bật Sentry ở cuối trang.

Hai project, năm app ​

Project SentryDSNAppChạy ở
conan-workersSENTRY_DSN_WORKERSapi.conan.school, conan-tool-plane, phần SSR của wwwCloudflare Workers / Pages Functions
conan-webSENTRY_DSN_WEBwww, tokyo (cả inside6.com), adminTrình duyệt

Mọi sự kiện mang tag app (api, tool-plane, www-ssr, www, tokyo, admin). Tokyo thêm tag brand (conan hay inside6): Sentry chạy trên inside6.com, khác GTM, vì lỗi của trang đó là lỗi của chính mã này chứ không phải tài sản đo lường của thương hiệu khác.

DSN là giá trị công khai (nó nằm trong JavaScript gửi xuống mọi trình duyệt), nên nó ở trong git, khai đúng một chỗ: packages/observability/dsn.ts. app/env.public.ts của www và tokyo re-export từ đó, không khai lại. Thứ không bao giờ vào git là auth token của Sentry (tải source map), đó là secret SENTRY_AUTH_TOKEN của CI.

Bắt ở đâu ​

AppĐiểm bắtFile
apiwithSentry bọc fetch + scheduled (lỗi lọt khỏi Hono, lỗi scheduled ngoài attempt)src/shared/sentry.ts, src/index.ts
apiapp.onError, chỉ status >= 500, tag route, request_id (gắn thẳng lên sự kiện)src/index.ts
api5xx tự trả (c.json({ error }, 500), 246 chỗ), fingerprint ['http-5xx', method, route]src/middlewares/logger.ts
apiattempt() của cron, tag cron + jobsrc/index.ts
apitunnel cho admin: POST /api/system/sentry-tunnelsrc/domains/system/sentry-tunnel.ts
tool-planewithSentry + app.onError (giữ nguyên hành vi mặc định của Hono) + nhánh 500 của /mcpsrc/sentry.ts, src/index.ts
www SSRsentryPagesPlugin dựng scope theo request; handleError (lỗi loader/action) và onError của renderToReadableStreamfunctions/_middleware.ts, app/entry.server.tsx
www trình duyệtentry.client.tsx (init trước hydrate, onUncaughtError), ErrorBoundary gốcapp/lib/sentry.client.ts, app/root.tsx
tokyoinit trong main.tsx, RouteError báo lỗi, onUncaughtErrorapp/lib/sentry.ts, app/main.tsx
admininit qua tunnel, Sentry.ErrorBoundary bọc cả app với màn hình lỗi; mã sự kiện chỉ hiện khi SDK thật sự gửi (isEnabled())src/lib/sentry.ts, src/main.tsx, src/ErrorFallback.tsx

Vì sao api có bốn điểm bắt chứ không chỉ withSentry ​

app.onError biến mọi lỗi ném ra thành một phản hồi 500 tử tế, nên withSentry không bao giờ thấy lỗi nào bay qua nó. Và phần lớn lỗi của api không ném gì cả: 246 handler tự trả 500 bằng tay. Chỉ bọc withSentry thì Sentry xanh tuyệt đối trong lúc api đang trả 500 hàng loạt. loggerMiddleware là chỗ duy nhất thấy mọi phản hồi, nên nó báo 5xx khi c.error vắng (lỗi đã ném thì app.onError báo rồi). Sự kiện mang câu báo lỗi trong thân phản hồi, nhưng fingerprint gom theo route: cùng một route hỏng với hai câu khác nhau vẫn là một việc phải sửa.

request_id là sợi dây nối: cùng một giá trị nằm ở tag Sentry, ở dòng log Workers Logs, và ở request_id trong phản hồi lỗi người dùng nhìn thấy. Ba hàm capture* gắn nó thẳng lên sự kiện của chúng (c.get('requestId')), không dựa vào tag dùng chung. tagRequest ở apiStandards bỏ qua OPTIONS và HEAD: withSentry cho hai phương thức đó đi thẳng mà không dựng scope riêng, nên setTag của chúng rơi vào scope mặc định của isolate, và lượt cron kế tiếp (nhân bản scope mặc định) gửi lỗi mang request_id của một preflight CORS không liên quan. Đã thử ở máy: bỏ dòng bỏ qua thì sự kiện cron mang đúng request_id của lượt OPTIONS gửi trước đó.

ErrorBoundary và lỗi loader ​

Lỗi loader của React Router không đi qua React: router đặt nó vào state rồi vẽ ErrorBoundary. onCaughtError/onUncaughtError của React không thấy nó. Nên ErrorBoundary gốc ở www và RouteError ở tokyo tự báo trong một useEffect. React chỉ được giao onUncaughtError (lỗi lọt khỏi mọi boundary); thêm onCaughtError là báo hai lần. 4xx không báo; Response 5xx do loader ném ra thì báo (máy chủ không gọi handleError cho nó).

Ở www, lỗi ném ở loader máy chủ thì ErrorBoundary trình duyệt không báo lại. React Router chế độ production thay nó bằng new Error("Unexpected Server Error") và xoá stack trước khi gửi xuống, cả trong HTML SSR lẫn trong .data của lượt chuyển trang (sanitizeError ở server-runtime). Bản gốc có stack đã đi vào conan-workers qua handleError; báo thêm bản đã xoá là hai sự kiện cho một lỗi, và mọi lỗi máy chủ của mọi route gom về MỘT issue chung chung ở conan-web, không stack, không route. reportBoundaryError nhận ra bản đã xoá bằng đúng hai dấu hiệu đó (thông báo khớp, stack rỗng).

Không dữ liệu cá nhân ​

Bẫy mặc định của v11 ​

Sentry v11 bỏ sendDefaultPii và thay bằng dataCollection, mà mặc định của nó là thu hết. Đọc thẳng từ @sentry/core 11.7 (resolveDataCollectionOptions): header request và response, cookie, tham số truy vấn, tự điền user.* (kể cả suy IP), và httpBodies mặc định gồm incomingRequest. Với @sentry/cloudflare, dòng cuối nghĩa là SDK đọc thân của mọi request POST vào Worker (cỡ "medium") và gắn vào sự kiện: mật khẩu ở form đăng nhập, payload webhook thanh toán. Một Sentry.init({ dsn }) chép từ tài liệu mẫu chạy hoàn hảo và gửi tất cả những thứ đó đi, không lỗi, không cảnh báo.

Cài đặt của mình ​

Mọi lệnh khởi tạo lấy tuỳ chọn từ conanSentryOptions() trong packages/observability/sentry.ts (file không import gói nào, kiểu viết theo cấu trúc để biên dịch được ở cả năm app):

  • dataCollection: tắt đủ 11 khoá (userInfo, cookies, httpHeaders, httpBodies: [], urlQueryParams, graphQL, genAI, databaseQueryData, queues, stackFrameVariables, frameContextLines: 0).
  • Chỉ bắt lỗi: tracesSampleRate: 0, không Replay, không Feedback, không browserTracingIntegration, gỡ integration BrowserSession (phiên release health) và Console (log của api ghi actor_id, đường dẫn, có khi cả email người nhận thư; chúng đã ở Workers Logs).
  • tracePropagationTargets: []: mặc định phía server gắn sentry-trace/baggage vào mọi request đi ra, kể cả request ký sẵn tới R2 và request tới nhà cung cấp AI.
  • beforeSend (scrubEvent): cắt truy vấn và phần băm khỏi URL; xoá header, cookie, thân, chuỗi truy vấn của request; user chỉ còn id; bỏ extra; context chỉ giữ danh sách cho phép (trace, runtime, os, browser, device, app, culture, cloud_resource, react); che email, Bearer, JWT, token có tiền tố (cta_, ghp_, github_pat_, sk_...) trong thông báo lỗi. beforeBreadcrumb làm tương tự cho url/from/to của breadcrumb.
  • dsn luôn là chuỗi, kể cả rỗng: khi dsn là undefined, @sentry/cloudflare rơi về biến môi trường SENTRY_DSN, tức một biến đặt nhầm ở dashboard bật Sentry mà repo không hề biết.
  • setUser chỉ { id }. www không có danh tính, không gọi. Tool Plane không gọi (danh tính là agent, đã có trong audit_logs).

Đã thử thật bằng một máy ingest giả ở máy (14.10.2026): request mang cookie, Bearer, ?email= và ?token= vào api, www SSR và tool-plane; sự kiện tới nơi có URL không truy vấn, không header, email trong thông báo lỗi thành [email].

Đánh đổi có chủ ý: không gửi header nào nghĩa là cả User-Agent, nên sự kiện trình duyệt không có tên trình duyệt/hệ điều hành (Sentry suy ra chúng từ header đó). Muốn có thì nới đúng một chỗ: httpHeaders: { request: { allow: ['User-Agent'] }, response: false } trong noDataCollection(), và sửa luật i của gác cho khớp.

Bảo vệ thêm ở phía Sentry ​

Trong cả hai project: Settings, Security & Privacy, bật Prevent Storing of IP Addresses và Data Scrubber (mặc định đã bật). SDK đã báo infer_ip: never; hai công tắc này là lớp thứ hai cho ngày ai đó lỡ nới SDK.

Tunnel của admin ​

CSP của admin là connect-src 'self' (public/_headers) và giữ nguyên. Gửi thẳng sang máy chủ ingest thì trình duyệt chặn im lặng: SDK nuốt lỗi mạng của chính nó, trang chạy bình thường, dashboard trống, và trống trông y như "admin không có lỗi". Nới CSP cho một miền bên thứ ba trên trang quản trị là đổi một rủi ro lấy một tiện ích.

Admin gửi về /api/system/sentry-tunnel cùng origin; admin-router chuyển /api/* sang api, nơi sentry-tunnel.ts:

  • trả 404 kèm câu "Sentry chưa bật" khi SENTRY_DSN_WEB rỗng, không trả 200 giả vờ đã nhận;
  • đọc thân với trần 256 KB (không tin Content-Length), từ chối dòng đầu không phải JSON;
  • chỉ nhận phong bì có DSN cùng máy chủ và cùng số project với SENTRY_DSN_WEB, và dựng URL đích từ DSN của repo, không bao giờ từ DSN phong bì tự xưng: không thành open relay;
  • không chuyển tiếp header nào của người gửi (request tới đây mang cookie phiên và cf-access-jwt-assertion của người quản trị), không chuyển IP;
  • trả lại đúng mã của Sentry kèm x-sentry-rate-limits/retry-after, để SDK tự giãn nhịp gửi.

Không thêm header CORS nào: không có tracing nên không có sentry-trace/baggage, và admin gọi cùng origin. 5xx của chính tunnel không bị báo ngược vào Sentry. Phép kiểm: npm run test:sentry-tunnel (8 trường hợp, gồm máy chủ lạ và máy chủ có hậu tố đánh lừa).

Release và source map ​

  • Worker đọc env.CONAN_RELEASE. _deploy-cloudflare.yml truyền nó ở bước Deploy Worker: wrangler deploy --var CONAN_RELEASE:<SHA>, SHA lấy từ github.sha (mọi Worker deploy qua đó nhận biến này, vô hại với Worker không đọc nó). Trình duyệt đọc import.meta.env.VITE_SENTRY_RELEASE, đặt ở env của bước Build cùng file. Gác luật l đỏ nếu một trong hai dòng mất.
  • Tên môi trường suy từ release (environmentFor ở packages/observability/sentry.ts): có SHA là production, không có là no-release, cố ý KHÔNG gọi là development. Thiếu biến có thể là wrangler dev ở máy, nhưng cũng có thể là CI đánh rơi biến; gọi nó là development thì một production mất biến trông như tiếng ồn của máy dev, và mọi bộ lọc environment:production thấy không lỗi nào trong lúc api đang hỏng. Bước smoke sau deploy so release ở /api/system/diagnostics với SHA.
  • www SSR: functions/_middleware.ts không đi qua Vite, nên nó đọc SENTRY_RELEASE mà entry.server.tsx export từ bản build, dự phòng bằng CF_PAGES_COMMIT_SHA của Pages.
  • Source map 'hidden' ở www, tokyo, admin, và đường deploy (_deploy-cloudflare.yml) có hai bước đi kèm, thêm cùng PR: "Sentry source map (Pages)" tải map lên Sentry dưới release = SHA (gắn debug id bằng sentry-cli sourcemaps inject) khi đã có secret SENTRY_AUTH_TOKEN, chưa có thì in notice "Sentry chưa bật"; rồi "Không publish source map" luôn xoá **/*.map trước wrangler pages deploy, có Sentry hay không. 'hidden' một mình không đủ: nó chỉ bỏ chú thích sourceMappingURL, file .map vẫn nằm trong build/client / dist và sẽ được phục vụ công khai, đoán tên chunk từ HTML là tải được, với admin-conan-school.pages.dev không nằm sau Access. Gác luật k đỏ nếu một vite.config bật map (true/'hidden') mà bước xoá mất, và luôn đỏ với 'inline'. Admin trước 14.10.2026 để sourcemap: true: mã nguồn đầy đủ của trang quản trị đi kèm từng file JS (các bản deploy cũ vẫn còn map, xem mục việc của chủ repo).
  • Worker: map KHÔNG gửi lên Cloudflare bằng --upload-source-maps (map có trần riêng, vượt trần là deploy hỏng). Bước "Sentry source map (Worker)" bundle lại bằng wrangler deploy --dry-run --outdir rồi tải index.js.map lên Sentry dưới release = SHA; Sentry ghép theo release + ~/index.js.
  • Năm workflow deploy (api, tool-plane, www, com, admin) có packages/observability/** trong paths. Thiếu nó thì điền DSN, merge, và không app nào deploy lại: Sentry ngủ tiếp trong khi ai cũng tưởng đã bật. Gác luật h đỏ nếu thiếu.

nodejs_compat cho api ​

@sentry/cloudflare cần AsyncLocalStorage (node:async_hooks) để mỗi request có scope riêng. api trước đó không bật cờ nào (ngày tương thích 2026-07-19, nodejs_compat chỉ mặc định từ 2026-08-04), nên wrangler.toml, wrangler.migration-smoke.toml và wrangler.access-canary.toml thêm compatibility_flags = ["nodejs_compat"]. Bundle đo bằng wrangler deploy --dry-run:

WorkerTrướcSau
api5431.96 KiB / gzip 1064.34 KiB5942.63 KiB / gzip 1178.95 KiB
tool-plane (đã có nodejs_compat)923.39 KiB / gzip 157.70 KiB1422.25 KiB / gzip 269.91 KiB
www Pages Functions4.06 MB / gzip 0.73 MB4.49 MB / gzip 0.83 MB

Import ngoài duy nhất mới thêm là node:async_hooks. Một thay đổi hành vi đi kèm: với ngày tương thích từ 2025-04-01, nodejs_compat cho process.env chứa các biến và secret của Worker. Mã của api không đọc process ở đâu; thư viện nào rẽ nhánh theo typeof process thì bây giờ thấy nó có mặt.

Cấu hình kiểm khói wrangler.migration-smoke.toml chạy với TEST_MODE=true nên Sentry tắt ở đó (enabled: env.TEST_MODE !== 'true'), dù DSN đã điền.

Chia việc với sổ lỗi ​

SentrySổ lỗi tự động
Thấy gìTừng lỗi runtime kèm stack, release, breadcrumb, request_idLượt CI đỏ, số 5xx theo zone, exception Worker đếm theo ngày
Trả lời câu"Dòng mã nào, bản deploy nào, bắt đầu từ lúc nào""Lỗi nào lặp lại, chốt chặn nào chưa đủ"
Báo độngCảnh báo của Sentry (issue mới, tăng đột biến)Issue GitHub nhãn error-ledger, tự mở lại khi lặp

Không bật tính năng tạo issue GitHub của Sentry. Sổ lỗi đã là nơi duy nhất sinh issue cho lỗi runtime, theo dấu vân tay; hai nguồn cùng mở issue là hai issue cho một lỗi, với hai vòng đời khác nhau, và nhãn error-ledger-repeat (thước đo của "cấm lặp lại") mất nghĩa. Lệnh sentry cho sổ lỗi (kéo issue Sentry vào sổ theo dấu vân tay) là việc của giai đoạn 2.

Giai đoạn 2 (chưa làm) ​

  • 38 lớp Workflow của api (export { ...Workflow } ở đầu src/index.ts) qua instrumentWorkflowWithSentry. Chưa bọc vì instance workflow đang chạy sống qua lượt deploy, và hành vi phát lại (replay) của lớp bọc với các step đã xong chưa được kiểm. Lỗi trong step hiện chỉ nằm ở Workers Logs và ở bảng run của từng workflow.
  • Durable Object của play.conan.school (GameRoom, GameHistory) qua instrumentDurableObjectWithSentry.
  • Frontend còn lại: media, play, mam, studio, video.
  • Lệnh sentry cho scripts/error-ledger.mjs: đọc issue Sentry qua API (cần token chỉ đọc) và gộp vào sổ theo dấu vân tay.
  • onRecoverableError (lỗi hydrate) ở www: có ích, vì nó là triệu chứng của HTML cũ trong bộ đệm biên đi với bundle mới, nhưng tiện ích trình duyệt sửa DOM cũng gây ra nó; cần lọc trước khi bật.

Gác ​

bash
node scripts/check-sentry.mjs             # soi repo, tự chạy 33 mẫu tự kiểm trước
node scripts/check-sentry.mjs --self-test # chỉ tự kiểm + chạy thật packages/observability/sentry.ts
node scripts/check-sentry.mjs --require-sdk  # sau npm ci: so khoá dataCollection với SDK đã cài

Mười ba luật (a tới m), viết ở đầu script. Bốn luật đáng nói:

  • Luật j so 11 khoá của noDataCollection() với interface DataCollection của @sentry/core đã cài. Khi nâng SDK mà bản mới thêm một khoá, khoá đó mặc định bật; gác đỏ cho tới khi khoá được thêm vào. Không có node_modules (job changes của CI) thì luật này chỉ in ghi chú; job api chạy lại với --require-sdk sau npm ci.
  • Luật i không đọc mã bằng regex: nó chạy thật sentry.ts trong một tiến trình con có tách kiểu và đưa vào một sự kiện đầy email, token, IP, cookie, rồi kiểm cái còn lại; kiểm cả tên môi trường (production khi có release, no-release khi không).
  • Luật k, l đọc _deploy-cloudflare.yml: source map chỉ được bật khi bước deploy xoá *.map, và SHA phải đi vào cả Worker lẫn bước build.
  • Luật m: gác tự kiểm nó có được gọi không. Một gác không chạy thì không phải gác (skill ci-guards Luật 2), và với gác này còn tệ hơn: dòng ::warning:: Sentry CHƯA BẬT, cách duy nhất trạng thái ngủ lộ ra, không in ở đâu cả trong khi tài liệu nói nó in ở mọi lượt CI. Luật m đỏ khi thiếu bất kỳ mảnh nào dưới đây. Chạy gác ở máy (hay qua check:guards của vòng đêm) là thấy ngay một lần nối bị rơi.

Phần nối phải có (từ 14.10.2026, sau PR-A của CI và gác):

  • scripts/run-guards.mjs, mảng GUARDS: dòng { id: 'sentry', run: [NODE, 'scripts/check-sentry.mjs'] }. Đó là danh sách gác duy nhất: CI (job changes), check:guards, qc:nightly và hook trước push cùng đọc.
  • scripts/ci/select-jobs.mjs: hằng OBS nằm trong luật của api, tool_plane, admin, com, www. Luật m không đọc chữ mà gọi selectJobs với một PR chỉ đổi packages/observability/sentry.ts và đòi đủ năm job bật.
  • Job api của ci-checks.yml:
yaml
      - name: Tunnel Sentry không thành open relay
        working-directory: api.conan.school
        run: npm run test:sentry-tunnel
      - name: Khoá dataCollection khớp @sentry/core đã cài
        run: node scripts/check-sentry.mjs --require-sdk

--require-sdk CHỈ chạy luật j: job api lấy cây sparse (không có bốn app kia), các luật đọc cả repo chạy ở job changes. Bộ lọc OBS không phải trang trí: thiếu nó, PR điền DSN (chỉ đổi packages/observability/dsn.ts) không chạy typecheck hay build của app nào, CI xanh, rồi lúc merge paths của năm workflow deploy cùng nổ và một lỗi trong file đó đi thẳng lên production cả năm app.

Bẫy đã gặp khi dựng ​

  1. Gác bản đầu đếm hụt chỗ khởi tạo SSR của www. Sentry.sentryPagesPlugin<Env>(...) có đối số kiểu giữa tên hàm và dấu ngoặc, regex chỉ tìm tên(. Gác báo xanh với "www 1" trong khi www có hai chỗ; một lời gọi có kiểu tường minh tự viết tuỳ chọn sẽ lọt khỏi luật a. Đã sửa, kèm mẫu tự kiểm.
  2. 404 của bot thành lỗi. handleError của React Router nhận cả ErrorResponse 404 ("No route matches URL"). Báo thẳng là mỗi lượt bot dò /.env, /wp-admin thành một sự kiện (sổ lỗi Cloudflare đếm hơn 500 lượt một tuần, C11), chôn lỗi thật dưới tiếng ồn. Giờ bỏ qua mọi 4xx.
  3. Tokyo dựng id người dùng rơi về email. Loader ở root.tsx lấy identity.id || identity.sub || identity.email; id vắng thì email thành "id", và chuỗi đó đi sang GTM dưới tên user_id. Sentry và GTM giờ cùng dùng telemetryUserId = identity.id || null.
  4. .gitignore đưa token vào git. Dòng !.env.sentry-build-plugin (từ commit đầu tiên của repo) bỏ ignore đúng file mà trình cài đặt của Sentry ghi auth token vào. Đã đổi thành ignore.

Rà soát độc lập sau đó (cùng ngày) bắt thêm sáu chỗ, cả sáu thuộc loại "trông như đang chạy":

  1. 'hidden' mà vẫn công khai. Bản đầu bật sourcemap: 'hidden' ở www và tokyo (trước đó hai site không sinh map nào) trong khi bước xoá map chỉ là một dòng "việc của người khác" trong báo cáo. Merge là 254 file .map lên production, Sentry vẫn ngủ nên không ai hưởng lợi. Đã tắt map; luật k.
  2. Gác chưa được nối mà tài liệu nói nó chạy. check-sentry.mjs và test:sentry-tunnel không được gọi ở đâu, bộ lọc changes không biết packages/observability/, trong khi trang này và skill nói CI in cảnh báo ở mọi lượt. Phần nối nằm ở file dùng chung nên đi bằng đoạn dán; luật m làm cho việc quên dán thành màu đỏ thay vì im lặng.
  3. Production mang nhãn development. Mã nói "CI luôn truyền CONAN_RELEASE" mà không workflow nào truyền. Mọi lỗi api production sẽ đi với environment development, không release. Đã truyền ở _deploy-cloudflare.yml, đổi tên dự phòng thành no-release; luật l.
  4. Mã sự kiện không tồn tại. Màn hình lỗi của admin hiện "mã sự kiện" lấy từ ErrorBoundary, mà captureException của @sentry/core 11.7 trả một UUID mới kể cả khi không có client hay client đã tắt (scope.js). Với DSN rỗng, người dùng được bảo gửi một mã mà Sentry không có. Giờ quyết định bằng Sentry.isEnabled(); tắt thì màn hình nói thẳng "lỗi này CHƯA được báo đi đâu".
  5. Báo hai lần lỗi máy chủ của www (xem mục ErrorBoundary): một bản có stack ở conan-workers, một bản "Unexpected Server Error" không stack ở conan-web, gom mọi route về một issue.
  6. request_id rò sang sự kiện cron từ một lượt OPTIONS/HEAD (xem mục api).

Bật Sentry ​

Chỉ chủ tài khoản làm được:

  1. sentry.io, tạo tổ chức (nếu chưa có), rồi hai project: conan-workers (nền tảng Cloudflare Workers) và conan-web (nền tảng React). Bỏ qua bước "install the SDK" của trình hướng dẫn, mã đã có.
  2. Mỗi project: Settings, Client Keys (DSN), chép DSN. Dán vào packages/observability/dsn.ts: conan-web vào SENTRY_DSN_WEB, conan-workers vào SENTRY_DSN_WORKERS. Mở PR; gác kiểm định dạng, dòng ::warning:: Sentry CHƯA BẬT biến mất khỏi CI.
  3. Mỗi project: Settings, Security & Privacy: bật Prevent Storing of IP Addresses, giữ Data Scrubber bật.
  4. Settings, Integrations: không cài GitHub integration để tạo issue (xem mục chia việc). Nối GitHub chỉ để liên kết commit thì được.
  5. Source map (không bắt buộc để bật, làm sau cũng được): tạo Organization Auth Token (Settings, Auth Tokens, quyền project:releases + org:read), rồi ở repo GitHub (Settings, Secrets and variables, Actions) tạo bốn secret: SENTRY_AUTH_TOKEN (token), SENTRY_ORG (slug tổ chức), SENTRY_PROJECT_WEB (conan-web), SENTRY_PROJECT_WORKERS (conan-workers). Đường deploy đã có sẵn bước tải map, nên lượt deploy kế tiếp tự tải; notice "Sentry chưa bật" ở bước đó biến mất. Không chạy npx @sentry/wizard trong repo: nó ghi token vào .env.sentry-build-plugin.
  6. Cảnh báo: mỗi project một rule "A new issue is created" gửi email. Đừng lọc theo environment:production cho tới khi bước smoke xác nhận release đúng SHA, nếu không lỗi của một bản deploy thiếu release rơi vào no-release và không ai được báo.
  7. Merge PR điền DSN. Năm workflow deploy chạy lại vì paths của chúng có packages/observability/**.
  8. Kiểm đã bật thật, không tin dòng warning biến mất là đủ:
    • api đã lên bản có DSN: POST https://api.conan.school/api/system/sentry-tunnel với thân x trả 400 "Dòng đầu của phong bì không phải JSON", không còn 404 "Sentry chưa bật".
    • trình duyệt: mở admin, trong console gõ setTimeout(() => { throw new Error('sentry-thu') }) (lỗi ném trong setTimeout đi qua window.onerror, nơi SDK lắng nghe; lỗi ném thẳng ở dòng lệnh console thì không). Sự kiện phải hiện ở conan-web với tag app:admin trong vài giây; nó đi qua tunnel nên cũng kiểm luôn đường đó.
    • Workers: chờ lỗi thật đầu tiên, hoặc xem Stats của conan-workers sau một ngày có lưu lượng.

Tài liệu nội bộ nền tảng Conan School.