Skip to content

Commit bceb449

Browse files
committed
Integrate Sentry for error tracking and monitoring
1 parent f3401fd commit bceb449

11 files changed

+4046
-869
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,6 @@ next-env.d.ts
4444

4545
public/robots.txt
4646
public/sitemap-0.xml
47-
public/sitemap.xml
47+
public/sitemap.xml
48+
# Sentry Config File
49+
.env.sentry-build-plugin

app/global-error.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"use client";
2+
3+
import * as Sentry from "@sentry/nextjs";
4+
import NextError from "next/error";
5+
import { useEffect } from "react";
6+
7+
export default function GlobalError({
8+
error,
9+
}: {
10+
error: Error & { digest?: string };
11+
}) {
12+
useEffect(() => {
13+
Sentry.captureException(error);
14+
}, [error]);
15+
16+
return (
17+
<html>
18+
<body>
19+
{/* `NextError` is the default Next.js error page component. Its type
20+
definition requires a `statusCode` prop. However, since the App Router
21+
does not expose status codes for errors, we simply pass 0 to render a
22+
generic error message. */}
23+
<NextError statusCode={0} />
24+
</body>
25+
</html>
26+
);
27+
}

functions/api/sentry-tunnel.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { ExecutionContext } from "@cloudflare/workers-types";
2+
3+
interface Env {
4+
SENTRY_DSN: string;
5+
}
6+
7+
interface SentryResponse {
8+
error?: string;
9+
message?: string;
10+
}
11+
12+
export async function onRequestPost({
13+
request,
14+
env,
15+
}: {
16+
request: Request;
17+
env: Env;
18+
}) {
19+
try {
20+
const dsn = new URL(env.SENTRY_DSN);
21+
const projectId = dsn.pathname.split("/").pop();
22+
if (!projectId) {
23+
throw new Error("Invalid SENTRY_DSN: missing project ID");
24+
}
25+
26+
const sentryIngestUrl = `https://${dsn.host}/api/${projectId}/envelope/`;
27+
28+
const newRequest = new Request(sentryIngestUrl, {
29+
method: "POST",
30+
headers: {
31+
"Content-Type": "application/x-sentry-envelope",
32+
"X-Forwarded-For": request.headers.get("CF-Connecting-IP") || "",
33+
},
34+
body: await request.arrayBuffer(),
35+
});
36+
37+
const response = await fetch(newRequest);
38+
39+
return new Response(response.body, {
40+
status: response.status,
41+
headers: {
42+
...corsHeaders(),
43+
"Content-Type": "application/x-sentry-envelope",
44+
},
45+
});
46+
} catch (error: unknown) {
47+
console.error("Sentry proxy failed:", error);
48+
const errorMessage =
49+
error instanceof Error ? error.message : "Unknown error";
50+
return new Response(
51+
JSON.stringify({
52+
error: "Internal server error",
53+
message: `Failed to proxy Sentry request: ${errorMessage}`,
54+
} as SentryResponse),
55+
{
56+
status: 500,
57+
headers: corsHeaders(),
58+
}
59+
);
60+
}
61+
}
62+
63+
export async function onRequestOptions() {
64+
return new Response(null, {
65+
status: 204,
66+
headers: corsHeaders(),
67+
});
68+
}
69+
70+
function corsHeaders(): Record<string, string> {
71+
return {
72+
"Access-Control-Allow-Origin": "*",
73+
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
74+
"Access-Control-Allow-Headers": "Content-Type",
75+
"Cache-Control": "public, max-age=300",
76+
};
77+
}

instrumentation-client.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// This file configures the initialization of Sentry on the client.
2+
// The added config here will be used whenever a users loads a page in their browser.
3+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
4+
5+
import * as Sentry from "@sentry/nextjs";
6+
7+
Sentry.init({
8+
dsn: "https://8296abef723d97e7b8d5a15d1e93be1f@o4509816683823104.ingest.us.sentry.io/4509831375683584",
9+
tunnel: "/api/sentry-tunnel",
10+
// Add optional integrations for additional features
11+
integrations: [Sentry.replayIntegration()],
12+
13+
// Define how likely traces are sampled. Adjust this value in production, or use tracesSampler for greater control.
14+
tracesSampleRate: 1,
15+
// Enable logs to be sent to Sentry
16+
enableLogs: true,
17+
18+
// Define how likely Replay events are sampled.
19+
// This sets the sample rate to be 10%. You may want this to be 100% while
20+
// in development and sample at a lower rate in production
21+
replaysSessionSampleRate: 0.1,
22+
23+
// Define how likely Replay events are sampled when an error occurs.
24+
replaysOnErrorSampleRate: 1.0,
25+
26+
// Setting this option to true will print useful information to the console while you're setting up Sentry.
27+
debug: false,
28+
});
29+
30+
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;

instrumentation.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import * as Sentry from "@sentry/nextjs";
2+
3+
export async function register() {
4+
if (process.env.NEXT_RUNTIME === "nodejs") {
5+
await import("./sentry.server.config");
6+
}
7+
8+
if (process.env.NEXT_RUNTIME === "edge") {
9+
await import("./sentry.edge.config");
10+
}
11+
}
12+
13+
export const onRequestError = Sentry.captureRequestError;

next.config.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { withSentryConfig } from "@sentry/nextjs";
12
import type { NextConfig } from "next";
23

34
const nextConfig: NextConfig = {
@@ -15,4 +16,34 @@ const nextConfig: NextConfig = {
1516
},
1617
};
1718

18-
export default nextConfig;
19+
export default withSentryConfig(nextConfig, {
20+
// For all available options, see:
21+
// https://www.npmjs.com/package/@sentry/webpack-plugin#options
22+
23+
org: "apisguru",
24+
project: "apis-guru",
25+
26+
// Only print logs for uploading source maps in CI
27+
silent: !process.env.CI,
28+
29+
// For all available options, see:
30+
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
31+
32+
// Upload a larger set of source maps for prettier stack traces (increases build time)
33+
widenClientFileUpload: true,
34+
35+
// Uncomment to route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
36+
// This can increase your server load as well as your hosting bill.
37+
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
38+
// side errors will fail.
39+
// tunnelRoute: "/monitoring",
40+
41+
// Automatically tree-shake Sentry logger statements to reduce bundle size
42+
disableLogger: true,
43+
44+
// Enables automatic instrumentation of Vercel Cron Monitors. (Does not yet work with App Router route handlers.)
45+
// See the following for more information:
46+
// https://docs.sentry.io/product/crons/
47+
// https://vercel.com/docs/cron-jobs
48+
automaticVercelMonitors: true,
49+
});

0 commit comments

Comments
 (0)