feat: umami migration

This commit is contained in:
2026-02-07 01:11:28 +01:00
parent 29d474a102
commit 9f6168592c
27 changed files with 3310 additions and 193 deletions

View File

@@ -1,4 +1,27 @@
export type ErrorReportingUser = {
id?: string;
email?: string;
username?: string;
};
export type ErrorReportingLevel =
| "fatal"
| "error"
| "warning"
| "info"
| "debug"
| "log";
export interface ErrorReportingService {
captureException(error: unknown, context?: Record<string, unknown>): void;
captureMessage(message: string, context?: Record<string, unknown>): void;
captureException(
error: unknown,
context?: Record<string, unknown>,
): Promise<string | undefined> | string | undefined;
captureMessage(
message: string,
level?: ErrorReportingLevel,
): Promise<string | undefined> | string | undefined;
setUser(user: ErrorReportingUser | null): void;
setTag(key: string, value: string): void;
withScope<T>(fn: () => T, context?: Record<string, unknown>): T;
}

View File

@@ -1,48 +1,74 @@
import * as Sentry from "@sentry/nextjs";
import type { ErrorReportingService } from "./error-reporting-service";
import type {
ErrorReportingLevel,
ErrorReportingService,
ErrorReportingUser,
} from "./error-reporting-service";
import type { NotificationService } from "../notifications/notification-service";
export interface GlitchtipConfig {
enabled: boolean;
}
type SentryLike = typeof Sentry;
export type GlitchtipErrorReportingServiceOptions = {
enabled: boolean;
};
// GlitchTip speaks the Sentry protocol; @sentry/nextjs can send to GlitchTip via DSN.
export class GlitchtipErrorReportingService implements ErrorReportingService {
constructor(
private readonly config: GlitchtipConfig,
private readonly options: GlitchtipErrorReportingServiceOptions,
private readonly notifications?: NotificationService,
private readonly sentry: SentryLike = Sentry,
) {}
captureException(error: unknown, context?: Record<string, unknown>) {
if (!this.config.enabled) return;
Sentry.withScope((scope) => {
if (context) {
scope.setExtras(context);
}
Sentry.captureException(error);
});
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) {
this.notifications
.notify({
title: "🚨 Exception Captured",
message: error instanceof Error ? error.message : String(error),
priority: 10,
})
.catch((err) =>
console.error("Failed to send notification for exception", err),
);
const errorMessage =
error instanceof Error ? error.message : String(error);
const contextStr = context
? `\nContext: ${JSON.stringify(context, null, 2)}`
: "";
await this.notifications.notify({
title: "🔥 Critical Error Captured",
message: `Error: ${errorMessage}${contextStr}`,
priority: 7,
});
}
return result;
}
captureMessage(message: string, context?: Record<string, unknown>) {
if (!this.config.enabled) return;
captureMessage(message: string, level: ErrorReportingLevel = "error") {
if (!this.options.enabled) return undefined;
return this.sentry.captureMessage(message, level as any) as any;
}
Sentry.withScope((scope) => {
setUser(user: ErrorReportingUser | null) {
if (!this.options.enabled) return;
this.sentry.setUser(user as any);
}
setTag(key: string, value: string) {
if (!this.options.enabled) return;
this.sentry.setTag(key, value);
}
withScope<T>(fn: () => T, context?: Record<string, unknown>) {
if (!this.options.enabled) return fn();
return this.sentry.withScope((scope) => {
if (context) {
scope.setExtras(context);
for (const [key, value] of Object.entries(context)) {
scope.setExtra(key, value);
}
}
Sentry.captureMessage(message);
return fn();
});
}
}

View File

@@ -1,6 +1,22 @@
import type { ErrorReportingService } from "./error-reporting-service";
import type {
ErrorReportingLevel,
ErrorReportingService,
ErrorReportingUser,
} from "./error-reporting-service";
export class NoopErrorReportingService implements ErrorReportingService {
captureException() {}
captureMessage() {}
async captureException(_error: unknown, _context?: Record<string, unknown>) {
return undefined;
}
async captureMessage(_message: string, _level?: ErrorReportingLevel) {
return undefined;
}
setUser(_user: ErrorReportingUser | null) {}
setTag(_key: string, _value: string) {}
withScope<T>(fn: () => T, _context?: Record<string, unknown>) {
return fn();
}
}