src/lib/sentry.ts
Annotation non disponible
Lance npm run annotate (nécessite ANTHROPIC_API_KEY dans .env.local) pour générer une annotation française par Claude Haiku 4.5.
6 exports
setRequestUsercaptureErrorcaptureMessagewithSentrySentryContextSentryRequestUser
Code source· typescript
/**
* Helpers Sentry backend wari.pro.
*
* `instrumentation.ts` init le SDK au démarrage Next.js 16 (server + edge).
* Ici on expose des helpers user-friendly à utiliser dans les routes API.
*
* Usage :
* import { captureError, captureMessage } from "@/lib/sentry";
* try { ... } catch (e) { captureError(e, { route: '/api/...', tenantId }); }
*
* Le tag user est posé automatiquement par `getSessionFromRequest()` via
* `setRequestUser(session)` — toute route qui résout la session enrichit
* Sentry pour la durée de la requête. Les tags PER capture (ctx) override
* les tags request si conflit.
*/
import * as Sentry from "@sentry/nextjs";
import { isBetaTester } from "@/lib/beta-testers";
export type SentryContext = {
route?: string;
tenantId?: string | null;
userId?: string | null;
role?: string | null;
email?: string | null;
extra?: Record<string, unknown>;
};
export type SentryRequestUser = {
userId: string;
email?: string | null;
role: string;
tenantId?: string | null;
} | null;
/**
* Pose le user Sentry pour la durée de la requête courante (scope global
* implicite). Doit être appelé après extract de la session — ce que fait
* `getSessionFromRequest()` automatiquement. Aucun besoin d'appeler ce
* helper depuis les routes individuelles.
*
* Si l'email matche `BETA_TESTER_EMAILS` :
* - `setTag('beta_tester', 'true')`
* - `setTag('environment', 'beta')` (override NODE_ENV pour ce user)
*
* Safe à appeler même si SENTRY_DSN absent (no-op gracieux).
*/
export function setRequestUser(session: SentryRequestUser): void {
if (!process.env.SENTRY_DSN) return;
try {
if (!session) {
Sentry.setUser(null);
return;
}
Sentry.setUser({
id: session.userId,
...(session.email ? { email: session.email } : {}),
});
Sentry.setTag("role", session.role);
if (session.tenantId) Sentry.setTag("tenantId", session.tenantId);
if (isBetaTester(session.email)) {
Sentry.setTag("beta_tester", "true");
Sentry.setTag("environment", "beta");
}
} catch {
// Sentry ne doit jamais casser le flow applicatif
}
}
/**
* Capture une exception avec contexte enrichi.
* Safe à appeler même si SENTRY_DSN absent (no-op gracieux).
*/
export function captureError(error: unknown, ctx: SentryContext = {}): void {
if (!process.env.SENTRY_DSN) return;
try {
Sentry.withScope((scope) => {
if (ctx.route) scope.setTag("route", ctx.route);
if (ctx.tenantId) scope.setTag("tenantId", ctx.tenantId);
if (ctx.role) scope.setTag("role", ctx.role);
if (ctx.userId) {
scope.setUser({
id: ctx.userId,
...(ctx.email ? { email: ctx.email } : {}),
});
}
if (isBetaTester(ctx.email)) {
scope.setTag("beta_tester", "true");
scope.setTag("environment", "beta");
}
if (ctx.extra) {
for (const [k, v] of Object.entries(ctx.extra)) {
scope.setExtra(k, v);
}
}
Sentry.captureException(error);
});
} catch {
// Sentry ne doit jamais casser le flow applicatif
}
}
/**
* Capture un message informatif (warning / info breadcrumb-style).
*/
export function captureMessage(
message: string,
level: "info" | "warning" | "error" = "info",
ctx: SentryContext = {},
): void {
if (!process.env.SENTRY_DSN) return;
try {
Sentry.withScope((scope) => {
if (ctx.route) scope.setTag("route", ctx.route);
if (ctx.tenantId) scope.setTag("tenantId", ctx.tenantId);
if (ctx.role) scope.setTag("role", ctx.role);
if (ctx.userId) {
scope.setUser({
id: ctx.userId,
...(ctx.email ? { email: ctx.email } : {}),
});
}
if (isBetaTester(ctx.email)) {
scope.setTag("beta_tester", "true");
scope.setTag("environment", "beta");
}
if (ctx.extra) {
for (const [k, v] of Object.entries(ctx.extra)) {
scope.setExtra(k, v);
}
}
scope.setLevel(level);
Sentry.captureMessage(message);
});
} catch {
// no-op
}
}
/**
* Wrapper async pour capturer automatiquement les erreurs d'une route.
* Usage :
* export const GET = withSentry("/api/mobile/foo", async (req) => { ... });
*/
export function withSentry<T extends (...args: unknown[]) => Promise<unknown>>(
route: string,
handler: T,
): T {
return (async (...args: Parameters<T>) => {
try {
return await handler(...args);
} catch (error) {
captureError(error, { route });
throw error; // re-throw pour que Next.js retourne 500 standard
}
}) as T;
}
/**
* Helpers Sentry backend wari.pro.
*
* `instrumentation.ts` init le SDK au démarrage Next.js 16 (server + edge).
* Ici on expose des helpers user-friendly à utiliser dans les routes API.
*
* Usage :
* import { captureError, captureMessage } from "@/lib/sentry";
* try { ... } catch (e) { captureError(e, { route: '/api/...', tenantId }); }
*
* Le tag user est posé automatiquement par `getSessionFromRequest()` via
* `setRequestUser(session)` — toute route qui résout la session enrichit
* Sentry pour la durée de la requête. Les tags PER capture (ctx) override
* les tags request si conflit.
*/
import * as Sentry from "@sentry/nextjs";
import { isBetaTester } from "@/lib/beta-testers";
export type SentryContext = {
route?: string;
tenantId?: string | null;
userId?: string | null;
role?: string | null;
email?: string | null;
extra?: Record<string, unknown>;
};
export type SentryRequestUser = {
userId: string;
email?: string | null;
role: string;
tenantId?: string | null;
} | null;
/**
* Pose le user Sentry pour la durée de la requête courante (scope global
* implicite). Doit être appelé après extract de la session — ce que fait
* `getSessionFromRequest()` automatiquement. Aucun besoin d'appeler ce
* helper depuis les routes individuelles.
*
* Si l'email matche `BETA_TESTER_EMAILS` :
* - `setTag('beta_tester', 'true')`
* - `setTag('environment', 'beta')` (override NODE_ENV pour ce user)
*
* Safe à appeler même si SENTRY_DSN absent (no-op gracieux).
*/
export function setRequestUser(session: SentryRequestUser): void {
if (!process.env.SENTRY_DSN) return;
try {
if (!session) {
Sentry.setUser(null);
return;
}
Sentry.setUser({
id: session.userId,
...(session.email ? { email: session.email } : {}),
});
Sentry.setTag("role", session.role);
if (session.tenantId) Sentry.setTag("tenantId", session.tenantId);
if (isBetaTester(session.email)) {
Sentry.setTag("beta_tester", "true");
Sentry.setTag("environment", "beta");
}
} catch {
// Sentry ne doit jamais casser le flow applicatif
}
}
/**
* Capture une exception avec contexte enrichi.
* Safe à appeler même si SENTRY_DSN absent (no-op gracieux).
*/
export function captureError(error: unknown, ctx: SentryContext = {}): void {
if (!process.env.SENTRY_DSN) return;
try {
Sentry.withScope((scope) => {
if (ctx.route) scope.setTag("route", ctx.route);
if (ctx.tenantId) scope.setTag("tenantId", ctx.tenantId);
if (ctx.role) scope.setTag("role", ctx.role);
if (ctx.userId) {
scope.setUser({
id: ctx.userId,
...(ctx.email ? { email: ctx.email } : {}),
});
}
if (isBetaTester(ctx.email)) {
scope.setTag("beta_tester", "true");
scope.setTag("environment", "beta");
}
if (ctx.extra) {
for (const [k, v] of Object.entries(ctx.extra)) {
scope.setExtra(k, v);
}
}
Sentry.captureException(error);
});
} catch {
// Sentry ne doit jamais casser le flow applicatif
}
}
/**
* Capture un message informatif (warning / info breadcrumb-style).
*/
export function captureMessage(
message: string,
level: "info" | "warning" | "error" = "info",
ctx: SentryContext = {},
): void {
if (!process.env.SENTRY_DSN) return;
try {
Sentry.withScope((scope) => {
if (ctx.route) scope.setTag("route", ctx.route);
if (ctx.tenantId) scope.setTag("tenantId", ctx.tenantId);
if (ctx.role) scope.setTag("role", ctx.role);
if (ctx.userId) {
scope.setUser({
id: ctx.userId,
...(ctx.email ? { email: ctx.email } : {}),
});
}
if (isBetaTester(ctx.email)) {
scope.setTag("beta_tester", "true");
scope.setTag("environment", "beta");
}
if (ctx.extra) {
for (const [k, v] of Object.entries(ctx.extra)) {
scope.setExtra(k, v);
}
}
scope.setLevel(level);
Sentry.captureMessage(message);
});
} catch {
// no-op
}
}
/**
* Wrapper async pour capturer automatiquement les erreurs d'une route.
* Usage :
* export const GET = withSentry("/api/mobile/foo", async (req) => { ... });
*/
export function withSentry<T extends (...args: unknown[]) => Promise<unknown>>(
route: string,
handler: T,
): T {
return (async (...args: Parameters<T>) => {
try {
return await handler(...args);
} catch (error) {
captureError(error, { route });
throw error; // re-throw pour que Next.js retourne 500 standard
}
}) as T;
}