chore(sentry): make sampling config environment-driven
This commit is contained in:
@@ -3,29 +3,23 @@
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import {
|
||||
getSentryReplayOnErrorSampleRate,
|
||||
getSentryReplaySessionSampleRate,
|
||||
getSharedSentryOptions,
|
||||
} from "@/utils/sentry-config";
|
||||
|
||||
Sentry.init({
|
||||
dsn: "https://a9390ae6d4b06c851854d48b070bb4cf@o4511511166713856.ingest.us.sentry.io/4511612532228096",
|
||||
...getSharedSentryOptions(),
|
||||
|
||||
// Add optional integrations for additional features
|
||||
integrations: [Sentry.replayIntegration()],
|
||||
|
||||
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
||||
tracesSampleRate: 1,
|
||||
// Enable logs to be sent to Sentry
|
||||
enableLogs: true,
|
||||
|
||||
// Define how likely Replay events are sampled.
|
||||
// This sets the sample rate to be 10%. You may want this to be 100% while
|
||||
// in development and sample at a lower rate in production
|
||||
replaysSessionSampleRate: 0.1,
|
||||
replaysSessionSampleRate: getSentryReplaySessionSampleRate(),
|
||||
|
||||
// Define how likely Replay events are sampled when an error occurs.
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
|
||||
// Enable sending user PII (Personally Identifiable Information)
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
|
||||
sendDefaultPii: true,
|
||||
replaysOnErrorSampleRate: getSentryReplayOnErrorSampleRate(),
|
||||
});
|
||||
|
||||
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user