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.tscòn rỗng, nên không Worker, không trang nào gửi gì cho Sentry. Gácscripts/check-sentry.mjs(chạy quascripts/run-guards.mjsở jobchangescủaci-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 Sentry | DSN | App | Chạy ở |
|---|---|---|---|
conan-workers | SENTRY_DSN_WORKERS | api.conan.school, conan-tool-plane, phần SSR của www | Cloudflare Workers / Pages Functions |
conan-web | SENTRY_DSN_WEB | www, tokyo (cả inside6.com), admin | Trì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ắt | File |
|---|---|---|
| api | withSentry bọc fetch + scheduled (lỗi lọt khỏi Hono, lỗi scheduled ngoài attempt) | src/shared/sentry.ts, src/index.ts |
| api | app.onError, chỉ status >= 500, tag route, request_id (gắn thẳng lên sự kiện) | src/index.ts |
| api | 5xx tự trả (c.json({ error }, 500), 246 chỗ), fingerprint ['http-5xx', method, route] | src/middlewares/logger.ts |
| api | attempt() của cron, tag cron + job | src/index.ts |
| api | tunnel cho admin: POST /api/system/sentry-tunnel | src/domains/system/sentry-tunnel.ts |
| tool-plane | withSentry + app.onError (giữ nguyên hành vi mặc định của Hono) + nhánh 500 của /mcp | src/sentry.ts, src/index.ts |
| www SSR | sentryPagesPlugin dựng scope theo request; handleError (lỗi loader/action) và onError của renderToReadableStream | functions/_middleware.ts, app/entry.server.tsx |
| www trình duyệt | entry.client.tsx (init trước hydrate, onUncaughtError), ErrorBoundary gốc | app/lib/sentry.client.ts, app/root.tsx |
| tokyo | init trong main.tsx, RouteError báo lỗi, onUncaughtError | app/lib/sentry.ts, app/main.tsx |
| admin | init 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ôngbrowserTracingIntegration, gỡ integrationBrowserSession(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ắnsentry-trace/baggagevà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;userchỉ cònid; 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.beforeBreadcrumblàm tương tự chourl/from/tocủa breadcrumb.dsnluôn là chuỗi, kể cả rỗng: khidsnlàundefined,@sentry/cloudflarerơi về biến môi trườngSENTRY_DSN, tức một biến đặt nhầm ở dashboard bật Sentry mà repo không hề biết.setUserchỉ{ 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_WEBrỗ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-assertioncủ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.ymltruyề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 đọcimport.meta.env.VITE_SENTRY_RELEASE, đặt ởenvcủ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àdevelopmentthì một production mất biến trông như tiếng ồn của máy dev, và mọi bộ lọcenvironment:productionthấy không lỗi nào trong lúc api đang hỏng. Bước smoke sau deploy soreleaseở/api/system/diagnosticsvới SHA. - www SSR:
functions/_middleware.tskhông đi qua Vite, nên nó đọcSENTRY_RELEASEmàentry.server.tsxexport từ bản build, dự phòng bằngCF_PAGES_COMMIT_SHAcủ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ằngsentry-cli sourcemaps inject) khi đã có secretSENTRY_AUTH_TOKEN, chưa có thì in notice "Sentry chưa bật"; rồi "Không publish source map" luôn xoá**/*.maptrướcwrangler pages deploy, có Sentry hay không.'hidden'một mình không đủ: nó chỉ bỏ chú thíchsourceMappingURL, file.mapvẫn nằm trongbuild/client/distvà sẽ được phục vụ công khai, đoán tên chunk từ HTML là tải được, vớiadmin-conan-school.pages.devkhông nằm sau Access. Gác luật k đỏ nếu mộtvite.configbậ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ằngwrangler deploy --dry-run --outdirrồi tảiindex.js.maplê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/**trongpaths. 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:
| Worker | Trước | Sau |
|---|---|---|
| api | 5431.96 KiB / gzip 1064.34 KiB | 5942.63 KiB / gzip 1178.95 KiB |
tool-plane (đã có nodejs_compat) | 923.39 KiB / gzip 157.70 KiB | 1422.25 KiB / gzip 269.91 KiB |
| www Pages Functions | 4.06 MB / gzip 0.73 MB | 4.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
| Sentry | Sổ lỗi tự động | |
|---|---|---|
| Thấy gì | Từng lỗi runtime kèm stack, release, breadcrumb, request_id | Lượ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 động | Cả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 }ở đầusrc/index.ts) quainstrumentWorkflowWithSentry. 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) quainstrumentDurableObjectWithSentry. - Frontend còn lại: media, play, mam, studio, video.
- Lệnh
sentrychoscripts/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
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àiMườ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 interfaceDataCollectioncủ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(jobchangescủa CI) thì luật này chỉ in ghi chú; job api chạy lại với--require-sdksaunpm ci. - Luật i không đọc mã bằng regex: nó chạy thật
sentry.tstrong 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 (productionkhi có release,no-releasekhi 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-guardsLuậ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 quacheck:guardscủ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ảngGUARDS: dòng{ id: 'sentry', run: [NODE, 'scripts/check-sentry.mjs'] }. Đó là danh sách gác duy nhất: CI (jobchanges),check:guards,qc:nightlyvà hook trước push cùng đọc.scripts/ci/select-jobs.mjs: hằngOBSnằm trong luật của api, tool_plane, admin, com, www. Luật m không đọc chữ mà gọiselectJobsvới một PR chỉ đổipackages/observability/sentry.tsvà đòi đủ năm job bật.- Job api của
ci-checks.yml:
- 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
- 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ìmtê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. - 404 của bot thành lỗi.
handleErrorcủa React Router nhận cảErrorResponse404 ("No route matches URL"). Báo thẳng là mỗi lượt bot dò/.env,/wp-adminthà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. - Tokyo dựng id người dùng rơi về email. Loader ở
root.tsxlấyidentity.id || identity.sub || identity.email; id vắng thì email thành "id", và chuỗi đó đi sang GTM dưới tênuser_id. Sentry và GTM giờ cùng dùngtelemetryUserId = identity.id || null. .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":
'hidden'mà vẫn công khai. Bản đầu bậtsourcemap: '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.maplên production, Sentry vẫn ngủ nên không ai hưởng lợi. Đã tắt map; luật k.- Gác chưa được nối mà tài liệu nói nó chạy.
check-sentry.mjsvàtest:sentry-tunnelkhông được gọi ở đâu, bộ lọcchangeskhông biếtpackages/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. - 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 environmentdevelopment, không release. Đã truyền ở_deploy-cloudflare.yml, đổi tên dự phòng thànhno-release; luật l. - 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àcaptureExceptioncủa@sentry/core11.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ằngSentry.isEnabled(); tắt thì màn hình nói thẳng "lỗi này CHƯA được báo đi đâu". - 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. - 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:
- 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ó. - Mỗi project: Settings, Client Keys (DSN), chép DSN. Dán vào
packages/observability/dsn.ts:conan-webvàoSENTRY_DSN_WEB,conan-workersvàoSENTRY_DSN_WORKERS. Mở PR; gác kiểm định dạng, dòng::warning:: Sentry CHƯA BẬTbiến mất khỏi CI. - Mỗi project: Settings, Security & Privacy: bật Prevent Storing of IP Addresses, giữ Data Scrubber bật.
- 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.
- 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ạynpx @sentry/wizardtrong repo: nó ghi token vào.env.sentry-build-plugin. - Cảnh báo: mỗi project một rule "A new issue is created" gửi email. Đừng lọc theo
environment:productioncho tới khi bước smoke xác nhậnreleaseđúng SHA, nếu không lỗi của một bản deploy thiếu release rơi vàono-releasevà không ai được báo. - Merge PR điền DSN. Năm workflow deploy chạy lại vì
pathscủa chúng cópackages/observability/**. - 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-tunnelvới thânxtrả 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 trongsetTimeoutđi quawindow.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-webvới tagapp:admintrong 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-workerssau một ngày có lưu lượng.
- api đã lên bản có DSN: