Skip to content

Commit

Permalink
Sentry (#27)
Browse files Browse the repository at this point in the history
* feat: add sentry
* feat: Add proxy for sentry
  • Loading branch information
fastner authored Aug 12, 2024
1 parent a9ae08a commit 6c30c3e
Show file tree
Hide file tree
Showing 12 changed files with 1,202 additions and 13 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ app/components/favicon/icon-192.png
app/components/favicon/icon-512.png
app/components/favicon/favicon.ico
public/favicon.ico

# Sentry Config File
.sentryclirc
8 changes: 6 additions & 2 deletions app/components/hooks/webVitals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,13 @@ async function sendToAnalytics(metric: Metric) {

// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (navigator.sendBeacon) {
navigator.sendBeacon("/analytics", body)
navigator.sendBeacon("/analytics/performance", body)
} else {
await fetch("/analytics", { body, method: "POST", keepalive: true })
await fetch("/analytics/performance", {
body,
method: "POST",
keepalive: true
})
}
}

Expand Down
22 changes: 19 additions & 3 deletions app/entry.client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,27 @@
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
* For more information, see https://remix.run/file-conventions/entry.client
*/

import { RemixBrowser } from "@remix-run/react"
import { startTransition, StrictMode } from "react"
import { RemixBrowser, useLocation, useMatches } from "@remix-run/react"
import * as Sentry from "@sentry/remix"
import { startTransition, StrictMode, useEffect } from "react"
import { hydrateRoot } from "react-dom/client"

Sentry.init({
dsn: "https://6b45822a5e03ffef8414c88ea39e5d9c@o4507763603013632.ingest.de.sentry.io/4507763617300560",
tunnel: "/analytics/sentry",
tracesSampleRate: 1,
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1,

integrations: [
Sentry.browserTracingIntegration({
useEffect,
useLocation,
useMatches
})
]
})

