Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/dependency-review-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@ allow-ghsas:
- GHSA-v2wj-q39q-566r
# esbuil, used in browser bundler plugin E2E tests
- GHSA-gv7w-rqvm-qjhr
# astro 4, our minimum supported version, kept as a devDependency so the SDK
# keeps typechecking against it.
# Once our minimum supported version is over 6.4.6 these can be removed.
- GHSA-wrwg-2hg8-v723
- GHSA-8hv8-536x-4wqp
- GHSA-2pvr-wf23-7pc7
# sharp, pulled in as an optional dependency of astro 4 (^0.33.3). Only fixed in
# 0.35.0, which is outside that range. Already present on develop via sharp 0.32.6
# and 0.34.5, both of which are affected by the same advisory.
- GHSA-f88m-g3jw-g9cj
28 changes: 14 additions & 14 deletions packages/astro/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,7 @@ SENTRY_AUTH_TOKEN="your-token"
### Server Instrumentation

For Astro apps configured for (hybrid) Server Side Rendering (SSR), the Sentry integration will automatically add
middleware to your server to instrument incoming requests **if you're using Astro 3.5.2 or newer**.

If you're using Astro <3.5.2, complete the setup by adding the Sentry middleware to your `src/middleware.js` file:

```javascript
// src/middleware.js
import { sequence } from 'astro:middleware';
import * as Sentry from '@sentry/astro';

export const onRequest = sequence(
Sentry.handleRequest(),
// Add your other handlers after Sentry.handleRequest()
);
```
middleware to your server to instrument incoming requests.

The Sentry middleware enhances the data collected by Sentry on the server side by:

Expand Down Expand Up @@ -101,6 +88,19 @@ export default defineConfig({
});
```

If you opt out but still want the middleware, add it manually to your `src/middleware.js` file:

```javascript
// src/middleware.js
import { sequence } from 'astro:middleware';
import * as Sentry from '@sentry/astro';

