Skip to content

Commit

Permalink
feat(astro): Add sentryAstro integration (#9218)
Browse files Browse the repository at this point in the history
Add the `sentryAstro` Astro integration which will be the core
component of the Astro SDK.

The integration takes care of:

* injecting SDK init code into the client and server entry points. 
* emitting source maps and adding the Sentry Vite plugin for source maps upload

The idea is: Basic SDK setup will only require `sentryAstro` to be added
by users. Everything else happens automatically. We also support a more
custom setup but it will require additional config files. The reason is
that we can only inject serialized code. We cannot take e.g. function
callbacks (e.g. `beforeSend`) in the options of `sentryAstro`.

---------

Co-authored-by: Francesco Novy <francesco.novy@sentry.io>
  • Loading branch information
Lms24 and mydea committed Oct 13, 2023
1 parent 8bc4d73 commit 1ba8d9f
Show file tree
Hide file tree
Showing 19 changed files with 638 additions and 22 deletions.
7 changes: 7 additions & 0 deletions packages/astro/.eslintrc.js → packages/astro/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,12 @@ module.exports = {
project: ['tsconfig.test.json'],
},
},
{
files: ['src/integration/**', 'src/server/**'],
rules: {
'@sentry-internal/sdk/no-optional-chaining': 'off',
'@sentry-internal/sdk/no-nullish-coalescing': 'off',
},
},
],
};
10 changes: 10 additions & 0 deletions packages/astro/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# The paths in this file are specified so that they align with the file structure in `./build` after this file is copied
# into it by the prepack script `scripts/prepack.ts`.

*

!/cjs/**/*
!/esm/**/*
!/types/**/*
!/types-ts3.8/**/*
!/integration/**/*
96 changes: 95 additions & 1 deletion packages/astro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,98 @@ This package is a wrapper around `@sentry/node` for the server and `@sentry/brow

## Installation and Setup

TODO
### 1. Registering the Sentry Astro integration:

Add the `sentryAstro` integration to your `astro.config.mjs` file:

```javascript
import { sentryAstro } from "@sentry/astro/integration";

export default defineConfig({
// Rest of your Astro project config
integrations: [
sentryAstro({
dsn: '__DSN__',
}),
],
})
```

This is the easiest way to configure Sentry in an Astro project.
You can pass a few additional options to `sentryAstro` but the SDK comes preconfigured in an opinionated way.
If you want to fully customize your SDK setup, you can do so, too:

### 2. [Optional] Uploading Source Maps

To upload source maps to Sentry, simply add the `project` and `authToken` options to `sentryAstro`:

```js
// astro.config.mjs
import { sentryAstro } from "@sentry/astro/integration";

export default defineConfig({
// Rest of your Astro project config
integrations: [
sentryAstro({
dsn: '__DSN__',
project: 'your-project-slug',
authToken: import.meta.env('SENTRY_AUTH_TOKEN'),
}),
],
})
```

You can also define these values as environment variables in e.g. a `.env` file
or in you CI configuration:

```sh
SENTRY_PROJECT="your-project"
SENTRY_AUTH_TOKEN="your-token"
```

Follow [this guide](https://docs.sentry.io/product/accounts/auth-tokens/#organization-auth-tokens) to create an auth token.

### 3. [Optional] Advanced Configuration

To fully customize and configure Sentry in an Astro project, follow step 1 and in addition,
add a `sentry.client.config.(js|ts)` and `sentry.server.config(js|ts)` file to the root directory of your project.
Inside these files, you can call `Sentry.init()` and use the full range of Sentry options.

Configuring the client SDK:

```js
// sentry.client.config.ts or sentry.server.config.ts
import * as Sentry from "@sentry/astro";

Sentry.init({
dsn: "__DSN__",
beforeSend(event) {
console.log("Sending event on the client");
return event;
},
tracesSampler: () => {/* ... */}
});
```

**Important**: Once you created a sentry config file, the SDK options passed to `sentryAstro` will be ignored for the respective runtime. You can also only define create of the two files.

#### 3.1 Custom file location

If you want to move the `sentry.*.config` files to another location,
you can specify the file path, relative to the project root, in `sentryAstro`:

```js
// astro.config.mjs
import { sentryAstro } from "@sentry/astro/integration";

