mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Removes Sentry tracing entirely (BullMQ idle polling burned 4.8M transactions in 2 days at the baked 0.1 rate), decouples PostHog sampling, and replaces the type-only error scrub with a vetted-field sanitizer plus SafeError/ToolInputError contracts. One classified capture path with per-signature throttles and a per-process ceiling makes storms impossible (NODE-1E was 4,541 events from one 30s loop). Browser errors move to a dedicated web Sentry project with their own source maps. Adds the SNAPOTTER_TELEMETRY runtime kill switch and silences test fleets. Crash fixes: remote 204/304 SSRF process kill (NODE-20), conversion-preset boot crash loop (NODE-21), Redis version preflight + unhandled subscribe rejection (NODE-1T), Sign PDF on plain-http origins (NODE-1K/1M), wavesurfer/pdf.js teardown rejections (NODE-1P/1N), bundle-import ZlibError to 400 (NODE-1Z), chart-maker input errors declassified (NODE-1H/1J), asset requests skip the session DB lookup (NODE-1D).
55 lines
2.5 KiB
TypeScript
55 lines
2.5 KiB
TypeScript
import { existsSync } from "node:fs";
|
|
import { ANALYTICS_BAKED } from "@snapotter/shared";
|
|
import { analyticsEnabled, gatePrimed, telemetryEnvKilled } from "./lib/analytics-gate.js";
|
|
import { buildBeforeSend } from "./lib/sentry-scrub.js";
|
|
|
|
// Sentry inits at process load, before the gate cache is primed. Until the
|
|
// first successful read, stay silent rather than emit on the default-ON cache,
|
|
// so an opted-out instance never reports even a boot-window crash.
|
|
const sentryActive = () => gatePrimed() && analyticsEnabled();
|
|
|
|
// All-in-one detection: docker/entrypoint.sh exports EMBEDDED_MODE=1 before
|
|
// exec'ing s6-overlay, and the snapotter service run script is with-contenv,
|
|
// so the marker reaches this process. URL absence is not a usable signal:
|
|
// embedded mode sets loopback DATABASE_URL/REDIS_URL before boot, and native
|
|
// dev commonly leaves DATABASE_URL unset (config.ts defaults it).
|
|
function deployMode(): string {
|
|
if (process.env.EMBEDDED_MODE) return "embedded";
|
|
if (existsSync("/.dockerenv")) return "external";
|
|
return "native";
|
|
}
|
|
|
|
if (ANALYTICS_BAKED.sentryDsn && !telemetryEnvKilled()) {
|
|
try {
|
|
const Sentry = await import("@sentry/node");
|
|
const { APP_VERSION } = await import("@snapotter/shared");
|
|
// The Docker build sets SENTRY_RELEASE to the release version so errors
|
|
// attribute to a build; falls back to APP_VERSION for non-image runs.
|
|
const release = process.env.SENTRY_RELEASE || APP_VERSION;
|
|
|
|
// buildBeforeSend is typed on loose Record shapes so sentry-scrub.ts never
|
|
// imports @sentry/node; cast at this one boundary to the SDK callback type.
|
|
type SentryOptions = NonNullable<Parameters<typeof Sentry.init>[0]>;
|
|
|
|
Sentry.init({
|
|
dsn: ANALYTICS_BAKED.sentryDsn,
|
|
release,
|
|
environment: process.env.SNAPOTTER_ENV || "production",
|
|
sendDefaultPii: false,
|
|
// Errors only. No traces options are set at all, so the SDK never
|
|
// starts traces and BullMQ/pg idle polling can't become transactions
|
|
// again (the July 2026 quota incident).
|
|
integrations: [Sentry.httpIntegration({ trackIncomingRequestsAsSessions: false })],
|
|
sendClientReports: false,
|
|
maxBreadcrumbs: 0,
|
|
beforeBreadcrumb: () => null,
|
|
initialScope: { tags: { deploy_mode: deployMode() } },
|
|
beforeSend: buildBeforeSend(sentryActive) as unknown as SentryOptions["beforeSend"],
|
|
});
|
|
|
|
console.log("[sentry] initialized (errors only), release:", release);
|
|
} catch {
|
|
// @sentry/node not available
|
|
}
|
|
}
|