Skip to content

Commit cdf46ec

Browse files
committed
feat(astro): Add server and client SDK init functions
1 parent aeb4462 commit cdf46ec

File tree

10 files changed

+364
-4
lines changed

10 files changed

+364
-4
lines changed

packages/astro/package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
"peerDependencies": {
2020
"astro": "1.x"
2121
},
22+
"dependencies": {
23+
"@sentry/browser": "7.73.0",
24+
"@sentry/node": "7.73.0",
25+
"@sentry/core": "7.73.0",
26+
"@sentry/utils": "7.73.0",
27+
"@sentry/types": "7.73.0"
28+
},
2229
"devDependencies": {
2330
"astro": "^3.2.3",
2431
"rollup": "^3.20.2",

packages/astro/src/client/sdk.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { BrowserOptions } from '@sentry/browser';
2+
import { BrowserTracing, init as initBrowserSdk } from '@sentry/browser';
3+
import { configureScope, hasTracingEnabled } from '@sentry/core';
4+
import { addOrUpdateIntegration } from '@sentry/utils';
5+
6+
import { applySdkMetadata } from '../common/metadata';
7+
8+
// Treeshakable guard to remove all code related to tracing
9+
declare const __SENTRY_TRACING__: boolean;
10+
11+
/**
12+
* Initialize the client side of the Sentry Astro SDK.
13+
*
14+
* @param options Configuration options for the SDK.
15+
*/
16+
export function init(options: BrowserOptions): void {
17+
applySdkMetadata(options, ['astro', 'browser']);
18+
19+
addClientIntegrations(options);
20+
21+
initBrowserSdk(options);
22+
23+
configureScope(scope => {
24+
scope.setTag('runtime', 'browser');
25+
});
26+
}
27+
28+
function addClientIntegrations(options: BrowserOptions): void {
29+
let integrations = options.integrations || [];
30+
31+
// This evaluates to true unless __SENTRY_TRACING__ is text-replaced with "false",
32+
// in which case everything inside will get treeshaken away
33+
if (typeof __SENTRY_TRACING__ === 'undefined' || __SENTRY_TRACING__) {
34+
if (hasTracingEnabled(options)) {
35+
const defaultBrowserTracingIntegration = new BrowserTracing({});
36+
37+
integrations = addOrUpdateIntegration(defaultBrowserTracingIntegration, integrations);
38+
}
39+
}
40+
41+
options.integrations = integrations;
42+
}

packages/astro/src/common/metadata.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { SDK_VERSION } from '@sentry/core';
2+
import type { Options, SdkInfo } from '@sentry/types';
3+
4+
const PACKAGE_NAME_PREFIX = 'npm:@sentry/';
5+
6+
/**
7+
* A builder for the SDK metadata in the options for the SDK initialization.
8+
*
9+
* Note: This function is identical to `buildMetadata` in Remix and NextJS and SvelteKit.
10+
* We don't extract it for bundle size reasons.
11+
* @see https://github.com/getsentry/sentry-javascript/pull/7404
12+
* @see https://github.com/getsentry/sentry-javascript/pull/4196
13+
*
14+
* If you make changes to this function consider updating the others as well.
15+
*
16+
* @param options SDK options object that gets mutated
17+
* @param names list of package names
18+
*/
19+
export function applySdkMetadata(options: Options, names: string[]): void {
20+
options._metadata = options._metadata || {};
21+
options._metadata.sdk =
22+
options._metadata.sdk ||
23+
({
24+
name: 'sentry.javascript.astro',
25+
packages: names.map(name => ({
26+
name: `${PACKAGE_NAME_PREFIX}${name}`,
27+
version: SDK_VERSION,
28+
})),
29+
version: SDK_VERSION,
30+
} as SdkInfo);
31+
}

packages/astro/src/index.client.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
export const client = true;
1+
export * from '@sentry/browser';
2+
3+
export { init } from './client/sdk';

packages/astro/src/index.server.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,60 @@
1-
export const server = true;
1+
// Node SDK exports
2+
// Unfortunately, we cannot `export * from '@sentry/node'` because in prod builds,
3+
// Vite puts these exports into a `default` property (Sentry.default) rather than
4+
// on the top - level namespace.
5+
// Hence, we export everything from the Node SDK explicitly:
6+
export {
7+
addGlobalEventProcessor,
8+
addBreadcrumb,
9+
captureException,
10+
captureEvent,
11+
captureMessage,
12+
captureCheckIn,
13+
configureScope,
14+
createTransport,
15+
extractTraceparentData,
16+
getActiveTransaction,
17+
getHubFromCarrier,
18+
getCurrentHub,
19+
Hub,
20+
makeMain,
21+
Scope,
22+
startTransaction,
23+
SDK_VERSION,
24+
setContext,
25+
setExtra,
26+
setExtras,
27+
setTag,
28+
setTags,
29+
setUser,
30+
spanStatusfromHttpCode,
31+
trace,
32+
withScope,
33+
autoDiscoverNodePerformanceMonitoringIntegrations,
34+
makeNodeTransport,
35+
defaultIntegrations,
36+
defaultStackParser,
37+
lastEventId,
38+
flush,
39+
close,
40+
getSentryRelease,
41+
addRequestDataToEvent,
42+
DEFAULT_USER_INCLUDES,
43+
extractRequestData,
44+
deepReadDirSync,
45+
Integrations,
46+
Handlers,
47+
setMeasurement,
48+
getActiveSpan,
49+
startSpan,
50+
// eslint-disable-next-line deprecation/deprecation
51+
startActiveSpan,
52+
startInactiveSpan,
53+
startSpanManual,
54+
continueTrace,
55+
} from '@sentry/node';
56+
57+
// We can still leave this for the carrier init and type exports
58+
export * from '@sentry/node';
59+
60+
export { init } from './server/sdk';

packages/astro/src/index.types.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,25 @@
1-
export type Placeholder = true;
1+
/* eslint-disable import/export */
2+
3+
// We export everything from both the client part of the SDK and from the server part.
4+
// Some of the exports collide, which is not allowed, unless we redifine the colliding
5+
// exports in this file - which we do below.
6+
export * from './index.client';
7+
export * from './index.server';
8+
9+
import type { Integration, Options, StackParser } from '@sentry/types';
10+
11+
import type * as clientSdk from './index.client';
12+
import type * as serverSdk from './index.server';
13+
14+
/** Initializes Sentry Astro SDK */
15+
export declare function init(options: Options | clientSdk.BrowserOptions | serverSdk.NodeOptions): void;
16+
17+
// We export a merged Integrations object so that users can (at least typing-wise) use all integrations everywhere.
18+
export declare const Integrations: typeof clientSdk.Integrations & typeof serverSdk.Integrations;
19+
20+
export declare const defaultIntegrations: Integration[];
21+
export declare const defaultStackParser: StackParser;
22+
23+
export declare function close(timeout?: number | undefined): PromiseLike<boolean>;
24+
export declare function flush(timeout?: number | undefined): PromiseLike<boolean>;
25+
export declare function lastEventId(): string | undefined;

packages/astro/src/server/sdk.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { configureScope } from '@sentry/core';
2+
import type { NodeOptions } from '@sentry/node';
3+
import { init as initNodeSdk } from '@sentry/node';
4+
5+
import { applySdkMetadata } from '../common/metadata';
6+
7+
/**
8+
*
9+
* @param options
10+
*/
11+
export function init(options: NodeOptions): void {
12+
applySdkMetadata(options, ['astro', 'node']);
13+
14+
initNodeSdk(options);
15+
16+
configureScope(scope => {
17+
scope.setTag('runtime', 'node');
18+
});
19+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import type { BrowserClient } from '@sentry/browser';
2+
import * as SentryBrowser from '@sentry/browser';
3+
import { BrowserTracing, getCurrentHub, SDK_VERSION, WINDOW } from '@sentry/browser';
4+
import { vi } from 'vitest';
5+
6+
import { init } from '../../../astro/src/client/sdk';
7+
8+
const browserInit = vi.spyOn(SentryBrowser, 'init');
9+
10+
describe('Sentry client SDK', () => {
11+
describe('init', () => {
12+
afterEach(() => {
13+
vi.clearAllMocks();
14+
WINDOW.__SENTRY__.hub = undefined;
15+
});
16+
17+
it('adds Astro metadata to the SDK options', () => {
18+
expect(browserInit).not.toHaveBeenCalled();
19+
20+
init({});
21+
22+
expect(browserInit).toHaveBeenCalledTimes(1);
23+
expect(browserInit).toHaveBeenCalledWith(
24+
expect.objectContaining({
25+
_metadata: {
26+
sdk: {
27+
name: 'sentry.javascript.astro',
28+
version: SDK_VERSION,
29+
packages: [
30+
{ name: 'npm:@sentry/astro', version: SDK_VERSION },
31+
{ name: 'npm:@sentry/browser', version: SDK_VERSION },
32+
],
33+
},
34+
},
35+
}),
36+
);
37+
});
38+
39+
it('sets the runtime tag on the scope', () => {
40+
const currentScope = getCurrentHub().getScope();
41+
42+
// @ts-expect-error need access to protected _tags attribute
43+
expect(currentScope._tags).toEqual({});
44+
45+
init({ dsn: 'https://[email protected]/1337' });
46+
47+
// @ts-expect-error need access to protected _tags attribute
48+
expect(currentScope._tags).toEqual({ runtime: 'browser' });
49+
});
50+
51+
describe('automatically adds integrations', () => {
52+
it.each([
53+
['tracesSampleRate', { tracesSampleRate: 0 }],
54+
['tracesSampler', { tracesSampler: () => 1.0 }],
55+
['enableTracing', { enableTracing: true }],
56+
])('adds the BrowserTracing integration if tracing is enabled via %s', (_, tracingOptions) => {
57+
init({
58+
dsn: 'https://[email protected]/1337',
59+
...tracingOptions,
60+
});
61+
62+
const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
63+
const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById('BrowserTracing');
64+
65+
expect(integrationsToInit).toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
66+
expect(browserTracing).toBeDefined();
67+
});
68+
69+
it.each([
70+
['enableTracing', { enableTracing: false }],
71+
['no tracing option set', {}],
72+
])("doesn't add the BrowserTracing integration if tracing is disabled via %s", (_, tracingOptions) => {
73+
init({
74+
dsn: 'https://[email protected]/1337',
75+
...tracingOptions,
76+
});
77+
78+
const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
79+
const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById('BrowserTracing');
80+
81+
expect(integrationsToInit).not.toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
82+
expect(browserTracing).toBeUndefined();
83+
});
84+
85+
it("doesn't add the BrowserTracing integration if `__SENTRY_TRACING__` is set to false", () => {
86+
globalThis.__SENTRY_TRACING__ = false;
87+
88+
init({
89+
dsn: 'https://[email protected]/1337',
90+
enableTracing: true,
91+
});
92+
93+
const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
94+
const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById('BrowserTracing');
95+
96+
expect(integrationsToInit).not.toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
97+
expect(browserTracing).toBeUndefined();
98+
99+
delete globalThis.__SENTRY_TRACING__;
100+
});
101+
102+
it('Overrides the automatically default BrowserTracing instance with a a user-provided instance', () => {
103+
init({
104+
dsn: 'https://[email protected]/1337',
105+
integrations: [new BrowserTracing({ finalTimeout: 10, startTransactionOnLocationChange: false })],
106+
enableTracing: true,
107+
});
108+
109+
const integrationsToInit = browserInit.mock.calls[0][0]?.integrations;
110+
111+
const browserTracing = (getCurrentHub().getClient() as BrowserClient)?.getIntegrationById(
112+
'BrowserTracing',
113+
) as BrowserTracing;
114+
const options = browserTracing.options;
115+
116+
expect(integrationsToInit).toContainEqual(expect.objectContaining({ name: 'BrowserTracing' }));
117+
expect(browserTracing).toBeDefined();
118+
119+
// This shows that the user-configured options are still here
120+
expect(options.finalTimeout).toEqual(10);
121+
});
122+
});
123+
});
124+
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { getCurrentHub } from '@sentry/core';
2+
import * as SentryNode from '@sentry/node';
3+
import { SDK_VERSION } from '@sentry/node';
4+
import { GLOBAL_OBJ } from '@sentry/utils';
5+
import { vi } from 'vitest';
6+
7+
import { init } from '../../src/server/sdk';
8+
9+
const nodeInit = vi.spyOn(SentryNode, 'init');
10+
11+
describe('Sentry server SDK', () => {
12+
describe('init', () => {
13+
afterEach(() => {
14+
vi.clearAllMocks();
15+
GLOBAL_OBJ.__SENTRY__.hub = undefined;
16+
});
17+
18+
it('adds Astro metadata to the SDK options', () => {
19+
expect(nodeInit).not.toHaveBeenCalled();
20+
21+
init({});
22+
23+
expect(nodeInit).toHaveBeenCalledTimes(1);
24+
expect(nodeInit).toHaveBeenCalledWith(
25+
expect.objectContaining({
26+
_metadata: {
27+
sdk: {
28+
name: 'sentry.javascript.astro',
29+
version: SDK_VERSION,
30+
packages: [
31+
{ name: 'npm:@sentry/astro', version: SDK_VERSION },
32+
{ name: 'npm:@sentry/node', version: SDK_VERSION },
33+
],
34+
},
35+
},
36+
}),
37+
);
38+
});
39+
40+
it('sets the runtime tag on the scope', () => {
41+
const currentScope = getCurrentHub().getScope();
42+
43+
// @ts-expect-error need access to protected _tags attribute
44+
expect(currentScope._tags).toEqual({});
45+
46+
init({ dsn: 'https://[email protected]/1337' });
47+
48+
// @ts-expect-error need access to protected _tags attribute
49+
expect(currentScope._tags).toEqual({ runtime: 'node' });
50+
});
51+
});
52+
});

packages/sveltekit/src/common/metadata.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const PACKAGE_NAME_PREFIX = 'npm:@sentry/';
88
*
99
* Note: This function is identical to `buildMetadata` in Remix and NextJS.
1010
* We don't extract it for bundle size reasons.
11-
* If you make changes to this function consider updating the othera as well.
11+
* If you make changes to this function consider updating the others as well.
1212
*
1313
* @param options SDK options object that gets mutated
1414
* @param names list of package names

0 commit comments

Comments
 (0)