Skip to content
Open
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
4 changes: 2 additions & 2 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ module.exports = [
import: createImport('init'),
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
gzip: true,
limit: '156 KB',
limit: '157 KB',
disablePlugins: ['@size-limit/esbuild'],
},
{
Expand Down Expand Up @@ -465,7 +465,7 @@ module.exports = [
path: 'packages/node/build/esm/index.js',
import: createImport('init'),
gzip: true,
limit: '135 KB',
limit: '137 KB',
disablePlugins: ['@size-limit/esbuild'],
ignore: [...builtinModules, ...nodePrefixedBuiltinModules],
modifyWebpackConfig: function (config) {
Expand Down
3 changes: 3 additions & 0 deletions dev-packages/node-integration-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"generic-pool": "^3.9.0",
"graphql": "^16.11.0",
"groq-sdk": "1.6.0",
"h3": "2.0.1-rc.22",
"graphql-tag": "^2.12.7",
"hono": "^4.13.7",
"http-terminator": "^3.2.0",
Expand Down Expand Up @@ -102,7 +103,9 @@
"reflect-metadata": "0.2.2",
"together-ai": "0.54.0",
"rxjs": "^7.8.2",
"srvx": "^0.11.22",
"tedious": "^19.2.1",
"unstorage": "2.0.0-alpha.7",
"winston": "^3.17.0",
"ws": "^8.20.1",
"yargs": "^16.2.0"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
release: '1.0',
tracesSampleRate: 1.0,
transport: loggingTransport,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { sendPortToRunner } from '@sentry-internal/node-integration-tests';
import { H3, serve } from 'h3';
import { tracingPlugin as h3TracingPlugin } from 'h3/tracing';
import { tracingPlugin as srvxTracingPlugin } from 'srvx/tracing';
import { createStorage } from 'unstorage';
import { withTracing } from 'unstorage/tracing';

// Enables the same diagnostics channels a Nitro app publishes, without a Nitro build.
const storage = withTracing(createStorage());

const app = new H3({ plugins: [h3TracingPlugin()] });

app.get('/hello/:name', event => `Hello, ${event.context.params.name}!`);

app.get('/cache', async () => {
await storage.setItem('cache:key', 'value');
return storage.getItem('cache:key');
});

const server = serve(app, { port: 0, silent: true, plugins: [srvxTracingPlugin()] });

await server.ready();
sendPortToRunner(new URL(server.url).port);
106 changes: 106 additions & 0 deletions dev-packages/node-integration-tests/suites/tracing/nitro/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { afterAll, describe, expect, vi } from 'vitest';
import { supports } from '../../../utils';
import { cleanupChildProcesses, createEsmTests } from '../../../utils/runner';

// h3 v2 and srvx are ESM-only and require Node >= 20.
describe.runIf(supports({ min: 20 }))('nitro integrations', () => {
afterAll(() => {
cleanupChildProcesses();
});

createEsmTests(__dirname, 'scenario.mjs', 'instrument.mjs', (createRunner, test) => {
test('creates h3 and srvx spans and sets Server-Timing headers', async () => {
let traceId: string | undefined;

const runner = createRunner()
.expect({
span: container => {
const segment = container.items.find(span => span.is_segment);
const srvxSpan = container.items.find(
span => span.attributes['sentry.origin']?.value === 'auto.http.nitro.srvx',
);
const h3Span = container.items.find(
span => span.attributes['sentry.origin']?.value === 'auto.http.nitro.h3',
);

traceId = segment?.trace_id;

expect(segment?.name).toBe('GET /hello/:name');
expect(segment?.attributes['http.route']).toEqual({ type: 'string', value: '/hello/:name' });

// Bun publishes no `http.server.request.start`, so there the srvx span is the segment itself.
expect(srvxSpan).toMatchObject({
status: 'ok',
attributes: expect.objectContaining({
'sentry.op': { type: 'string', value: 'http.server' },
'http.response.status_code': { type: 'integer', value: 200 },
}),
});

expect(h3Span).toMatchObject({
name: 'GET /hello/:name',
parent_span_id: srvxSpan?.span_id,
status: 'ok',
attributes: expect.objectContaining({
'sentry.op': { type: 'string', value: 'http.server' },
'http.route': { type: 'string', value: '/hello/:name' },
}),
});
},
})
.start();

const port = await vi.waitFor(() => runner.getPort() ?? Promise.reject(new Error('port not ready')), {
timeout: 30_000,
});
const response = await fetch(`http://localhost:${port}/hello/world`);
expect(await response.text()).toBe('Hello, world!');

await runner.completed();

const serverTiming = response.headers.get('server-timing');
expect(serverTiming).toContain(`sentry-trace;desc="${traceId}-`);
expect(serverTiming).toContain('baggage;desc="');
});

test('creates cache spans for unstorage operations', async () => {
const runner = createRunner()
.expect({
span: container => {
const segment = container.items.find(span => span.is_segment);
const cacheSpans = container.items.filter(
span => span.attributes['sentry.origin']?.value === 'auto.cache.nitro',
);

expect(segment?.name).toBe('GET /cache');
expect(cacheSpans).toEqual([
expect.objectContaining({
name: 'cache.put',
status: 'ok',
attributes: expect.objectContaining({
'sentry.op': { type: 'string', value: 'cache.put' },
'cache.key': { type: 'string', value: 'cache:key' },
'db.operation.name': { type: 'string', value: 'setItem' },
'db.system.name': { type: 'string', value: 'memory' },
}),
}),
expect.objectContaining({
name: 'cache.get',
status: 'ok',
attributes: expect.objectContaining({
'sentry.op': { type: 'string', value: 'cache.get' },
'cache.key': { type: 'string', value: 'cache:key' },
'cache.hit': { type: 'boolean', value: true },
'db.operation.name': { type: 'string', value: 'getItem' },
}),
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Tests skip conventions attribute constants

Low Severity

The new Nitro suite asserts span attributes and ops with string literals such as sentry.origin, sentry.op, http.route, http.response.status_code, cache.key, cache.hit, http.server, cache.put, and cache.get. Nearby suites import the matching @sentry/conventions constants instead. Flagged because the review rules require test assertions on span attributes and ops to use those constants, not string literals.

Fix in Cursor Fix in Web

Triggered by project rule: PR Review Guidelines for Cursor Bot

Reviewed by Cursor Bugbot for commit 1d35ca9. Configure here.

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.

that's a good point.

]);
},
})
.start();

expect(await runner.makeRequest('get', '/cache')).toBe('value');
await runner.completed();
});
});
});
2 changes: 2 additions & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export {
mongoIntegration,
mongooseIntegration,
mysql2Integration,
nitroIntegration,
nitroServerTimingIntegration,
mysqlIntegration,
nativeNodeFetchIntegration,
NodeClient,
Expand Down
2 changes: 2 additions & 0 deletions packages/aws-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ export {
mongooseIntegration,
mysqlIntegration,
mysql2Integration,
nitroIntegration,
nitroServerTimingIntegration,
redisIntegration,
tediousIntegration,
pinoIntegration,
Expand Down
2 changes: 2 additions & 0 deletions packages/bun/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ export {
mongooseIntegration,
mysqlIntegration,
mysql2Integration,
nitroIntegration,
nitroServerTimingIntegration,
redisIntegration,
tediousIntegration,
postgresIntegration,
Expand Down
2 changes: 2 additions & 0 deletions packages/deno/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ export {
mongooseIntegration,
mysqlIntegration,
mysql2Integration,
nitroIntegration,
nitroServerTimingIntegration,
mistralAIIntegration,
openAIIntegration,
groqIntegration,
Expand Down
63 changes: 3 additions & 60 deletions packages/deno/test/__snapshots__/mod.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -47,26 +47,7 @@ snapshot[`captureMessage 1`] = `
platform: "javascript",
sdk: {
integrations: [
"EventFilters",
"RequestData",
"FunctionToString",
"LinkedErrors",
"Dedupe",
"ConversationId",
"Breadcrumbs",
"Console",
"DenoContext",
"DenoServe",
"Fetch",
"DenoHttp",
"ContextLines",
"NormalizePaths",
"GlobalHandlers",
"Express",
"Fastify",
"Hapi",
"Hono",
"Koa",
"{{integrations}}",
],
name: "sentry.javascript.deno",
packages: [
Expand Down Expand Up @@ -136,26 +117,7 @@ snapshot[`captureMessage twice 1`] = `
platform: "javascript",
sdk: {
integrations: [
"EventFilters",
"RequestData",
"FunctionToString",
"LinkedErrors",
"Dedupe",
"ConversationId",
"Breadcrumbs",
"Console",
"DenoContext",
"DenoServe",
"Fetch",
"DenoHttp",
"ContextLines",
"NormalizePaths",
"GlobalHandlers",
"Express",
"Fastify",
"Hapi",
"Hono",
"Koa",
"{{integrations}}",
],
name: "sentry.javascript.deno",
packages: [
Expand Down Expand Up @@ -232,26 +194,7 @@ snapshot[`captureMessage twice 2`] = `
platform: "javascript",
sdk: {
integrations: [
"EventFilters",
"RequestData",
"FunctionToString",
"LinkedErrors",
"Dedupe",
"ConversationId",
"Breadcrumbs",
"Console",
"DenoContext",
"DenoServe",
"Fetch",
"DenoHttp",
"ContextLines",
"NormalizePaths",
"GlobalHandlers",
"Express",
"Fastify",
"Hapi",
"Hono",
"Koa",
"{{integrations}}",
],
name: "sentry.javascript.deno",
packages: [
Expand Down
6 changes: 6 additions & 0 deletions packages/deno/test/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@ function normalizeEvent(event: Event): Event {
}
}

// The default integration list is incidental to these snapshots, so collapse it to a placeholder.
// This keeps the snapshots stable when integrations are added to or removed from the defaults.
if (event.sdk?.integrations) {
event.sdk.integrations = ['{{integrations}}'];
}

if (event.contexts?.app?.app_start_time) {
event.contexts.app.app_start_time = '{{time}}';
}
Expand Down
2 changes: 2 additions & 0 deletions packages/elysia/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ export {
mongooseIntegration,
mysqlIntegration,
mysql2Integration,
nitroIntegration,
nitroServerTimingIntegration,
redisIntegration,
tediousIntegration,
postgresIntegration,
Expand Down
2 changes: 2 additions & 0 deletions packages/google-cloud-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@ export {
mongooseIntegration,
mysqlIntegration,
mysql2Integration,
nitroIntegration,
nitroServerTimingIntegration,
redisIntegration,
tediousIntegration,
pinoIntegration,
Expand Down
27 changes: 0 additions & 27 deletions packages/nitro/src/runtime/hooks/setServerTimingHeaders.ts

This file was deleted.

5 changes: 0 additions & 5 deletions packages/nitro/src/runtime/plugins/server.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { definePlugin } from 'nitro';
import { captureErrorHook } from '../hooks/captureErrorHook';
import { captureStorageEvents } from '../hooks/captureStorageEvents';
import { captureTracingEvents } from '../hooks/captureTracingEvents';

export default definePlugin(nitroApp => {
nitroApp.hooks.hook('error', captureErrorHook);

captureTracingEvents();
captureStorageEvents();
});
6 changes: 4 additions & 2 deletions packages/nitro/src/sdk.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Integration } from '@sentry/core';
import { applySdkMetadata } from '@sentry/core';
import type { NodeClient, NodeOptions } from '@sentry/node';
import { getDefaultIntegrations as getDefaultNodeIntegrations, init as nodeInit } from '@sentry/node';
import { getDefaultIntegrations as getDefaultNodeIntegrations, init as nodeInit, nitroIntegration } from '@sentry/node';

/**
* Initializes the Nitro SDK
Expand All @@ -28,5 +28,7 @@ export function init(options: NodeOptions | undefined = {}): NodeClient | undefi
* @returns The default integrations for the Nitro SDK.
*/
export function getDefaultIntegrations(options: NodeOptions): Integration[] | undefined {
return [...getDefaultNodeIntegrations(options)];
// `nitroIntegration` is also part of the Node tracing integrations, but add it explicitly so it is
// always present for the Nitro SDK regardless of that gating (deduped by name when both are added).
return [...getDefaultNodeIntegrations(options), nitroIntegration()];

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.

This adds nitroIntegration also when tracing is off, no? We should gate this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this is fine here, as we know nitro is relevant when using the nitro sdk I'd say (it is also the same behavior as before, where we always added this)?

}
2 changes: 2 additions & 0 deletions packages/node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export {
mysqlIntegration,
mistralAIIntegration,
mysql2Integration,
nitroIntegration,
nitroServerTimingIntegration,
openAIIntegration,
togetherAIIntegration,
typesafeIntegration,
Expand Down
Loading
Loading