Skip to content

Instantly share code, notes, and snippets.

@cameronmcefee
Last active June 21, 2023 11:19
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cameronmcefee/287ead01064485731e2f81f0b5e762fc to your computer and use it in GitHub Desktop.
Save cameronmcefee/287ead01064485731e2f81f0b5e762fc to your computer and use it in GitHub Desktop.
Here's a way to get Sentry working in framework components with Astro.

This example isn't comprehensive, so you can't just copy and paste. However, it should be a good template to base your own solution off. I chose to use React but you can use any framework.

Note: Sentry has a Vite plugin. As of Astro 1.9.0, Astro runs Vite plugins twice during the build process, which results in map files being submitted twice. Until this is resolved, it doesn't seem like an acceptable solution.

SENTRY_AUTH_TOKEN=your-token-here
import { loadEnv } from 'vite'
import sentryIntegration from './sentry.js'
// Don't commit your Sentry auth token. Just use it as an environment variable.
//
// So far I haven't found a way to set environment variables from within the integration
// so we have to generate it ourselves externally to make it available to our framework components.
const { SENTRY_AUTH_TOKEN, PUBLIC_SENTRY_RELEASE } = loadEnv(
process.env.NODE_ENV,
process.cwd(),
''
)
export default defineConfig({
integrations: [
sentryIntegration({
authToken: SENTRY_AUTH_TOKEN,
org: 'guideguide',
project: 'website',
release: PUBLIC_SENTRY_RELEASE,
}),
],
})
import * as Sentry from '@sentry/react'
// Since Astro works with the "islands" mentality, you'll have to initialize Sentry
// at the top level of every component you use (not every component, just the one in
// your .astro file — the one that needs a template directive like "client:idle").
// Putting that step in a lib makes it easier to maintain if you have multiple components on a page.
// Use an environment variable for this if you prefer.
const dsn = `your-dsn`
const initSentry = () => {
if (typeof window !== 'undefined') {
Sentry.init({
dsn,
release: import.meta.env.PUBLIC_SENTRY_RELEASE,
})
}
}
export default initSentry
// I'm using React, but anything will do. Just be sure you use the appropriate Sentry SDK
import * as Sentry from '@sentry/react'
import initSentry from './init-sentry'
initSentry()
const MyComponent = () => {
// This will get sent to Sentry
Sentry.captureException(new Error("Hello World Error"))
return <p>Hello world</p>
}
export default MyComponent
---
import MyComponent from './my-component'
---
<MyComponent client:idle />
{
"scripts": {
"build": "export PUBLIC_SENTRY_RELEASE=$(git rev-parse HEAD) && astro build"
},
"devDependencies": {
"@sentry/cli": "^2.5.2",
"@sentry/react": "^7.8.1",
}
}
import type { AstroIntegration } from 'astro'
import { globby } from 'globby'
import { unlink } from 'fs/promises'
import SentryCli from '@sentry/cli'
// The integration portion of this solution handles sending sourcemaps to Sentry.
// I want my plugin options to be the Sentry CLI options plus a release string.
type PluginOptions = ConstructorParameters<typeof SentryCli>[1] & {
release: string
}
function sentryIntegration(opts: PluginOptions): AstroIntegration {
// As of publishing, I haven't found a way to programatically set environment variables,
// which is necessary to be able to pass the release to the Framework Component that loads sentry.
// Instead, we pass that in manually when running the build script and load it here.
const { authToken, org, project, release } = opts || {}
return {
name: 'Sentry',
hooks: {
// Before the build we configure Vite to produce sourcemaps. 'hidden' is chosen in this example
// because we're uploading our sourcemaps to sentry and don't need to include their urls in the
// associated JavaScript.
'astro:build:setup': async ({ vite }) => {
if (!!authToken) {
vite.build ||= {}
vite.build.sourcemap = true
}
},
'astro:build:done': async ({ dir }) => {
if (!!authToken) {
// Astro compliation produces a lot of .mjs.map files which I *believe* are
// for assets that are involved in static compliation — things like the compiled
// JavaScript version of .astro page files. We don't need to send those to Sentry
// because they aren't relevant to runtime, so we remove them here before we
// create the Sentry release.
const mjsMaps = await globby([`${dir.pathname}/**/*.mjs.map`])
await Promise.all(mjsMaps.map(x => unlink(x)))
// Submit the sourcemaps to Sentry
const { releases } = new SentryCli(null, { org, project, authToken })
await releases.new(release)
await releases.uploadSourceMaps(release, { include: [dir.pathname] })
await releases.finalize(release)
// Delete all the sourcemap files so they don't get published. Unfortunatelly,
// we don't have access to Vite's glob helper so I'm using globby here.
const maps = await globby([`${dir.pathname}/**/*.map`])
await Promise.all(maps.map(x => unlink(x)))
}
},
},
}
}
export default sentryIntegration
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment