Skip to content

Commit c511b01

Browse files
committed
Update sentry
1 parent bac7bd4 commit c511b01

File tree

16 files changed

+1072
-715
lines changed

16 files changed

+1072
-715
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ SESSION_SECRET="super-duper-s3cret"
66
HONEYPOT_SECRET="super-duper-s3cret"
77
RESEND_API_KEY="re_blAh_blaHBlaHblahBLAhBlAh"
88
SENTRY_DSN="your-dsn"
9+
SENTRY_ENVIRONMENT="development"
910

1011
# this is set to a random value in the Dockerfile
1112
INTERNAL_COMMAND_TOKEN="some-made-up-token"

.github/workflows/deploy.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,7 @@ jobs:
215215
if: ${{ github.ref == 'refs/heads/dev' }}
216216
run: |
217217
flyctl deploy \
218+
--env SENTRY_ENVIRONMENT=staging \
218219
--image "registry.fly.io/${{ steps.app_name.outputs.value }}-staging:${{ github.sha }}" \
219220
--app ${{ steps.app_name.outputs.value }}-staging
220221
env:
@@ -224,6 +225,18 @@ jobs:
224225
if: ${{ github.ref == 'refs/heads/main' }}
225226
run: |
226227
flyctl deploy \
228+
--env SENTRY_ENVIRONMENT=production \
227229
--image "registry.fly.io/${{ steps.app_name.outputs.value }}:${{ github.sha }}"
228230
env:
229231
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
232+
233+
- name: Create Sentry release
234+
env:
235+
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
236+
# SENTRY_ORG: 'your sentry org id or slug'
237+
# SENTRY_PROJECT: 'your sentry project id or slug'
238+
if: ${{ env.SENTRY_AUTH_TOKEN }}
239+
uses: getsentry/action-release@v3
240+
with:
241+
environment:
242+
${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}

app/entry.client.tsx

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,44 @@
1+
import * as Sentry from '@sentry/react-router'
12
import { startTransition } from 'react'
23
import { hydrateRoot } from 'react-dom/client'
34
import { HydratedRouter } from 'react-router/dom'
45

5-
if (ENV.MODE === 'production' && ENV.SENTRY_DSN) {
6-
void import('./utils/monitoring.client.tsx').then(({ init }) => init())
7-
}
6+
Sentry.init({
7+
// Sentry will only send requests if SENTRY_DSN is defined
8+
dsn: ENV.MODE === 'production' ? ENV.SENTRY_DSN : undefined,
9+
// See https://spotlightjs.com/ for how to install the Spotlight Desktop app for local development
10+
spotlight: ENV.MODE === 'development',
11+
environment: ENV.MODE,
12+
beforeSend(event) {
13+
if (event.request?.url) {
14+
const url = new URL(event.request.url)
15+
if (
16+
url.protocol === 'chrome-extension:' ||
17+
url.protocol === 'moz-extension:'
18+
) {
19+
// This error is from a browser extension, ignore it
20+
return null
21+
}
22+
}
23+
return event
24+
},
25+
integrations: [
26+
Sentry.reactRouterTracingIntegration(),
27+
Sentry.replayIntegration(),
28+
],
29+
30+
// Set tracesSampleRate to 1.0 to capture 100%
31+
// of transactions for performance monitoring.
32+
// We recommend adjusting this value in production
33+
tracesSampleRate: 1.0,
34+
35+
// Capture Replay for 10% of all sessions,
36+
// plus for 100% of sessions with an error
37+
replaysSessionSampleRate: 0.1,
38+
replaysOnErrorSampleRate: 1.0,
39+
40+
enableLogs: true,
41+
})
842

943
startTransition(() => {
1044
hydrateRoot(document, <HydratedRouter />)

app/entry.server.tsx

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,11 @@
11
import crypto from 'node:crypto'
22
import { PassThrough } from 'node:stream'
3-
import { styleText } from 'node:util'
43
import { contentSecurity } from '@nichtsam/helmet/content'
54
import { createReadableStreamFromReadable } from '@react-router/node'
65
import * as Sentry from '@sentry/react-router'
76
import { isbot } from 'isbot'
87
import { renderToPipeableStream } from 'react-dom/server'
9-
import {
10-
ServerRouter,
11-
type LoaderFunctionArgs,
12-
type ActionFunctionArgs,
13-
type HandleDocumentRequestFunction,
14-
} from 'react-router'
8+
import { type HandleDocumentRequestFunction, ServerRouter } from 'react-router'
159
import { getEnv, init } from './utils/env.server.ts'
1610
import { getInstanceInfo } from './utils/litefs.server.ts'
1711
import { NonceProvider } from './utils/nonce-provider.ts'
@@ -26,7 +20,7 @@ const MODE = process.env.NODE_ENV ?? 'development'
2620

2721
type DocRequestArgs = Parameters<HandleDocumentRequestFunction>
2822

29-
export default async function handleRequest(...args: DocRequestArgs) {
23+
async function handleRequest(...args: DocRequestArgs) {
3024
const [request, responseStatusCode, responseHeaders, reactRouterContext] =
3125
args
3226
const { currentInstance, primaryInstance } = await getInstanceInfo()
@@ -74,6 +68,10 @@ export default async function handleRequest(...args: DocRequestArgs) {
7468
'connect-src': [
7569
MODE === 'development' ? 'ws:' : undefined,
7670
process.env.SENTRY_DSN ? '*.sentry.io' : undefined,
71+
// Spotlight (SSE to the sidecar)
72+
MODE === 'development'
73+
? 'http://localhost:8969'
74+
: undefined,
7775
"'self'",
7876
],
7977
'font-src': ["'self'"],
@@ -96,7 +94,8 @@ export default async function handleRequest(...args: DocRequestArgs) {
9694
status: didError ? 500 : responseStatusCode,
9795
}),
9896
)
99-
pipe(body)
97+
// this enables distributed tracing between client and server!
98+
pipe(Sentry.getMetaTagTransformer(body))
10099
},
101100
onShellError: (err: unknown) => {
102101
reject(err)
@@ -122,21 +121,6 @@ export async function handleDataRequest(response: Response) {
122121
return response
123122
}
124123

125-
export function handleError(
126-
error: unknown,
127-
{ request }: LoaderFunctionArgs | ActionFunctionArgs,
128-
): void {
129-
// Skip capturing if the request is aborted as Remix docs suggest
130-
// Ref: https://remix.run/docs/en/main/file-conventions/entry.server#handleerror
131-
if (request.signal.aborted) {
132-
return
133-
}
134-
135-
if (error instanceof Error) {
136-
console.error(styleText('red', String(error.stack)))
137-
} else {
138-
console.error(error)
139-
}
124+
export const handleError = Sentry.createSentryHandleError({ logErrors: true })
140125

141-
Sentry.captureException(error)
142-
}
126+
export default Sentry.wrapSentryHandleRequest(handleRequest)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as Sentry from '@sentry/react-router'
2+
import { requireUserWithRole } from '../../utils/permissions.server.ts'
3+
import { type Route } from './+types/api.sentry-example-api.ts'
4+
5+
class SentryExampleBackendError extends Error {
6+
constructor(message: string | undefined) {
7+
super(message)
8+
this.name = 'SentryExampleBackendError'
9+
}
10+
}
11+
12+
export async function loader({ request }: Route.LoaderArgs) {
13+
await requireUserWithRole(request, 'admin')
14+
15+
await Sentry.startSpan(
16+
{
17+
name: 'Example Backend Span',
18+
op: 'test',
19+
},
20+
async () => {
21+
// Simulate some backend work
22+
await new Promise((resolve) => setTimeout(resolve, 100))
23+
},
24+
)
25+
26+
throw new SentryExampleBackendError(
27+
'This error is raised on the backend API route.',
28+
)
29+
}

0 commit comments

Comments
 (0)