export default defineConfig({
// Rest of your Astro project config
integrations: [
sentryAstro({
dsn: '__DSN__',
clientInitPath: '.config/sentry.client.init.js',
serverInitPath: '.config/sentry.server.init.js',
}),
],
})
```
17 changes: 14 additions & 3 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,33 @@
"engines": {
"node": ">=18.14.1"
},
"main": "build/cjs/index.server.js",
"type": "module",
"main": "build/cjs/index.client.js",
"module": "build/esm/index.server.js",
"browser": "build/esm/index.client.js",
"types": "build/types/index.types.d.ts",
"exports": {
".": {
"node": "./build/esm/index.server.js",
"browser": "./build/esm/index.client.js",
"import": "./build/esm/index.client.js",
"require": "./build/cjs/index.server.js",
"types": "./build/types/index.types.d.ts"
}
},
"publishConfig": {
"access": "public"
},
"peerDependencies": {
"astro": "1.x"
"astro": "3.x"
},
"dependencies": {
"@sentry/browser": "7.73.0",
"@sentry/node": "7.73.0",
"@sentry/core": "7.73.0",
"@sentry/utils": "7.73.0",
"@sentry/types": "7.73.0"
"@sentry/types": "7.73.0",
"@sentry/vite-plugin": "^2.8.0"
},
"devDependencies": {
"astro": "^3.2.3",
Expand Down
7 changes: 6 additions & 1 deletion packages/astro/rollup.npm.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { makeBaseNPMConfig, makeNPMConfigVariants } from '../../rollup/index.js';

export default makeNPMConfigVariants(
const variants = makeNPMConfigVariants(
makeBaseNPMConfig({
entrypoints: ['src/index.server.ts', 'src/index.client.ts'],
packageSpecificConfig: {
output: {
dynamicImportInCjs: true,
exports: 'named',
},
},
// Astro is Node 18+ no need to add polyfills
addPolyfills: false,
}),
);

export default variants;
20 changes: 20 additions & 0 deletions packages/astro/scripts/syncIntegration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* eslint-disable no-console */

import * as fse from 'fs-extra';
import * as path from 'path';

const buildDir = path.resolve('build');
const srcIntegrationDir = path.resolve(path.join('src', 'integration'));
const destIntegrationDir = path.resolve(path.join(buildDir, 'integration'));

try {
fse.copySync(srcIntegrationDir, destIntegrationDir, {
filter: (src, _) => {
return !src.endsWith('.md');
},
});
console.log('\nCopied Astro integration to ./build/integration\n');
} catch (e) {
console.error('\nError while copying integration to build dir:');
console.error(e);
}
5 changes: 5 additions & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
// Unfortunately, we cannot `export * from '@sentry/node'` because in prod builds,
// Vite puts these exports into a `default` property (Sentry.default) rather than
// on the top - level namespace.

import { sentryAstro } from './integration';

// Hence, we export everything from the Node SDK explicitly:
export {
addGlobalEventProcessor,
Expand Down Expand Up @@ -58,3 +61,5 @@ export {
export * from '@sentry/node';

export { init } from './server/sdk';

export default sentryAstro;
81 changes: 81 additions & 0 deletions packages/astro/src/integration/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/* eslint-disable no-console */
import { sentryVitePlugin } from '@sentry/vite-plugin';
import type { AstroIntegration } from 'astro';
import * as fs from 'fs';
import * as path from 'path';

import { buildClientSnippet, buildSdkInitFileImportSnippet, buildServerSnippet } from './snippets';
import type { SentryOptions } from './types';

const PKG_NAME = '@sentry/astro';

export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
return {
name: PKG_NAME,
hooks: {
'astro:config:setup': async ({ updateConfig, injectScript }) => {
// The third param here enables loading of all env vars, regardless of prefix
// see: https://main.vitejs.dev/config/#using-environment-variables-in-config

// TODO: Ideally, we want to load the environment with vite like this:
// const env = loadEnv('production', process.cwd(), '');
// However, this currently throws a build error.
// Will revisit this later.
const env = process.env;

const uploadOptions = options.sourceMapsUploadOptions || {};

const shouldUploadSourcemaps = uploadOptions?.enabled ?? true;
const authToken = uploadOptions.authToken || env.SENTRY_AUTH_TOKEN;

if (shouldUploadSourcemaps && authToken) {
updateConfig({
vite: {
build: {
sourcemap: true,
},
plugins: [
sentryVitePlugin({
org: uploadOptions.org ?? env.SENTRY_ORG,
project: uploadOptions.project ?? env.SENTRY_PROJECT,
authToken: uploadOptions.authToken ?? env.SENTRY_AUTH_TOKEN,
telemetry: uploadOptions.telemetry ?? true,
}),
],
},
});
}

const pathToClientInit = options.clientInitPath
? path.resolve(options.clientInitPath)
: findDefaultSdkInitFile('client');
const pathToServerInit = options.serverInitPath
? path.resolve(options.serverInitPath)
: findDefaultSdkInitFile('server');

if (pathToClientInit) {
options.debug && console.log(`[sentry-astro] Using ${pathToClientInit} for client init.`);
injectScript('page', buildSdkInitFileImportSnippet(pathToClientInit));
} else {
options.debug && console.log('[sentry-astro] Using default client init.');
injectScript('page', buildClientSnippet(options || {}));
}

if (pathToServerInit) {
options.debug && console.log(`[sentry-astro] Using ${pathToServerInit} for server init.`);
injectScript('page-ssr', buildSdkInitFileImportSnippet(pathToServerInit));
} else {
options.debug && console.log('[sentry-astro] Using default server init.');
injectScript('page-ssr', buildServerSnippet(options || {}));
}
},
},
};
};

function findDefaultSdkInitFile(type: 'server' | 'client'): string | undefined {
const fileExtensions = ['ts', 'js', 'tsx', 'jsx', 'mjs', 'cjs', 'mts'];
return fileExtensions
.map(ext => path.resolve(path.join(process.cwd(), `sentry.${type}.config.${ext}`)))
.find(filename => fs.existsSync(filename));
}
45 changes: 45 additions & 0 deletions packages/astro/src/integration/snippets.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { SentryOptions } from './types';

/**
* Creates a snippet that imports a Sentry.init file.
*/
export function buildSdkInitFileImportSnippet(filePath: string): string {
return `import "${filePath}";`;
}

/**
* Creates a snippet that initializes Sentry on the client by choosing
* default options.
*/
export function buildClientSnippet(options: SentryOptions): string {
return `import * as Sentry from "@sentry/astro";
Sentry.init({
${buildCommonInitOptions(options)}
integrations: [new Sentry.BrowserTracing(), new Sentry.Replay()],
replaysSessionSampleRate: ${options.replaysSessionSampleRate ?? 0.1},
replaysOnErrorSampleRate: ${options.replaysOnErrorSampleRate ?? 1.0},
});`;
}

/**
* Creates a snippet that initializes Sentry on the server by choosing
* default options.
*/
export function buildServerSnippet(options: SentryOptions): string {
return `import * as Sentry from "@sentry/astro";
Sentry.init({
${buildCommonInitOptions(options)}
});`;
}

const buildCommonInitOptions = (options: SentryOptions): string => `dsn: ${
options.dsn ? JSON.stringify(options.dsn) : 'import.meta.env.PUBLIC_SENTRY_DSN'
},
debug: ${options.debug ? true : false},
environment: ${options.environment ? JSON.stringify(options.environment) : 'import.meta.env.PUBLIC_VERCEL_ENV'},
release: ${options.release ? JSON.stringify(options.release) : 'import.meta.env.PUBLIC_VERCEL_GIT_COMMIT_SHA'},
tracesSampleRate: ${options.tracesSampleRate ?? 1.0},${
options.sampleRate ? `\n sampleRate: ${options.sampleRate},` : ''
}`;

0 comments on commit 1ba8d9f

Please sign in to comment.