startTransition(() => {
hydrateRoot(
document,
Expand Down
10 changes: 8 additions & 2 deletions app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
/* eslint-disable max-params */
/* eslint-disable @typescript-eslint/prefer-promise-reject-errors */

/**
* By default, Remix will handle generating the HTTP Response for you.
* You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨
* For more information, see https://remix.run/file-conventions/entry.server
*/

import { PassThrough } from "node:stream"
import zlib from "node:zlib"

import type { AppLoadContext, EntryContext } from "@remix-run/node"
import { createReadableStreamFromReadable } from "@remix-run/node"
import { RemixServer } from "@remix-run/react"
import * as Sentry from "@sentry/remix"
import { isbot } from "isbot"
import { renderToPipeableStream } from "react-dom/server"

export const handleError = Sentry.wrapHandleErrorWithSentry(
(error, { request }) => {
// Custom handleError implementation
console.error("An error occurred", error, request)
}
)

const ABORT_DELAY = 5000
const INTERNAL_SERVER_ERROR = 500
const DEFAULT_CACHE_TIME_SECS = 60
Expand Down
10 changes: 9 additions & 1 deletion app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import {
Meta,
Outlet,
Scripts,
ScrollRestoration
ScrollRestoration,
useRouteError
} from "@remix-run/react"
import { captureRemixErrorBoundaryError } from "@sentry/remix"
import type { PropsWithChildren } from "react"

import { useWebVitals } from "./components/hooks/webVitals"
Expand Down Expand Up @@ -44,6 +46,12 @@ export function Layout({ children }: PropsWithChildren) {
)
}

export function ErrorBoundary() {
const error = useRouteError()
captureRemixErrorBoundaryError(error)
return <div>Something went wrong</div>
}

export default function App() {
useWebVitals()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// This route is used to collect the Web Core Vitals data from the browser and store it in a DynamoDB table.
// POST to /analytics/performance only
import awsLite from "@aws-lite/client"
import awsDynamoDB from "@aws-lite/dynamodb"
import type { ActionFunctionArgs } from "@remix-run/node"
Expand Down
40 changes: 40 additions & 0 deletions app/routes/analytics.sentry.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// This route is used as proxy to send the Sentry events from the browser to Sentry.
// POST to /analytics/sentry only

import type { ActionFunctionArgs } from "@remix-run/node"

const SENTRY_HOST = "o4507763603013632.ingest.de.sentry.io"
// eslint-disable-next-line @typescript-eslint/naming-convention
const SENTRY_PROJECT_IDS = new Set(["4507763617300560"])

export async function action({ request }: ActionFunctionArgs) {
try {
const envelope = await request.text()
const piece = envelope.split("\n")[0]
const header = JSON.parse(piece) as Record<string, string>
const dsn = new URL(header.dsn)
const projectId = dsn.pathname.replace("/", "")

if (dsn.hostname !== SENTRY_HOST) {
throw new Error(`Invalid sentry hostname: ${dsn.hostname}`)
}

if (!projectId || !SENTRY_PROJECT_IDS.has(projectId)) {
throw new Error(`Invalid sentry project id: ${projectId}`)
}

const upstreamSentryUrl = `https://${SENTRY_HOST}/api/${projectId}/envelope/`
await fetch(upstreamSentryUrl, { method: "POST", body: envelope })

return new Response("{}", {
status: 200,
headers: { "Content-Type": "application/json" }
})
} catch (error) {
console.error("error tunneling to sentry", error)
return new Response(
JSON.stringify({ error: "error tunneling to sentry" }),
{ status: 500, headers: { "Content-Type": "application/json" } }
)
}
}
66 changes: 66 additions & 0 deletions app/routes/sentry-example-page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
export default function SentryExamplePage() {
return (
<div>
<main
style={{
minHeight: "100vh",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
}}
>
<h1 style={{ fontSize: "4rem", margin: "14px 0" }}>
{/* biome-ignore lint/a11y/noSvgWithoutTitle: <explanation> */}
<svg
style={{
height: "1em"
}}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 200 44"
>
<path
fill="currentColor"
d="M124.32,28.28,109.56,9.22h-3.68V34.77h3.73V15.19l15.18,19.58h3.26V9.22h-3.73ZM87.15,23.54h13.23V20.22H87.14V12.53h14.93V9.21H83.34V34.77h18.92V31.45H87.14ZM71.59,20.3h0C66.44,19.06,65,18.08,65,15.7c0-2.14,1.89-3.59,4.71-3.59a12.06,12.06,0,0,1,7.07,2.55l2-2.83a14.1,14.1,0,0,0-9-3c-5.06,0-8.59,3-8.59,7.27,0,4.6,3,6.19,8.46,7.52C74.51,24.74,76,25.78,76,28.11s-2,3.77-5.09,3.77a12.34,12.34,0,0,1-8.3-3.26l-2.25,2.69a15.94,15.94,0,0,0,10.42,3.85c5.48,0,9-2.95,9-7.51C79.75,23.79,77.47,21.72,71.59,20.3ZM195.7,9.22l-7.69,12-7.64-12h-4.46L186,24.67V34.78h3.84V24.55L200,9.22Zm-64.63,3.46h8.37v22.1h3.84V12.68h8.37V9.22H131.08ZM169.41,24.8c3.86-1.07,6-3.77,6-7.63,0-4.91-3.59-8-9.38-8H154.67V34.76h3.8V25.58h6.45l6.48,9.2h4.44l-7-9.82Zm-10.95-2.5V12.6h7.17c3.74,0,5.88,1.77,5.88,4.84s-2.29,4.86-5.84,4.86Z M29,2.26a4.67,4.67,0,0,0-8,0L14.42,13.53A32.21,32.21,0,0,1,32.17,40.19H27.55A27.68,27.68,0,0,0,12.09,17.47L6,28a15.92,15.92,0,0,1,9.23,12.17H4.62A.76.76,0,0,1,4,39.06l2.94-5a10.74,10.74,0,0,0-3.36-1.9l-2.91,5a4.54,4.54,0,0,0,1.69,6.24A4.66,4.66,0,0,0,4.62,44H19.15a19.4,19.4,0,0,0-8-17.31l2.31-4A23.87,23.87,0,0,1,23.76,44H36.07a35.88,35.88,0,0,0-16.41-31.8l4.67-8a.77.77,0,0,1,1.05-.27c.53.29,20.29,34.77,20.66,35.17a.76.76,0,0,1-.68,1.13H40.6q.09,1.91,0,3.81h4.78A4.59,4.59,0,0,0,50,39.43a4.49,4.49,0,0,0-.62-2.28Z"
/>
</svg>
</h1>

<p>Get started by sending us a sample error:</p>

<button
type="button"
style={{
padding: "12px",
cursor: "pointer",
backgroundColor: "#AD6CAA",
borderRadius: "4px",
border: "none",
color: "white",
fontSize: "14px",
margin: "18px"
}}
onClick={() => {
throw new Error("Sentry Example Frontend Error")
}}
>
Throw error!
</button>

<p>
Next, look for the error on the{" "}
<a href="https://sebastian-software-gmbh.sentry.io/issues/?project=4507763617300560">
Issues Page
</a>
.
</p>
<p style={{ marginTop: "24px" }}>
For more information, see{" "}
<a href="https://docs.sentry.io/platforms/javascript/guides/remix/">
https://docs.sentry.io/platforms/javascript/guides/remix/
</a>
</p>
</main>
</div>
)
}
7 changes: 7 additions & 0 deletions instrumentation.server.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import * as Sentry from "@sentry/remix"

Sentry.init({
dsn: "https://6b45822a5e03ffef8414c88ea39e5d9c@o4507763603013632.ingest.de.sentry.io/4507763617300560",
tracesSampleRate: 1,
autoInstrumentRemix: true
})
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"build": "remix vite:build",
"dev": "remix vite:dev",
"sst:dev": "sst dev --stage development remix vite:dev",
"start": "remix-serve ./build/server/index.js",
"start": "NODE_OPTIONS='--import ./instrumentation.server.mjs' remix-serve ./build/server/index.js",
"opt:svg": "svgo --folder app --recursive",
"update:latest": "ncu --deep --upgrade --interactive",
"update:minor": "ncu --deep --upgrade --interactive --target minor",
Expand All @@ -32,6 +32,8 @@
"@remix-run/node": "^2.11.0",
"@remix-run/react": "^2.11.0",
"@remix-run/serve": "^2.11.0",
"@sentry/remix": "^8.25.0",
"@sentry/vite-plugin": "^2.22.0",
"@unpic/react": "^0.1.14",
"clsx": "^2.1.1",
"feed": "^4.2.2",
Expand Down
Loading

0 comments on commit 6c30c3e

Please sign in to comment.