export const onRequest = sequence(
Sentry.handleRequest(),
// Add your other handlers after Sentry.handleRequest()
);
```

## Configuration

Check out our docs for configuring your SDK setup:
Expand Down
4 changes: 2 additions & 2 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@
"access": "public"
},
"peerDependencies": {
"astro": ">=3.x || >=4.0.0-beta || >=7.0.0-beta"
"astro": ">=4.0.0-beta || >=7.0.0-beta"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am just wondering why we don't support the beta versions of 5 and 6. But almost no one is using them, so I guess it's fine to keep them out.

},
"dependencies": {
"@sentry/browser": "10.67.0",
Expand All @@ -63,7 +63,7 @@
"@sentry/bundler-plugins": "10.67.0"
},
"devDependencies": {
"astro": "^3.5.0",
"astro": "^4.16.19",
"vite": "^6.4.3"
},
"scripts": {
Expand Down
11 changes: 8 additions & 3 deletions packages/astro/src/integration/cloudflare.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import type { AstroConfig } from 'astro';
import { builtinModules } from 'module';
import type { Plugin } from 'vite';

// Derived from Astro's own config type rather than imported from `vite` directly: Astro bundles its
// own Vite version, which differs across the Astro majors we support. A plugin typed against any
// single Vite version is not assignable to `updateConfig({ vite: { plugins } })` for the others.
type VitePlugin = Extract<NonNullable<NonNullable<AstroConfig['vite']>['plugins']>[number], { name: string }>;

// Build a set of all Node.js built-in module names, including both
// bare names (e.g. "fs") and "node:" prefixed names (e.g. "node:fs").
Expand All @@ -14,7 +19,7 @@ const NODE_BUILTINS = new Set(builtinModules.flatMap(m => [m, `node:${m}`]));
* modules. Vite correctly externalizes them, but warns about it. These warnings are
* harmless since Cloudflare Workers support Node.js built-ins under the `node:` prefix.
*/
export function sentryCloudflareNodeWarningPlugin(): Plugin {
export function sentryCloudflareNodeWarningPlugin(): VitePlugin {
return {
name: 'sentry-astro-cloudflare-suppress-node-warnings',
enforce: 'pre',
Expand Down Expand Up @@ -46,7 +51,7 @@ export function sentryCloudflareNodeWarningPlugin(): Plugin {
* - Per-request isolation scopes via `wrapRequestHandler`
* - Trace context propagation
*/
export function sentryCloudflareVitePlugin(): Plugin {
export function sentryCloudflareVitePlugin(): VitePlugin {
return {
name: 'sentry-astro-cloudflare',
enforce: 'post',
Expand Down
6 changes: 1 addition & 5 deletions packages/astro/src/integration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,7 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => {
const isSSR = config && (config.output === 'server' || config.output === 'hybrid' || !!config.adapter);
const shouldAddMiddleware = sdkEnabled.server && autoInstrumentation?.requestHandler !== false;

// Guarding calling the addMiddleware function because it was only introduced in astro@3.5.0
// Users on older versions of astro will need to add the middleware manually.
const supportsAddMiddleware = typeof addMiddleware === 'function';

if (supportsAddMiddleware && isSSR && shouldAddMiddleware) {
if (isSSR && shouldAddMiddleware) {
addMiddleware({
order: 'pre',
entrypoint: '@sentry/astro/middleware',
Expand Down
11 changes: 5 additions & 6 deletions packages/astro/src/integration/middleware/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ type MiddlewareNext = () => Promise<Response>;
type MiddlewareHandler = (ctx: unknown, next: MiddlewareNext) => Promise<Response> | Response | Promise<void> | void;

/**
* This export is used by our integration to automatically add the middleware
* to astro ^3.5.0 projects.
* This export is used by our integration to automatically add the middleware.
*
* It's not possible to pass options at this moment, so we'll call our middleware
* factory function with the default options. Users can deactivate the automatic
Expand All @@ -16,9 +15,9 @@ export const onRequest: MiddlewareHandler = (ctx, next) => {
const middleware = handleRequest();

// `onRequest` deliberately uses framework-agnostic parameter types so the published
// `@sentry/astro/middleware` declaration does not reference Astro-version-specific types
// (e.g. `MiddlewareResponseHandler`, which is absent in some supported Astro versions).
// The handler returned by `handleRequest()` is typed against Astro's own types, so we cast
// back to its expected parameter types here – the runtime shapes are identical.
// `@sentry/astro/middleware` declaration does not reference Astro's own types, which are
// shaped differently across the Astro majors we support. The handler returned by
// `handleRequest()` is typed against Astro's types, so we cast back to its expected
// parameter types here – the runtime shapes are identical.
return middleware(ctx as Parameters<typeof middleware>[0], next as Parameters<typeof middleware>[1]);
};
4 changes: 0 additions & 4 deletions packages/astro/src/integration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,6 @@ type InstrumentationOptions = {
* - enable distributed tracing between server and client
* - annotate server errors with more information
*
* This middleware will only be added automatically in Astro 3.5.0 and newer.
* For older versions, add the `Sentry.handleRequest` middleware manually
* in your `src/middleware.js` file.
*
* @default true in SSR/hybrid mode, false in SSG/static mode
*/
requestHandler?: boolean;
Expand Down
25 changes: 8 additions & 17 deletions packages/astro/src/server/middleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
winterCGHeadersToDict,
withIsolationScope,
} from '@sentry/node';
import type { APIContext, MiddlewareResponseHandler, RoutePart } from 'astro';
import type { APIContext, MiddlewareHandler, MiddlewareNext, RoutePart } from 'astro';

type MiddlewareOptions = {
/**
Expand Down Expand Up @@ -63,7 +63,7 @@ type AstroLocalsWithSentry = Record<string, unknown> & {
__sentry_wrapped__?: boolean;
};

export const handleRequest: (options?: MiddlewareOptions) => MiddlewareResponseHandler = options => {
export const handleRequest: (options?: MiddlewareOptions) => MiddlewareHandler = options => {
const handlerOptions = {
trackClientIp: false,
...options,
Expand Down Expand Up @@ -102,10 +102,7 @@ export const handleRequest: (options?: MiddlewareOptions) => MiddlewareResponseH
};
};

async function handleStaticRoute(
ctx: Parameters<MiddlewareResponseHandler>[0],
next: Parameters<MiddlewareResponseHandler>[1],
): Promise<Response> {
async function handleStaticRoute(ctx: APIContext, next: MiddlewareNext): Promise<Response> {
const parametrizedRoute = getParametrizedRoute(ctx);
try {
const originalResponse = await next();
Expand All @@ -120,11 +117,7 @@ async function handleStaticRoute(
}
}

async function enhanceHttpServerSpan(
ctx: Parameters<MiddlewareResponseHandler>[0],
next: Parameters<MiddlewareResponseHandler>[1],
rootSpan: Span,
): Promise<Response> {
async function enhanceHttpServerSpan(ctx: APIContext, next: MiddlewareNext, rootSpan: Span): Promise<Response> {
// Make sure we don't accidentally double wrap (e.g. user added middleware and integration auto added it)
const locals = ctx.locals as AstroLocalsWithSentry | undefined;
if (locals?.__sentry_wrapped__) {
Expand Down Expand Up @@ -170,8 +163,8 @@ async function enhanceHttpServerSpan(
}

async function instrumentRequestStartHttpServerSpan(
ctx: Parameters<MiddlewareResponseHandler>[0],
next: Parameters<MiddlewareResponseHandler>[1],
ctx: APIContext,
next: MiddlewareNext,
options: MiddlewareOptions,
): Promise<Response> {
// Make sure we don't accidentally double wrap (e.g. user added middleware and integration auto added it)
Expand Down Expand Up @@ -398,7 +391,7 @@ function tryDecodeUrl(url: string): string | undefined {
* We can check this by looking at the middleware's `clientAddress` context property because accessing
* this prop in a static route will throw an error which we can conveniently catch.
*/
function checkIsDynamicPageRequest(context: Parameters<MiddlewareResponseHandler>[0]): boolean {
function checkIsDynamicPageRequest(context: APIContext): boolean {
try {
return context.clientAddress != null;
} catch {
Expand All @@ -421,9 +414,7 @@ function joinRouteSegments(segments: RoutePart[][]): string {
return `/${parthArray.join('/')}`;
}

function getParametrizedRoute(
ctx: Parameters<MiddlewareResponseHandler>[0] & { routePattern?: string },
): string | undefined {
function getParametrizedRoute(ctx: APIContext & { routePattern?: string }): string | undefined {
try {
// `routePattern` is available after Astro 5
const contextWithRoutePattern = ctx;
Expand Down
1 change: 1 addition & 0 deletions packages/astro/test/integration/cloudflare.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const baseConfigHookObject = vi.hoisted(() => ({
logger: { warn: vi.fn(), info: vi.fn(), error: vi.fn() },
injectScript: vi.fn(),
updateConfig: vi.fn(),
addMiddleware: vi.fn(),
}));

describe('Cloudflare Pages vs Workers detection', () => {
Expand Down
62 changes: 21 additions & 41 deletions packages/astro/test/integration/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const config = {

const baseConfigHookObject = {
logger: { warn: vi.fn(), info: vi.fn() },
addMiddleware: vi.fn(),
};

describe('sentryAstro integration', () => {
Expand Down Expand Up @@ -452,31 +453,28 @@ describe('sentryAstro integration', () => {
);
});

it.each(['server', 'hybrid'])(
'adds middleware by default if in %s mode and `addMiddleware` is available',
async mode => {
const integration = sentryAstro({});
const addMiddleware = vi.fn();
const updateConfig = vi.fn();
const injectScript = vi.fn();
it.each(['server', 'hybrid'])('adds middleware by default if in %s mode', async mode => {
const integration = sentryAstro({});
const addMiddleware = vi.fn();
const updateConfig = vi.fn();
const injectScript = vi.fn();

expect(integration.hooks['astro:config:setup']).toBeDefined();
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({
// @ts-expect-error - we only need to pass what we actually use
config: { output: mode },
addMiddleware,
updateConfig,
injectScript,
});
expect(integration.hooks['astro:config:setup']).toBeDefined();
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({
// @ts-expect-error - we only need to pass what we actually use
config: { output: mode },
addMiddleware,
updateConfig,
injectScript,
});

expect(addMiddleware).toHaveBeenCalledTimes(1);
expect(addMiddleware).toHaveBeenCalledWith({
order: 'pre',
entrypoint: '@sentry/astro/middleware',
});
},
);
expect(addMiddleware).toHaveBeenCalledTimes(1);
expect(addMiddleware).toHaveBeenCalledWith({
order: 'pre',
entrypoint: '@sentry/astro/middleware',
});
});

it.each([{ output: 'static' }, { output: undefined }])(
"doesn't add middleware if in static mode (config %s)",
Expand Down Expand Up @@ -518,24 +516,6 @@ describe('sentryAstro integration', () => {
expect(addMiddleware).toHaveBeenCalledTimes(0);
});

it("doesn't add middleware (i.e. crash) if `addMiddleware` is N/A", async () => {
const integration = sentryAstro({ autoInstrumentation: { requestHandler: false } });
const updateConfig = vi.fn();
const injectScript = vi.fn();

expect(integration.hooks['astro:config:setup']).toBeDefined();
// @ts-expect-error - the hook exists and we only need to pass what we actually use
await integration.hooks['astro:config:setup']({
// @ts-expect-error - we only need to pass what we actually use
config: { output: 'server' },
updateConfig,
injectScript,
});

expect(updateConfig).toHaveBeenCalledTimes(1);
expect(injectScript).toHaveBeenCalledTimes(2);
});

it("doesn't add middleware if the SDK is disabled", () => {
const integration = sentryAstro({ enabled: false });
const addMiddleware = vi.fn();
Expand Down
Loading
Loading