mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-11 08:42:39 +02:00
Merge pull request #874 from MODSetter/dev
feat: enhance error handling with PostHog integration
This commit is contained in:
commit
1c7c21386f
5 changed files with 122 additions and 8 deletions
32
surfsense_web/app/error.tsx
Normal file
32
surfsense_web/app/error.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
"use client";
|
||||
|
||||
import posthog from "posthog-js";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function Error({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
posthog.captureException(error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-[50vh] flex-col items-center justify-center gap-4 text-center">
|
||||
<h2 className="text-2xl font-semibold">Something went wrong</h2>
|
||||
<p className="text-muted-foreground max-w-md">
|
||||
An unexpected error occurred. Please try again.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={reset}
|
||||
className="rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
|
||||
>
|
||||
Try again
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
surfsense_web/app/global-error.tsx
Normal file
28
surfsense_web/app/global-error.tsx
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
"use client";
|
||||
|
||||
import posthog from "posthog-js";
|
||||
import NextError from "next/error";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export default function GlobalError({
|
||||
error,
|
||||
reset,
|
||||
}: {
|
||||
error: Error & { digest?: string };
|
||||
reset: () => void;
|
||||
}) {
|
||||
useEffect(() => {
|
||||
posthog.captureException(error);
|
||||
}, [error]);
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>
|
||||
<NextError statusCode={0} />
|
||||
<button type="button" onClick={reset}>
|
||||
Try again
|
||||
</button>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
37
surfsense_web/instrumentation.ts
Normal file
37
surfsense_web/instrumentation.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import type { Instrumentation } from "next";
|
||||
|
||||
const POSTHOG_COOKIE_RE = /ph_phc_.*?_posthog=([^;]+)/;
|
||||
|
||||
export function register() {
|
||||
// No-op — PostHog server client is lazily initialized
|
||||
}
|
||||
|
||||
export const onRequestError: Instrumentation.onRequestError = async (err, request) => {
|
||||
if (process.env.NEXT_RUNTIME === "nodejs") {
|
||||
const { default: PostHogClient } = await import("./lib/posthog/server");
|
||||
|
||||
try {
|
||||
const posthog = PostHogClient();
|
||||
|
||||
let distinctId: string | undefined;
|
||||
const rawCookie = request.headers.cookie;
|
||||
if (rawCookie) {
|
||||
const cookieString = Array.isArray(rawCookie) ? rawCookie.join("; ") : rawCookie;
|
||||
const postHogCookieMatch = cookieString.match(POSTHOG_COOKIE_RE);
|
||||
if (postHogCookieMatch?.[1]) {
|
||||
try {
|
||||
const decodedCookie = decodeURIComponent(postHogCookieMatch[1]);
|
||||
const postHogData = JSON.parse(decodedCookie);
|
||||
distinctId = postHogData.distinct_id;
|
||||
} catch {
|
||||
// Cookie parsing failed — capture without distinct_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await posthog.captureException(err, distinctId);
|
||||
} catch {
|
||||
// PostHog server capture failed — don't let it affect the request
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import posthog from "posthog-js";
|
||||
import type { ZodType } from "zod";
|
||||
import { getBearerToken, handleUnauthorized, refreshAccessToken } from "../auth-utils";
|
||||
import { AppError, AuthenticationError, AuthorizationError, NotFoundError } from "../error";
|
||||
|
|
@ -231,6 +232,20 @@ class BaseApiService {
|
|||
return data;
|
||||
} catch (error) {
|
||||
console.error("Request failed:", JSON.stringify(error));
|
||||
if (!(error instanceof AuthenticationError)) {
|
||||
try {
|
||||
posthog.captureException(error, {
|
||||
api_url: url,
|
||||
api_method: options?.method ?? "GET",
|
||||
...(error instanceof AppError && {
|
||||
status_code: error.status,
|
||||
status_text: error.statusText,
|
||||
}),
|
||||
});
|
||||
} catch {
|
||||
// PostHog capture failed — don't block the error flow
|
||||
}
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
import { PostHog } from "posthog-node";
|
||||
|
||||
let posthogInstance: PostHog | null = null;
|
||||
|
||||
export default function PostHogClient() {
|
||||
if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) {
|
||||
throw new Error("NEXT_PUBLIC_POSTHOG_KEY is not set");
|
||||
}
|
||||
|
||||
const posthogClient = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
|
||||
if (!posthogInstance) {
|
||||
posthogInstance = new PostHog(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
|
||||
host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
|
||||
// Because server-side functions in Next.js can be short-lived,
|
||||
// we set flushAt to 1 and flushInterval to 0 to ensure events are sent immediately
|
||||
flushAt: 1,
|
||||
flushInterval: 0,
|
||||
});
|
||||
}
|
||||
|
||||
return posthogClient;
|
||||
return posthogInstance;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue