chore(sentry): make sampling config environment-driven
This commit is contained in:
@@ -18,6 +18,13 @@ NEXT_PUBLIC_API_CONNECT_TIMEOUT=30000
|
|||||||
NEXT_PUBLIC_API_RECEIVE_TIMEOUT=60000
|
NEXT_PUBLIC_API_RECEIVE_TIMEOUT=60000
|
||||||
NEXT_PUBLIC_API_SEND_TIMEOUT=30000
|
NEXT_PUBLIC_API_SEND_TIMEOUT=30000
|
||||||
|
|
||||||
|
# Sentry observability
|
||||||
|
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=1
|
||||||
|
NEXT_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE=0.1
|
||||||
|
NEXT_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE=1
|
||||||
|
NEXT_PUBLIC_SENTRY_SEND_DEFAULT_PII=false
|
||||||
|
NEXT_PUBLIC_SENTRY_ENABLE_LOGS=true
|
||||||
|
|
||||||
# next-auth v4(Auth.js)—— JWT 签名密钥
|
# next-auth v4(Auth.js)—— JWT 签名密钥
|
||||||
# dev 环境独立密钥(**不**与 test / prod 共用)
|
# dev 环境独立密钥(**不**与 test / prod 共用)
|
||||||
AUTH_SECRET=PinzhujxR/qMPrY7bAor8qQlwmmy1tJJCex9Tomhk2c=
|
AUTH_SECRET=PinzhujxR/qMPrY7bAor8qQlwmmy1tJJCex9Tomhk2c=
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ NEXT_PUBLIC_API_CONNECT_TIMEOUT=30000
|
|||||||
NEXT_PUBLIC_API_RECEIVE_TIMEOUT=60000
|
NEXT_PUBLIC_API_RECEIVE_TIMEOUT=60000
|
||||||
NEXT_PUBLIC_API_SEND_TIMEOUT=30000
|
NEXT_PUBLIC_API_SEND_TIMEOUT=30000
|
||||||
|
|
||||||
|
# Sentry observability
|
||||||
|
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=1
|
||||||
|
NEXT_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE=0.1
|
||||||
|
NEXT_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE=1
|
||||||
|
NEXT_PUBLIC_SENTRY_SEND_DEFAULT_PII=false
|
||||||
|
NEXT_PUBLIC_SENTRY_ENABLE_LOGS=true
|
||||||
|
|
||||||
# next-auth v4(Auth.js)—— JWT 签名密钥
|
# next-auth v4(Auth.js)—— JWT 签名密钥
|
||||||
# test 环境独立密钥(**不**与 dev / prod 共用)
|
# test 环境独立密钥(**不**与 dev / prod 共用)
|
||||||
AUTH_SECRET=rKGeHQ/8tKPoh12Kiedil5zHuj9ZfPA0NwY9bgwRqkE=
|
AUTH_SECRET=rKGeHQ/8tKPoh12Kiedil5zHuj9ZfPA0NwY9bgwRqkE=
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ NEXT_PUBLIC_API_CONNECT_TIMEOUT=30000
|
|||||||
NEXT_PUBLIC_API_RECEIVE_TIMEOUT=60000
|
NEXT_PUBLIC_API_RECEIVE_TIMEOUT=60000
|
||||||
NEXT_PUBLIC_API_SEND_TIMEOUT=30000
|
NEXT_PUBLIC_API_SEND_TIMEOUT=30000
|
||||||
|
|
||||||
|
# Sentry observability
|
||||||
|
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=0.1
|
||||||
|
NEXT_PUBLIC_SENTRY_REPLAYS_SESSION_SAMPLE_RATE=0.01
|
||||||
|
NEXT_PUBLIC_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE=1
|
||||||
|
NEXT_PUBLIC_SENTRY_SEND_DEFAULT_PII=false
|
||||||
|
NEXT_PUBLIC_SENTRY_ENABLE_LOGS=true
|
||||||
|
|
||||||
# next-auth v4(Auth.js)—— JWT 签名密钥
|
# next-auth v4(Auth.js)—— JWT 签名密钥
|
||||||
# production 环境独立密钥(**最**重要,**签**发**用**户 session cookie)
|
# production 环境独立密钥(**最**重要,**签**发**用**户 session cookie)
|
||||||
AUTH_SECRET=XypsCWoLCNmwOaq4taIMJHe4+cU9YZBaYN4VMTtmSik=
|
AUTH_SECRET=XypsCWoLCNmwOaq4taIMJHe4+cU9YZBaYN4VMTtmSik=
|
||||||
|
|||||||
+2
-12
@@ -5,16 +5,6 @@
|
|||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
|
||||||
Sentry.init({
|
import { getSharedSentryOptions } from "@/utils/sentry-config";
|
||||||
dsn: "https://a9390ae6d4b06c851854d48b070bb4cf@o4511511166713856.ingest.us.sentry.io/4511612532228096",
|
|
||||||
|
|
||||||
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
Sentry.init(getSharedSentryOptions());
|
||||||
tracesSampleRate: 1,
|
|
||||||
|
|
||||||
// Enable logs to be sent to Sentry
|
|
||||||
enableLogs: true,
|
|
||||||
|
|
||||||
// Enable sending user PII (Personally Identifiable Information)
|
|
||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
|
|
||||||
sendDefaultPii: true,
|
|
||||||
});
|
|
||||||
|
|||||||
+2
-12
@@ -4,16 +4,6 @@
|
|||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
|
||||||
Sentry.init({
|
import { getSharedSentryOptions } from "@/utils/sentry-config";
|
||||||
dsn: "https://a9390ae6d4b06c851854d48b070bb4cf@o4511511166713856.ingest.us.sentry.io/4511612532228096",
|
|
||||||
|
|
||||||
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
|
Sentry.init(getSharedSentryOptions());
|
||||||
tracesSampleRate: 1,
|
|
||||||
|
|
||||||
// Enable logs to be sent to Sentry
|
|
||||||
enableLogs: true,
|
|
||||||
|
|
||||||
// Enable sending user PII (Personally Identifiable Information)
|
|
||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
|
|
||||||
sendDefaultPii: true,
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -3,29 +3,23 @@
|
|||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||||
|
|
||||||
import * as Sentry from "@sentry/nextjs";
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
import {
|
||||||
|
getSentryReplayOnErrorSampleRate,
|
||||||
|
getSentryReplaySessionSampleRate,
|
||||||
|
getSharedSentryOptions,
|
||||||
|
} from "@/utils/sentry-config";
|
||||||
|
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
dsn: "https://a9390ae6d4b06c851854d48b070bb4cf@o4511511166713856.ingest.us.sentry.io/4511612532228096",
|
...getSharedSentryOptions(),
|
||||||
|
|
||||||
// Add optional integrations for additional features
|
// Add optional integrations for additional features
|
||||||
integrations: [Sentry.replayIntegration()],
|
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.
|
// Define how likely Replay events are sampled.
|
||||||
// This sets the sample rate to be 10%. You may want this to be 100% while
|
replaysSessionSampleRate: getSentryReplaySessionSampleRate(),
|
||||||
// in development and sample at a lower rate in production
|
|
||||||
replaysSessionSampleRate: 0.1,
|
|
||||||
|
|
||||||
// Define how likely Replay events are sampled when an error occurs.
|
// Define how likely Replay events are sampled when an error occurs.
|
||||||
replaysOnErrorSampleRate: 1.0,
|
replaysOnErrorSampleRate: getSentryReplayOnErrorSampleRate(),
|
||||||
|
|
||||||
// Enable sending user PII (Personally Identifiable Information)
|
|
||||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
|
|
||||||
sendDefaultPii: true,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
|
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