chore(sentry): make sampling config environment-driven

This commit is contained in:
2026-07-02 13:04:13 +08:00
parent d8a28bb438
commit bf7d47050d
8 changed files with 173 additions and 38 deletions
+58
View File
@@ -0,0 +1,58 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import {
getSentryReplaySessionSampleRate,
getSharedSentryOptions,
readBoolean,
readSampleRate,
} from "../sentry-config";
describe("sentry config", () => {
afterEach(() => {
vi.unstubAllEnvs();
});
it("uses conservative production defaults", () => {
vi.stubEnv("NEXT_PUBLIC_APP_ENV", "production");
expect(getSharedSentryOptions()).toMatchObject({
tracesSampleRate: 0.1,
enableLogs: true,
sendDefaultPii: false,
});
expect(getSentryReplaySessionSampleRate()).toBe(0.01);
});
it("keeps non-production sampling high for local debugging", () => {
vi.stubEnv("NEXT_PUBLIC_APP_ENV", "development");
expect(getSharedSentryOptions().tracesSampleRate).toBe(1);
expect(getSentryReplaySessionSampleRate()).toBe(0.1);
});
it("allows valid environment overrides", () => {
vi.stubEnv("NEXT_PUBLIC_APP_ENV", "production");
vi.stubEnv("NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE", "0.25");
vi.stubEnv("NEXT_PUBLIC_SENTRY_ENABLE_LOGS", "false");
vi.stubEnv("NEXT_PUBLIC_SENTRY_SEND_DEFAULT_PII", "true");
vi.stubEnv("NEXT_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE", "0.05");
expect(getSharedSentryOptions()).toMatchObject({
tracesSampleRate: 0.25,
enableLogs: false,
sendDefaultPii: true,
});
expect(getSentryReplaySessionSampleRate()).toBe(0.05);
});
it("falls back for invalid sample rates and booleans", () => {
expect(readSampleRate("1.2", 0.1)).toBe(0.1);
expect(readSampleRate("-0.1", 0.1)).toBe(0.1);
expect(readSampleRate("abc", 0.1)).toBe(0.1);
expect(readSampleRate("0.5", 0.1)).toBe(0.5);
expect(readBoolean("maybe", false)).toBe(false);
expect(readBoolean("yes", false)).toBe(true);
expect(readBoolean("off", true)).toBe(false);
});
});
+82
View File
@@ -0,0 +1,82 @@
const SENTRY_DSN =
"https://a9390ae6d4b06c851854d48b070bb4cf@o4511511166713856.ingest.us.sentry.io/4511612532228096";
const PRODUCTION_TRACE_SAMPLE_RATE = 0.1;
const NON_PRODUCTION_TRACE_SAMPLE_RATE = 1;
const PRODUCTION_REPLAY_SESSION_SAMPLE_RATE = 0.01;
const NON_PRODUCTION_REPLAY_SESSION_SAMPLE_RATE = 0.1;
const DEFAULT_REPLAY_ON_ERROR_SAMPLE_RATE = 1;
export interface SharedSentryOptions {
dsn: string;
tracesSampleRate: number;
enableLogs: boolean;
sendDefaultPii: boolean;
}
export function getSharedSentryOptions(): SharedSentryOptions {
const isProduction = process.env.NEXT_PUBLIC_APP_ENV === "production";
return {
dsn: SENTRY_DSN,
tracesSampleRate: readSampleRate(
process.env.NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE ??
process.env.SENTRY_TRACES_SAMPLE_RATE,
isProduction
? PRODUCTION_TRACE_SAMPLE_RATE
: NON_PRODUCTION_TRACE_SAMPLE_RATE,
),
enableLogs: readBoolean(
process.env.NEXT_PUBLIC_SENTRY_ENABLE_LOGS ??
process.env.SENTRY_ENABLE_LOGS,
true,
),
sendDefaultPii: readBoolean(
process.env.NEXT_PUBLIC_SENTRY_SEND_DEFAULT_PII ??
process.env.SENTRY_SEND_DEFAULT_PII,
false,
),
};
}
export function getSentryReplaySessionSampleRate(): number {
const isProduction = process.env.NEXT_PUBLIC_APP_ENV === "production";
return readSampleRate(
process.env.NEXT_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE,
isProduction
? PRODUCTION_REPLAY_SESSION_SAMPLE_RATE
: NON_PRODUCTION_REPLAY_SESSION_SAMPLE_RATE,
);
}
export function getSentryReplayOnErrorSampleRate(): number {
return readSampleRate(
process.env.NEXT_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE,
DEFAULT_REPLAY_ON_ERROR_SAMPLE_RATE,
);
}
export function readSampleRate(
value: string | undefined,
fallback: number,
): number {
if (value === undefined || value.trim().length === 0) return fallback;
const parsed = Number(value);
if (!Number.isFinite(parsed)) return fallback;
if (parsed < 0 || parsed > 1) return fallback;
return parsed;
}
export function readBoolean(
value: string | undefined,
fallback: boolean,
): boolean {
if (value === undefined || value.trim().length === 0) return fallback;
const normalized = value.trim().toLowerCase();
if (["1", "true", "yes", "on"].includes(normalized)) return true;
if (["0", "false", "no", "off"].includes(normalized)) return false;
return fallback;
}