Adding Sentry is basically done now. We have a nice lil component to help us get started to!
This commit is contained in:
16
src/app/(sentry)/api/sentry/example/route.ts
Normal file
16
src/app/(sentry)/api/sentry/example/route.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
class SentryExampleAPIError extends Error {
|
||||
constructor(message: string | undefined) {
|
||||
super(message);
|
||||
this.name = 'SentryExampleAPIError';
|
||||
}
|
||||
}
|
||||
// A faulty API route to test Sentry's error monitoring
|
||||
export function GET() {
|
||||
throw new SentryExampleAPIError(
|
||||
'This error is raised on the backend called by the example page.',
|
||||
);
|
||||
return NextResponse.json({ data: 'Testing Sentry Error...' });
|
||||
}
|
@@ -4,14 +4,51 @@ import { FetchDataSteps } from '@/components/default/tutorial';
|
||||
import { InfoIcon } from 'lucide-react';
|
||||
import { getUser } from '@/lib/actions';
|
||||
import type { User } from '@/utils/supabase';
|
||||
import Link from 'next/link';
|
||||
import { TestSentryCard } from '@/components/default/sentry';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Separator,
|
||||
} from '@/components/ui';
|
||||
|
||||
const HomePage = async () => {
|
||||
const response = await getUser();
|
||||
if (!response.success || !response.data) {
|
||||
return (
|
||||
<main className='w-full items-center justify-center'>
|
||||
<div className='flex p-5 items-center justify-center'>
|
||||
<h1>Make sure you can sign in!</h1>
|
||||
<div className='flex flex-col p-5 items-center justify-center space-y-6'>
|
||||
<Card className='md:min-w-2xl'>
|
||||
<CardHeader className='flex flex-col items-center'>
|
||||
<CardTitle className='text-3xl'>
|
||||
Welcome to the T3 Supabase Template!
|
||||
</CardTitle>
|
||||
<CardDescription className='text-[1.0rem] mb-2'>
|
||||
A great place to start is by creating a new user account &
|
||||
ensuring you can sign in!
|
||||
</CardDescription>
|
||||
<Button
|
||||
asChild
|
||||
size='sm'
|
||||
variant={'default'}
|
||||
className='w-1/3 items-center'
|
||||
>
|
||||
<Link href='/sign-up'>Sign up</Link>
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<Separator className='bg-accent' />
|
||||
<CardContent className='flex flex-col px-5 py-2 items-center justify-center'>
|
||||
<CardTitle className='text-lg mb-6 w-2/3 text-center'>
|
||||
You can also test out your connection to Sentry if you want to
|
||||
start there!
|
||||
</CardTitle>
|
||||
<TestSentryCard />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
@@ -30,14 +67,15 @@ const HomePage = async () => {
|
||||
</div>
|
||||
</div>
|
||||
<div className='flex flex-col gap-2 items-start'>
|
||||
<h2 className='font-bold text-2xl mb-4'>Your user details</h2>
|
||||
<h2 className='font-bold text-3xl mb-4'>Your user details</h2>
|
||||
<pre
|
||||
className='text-xs font-mono p-3 rounded
|
||||
border max-h-32 overflow-auto'
|
||||
className='text-sm font-mono p-3 rounded
|
||||
border max-h-50 overflow-auto'
|
||||
>
|
||||
{JSON.stringify(user, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
<TestSentryCard />
|
||||
<div>
|
||||
<h2 className='font-bold text-2xl mb-4'>Next steps</h2>
|
||||
<FetchDataSteps />
|
||||
|
125
src/components/default/sentry/TestSentry.tsx
Normal file
125
src/components/default/sentry/TestSentry.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
'use client';
|
||||
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
Separator,
|
||||
} from '@/components/ui';
|
||||
import Link from 'next/link';
|
||||
import { CheckCircle, MessageCircleWarning } from 'lucide-react';
|
||||
|
||||
class SentryExampleFrontendError extends Error {
|
||||
constructor(message: string | undefined) {
|
||||
super(message);
|
||||
this.name = 'SentryExampleFrontendError';
|
||||
}
|
||||
}
|
||||
|
||||
export const TestSentryCard = () => {
|
||||
const [hasSentError, setHasSentError] = useState(false);
|
||||
const [isConnected, setIsConnected] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const checkConnectivity = async () => {
|
||||
const result = await Sentry.diagnoseSdkConnectivity();
|
||||
setIsConnected(result !== 'sentry-unreachable');
|
||||
};
|
||||
checkConnectivity().catch((error) => {
|
||||
console.error('Error trying to connect to Sentry: ', error);
|
||||
});
|
||||
}, []);
|
||||
|
||||
const createError = async () => {
|
||||
await Sentry.startSpan(
|
||||
{
|
||||
name: 'Example Frontend Span',
|
||||
op: 'test',
|
||||
},
|
||||
async () => {
|
||||
const res = await fetch('/api/sentry/example');
|
||||
if (!res.ok) {
|
||||
setHasSentError(true);
|
||||
throw new SentryExampleFrontendError(
|
||||
'This error is raised in our TestSentry component on the main page.',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className='flex flex-row my-auto space-x-4'>
|
||||
<svg
|
||||
height='40'
|
||||
width='40'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
>
|
||||
<path
|
||||
d='M21.85 2.995a3.698 3.698 0 0 1 1.353 1.354l16.303 28.278a3.703 3.703 0 0 1-1.354 5.053 3.694 3.694 0 0 1-1.848.496h-3.828a31.149 31.149 0 0 0 0-3.09h3.815a.61.61 0 0 0 .537-.917L20.523 5.893a.61.61 0 0 0-1.057 0l-3.739 6.494a28.948 28.948 0 0 1 9.63 10.453 28.988 28.988 0 0 1 3.499 13.78v1.542h-9.852v-1.544a19.106 19.106 0 0 0-2.182-8.85 19.08 19.08 0 0 0-6.032-6.829l-1.85 3.208a15.377 15.377 0 0 1 6.382 12.484v1.542H3.696A3.694 3.694 0 0 1 0 34.473c0-.648.17-1.286.494-1.849l2.33-4.074a8.562 8.562 0 0 1 2.689 1.536L3.158 34.17a.611.611 0 0 0 .538.917h8.448a12.481 12.481 0 0 0-6.037-9.09l-1.344-.772 4.908-8.545 1.344.77a22.16 22.16 0 0 1 7.705 7.444 22.193 22.193 0 0 1 3.316 10.193h3.699a25.892 25.892 0 0 0-3.811-12.033 25.856 25.856 0 0 0-9.046-8.796l-1.344-.772 5.269-9.136a3.698 3.698 0 0 1 3.2-1.849c.648 0 1.285.17 1.847.495Z'
|
||||
fill='currentcolor'
|
||||
/>
|
||||
</svg>
|
||||
<CardTitle className='text-3xl my-auto'>Test Sentry</CardTitle>
|
||||
</div>
|
||||
<CardDescription className='text-[1.0rem]'>
|
||||
Click the button below & view the sample error on{' '}
|
||||
<Link
|
||||
href={`${process.env.NEXT_PUBLIC_SENTRY_URL}`}
|
||||
className='text-accent-foreground underline hover:text-primary'
|
||||
>
|
||||
the Sentry website
|
||||
</Link>
|
||||
. Navigate to the {"'"}Issues{"'"} page & you should see the sample
|
||||
error!
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className='flex flex-row gap-4 my-auto'>
|
||||
<Button
|
||||
type='button'
|
||||
onClick={createError}
|
||||
className='cursor-pointer text-md my-auto py-6'
|
||||
>
|
||||
<span>Throw Sample Error</span>
|
||||
</Button>
|
||||
{hasSentError ? (
|
||||
<div className='rounded-md bg-green-500/80 dark:bg-green-500/50 py-2 px-4 flex flex-row gap-2 my-auto'>
|
||||
<CheckCircle size={30} className='my-auto' />
|
||||
<p className='text-lg'>Sample error was sent to Sentry!</p>
|
||||
</div>
|
||||
) : !isConnected ? (
|
||||
<div className='rounded-md bg-red-600/50 dark:bg-red-500/50 py-2 px-4 flex flex-row gap-2 my-auto'>
|
||||
<MessageCircleWarning size={40} className='my-auto' />
|
||||
<p>
|
||||
Wait! The Sentry SDK is not able to reach Sentry right now -
|
||||
this may be due to an adblocker. For more information, see{' '}
|
||||
<Link
|
||||
href='https://docs.sentry.io/platforms/javascript/guides/nextjs/troubleshooting/#the-sdk-is-not-sending-any-data'
|
||||
className='text-accent-foreground underline hover:text-primary'
|
||||
>
|
||||
the troubleshooting guide.
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className='success_placeholder' />
|
||||
)}
|
||||
</div>
|
||||
<Separator className='my-4 bg-accent' />
|
||||
<p className='description'>
|
||||
Warning! Sometimes Adblockers will prevent errors from being sent to
|
||||
Sentry.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
1
src/components/default/sentry/index.tsx
Normal file
1
src/components/default/sentry/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { TestSentryCard } from './TestSentry';
|
13
src/env.js
13
src/env.js
@@ -7,11 +7,12 @@ export const env = createEnv({
|
||||
* This way you can ensure the app isn't built with invalid env vars.
|
||||
*/
|
||||
server: {
|
||||
NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
|
||||
NODE_ENV: z
|
||||
.enum(['development', 'test', 'production'])
|
||||
.default('development'),
|
||||
NEXT_RUNTIME: z.enum(['nodejs', 'edge']).default('nodejs'),
|
||||
SENTRY_URL: z.string().url().default('https://sentry.gbrown.org'),
|
||||
SENTRY_AUTH_TOKEN: z.string().min(1),
|
||||
CI: z.enum(['true', 'false']).default('true'),
|
||||
CI: z.enum(['true', 'false']).default('false'),
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -24,6 +25,10 @@ export const env = createEnv({
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY: z.string().min(1),
|
||||
NEXT_PUBLIC_SITE_URL: z.string().url().default('http://localhost:3000'),
|
||||
NEXT_PUBLIC_SENTRY_DSN: z.string().min(1),
|
||||
NEXT_PUBLIC_SENTRY_URL: z
|
||||
.string()
|
||||
.url()
|
||||
.default('https://sentry.gbrown.org'),
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -33,7 +38,6 @@ export const env = createEnv({
|
||||
runtimeEnv: {
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
NEXT_RUNTIME: process.env.NEXT_RUNTIME,
|
||||
SENTRY_URL: process.env.SENTRY_URL,
|
||||
SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN,
|
||||
CI: process.env.CI,
|
||||
|
||||
@@ -41,6 +45,7 @@ export const env = createEnv({
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||
NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
|
||||
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
NEXT_PUBLIC_SENTRY_URL: process.env.NEXT_PUBLIC_SENTRY_URL,
|
||||
},
|
||||
/**
|
||||
* Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially
|
||||
|
36
src/instrumentation-client.ts
Normal file
36
src/instrumentation-client.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
// This file configures the initialization of Sentry on the client.
|
||||
// The added config here will be used whenever a users loads a page in their browser.
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
|
||||
Sentry.init({
|
||||
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN!,
|
||||
|
||||
// Adds request headers and IP for users, for more info visit:
|
||||
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#sendDefaultPii
|
||||
sendDefaultPii: true,
|
||||
|
||||
// Set tracesSampleRate to 1.0 to capture 100%
|
||||
// of transactions for tracing.
|
||||
// We recommend adjusting this value in production
|
||||
// Learn more at
|
||||
// https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate
|
||||
tracesSampleRate: 1.0,
|
||||
// Replay may only be enabled for the client-side
|
||||
integrations: [Sentry.replayIntegration()],
|
||||
|
||||
// Capture Replay for 10% of all sessions,
|
||||
// plus for 100% of sessions with an error
|
||||
// Learn more at
|
||||
// https://docs.sentry.io/platforms/javascript/session-replay/configuration/#general-integration-configuration
|
||||
replaysSessionSampleRate: 0.1,
|
||||
replaysOnErrorSampleRate: 1.0,
|
||||
|
||||
// Note: if you want to override the automatic release value, do not set a
|
||||
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
|
||||
// that it will also get attached to your source maps
|
||||
});
|
||||
|
||||
// This export will instrument router navigations, and is only relevant if you enable tracing.
|
||||
// `captureRouterTransitionStart` is available from SDK version 9.12.0 onwards
|
||||
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
|
@@ -1,7 +1,6 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import type { Instrumentation } from 'next';
|
||||
|
||||
|
||||
export const register = async () => {
|
||||
if (process.env.NEXT_RUNTIME === 'edge') {
|
||||
await import('../sentry.edge.config');
|
||||
|
Reference in New Issue
Block a user