perf: deep react code splitting, next-intl payload scoping, and SVG hardware acceleration for PageSpeed 100
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 23s
Build & Deploy / 🧪 QA (push) Successful in 2m1s
Build & Deploy / 🏗️ Build (push) Successful in 7m43s
Build & Deploy / 🚀 Deploy (push) Successful in 26s
Build & Deploy / 🧪 Smoke Test (push) Successful in 1m10s
Build & Deploy / ⚡ Lighthouse (push) Successful in 3m20s
Build & Deploy / 🔔 Notify (push) Successful in 2s
All checks were successful
Build & Deploy / 🔍 Prepare (push) Successful in 23s
Build & Deploy / 🧪 QA (push) Successful in 2m1s
Build & Deploy / 🏗️ Build (push) Successful in 7m43s
Build & Deploy / 🚀 Deploy (push) Successful in 26s
Build & Deploy / 🧪 Smoke Test (push) Successful in 1m10s
Build & Deploy / ⚡ Lighthouse (push) Successful in 3m20s
Build & Deploy / 🔔 Notify (push) Successful in 2s
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import type {
|
||||
ErrorReportingLevel,
|
||||
ErrorReportingService,
|
||||
@@ -7,32 +6,66 @@ import type {
|
||||
import type { NotificationService } from '../notifications/notification-service';
|
||||
import type { LoggerService } from '../logging/logger-service';
|
||||
|
||||
type SentryLike = typeof Sentry;
|
||||
|
||||
export type GlitchtipErrorReportingServiceOptions = {
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
// GlitchTip speaks the Sentry protocol; @sentry/nextjs can send to GlitchTip via DSN.
|
||||
// Sentry is dynamically imported to avoid a ~100KB main-thread execution penalty on initial load.
|
||||
export class GlitchtipErrorReportingService implements ErrorReportingService {
|
||||
private logger: LoggerService;
|
||||
private sentryPromise: Promise<typeof import('@sentry/nextjs')> | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly options: GlitchtipErrorReportingServiceOptions,
|
||||
logger: LoggerService,
|
||||
private readonly notifications?: NotificationService,
|
||||
private readonly sentry: SentryLike = Sentry,
|
||||
) {
|
||||
this.logger = logger.child({ component: 'error-reporting-glitchtip' });
|
||||
|
||||
if (this.options.enabled) {
|
||||
if (typeof window !== 'undefined') {
|
||||
// On client-side, wait until idle before fetching Sentry
|
||||
if ('requestIdleCallback' in window) {
|
||||
window.requestIdleCallback(() => {
|
||||
this.getSentry();
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
this.getSentry();
|
||||
}, 3000);
|
||||
}
|
||||
} else {
|
||||
// Pre-fetch on server-side
|
||||
this.getSentry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getSentry(): Promise<typeof import('@sentry/nextjs')> {
|
||||
if (!this.sentryPromise) {
|
||||
this.sentryPromise = import('@sentry/nextjs').then((Sentry) => {
|
||||
// Client-side initialization must happen here since sentry.client.config.ts is empty
|
||||
if (typeof window !== 'undefined') {
|
||||
Sentry.init({
|
||||
dsn: 'https://public@errors.infra.mintel.me/1',
|
||||
tunnel: '/errors/api/relay',
|
||||
enabled: true,
|
||||
tracesSampleRate: 0,
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
replaysSessionSampleRate: 0.1,
|
||||
});
|
||||
}
|
||||
return Sentry;
|
||||
});
|
||||
}
|
||||
return this.sentryPromise;
|
||||
}
|
||||
|
||||
async captureException(error: unknown, context?: Record<string, unknown>) {
|
||||
if (!this.options.enabled) return undefined;
|
||||
const result = this.sentry.captureException(error, context as any) as any;
|
||||
|
||||
// Send to Gotify if it's considered critical or if we just want all exceptions there
|
||||
// For now, let's send all exceptions to Gotify as requested "notify me via gotify about critical error messages"
|
||||
// We'll treat all captureException calls as potentially critical or at least noteworthy
|
||||
if (this.notifications) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
const contextStr = context ? `\nContext: ${JSON.stringify(context, null, 2)}` : '';
|
||||
@@ -44,34 +77,33 @@ export class GlitchtipErrorReportingService implements ErrorReportingService {
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
const Sentry = await this.getSentry();
|
||||
return Sentry.captureException(error, context as any) as any;
|
||||
}
|
||||
|
||||
captureMessage(message: string, level: ErrorReportingLevel = 'error') {
|
||||
async captureMessage(message: string, level: ErrorReportingLevel = 'error') {
|
||||
if (!this.options.enabled) return undefined;
|
||||
return this.sentry.captureMessage(message, level as any) as any;
|
||||
const Sentry = await this.getSentry();
|
||||
return Sentry.captureMessage(message, level as any) as any;
|
||||
}
|
||||
|
||||
setUser(user: ErrorReportingUser | null) {
|
||||
if (!this.options.enabled) return;
|
||||
this.sentry.setUser(user as any);
|
||||
this.getSentry().then((Sentry) => Sentry.setUser(user as any));
|
||||
}
|
||||
|
||||
setTag(key: string, value: string) {
|
||||
if (!this.options.enabled) return;
|
||||
this.sentry.setTag(key, value);
|
||||
this.getSentry().then((Sentry) => Sentry.setTag(key, value));
|
||||
}
|
||||
|
||||
withScope<T>(fn: () => T, context?: Record<string, unknown>) {
|
||||
withScope<T>(fn: () => T, context?: Record<string, unknown>): T {
|
||||
if (!this.options.enabled) return fn();
|
||||
|
||||
return this.sentry.withScope((scope) => {
|
||||
if (context) {
|
||||
for (const [key, value] of Object.entries(context)) {
|
||||
scope.setExtra(key, value);
|
||||
}
|
||||
}
|
||||
return fn();
|
||||
});
|
||||
// Since withScope mandates executing fn() synchronously to return T,
|
||||
// and Sentry load is async, if context mapping is absolutely required
|
||||
// for this feature we would need an async API.
|
||||
// For now we degrade gracefully by just executing the function.
|
||||
return fn();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user