diff --git a/CHANGELOG.md b/CHANGELOG.md index 89eb3b20a452..95d043c5a6ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,15 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 7.66.0 + +- fix: Defer tracing decision to downstream SDKs when using SDK without performance (#8839) +- fix(nextjs): Fix `package.json` exports (#8895) +- fix(sveltekit): Ensure target file exists before applying auto instrumentation (#8881) +- ref: Use consistent console instrumentation (#8879) +- ref(browser): Refactor sentry breadcrumb to use hook (#8892) +- ref(tracing): Add `origin` to spans (#8765) + ## 7.65.0 - build: Remove build-specific polyfills (#8809) diff --git a/packages/angular/src/tracing.ts b/packages/angular/src/tracing.ts index 5378208a3404..852442af66eb 100644 --- a/packages/angular/src/tracing.ts +++ b/packages/angular/src/tracing.ts @@ -39,6 +39,7 @@ export function routingInstrumentation( customStartTransaction({ name: WINDOW.location.pathname, op: 'pageload', + origin: 'auto.pageload.angular', metadata: { source: 'url' }, }); } @@ -84,6 +85,7 @@ export class TraceService implements OnDestroy { activeTransaction = stashedStartTransaction({ name: strippedUrl, op: 'navigation', + origin: 'auto.navigation.angular', metadata: { source: 'url' }, }); } @@ -95,6 +97,7 @@ export class TraceService implements OnDestroy { this._routingSpan = activeTransaction.startChild({ description: `${navigationEvent.url}`, op: ANGULAR_ROUTING_OP, + origin: 'auto.ui.angular', tags: { 'routing.instrumentation': '@sentry/angular', url: strippedUrl, @@ -192,6 +195,7 @@ export class TraceDirective implements OnInit, AfterViewInit { this._tracingSpan = activeTransaction.startChild({ description: `<${this.componentName}>`, op: ANGULAR_INIT_OP, + origin: 'auto.ui.angular.trace_directive', }); } } @@ -233,6 +237,7 @@ export function TraceClassDecorator(): ClassDecorator { tracingSpan = activeTransaction.startChild({ description: `<${target.name}>`, op: ANGULAR_INIT_OP, + origin: 'auto.ui.angular.trace_class_decorator', }); } if (originalOnInit) { @@ -270,6 +275,7 @@ export function TraceMethodDecorator(): MethodDecorator { description: `<${target.constructor.name}>`, endTimestamp: now, op: `${ANGULAR_OP}.${String(propertyKey)}`, + origin: 'auto.ui.angular.trace_method_decorator', startTimestamp: now, }); } diff --git a/packages/angular/test/tracing.test.ts b/packages/angular/test/tracing.test.ts index a3375518466a..e1796f617150 100644 --- a/packages/angular/test/tracing.test.ts +++ b/packages/angular/test/tracing.test.ts @@ -48,6 +48,7 @@ describe('Angular Tracing', () => { expect(startTransaction).toHaveBeenCalledWith({ name: '/', op: 'pageload', + origin: 'auto.pageload.angular', metadata: { source: 'url' }, }); }); @@ -137,6 +138,7 @@ describe('Angular Tracing', () => { expect(customStartTransaction).toHaveBeenCalledWith({ name: url, op: 'pageload', + origin: 'auto.pageload.angular', metadata: { source: 'url' }, }); @@ -327,6 +329,7 @@ describe('Angular Tracing', () => { expect(customStartTransaction).toHaveBeenCalledWith({ name: url, op: 'navigation', + origin: 'auto.navigation.angular', metadata: { source: 'url' }, }); expect(transaction.setName).toHaveBeenCalledWith(result, 'route'); @@ -358,6 +361,7 @@ describe('Angular Tracing', () => { expect(transaction.startChild).toHaveBeenCalledWith({ op: 'ui.angular.init', + origin: 'auto.ui.angular.trace_directive', description: '', }); @@ -384,6 +388,7 @@ describe('Angular Tracing', () => { expect(transaction.startChild).toHaveBeenCalledWith({ op: 'ui.angular.init', + origin: 'auto.ui.angular.trace_directive', description: '', }); @@ -458,6 +463,7 @@ describe('Angular Tracing', () => { expect(transaction.startChild).toHaveBeenCalledWith({ description: '', op: 'ui.angular.init', + origin: 'auto.ui.angular.trace_class_decorator', }); expect(origNgOnInitMock).toHaveBeenCalledTimes(1); @@ -511,6 +517,7 @@ describe('Angular Tracing', () => { expect(transaction.startChild.mock.calls[0][0]).toEqual({ description: '', op: 'ui.angular.ngOnInit', + origin: 'auto.ui.angular.trace_method_decorator', startTimestamp: expect.any(Number), endTimestamp: expect.any(Number), }); @@ -518,6 +525,7 @@ describe('Angular Tracing', () => { expect(transaction.startChild.mock.calls[1][0]).toEqual({ description: '', op: 'ui.angular.ngAfterViewInit', + origin: 'auto.ui.angular.trace_method_decorator', startTimestamp: expect.any(Number), endTimestamp: expect.any(Number), }); diff --git a/packages/browser/src/client.ts b/packages/browser/src/client.ts index fba5dfc008e6..60579038a50a 100644 --- a/packages/browser/src/client.ts +++ b/packages/browser/src/client.ts @@ -15,8 +15,6 @@ import { createClientReportEnvelope, dsnToString, getSDKSource, logger } from '@ import { eventFromException, eventFromMessage } from './eventbuilder'; import { WINDOW } from './helpers'; -import type { Breadcrumbs } from './integrations'; -import { BREADCRUMB_INTEGRATION_ID } from './integrations/breadcrumbs'; import type { BrowserTransportOptions } from './transports/types'; import { createUserFeedbackEnvelope } from './userfeedback'; @@ -91,26 +89,6 @@ export class BrowserClient extends BaseClient { return eventFromMessage(this._options.stackParser, message, level, hint, this._options.attachStacktrace); } - /** - * @inheritDoc - */ - public sendEvent(event: Event, hint?: EventHint): void { - // We only want to add the sentry event breadcrumb when the user has the breadcrumb integration installed and - // activated its `sentry` option. - // We also do not want to use the `Breadcrumbs` class here directly, because we do not want it to be included in - // bundles, if it is not used by the SDK. - // This all sadly is a bit ugly, but we currently don't have a "pre-send" hook on the integrations so we do it this - // way for now. - const breadcrumbIntegration = this.getIntegrationById(BREADCRUMB_INTEGRATION_ID) as Breadcrumbs | undefined; - // We check for definedness of `addSentryBreadcrumb` in case users provided their own integration with id - // "Breadcrumbs" that does not have this function. - if (breadcrumbIntegration && breadcrumbIntegration.addSentryBreadcrumb) { - breadcrumbIntegration.addSentryBreadcrumb(event); - } - - super.sendEvent(event, hint); - } - /** * Sends user feedback to Sentry. */ diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index a7330d1fb23e..e41bedc8bf1c 100644 --- a/packages/browser/src/integrations/breadcrumbs.ts +++ b/packages/browser/src/integrations/breadcrumbs.ts @@ -41,8 +41,6 @@ interface BreadcrumbsOptions { /** maxStringLength gets capped to prevent 100 breadcrumbs exceeding 1MB event payload size */ const MAX_ALLOWED_STRING_LENGTH = 1024; -export const BREADCRUMB_INTEGRATION_ID = 'Breadcrumbs'; - /** * Default Breadcrumbs instrumentations * TODO: Deprecated - with v6, this will be renamed to `Instrument` @@ -51,7 +49,7 @@ export class Breadcrumbs implements Integration { /** * @inheritDoc */ - public static id: string = BREADCRUMB_INTEGRATION_ID; + public static id: string = 'Breadcrumbs'; /** * @inheritDoc @@ -104,28 +102,30 @@ export class Breadcrumbs implements Integration { if (this.options.history) { addInstrumentationHandler('history', _historyBreadcrumb); } - } - - /** - * Adds a breadcrumb for Sentry events or transactions if this option is enabled. - */ - public addSentryBreadcrumb(event: SentryEvent): void { if (this.options.sentry) { - getCurrentHub().addBreadcrumb( - { - category: `sentry.${event.type === 'transaction' ? 'transaction' : 'event'}`, - event_id: event.event_id, - level: event.level, - message: getEventDescription(event), - }, - { - event, - }, - ); + const client = getCurrentHub().getClient(); + client && client.on && client.on('beforeSendEvent', addSentryBreadcrumb); } } } +/** + * Adds a breadcrumb for Sentry events or transactions if this option is enabled. + */ +function addSentryBreadcrumb(event: SentryEvent): void { + getCurrentHub().addBreadcrumb( + { + category: `sentry.${event.type === 'transaction' ? 'transaction' : 'event'}`, + event_id: event.event_id, + level: event.level, + message: getEventDescription(event), + }, + { + event, + }, + ); +} + /** * A HOC that creaes a function that creates breadcrumbs from DOM API calls. * This is a HOC so that we get access to dom options in the closure. diff --git a/packages/browser/src/profiling/hubextensions.ts b/packages/browser/src/profiling/hubextensions.ts index 49763ac35659..187e4a463224 100644 --- a/packages/browser/src/profiling/hubextensions.ts +++ b/packages/browser/src/profiling/hubextensions.ts @@ -174,7 +174,11 @@ export function wrapTransactionWithProfiling(transaction: Transaction): Transact // This is temporary - we will use the collected span data to evaluate // if deferring txn.finish until profiler resolves is a viable approach. - const stopProfilerSpan = transaction.startChild({ description: 'profiler.stop', op: 'profiler' }); + const stopProfilerSpan = transaction.startChild({ + description: 'profiler.stop', + op: 'profiler', + origin: 'auto.profiler.browser', + }); return profiler .stop() diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 354417351d1b..cb106b958a53 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -322,6 +322,8 @@ export abstract class BaseClient implements Client { * @inheritDoc */ public sendEvent(event: Event, hint: EventHint = {}): void { + this.emit('beforeSendEvent', event, hint); + if (this._dsn) { let env = createEventEnvelope(event, this._dsn, this._options._metadata, this._options.tunnel); @@ -381,6 +383,9 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public on(hook: 'beforeEnvelope', callback: (envelope: Envelope) => void): void; + /** @inheritdoc */ + public on(hook: 'beforeSendEvent', callback: (event: Event, hint?: EventHint) => void): void; + /** @inheritdoc */ public on( hook: 'afterSendEvent', @@ -412,6 +417,9 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public emit(hook: 'beforeEnvelope', envelope: Envelope): void; + /** @inheritdoc */ + public emit(hook: 'beforeSendEvent', event: Event, hint?: EventHint): void; + /** @inheritdoc */ public emit(hook: 'afterSendEvent', event: Event, sendResponse: TransportMakeRequestResponse | void): void; diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index b72defc56016..8875f1c996f2 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -636,6 +636,5 @@ function generatePropagationContext(): PropagationContext { return { traceId: uuid4(), spanId: uuid4().substring(16), - sampled: false, }; } diff --git a/packages/core/src/tracing/span.ts b/packages/core/src/tracing/span.ts index 4301391ff1da..3ce158c8addf 100644 --- a/packages/core/src/tracing/span.ts +++ b/packages/core/src/tracing/span.ts @@ -4,6 +4,7 @@ import type { Primitive, Span as SpanInterface, SpanContext, + SpanOrigin, TraceContext, Transaction, } from '@sentry/types'; @@ -115,6 +116,11 @@ export class Span implements SpanInterface { */ public instrumenter: Instrumenter; + /** + * The origin of the span, giving context about what created the span. + */ + public origin?: SpanOrigin; + /** * You should never call the constructor manually, always use `Sentry.startTransaction()` * or call `startChild()` on an existing span. @@ -122,23 +128,15 @@ export class Span implements SpanInterface { * @hideconstructor * @hidden */ - public constructor(spanContext?: SpanContext) { - this.traceId = uuid4(); - this.spanId = uuid4().substring(16); - this.startTimestamp = timestampInSeconds(); - this.tags = {}; - this.data = {}; - this.instrumenter = 'sentry'; - - if (!spanContext) { - return this; - } - if (spanContext.traceId) { - this.traceId = spanContext.traceId; - } - if (spanContext.spanId) { - this.spanId = spanContext.spanId; - } + public constructor(spanContext: SpanContext = {}) { + this.traceId = spanContext.traceId || uuid4(); + this.spanId = spanContext.spanId || uuid4().substring(16); + this.startTimestamp = spanContext.startTimestamp || timestampInSeconds(); + this.tags = spanContext.tags || {}; + this.data = spanContext.data || {}; + this.instrumenter = spanContext.instrumenter || 'sentry'; + this.origin = spanContext.origin || 'manual'; + if (spanContext.parentSpanId) { this.parentSpanId = spanContext.parentSpanId; } @@ -155,24 +153,12 @@ export class Span implements SpanInterface { if (spanContext.name) { this.description = spanContext.name; } - if (spanContext.data) { - this.data = spanContext.data; - } - if (spanContext.tags) { - this.tags = spanContext.tags; - } if (spanContext.status) { this.status = spanContext.status; } - if (spanContext.startTimestamp) { - this.startTimestamp = spanContext.startTimestamp; - } if (spanContext.endTimestamp) { this.endTimestamp = spanContext.endTimestamp; } - if (spanContext.instrumenter) { - this.instrumenter = spanContext.instrumenter; - } } /** @@ -355,6 +341,7 @@ export class Span implements SpanInterface { tags?: { [key: string]: Primitive }; timestamp?: number; trace_id: string; + origin?: SpanOrigin; } { return dropUndefinedKeys({ data: Object.keys(this.data).length > 0 ? this.data : undefined, @@ -367,6 +354,7 @@ export class Span implements SpanInterface { tags: Object.keys(this.tags).length > 0 ? this.tags : undefined, timestamp: this.endTimestamp, trace_id: this.traceId, + origin: this.origin, }); } } diff --git a/packages/ember/addon/index.ts b/packages/ember/addon/index.ts index fbcea14d5533..ef38d3c382b3 100644 --- a/packages/ember/addon/index.ts +++ b/packages/ember/addon/index.ts @@ -87,7 +87,14 @@ export const instrumentRoutePerformance = (BaseRoute if (!currentTransaction) { return result; } - currentTransaction.startChild({ op, description, startTimestamp }).finish(); + currentTransaction + .startChild({ + op, + description, + origin: 'auto.ui.ember', + startTimestamp, + }) + .finish(); return result; }; diff --git a/packages/ember/addon/instance-initializers/sentry-performance.ts b/packages/ember/addon/instance-initializers/sentry-performance.ts index 2b962f4df2ae..0f00a6cec1fd 100644 --- a/packages/ember/addon/instance-initializers/sentry-performance.ts +++ b/packages/ember/addon/instance-initializers/sentry-performance.ts @@ -119,6 +119,7 @@ export function _instrumentEmberRouter( activeTransaction = startTransaction({ name: `route:${routeInfo.name}`, op: 'pageload', + origin: 'auto.pageload.ember', tags: { url, toRoute: routeInfo.name, @@ -141,6 +142,7 @@ export function _instrumentEmberRouter( activeTransaction = startTransaction({ name: `route:${toRoute}`, op: 'navigation', + origin: 'auto.navigation.ember', tags: { fromRoute, toRoute, @@ -150,6 +152,7 @@ export function _instrumentEmberRouter( transitionSpan = activeTransaction?.startChild({ op: 'ui.ember.transition', description: `route:${fromRoute} -> route:${toRoute}`, + origin: 'auto.ui.ember', }); }); @@ -211,6 +214,7 @@ function _instrumentEmberRunloop(config: EmberSentryConfig): void { activeTransaction ?.startChild({ op: `ui.ember.runloop.${queue}`, + origin: 'auto.ui.ember', startTimestamp: currentQueueStart, endTimestamp: now, }) @@ -287,6 +291,7 @@ function processComponentRenderAfter( activeTransaction?.startChild({ op, description: payload.containerKey || payload.object, + origin: 'auto.ui.ember', startTimestamp: begin.now, endTimestamp: now, }); @@ -370,6 +375,7 @@ function _instrumentInitialLoad(config: EmberSentryConfig): void { const transaction = getActiveTransaction(); const span = transaction?.startChild({ op: 'ui.ember.init', + origin: 'auto.ui.ember', startTimestamp, }); span?.finish(endTimestamp); diff --git a/packages/hub/test/scope.test.ts b/packages/hub/test/scope.test.ts index 982cba766a87..4c0d830340ec 100644 --- a/packages/hub/test/scope.test.ts +++ b/packages/hub/test/scope.test.ts @@ -20,7 +20,7 @@ describe('Scope', () => { expect(scope._propagationContext).toEqual({ traceId: expect.any(String), spanId: expect.any(String), - sampled: false, + sampled: undefined, dsc: undefined, parentSpanId: undefined, }); @@ -442,7 +442,7 @@ describe('Scope', () => { expect(scope._propagationContext).toEqual({ traceId: expect.any(String), spanId: expect.any(String), - sampled: false, + sampled: undefined, }); // @ts-expect-error accessing private property expect(scope._propagationContext).not.toEqual(oldPropagationContext); diff --git a/packages/integrations/src/captureconsole.ts b/packages/integrations/src/captureconsole.ts index d72da5f2b5c4..993fb9414052 100644 --- a/packages/integrations/src/captureconsole.ts +++ b/packages/integrations/src/captureconsole.ts @@ -1,5 +1,11 @@ import type { EventProcessor, Hub, Integration } from '@sentry/types'; -import { CONSOLE_LEVELS, fill, GLOBAL_OBJ, safeJoin, severityLevelFromString } from '@sentry/utils'; +import { + addInstrumentationHandler, + CONSOLE_LEVELS, + GLOBAL_OBJ, + safeJoin, + severityLevelFromString, +} from '@sentry/utils'; /** Send Console API calls as Sentry Events */ export class CaptureConsole implements Integration { @@ -34,46 +40,45 @@ export class CaptureConsole implements Integration { return; } - this._levels.forEach((level: string) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any - if (!(level in (GLOBAL_OBJ as any).console)) { + const levels = this._levels; + + addInstrumentationHandler('console', ({ args, level }: { args: unknown[]; level: string }) => { + if (!levels.includes(level)) { return; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access - fill((GLOBAL_OBJ as any).console, level, (originalConsoleMethod: () => any) => (...args: any[]): void => { - const hub = getCurrentHub(); - - if (hub.getIntegration(CaptureConsole)) { - hub.withScope(scope => { - scope.setLevel(severityLevelFromString(level)); - scope.setExtra('arguments', args); - scope.addEventProcessor(event => { - event.logger = 'console'; - return event; - }); + const hub = getCurrentHub(); - let message = safeJoin(args, ' '); - const error = args.find(arg => arg instanceof Error); - if (level === 'assert') { - if (args[0] === false) { - message = `Assertion failed: ${safeJoin(args.slice(1), ' ') || 'console.assert'}`; - scope.setExtra('arguments', args.slice(1)); - hub.captureMessage(message); - } - } else if (level === 'error' && error) { - hub.captureException(error); - } else { - hub.captureMessage(message); - } - }); - } + if (!hub.getIntegration(CaptureConsole)) { + return; + } - // this fails for some browsers. :( - if (originalConsoleMethod) { - originalConsoleMethod.apply(GLOBAL_OBJ.console, args); - } - }); + consoleHandler(hub, args, level); }); } } + +function consoleHandler(hub: Hub, args: unknown[], level: string): void { + hub.withScope(scope => { + scope.setLevel(severityLevelFromString(level)); + scope.setExtra('arguments', args); + scope.addEventProcessor(event => { + event.logger = 'console'; + return event; + }); + + let message = safeJoin(args, ' '); + const error = args.find(arg => arg instanceof Error); + if (level === 'assert') { + if (args[0] === false) { + message = `Assertion failed: ${safeJoin(args.slice(1), ' ') || 'console.assert'}`; + scope.setExtra('arguments', args.slice(1)); + hub.captureMessage(message); + } + } else if (level === 'error' && error) { + hub.captureException(error); + } else { + hub.captureMessage(message); + } + }); +} diff --git a/packages/integrations/test/captureconsole.test.ts b/packages/integrations/test/captureconsole.test.ts index 00e9f23c4564..0b851c493062 100644 --- a/packages/integrations/test/captureconsole.test.ts +++ b/packages/integrations/test/captureconsole.test.ts @@ -1,176 +1,194 @@ /* eslint-disable @typescript-eslint/unbound-method */ import type { Event, Hub, Integration } from '@sentry/types'; +import type { ConsoleLevel } from '@sentry/utils'; +import { addInstrumentationHandler, CONSOLE_LEVELS, GLOBAL_OBJ, originalConsoleMethods } from '@sentry/utils'; import { CaptureConsole } from '../src/captureconsole'; -const mockScope = { - setLevel: jest.fn(), - setExtra: jest.fn(), - addEventProcessor: jest.fn(), -}; - -const mockHub = { - withScope: jest.fn(callback => { - callback(mockScope); - }), - captureMessage: jest.fn(), - captureException: jest.fn(), -}; - -const mockConsole = { +const mockConsole: { [key in ConsoleLevel]: jest.Mock } = { debug: jest.fn(), log: jest.fn(), warn: jest.fn(), error: jest.fn(), assert: jest.fn(), info: jest.fn(), + trace: jest.fn(), }; -const getMockHubWithIntegration = (integration: Integration) => - ({ +function getMockHub(integration: Integration): Hub { + const mockScope = { + setLevel: jest.fn(), + setExtra: jest.fn(), + addEventProcessor: jest.fn(), + }; + + const mockHub = { + withScope: jest.fn(callback => { + callback(mockScope); + }), + captureMessage: jest.fn(), + captureException: jest.fn(), + getScope: jest.fn(() => mockScope), + }; + + return { ...mockHub, getIntegration: jest.fn(() => integration), - } as unknown as Hub); - -// We're using this to un-monkey patch the console after each test. -const originalConsole = Object.assign({}, global.console); + } as unknown as Hub; +} describe('CaptureConsole setup', () => { + // Ensure we've initialized the instrumentation so we can get the original one + addInstrumentationHandler('console', () => {}); + const _originalConsoleMethods = Object.assign({}, originalConsoleMethods); + beforeEach(() => { - // this suppresses output to the terminal running the tests, but doesn't interfere with our wrapping - Object.assign(global.console, mockConsole); + CONSOLE_LEVELS.forEach(key => { + originalConsoleMethods[key] = mockConsole[key]; + }); }); afterEach(() => { jest.clearAllMocks(); - // Un-monkey-patch the console functions - Object.assign(global.console, originalConsole); + CONSOLE_LEVELS.forEach(key => { + originalConsoleMethods[key] = _originalConsoleMethods[key]; + }); }); describe('monkeypatching', () => { - beforeEach(() => { - // for these tests only, we don't want to use the mock console, because we're testing for equality to methods from - // the original, so undo the global `beforeEach()` - Object.assign(global.console, originalConsole); - }); - it('should patch user-configured console levels', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['log', 'warn'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); - expect(global.console.error).toBe(originalConsole.error); // not monkey patched - expect(global.console.log).not.toBe(originalConsole.log); // monkey patched - expect(global.console.warn).not.toBe(originalConsole.warn); // monkey patched + GLOBAL_OBJ.console.error('msg 1'); + GLOBAL_OBJ.console.log('msg 2'); + GLOBAL_OBJ.console.warn('msg 3'); + + expect(mockHub.captureMessage).toHaveBeenCalledTimes(2); }); it('should fall back to default console levels if none are provided', () => { const captureConsoleIntegration = new CaptureConsole(); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); - // expect a set of defined console levels to have been monkey patched - expect(global.console.debug).not.toBe(originalConsole.debug); - expect(global.console.info).not.toBe(originalConsole.info); - expect(global.console.warn).not.toBe(originalConsole.warn); - expect(global.console.error).not.toBe(originalConsole.error); - expect(global.console.log).not.toBe(originalConsole.log); - expect(global.console.assert).not.toBe(originalConsole.assert); - expect(global.console.trace).not.toBe(originalConsole.trace); - - // any other fields should not have been patched - expect(global.console.table).toBe(originalConsole.table); + // Assert has a special handling + (['debug', 'info', 'warn', 'error', 'log', 'trace'] as const).forEach(key => { + GLOBAL_OBJ.console[key]('msg'); + }); + + GLOBAL_OBJ.console.assert(false); + + expect(mockHub.captureMessage).toHaveBeenCalledTimes(7); }); it('should not wrap any functions with an empty levels option', () => { const captureConsoleIntegration = new CaptureConsole({ levels: [] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); - // expect the default set of console levels not to have been monkey patched - expect(global.console.debug).toBe(originalConsole.debug); - expect(global.console.info).toBe(originalConsole.info); - expect(global.console.warn).toBe(originalConsole.warn); - expect(global.console.error).toBe(originalConsole.error); - expect(global.console.log).toBe(originalConsole.log); - expect(global.console.assert).toBe(originalConsole.assert); + CONSOLE_LEVELS.forEach(key => { + GLOBAL_OBJ.console[key]('msg'); + }); - // suppress output from the logging we're about to do - global.console.log = global.console.info = jest.fn(); - - // expect no message to be captured with console.log - global.console.log('some message'); - expect(mockHub.captureMessage).not.toHaveBeenCalled(); + expect(mockHub.captureMessage).toHaveBeenCalledTimes(0); }); }); it('setup should fail gracefully when console is not available', () => { - const consoleRef = global.console; + const consoleRef = GLOBAL_OBJ.console; // @ts-ignore remove console - delete global.console; + delete GLOBAL_OBJ.console; + const captureConsoleIntegration = new CaptureConsole(); + const mockHub = getMockHub(captureConsoleIntegration); expect(() => { - const captureConsoleIntegration = new CaptureConsole(); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); }).not.toThrow(); // reinstate initial console - global.console = consoleRef; + GLOBAL_OBJ.console = consoleRef; }); it('should set a level in the scope when console function is called', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['error'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); + const mockScope = mockHub.getScope(); + // call a wrapped function - global.console.error('some logging message'); + GLOBAL_OBJ.console.error('some logging message'); expect(mockScope.setLevel).toHaveBeenCalledTimes(1); expect(mockScope.setLevel).toHaveBeenCalledWith('error'); }); - it('should send arguments as extra data on failed assertion', () => { + it('should send arguments as extra data', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); - // call a wrapped function - global.console.log('some arg 1', 'some arg 2'); - global.console.log(); + const mockScope = mockHub.getScope(); + + GLOBAL_OBJ.console.log('some arg 1', 'some arg 2'); - expect(mockScope.setExtra).toHaveBeenCalledTimes(2); + expect(mockScope.setExtra).toHaveBeenCalledTimes(1); expect(mockScope.setExtra).toHaveBeenCalledWith('arguments', ['some arg 1', 'some arg 2']); + }); + + it('should send empty arguments as extra data', () => { + const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] }); + const mockHub = getMockHub(captureConsoleIntegration); + captureConsoleIntegration.setupOnce( + () => undefined, + () => mockHub, + ); + + const mockScope = mockHub.getScope(); + + GLOBAL_OBJ.console.log(); + + expect(mockScope.setExtra).toHaveBeenCalledTimes(1); expect(mockScope.setExtra).toHaveBeenCalledWith('arguments', []); }); it('should add an event processor that sets the `logger` field of events', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); + const mockScope = mockHub.getScope(); + // call a wrapped function - global.console.log('some message'); + GLOBAL_OBJ.console.log('some message'); expect(mockScope.addEventProcessor).toHaveBeenCalledTimes(1); - const addedEventProcessor = mockScope.addEventProcessor.mock.calls[0][0]; + const addedEventProcessor = (mockScope.addEventProcessor as jest.Mock).mock.calls[0][0]; const someEvent: Event = {}; addedEventProcessor(someEvent); @@ -179,12 +197,15 @@ describe('CaptureConsole setup', () => { it('should capture message on a failed assertion', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['assert'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); - global.console.assert(1 + 1 === 3); + const mockScope = mockHub.getScope(); + + GLOBAL_OBJ.console.assert(1 + 1 === 3); expect(mockScope.setExtra).toHaveBeenLastCalledWith('arguments', []); expect(mockHub.captureMessage).toHaveBeenCalledTimes(1); @@ -193,12 +214,15 @@ describe('CaptureConsole setup', () => { it('should capture correct message on a failed assertion with message', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['assert'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); - global.console.assert(1 + 1 === 3, 'expression is false'); + const mockScope = mockHub.getScope(); + + GLOBAL_OBJ.console.assert(1 + 1 === 3, 'expression is false'); expect(mockScope.setExtra).toHaveBeenLastCalledWith('arguments', ['expression is false']); expect(mockHub.captureMessage).toHaveBeenCalledTimes(1); @@ -207,23 +231,25 @@ describe('CaptureConsole setup', () => { it('should not capture message on a successful assertion', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['assert'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); - global.console.assert(1 + 1 === 2); + GLOBAL_OBJ.console.assert(1 + 1 === 2); }); it('should capture exception when console logs an error object with level set to "error"', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['error'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); const someError = new Error('some error'); - global.console.error(someError); + GLOBAL_OBJ.console.error(someError); expect(mockHub.captureException).toHaveBeenCalledTimes(1); expect(mockHub.captureException).toHaveBeenCalledWith(someError); @@ -231,13 +257,14 @@ describe('CaptureConsole setup', () => { it('should capture exception on `console.error` when no levels are provided in constructor', () => { const captureConsoleIntegration = new CaptureConsole(); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); const someError = new Error('some error'); - global.console.error(someError); + GLOBAL_OBJ.console.error(someError); expect(mockHub.captureException).toHaveBeenCalledTimes(1); expect(mockHub.captureException).toHaveBeenCalledWith(someError); @@ -245,13 +272,14 @@ describe('CaptureConsole setup', () => { it('should capture exception when console logs an error object in any of the args when level set to "error"', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['error'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); const someError = new Error('some error'); - global.console.error('Something went wrong', someError); + GLOBAL_OBJ.console.error('Something went wrong', someError); expect(mockHub.captureException).toHaveBeenCalledTimes(1); expect(mockHub.captureException).toHaveBeenCalledWith(someError); @@ -259,12 +287,13 @@ describe('CaptureConsole setup', () => { it('should capture message on `console.log` when no levels are provided in constructor', () => { const captureConsoleIntegration = new CaptureConsole(); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); - global.console.error('some message'); + GLOBAL_OBJ.console.error('some message'); expect(mockHub.captureMessage).toHaveBeenCalledTimes(1); expect(mockHub.captureMessage).toHaveBeenCalledWith('some message'); @@ -272,12 +301,13 @@ describe('CaptureConsole setup', () => { it('should capture message when console logs a non-error object with level set to "error"', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['error'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); - global.console.error('some non-error message'); + GLOBAL_OBJ.console.error('some non-error message'); expect(mockHub.captureMessage).toHaveBeenCalledTimes(1); expect(mockHub.captureMessage).toHaveBeenCalledWith('some non-error message'); @@ -286,12 +316,13 @@ describe('CaptureConsole setup', () => { it('should capture a message for non-error log levels', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['info'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); - global.console.info('some message'); + GLOBAL_OBJ.console.info('some message'); expect(mockHub.captureMessage).toHaveBeenCalledTimes(1); expect(mockHub.captureMessage).toHaveBeenCalledWith('some message'); @@ -299,70 +330,63 @@ describe('CaptureConsole setup', () => { it('should call the original console function when console members are called', () => { // Mock console log to test if it was called - const originalConsoleLog = global.console.log; + const originalConsoleLog = GLOBAL_OBJ.console.log; const mockConsoleLog = jest.fn(); - global.console.log = mockConsoleLog; + GLOBAL_OBJ.console.log = mockConsoleLog; const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); - global.console.log('some message 1', 'some message 2'); + GLOBAL_OBJ.console.log('some message 1', 'some message 2'); expect(mockConsoleLog).toHaveBeenCalledTimes(1); expect(mockConsoleLog).toHaveBeenCalledWith('some message 1', 'some message 2'); // Reset console log - global.console.log = originalConsoleLog; + GLOBAL_OBJ.console.log = originalConsoleLog; }); it('should not wrap any levels that are not members of console', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['log', 'someNonExistingLevel', 'error'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); // The provided level should not be created - expect((global.console as any)['someNonExistingLevel']).toBeUndefined(); - - // Ohter levels should be wrapped as expected - expect(global.console.log).not.toBe(originalConsole.log); - expect(global.console.error).not.toBe(originalConsole.error); + expect((GLOBAL_OBJ.console as any)['someNonExistingLevel']).toBeUndefined(); }); it('should wrap the console when the client does not have a registered captureconsole integration, but not capture any messages', () => { const captureConsoleIntegration = new CaptureConsole({ levels: ['log', 'error'] }); + const mockHub = getMockHub(null as any); // simulate not having the integration registered captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(null as any), // simulate not having the integration registered + () => mockHub, ); - // Console should be wrapped - expect(global.console.log).not.toBe(originalConsole.log); - expect(global.console.error).not.toBe(originalConsole.error); - // Should not capture messages - global.console.log('some message'); + GLOBAL_OBJ.console.log('some message'); expect(mockHub.captureMessage).not.toHaveBeenCalledWith(); }); it("should not crash when the original console methods don't exist at time of invocation", () => { - const originalConsoleLog = global.console.log; - global.console.log = undefined as any; // don't `delete` here, otherwise `fill` won't wrap the function + originalConsoleMethods.log = undefined; const captureConsoleIntegration = new CaptureConsole({ levels: ['log'] }); + const mockHub = getMockHub(captureConsoleIntegration); captureConsoleIntegration.setupOnce( () => undefined, - () => getMockHubWithIntegration(captureConsoleIntegration), + () => mockHub, ); expect(() => { - global.console.log('some message'); + GLOBAL_OBJ.console.log('some message'); }).not.toThrow(); - - global.console.log = originalConsoleLog; }); }); diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 9190bb3d28dd..507cb763372b 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -13,16 +13,6 @@ "module": "build/esm/index.server.js", "browser": "build/esm/index.client.js", "types": "build/types/index.types.d.ts", - "exports": { - ".": { - "import": "./build/esm/index.server.js", - "require": "./build/cjs/index.server.js", - "types": "./build/types/index.types.d.ts" - }, - "./requestAsyncStorageShim": { - "import": "./build/esm/config/templates/requestAsyncStorageShim.js" - } - }, "typesVersions": { "<4.9": { "build/npm/types/index.d.ts": [ diff --git a/packages/nextjs/src/client/performance.ts b/packages/nextjs/src/client/performance.ts index fd5f979a08b0..bdf1509d9ec1 100644 --- a/packages/nextjs/src/client/performance.ts +++ b/packages/nextjs/src/client/performance.ts @@ -178,6 +178,7 @@ export function nextRouterInstrumentation( const nextRouteChangeSpan = navigationTransaction.startChild({ op: 'ui.nextjs.route-change', description: 'Next.js Route Change', + origin: 'auto.navigation.nextjs', }); const finishRouteChangeSpan = (): void => { diff --git a/packages/nextjs/src/common/utils/edgeWrapperUtils.ts b/packages/nextjs/src/common/utils/edgeWrapperUtils.ts index f3023665d106..94ff5ad5bec4 100644 --- a/packages/nextjs/src/common/utils/edgeWrapperUtils.ts +++ b/packages/nextjs/src/common/utils/edgeWrapperUtils.ts @@ -23,6 +23,7 @@ export function withEdgeWrapping( span = prevSpan.startChild({ description: options.spanDescription, op: options.spanOp, + origin: 'auto.function.nextjs', }); } else if (req instanceof Request) { const sentryTrace = req.headers.get('sentry-trace') || ''; @@ -39,6 +40,7 @@ export function withEdgeWrapping( span = startTransaction({ name: options.spanDescription, op: options.spanOp, + origin: 'auto.ui.nextjs.withEdgeWrapping', ...traceparentData, metadata: { dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, diff --git a/packages/nextjs/src/common/utils/wrapperUtils.ts b/packages/nextjs/src/common/utils/wrapperUtils.ts index a3cf13736900..80c9a57b65b5 100644 --- a/packages/nextjs/src/common/utils/wrapperUtils.ts +++ b/packages/nextjs/src/common/utils/wrapperUtils.ts @@ -104,6 +104,7 @@ export function withTracedServerSideDataFetcher Pr { op: 'http.server', name: options.requestedRouteName, + origin: 'auto.function.nextjs', ...traceparentData, status: 'ok', metadata: { @@ -131,12 +132,14 @@ export function withTracedServerSideDataFetcher Pr dataFetcherSpan = spanToContinue.startChild({ op: 'function.nextjs', description: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`, + origin: 'auto.function.nextjs', status: 'ok', }); } else { dataFetcherSpan = startTransaction({ op: 'function.nextjs', name: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`, + origin: 'auto.function.nextjs', ...traceparentData, status: 'ok', metadata: { @@ -203,6 +206,7 @@ export async function callDataFetcherTraced Promis // route's transaction const span = transaction.startChild({ op: 'function.nextjs', + origin: 'auto.function.nextjs', description: `${dataFetchingMethodName} (${parameterizedRoute})`, status: 'ok', }); diff --git a/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts index 85ec0cb4b1c2..62ecd952b460 100644 --- a/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/common/wrapApiHandlerWithSentry.ts @@ -129,6 +129,7 @@ export function withSentry(apiHandler: NextApiHandler, parameterizedRoute?: stri { name: `${reqMethod}${reqPath}`, op: 'http.server', + origin: 'auto.http.nextjs', ...traceparentData, metadata: { dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, diff --git a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts index 81f030fa824c..c5db1c7f217f 100644 --- a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts +++ b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts @@ -43,6 +43,7 @@ export function wrapServerComponentWithSentry any> op: 'function.nextjs', name: `${componentType} Server Component (${componentRoute})`, status: 'ok', + origin: 'auto.function.nextjs', ...traceparentData, metadata: { source: 'component', diff --git a/packages/nextjs/src/config/loaders/wrappingLoader.ts b/packages/nextjs/src/config/loaders/wrappingLoader.ts index 4cc2425a33c0..1731722de7bb 100644 --- a/packages/nextjs/src/config/loaders/wrappingLoader.ts +++ b/packages/nextjs/src/config/loaders/wrappingLoader.ts @@ -191,7 +191,7 @@ export default function wrappingLoader( } templateCode = templateCode.replace( /__SENTRY_NEXTJS_REQUEST_ASYNC_STORAGE_SHIM__/g, - '@sentry/nextjs/requestAsyncStorageShim', + '@sentry/nextjs/build/esm/config/templates/requestAsyncStorageShim.js', ); } diff --git a/packages/nextjs/src/config/templates/requestAsyncStorageShim.ts b/packages/nextjs/src/config/templates/requestAsyncStorageShim.ts index bc0e23000815..44222403d026 100644 --- a/packages/nextjs/src/config/templates/requestAsyncStorageShim.ts +++ b/packages/nextjs/src/config/templates/requestAsyncStorageShim.ts @@ -7,9 +7,3 @@ export interface RequestAsyncStorage { } | undefined; } - -export const requestAsyncStorage: RequestAsyncStorage = { - getStore: () => { - return undefined; - }, -}; diff --git a/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts b/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts index 3f5ef8349c30..7ebf29099f3a 100644 --- a/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts +++ b/packages/nextjs/src/config/templates/serverComponentWrapperTemplate.ts @@ -28,7 +28,7 @@ if (typeof serverComponent === 'function') { let sentryTraceHeader: string | undefined | null = undefined; let baggageHeader: string | undefined | null = undefined; - // We try-catch here just in case the API around `requestAsyncStorage` changes unexpectedly since it is not public API + // We try-catch here just in `requestAsyncStorage` is undefined since it may not be defined try { const requestAsyncStore = requestAsyncStorage.getStore(); sentryTraceHeader = requestAsyncStore?.headers.get('sentry-trace'); diff --git a/packages/nextjs/test/config/withSentry.test.ts b/packages/nextjs/test/config/withSentry.test.ts index c7862e1473df..ba7c5fb3e865 100644 --- a/packages/nextjs/test/config/withSentry.test.ts +++ b/packages/nextjs/test/config/withSentry.test.ts @@ -80,6 +80,7 @@ describe('withSentry', () => { { name: 'GET http://dogs.are.great', op: 'http.server', + origin: 'auto.http.nextjs', metadata: { source: 'route', diff --git a/packages/node-experimental/src/integrations/express.ts b/packages/node-experimental/src/integrations/express.ts index 84e3f698a179..8a97431b0b0a 100644 --- a/packages/node-experimental/src/integrations/express.ts +++ b/packages/node-experimental/src/integrations/express.ts @@ -1,5 +1,6 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { ExpressInstrumentation } from '@opentelemetry/instrumentation-express'; +import { addOtelSpanData } from '@sentry/opentelemetry-node'; import type { Integration } from '@sentry/types'; import { NodePerformanceIntegration } from './NodePerformanceIntegration'; @@ -27,6 +28,14 @@ export class Express extends NodePerformanceIntegration implements Integra /** @inheritDoc */ public setupInstrumentation(): void | Instrumentation[] { - return [new ExpressInstrumentation()]; + return [ + new ExpressInstrumentation({ + requestHook(span) { + addOtelSpanData(span.spanContext().spanId, { + origin: 'auto.http.otel-express', + }); + }, + }), + ]; } } diff --git a/packages/node-experimental/src/integrations/fastify.ts b/packages/node-experimental/src/integrations/fastify.ts index 31ed6e3067bd..bff0f058c5d1 100644 --- a/packages/node-experimental/src/integrations/fastify.ts +++ b/packages/node-experimental/src/integrations/fastify.ts @@ -1,5 +1,6 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { FastifyInstrumentation } from '@opentelemetry/instrumentation-fastify'; +import { addOtelSpanData } from '@sentry/opentelemetry-node'; import type { Integration } from '@sentry/types'; import { NodePerformanceIntegration } from './NodePerformanceIntegration'; @@ -27,6 +28,14 @@ export class Fastify extends NodePerformanceIntegration implements Integra /** @inheritDoc */ public setupInstrumentation(): void | Instrumentation[] { - return [new FastifyInstrumentation()]; + return [ + new FastifyInstrumentation({ + requestHook(span) { + addOtelSpanData(span.spanContext().spanId, { + origin: 'auto.http.otel-fastify', + }); + }, + }), + ]; } } diff --git a/packages/node-experimental/src/integrations/graphql.ts b/packages/node-experimental/src/integrations/graphql.ts index 409e2b2d3cff..1515f69b6516 100644 --- a/packages/node-experimental/src/integrations/graphql.ts +++ b/packages/node-experimental/src/integrations/graphql.ts @@ -1,5 +1,6 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { GraphQLInstrumentation } from '@opentelemetry/instrumentation-graphql'; +import { addOtelSpanData } from '@sentry/opentelemetry-node'; import type { Integration } from '@sentry/types'; import { NodePerformanceIntegration } from './NodePerformanceIntegration'; @@ -30,6 +31,11 @@ export class GraphQL extends NodePerformanceIntegration implements Integra return [ new GraphQLInstrumentation({ ignoreTrivialResolveSpans: true, + responseHook(span) { + addOtelSpanData(span.spanContext().spanId, { + origin: 'auto.graphql.otel-graphql', + }); + }, }), ]; } diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts index b0f9555a60be..e8f5300f6f73 100644 --- a/packages/node-experimental/src/integrations/http.ts +++ b/packages/node-experimental/src/integrations/http.ts @@ -153,6 +153,7 @@ export class Http implements Integration { }, contexts: {}, metadata: {}, + origin: 'auto.http.otel-http', }; if (span.kind === SpanKind.SERVER) { diff --git a/packages/node-experimental/src/integrations/mongo.ts b/packages/node-experimental/src/integrations/mongo.ts index 96375a6202e5..2b8752d770ad 100644 --- a/packages/node-experimental/src/integrations/mongo.ts +++ b/packages/node-experimental/src/integrations/mongo.ts @@ -1,5 +1,6 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { MongoDBInstrumentation } from '@opentelemetry/instrumentation-mongodb'; +import { addOtelSpanData } from '@sentry/opentelemetry-node'; import type { Integration } from '@sentry/types'; import { NodePerformanceIntegration } from './NodePerformanceIntegration'; @@ -27,6 +28,14 @@ export class Mongo extends NodePerformanceIntegration implements Integrati /** @inheritDoc */ public setupInstrumentation(): void | Instrumentation[] { - return [new MongoDBInstrumentation({})]; + return [ + new MongoDBInstrumentation({ + responseHook(span) { + addOtelSpanData(span.spanContext().spanId, { + origin: 'auto.db.otel-mongo', + }); + }, + }), + ]; } } diff --git a/packages/node-experimental/src/integrations/mongoose.ts b/packages/node-experimental/src/integrations/mongoose.ts index de08f600a3a7..42d863966ea3 100644 --- a/packages/node-experimental/src/integrations/mongoose.ts +++ b/packages/node-experimental/src/integrations/mongoose.ts @@ -1,5 +1,6 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { MongooseInstrumentation } from '@opentelemetry/instrumentation-mongoose'; +import { addOtelSpanData } from '@sentry/opentelemetry-node'; import type { Integration } from '@sentry/types'; import { NodePerformanceIntegration } from './NodePerformanceIntegration'; @@ -27,6 +28,14 @@ export class Mongoose extends NodePerformanceIntegration implements Integr /** @inheritDoc */ public setupInstrumentation(): void | Instrumentation[] { - return [new MongooseInstrumentation({})]; + return [ + new MongooseInstrumentation({ + responseHook(span) { + addOtelSpanData(span.spanContext().spanId, { + origin: 'auto.db.otel-mongoose', + }); + }, + }), + ]; } } diff --git a/packages/node-experimental/src/integrations/mysql.ts b/packages/node-experimental/src/integrations/mysql.ts index 4308234b7410..3973f07f4685 100644 --- a/packages/node-experimental/src/integrations/mysql.ts +++ b/packages/node-experimental/src/integrations/mysql.ts @@ -27,6 +27,7 @@ export class Mysql extends NodePerformanceIntegration implements Integrati /** @inheritDoc */ public setupInstrumentation(): void | Instrumentation[] { + // Has no hook to adjust spans and add origin return [new MySQLInstrumentation({})]; } } diff --git a/packages/node-experimental/src/integrations/mysql2.ts b/packages/node-experimental/src/integrations/mysql2.ts index 278eeee4870f..f7d8f2c96fc9 100644 --- a/packages/node-experimental/src/integrations/mysql2.ts +++ b/packages/node-experimental/src/integrations/mysql2.ts @@ -1,5 +1,6 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { MySQL2Instrumentation } from '@opentelemetry/instrumentation-mysql2'; +import { addOtelSpanData } from '@sentry/opentelemetry-node'; import type { Integration } from '@sentry/types'; import { NodePerformanceIntegration } from './NodePerformanceIntegration'; @@ -27,6 +28,14 @@ export class Mysql2 extends NodePerformanceIntegration implements Integrat /** @inheritDoc */ public setupInstrumentation(): void | Instrumentation[] { - return [new MySQL2Instrumentation({})]; + return [ + new MySQL2Instrumentation({ + responseHook(span) { + addOtelSpanData(span.spanContext().spanId, { + origin: 'auto.db.otel-mysql2', + }); + }, + }), + ]; } } diff --git a/packages/node-experimental/src/integrations/nest.ts b/packages/node-experimental/src/integrations/nest.ts index a358500b1fdd..b7e47b2f49c8 100644 --- a/packages/node-experimental/src/integrations/nest.ts +++ b/packages/node-experimental/src/integrations/nest.ts @@ -27,6 +27,7 @@ export class Nest extends NodePerformanceIntegration implements Integratio /** @inheritDoc */ public setupInstrumentation(): void | Instrumentation[] { + // Does not have a hook to adjust spans and add origin return [new NestInstrumentation({})]; } } diff --git a/packages/node-experimental/src/integrations/postgres.ts b/packages/node-experimental/src/integrations/postgres.ts index aacb679a906c..f6d1414a7fc0 100644 --- a/packages/node-experimental/src/integrations/postgres.ts +++ b/packages/node-experimental/src/integrations/postgres.ts @@ -1,5 +1,6 @@ import type { Instrumentation } from '@opentelemetry/instrumentation'; import { PgInstrumentation } from '@opentelemetry/instrumentation-pg'; +import { addOtelSpanData } from '@sentry/opentelemetry-node'; import type { Integration } from '@sentry/types'; import { NodePerformanceIntegration } from './NodePerformanceIntegration'; @@ -27,6 +28,14 @@ export class Postgres extends NodePerformanceIntegration implements Integr /** @inheritDoc */ public setupInstrumentation(): void | Instrumentation[] { - return [new PgInstrumentation({})]; + return [ + new PgInstrumentation({ + requestHook(span) { + addOtelSpanData(span.spanContext().spanId, { + origin: 'auto.db.otel-postgres', + }); + }, + }), + ]; } } diff --git a/packages/node-experimental/src/integrations/prisma.ts b/packages/node-experimental/src/integrations/prisma.ts index d29c44560e29..203e9d8ed6b1 100644 --- a/packages/node-experimental/src/integrations/prisma.ts +++ b/packages/node-experimental/src/integrations/prisma.ts @@ -31,6 +31,7 @@ export class Prisma extends NodePerformanceIntegration implements Integrat /** @inheritDoc */ public setupInstrumentation(): void | Instrumentation[] { + // does not have a hook to adjust spans & add origin return [new PrismaInstrumentation({})]; } } diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 5d28be6d3c21..885fae1430d8 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -71,6 +71,7 @@ export function tracingHandler(): ( { name, op: 'http.server', + origin: 'auto.http.node.tracingHandler', ...traceparentData, metadata: { dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, diff --git a/packages/node/src/integrations/console.ts b/packages/node/src/integrations/console.ts index da0b684c4992..eb8a38980a64 100644 --- a/packages/node/src/integrations/console.ts +++ b/packages/node/src/integrations/console.ts @@ -1,6 +1,6 @@ import { getCurrentHub } from '@sentry/core'; import type { Integration } from '@sentry/types'; -import { fill, severityLevelFromString } from '@sentry/utils'; +import { addInstrumentationHandler, severityLevelFromString } from '@sentry/utils'; import * as util from 'util'; /** Console module integration */ @@ -19,37 +19,24 @@ export class Console implements Integration { * @inheritDoc */ public setupOnce(): void { - for (const level of ['debug', 'info', 'warn', 'error', 'log']) { - fill(console, level, createConsoleWrapper(level)); - } - } -} + addInstrumentationHandler('console', ({ args, level }: { args: unknown[]; level: string }) => { + const hub = getCurrentHub(); -/** - * Wrapper function that'll be used for every console level - */ -function createConsoleWrapper(level: string): (originalConsoleMethod: () => void) => void { - return function consoleWrapper(originalConsoleMethod: () => void): () => void { - const sentryLevel = severityLevelFromString(level); - - /* eslint-disable prefer-rest-params */ - return function (this: typeof console): void { - if (getCurrentHub().getIntegration(Console)) { - getCurrentHub().addBreadcrumb( - { - category: 'console', - level: sentryLevel, - message: util.format.apply(undefined, arguments), - }, - { - input: [...arguments], - level, - }, - ); + if (!hub.getIntegration(Console)) { + return; } - originalConsoleMethod.apply(this, arguments); - }; - /* eslint-enable prefer-rest-params */ - }; + hub.addBreadcrumb( + { + category: 'console', + level: severityLevelFromString(level), + message: util.format.apply(undefined, args), + }, + { + input: [...args], + level, + }, + ); + }); + } } diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 1102c0301095..95b68e2c8eb3 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -251,6 +251,7 @@ function _createWrappedRequestMethodFactory( const requestSpan = shouldCreateSpan(rawRequestUrl) ? parentSpan?.startChild({ op: 'http.client', + origin: 'auto.http.node.http', description: `${data['http.method']} ${data.url}`, data, }) diff --git a/packages/node/src/integrations/undici/index.ts b/packages/node/src/integrations/undici/index.ts index 0c69dec37d3f..aeb614b3fddd 100644 --- a/packages/node/src/integrations/undici/index.ts +++ b/packages/node/src/integrations/undici/index.ts @@ -304,6 +304,7 @@ function createRequestSpan( } return activeSpan?.startChild({ op: 'http.client', + origin: 'auto.http.node.undici', description: `${method} ${getSanitizedUrlString(url)}`, data, }); diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index e784eb308f54..0f1613a27345 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -208,10 +208,11 @@ describe('tracing', () => { const baggageHeader = request.getHeader('baggage') as string; const parts = sentryTraceHeader.split('-'); - expect(parts.length).toEqual(3); + + // Should not include sampling decision since we don't wanna influence the tracing decisions downstream + expect(parts.length).toEqual(2); expect(parts[0]).toEqual(traceId); expect(parts[1]).toEqual(expect.any(String)); - expect(parts[2]).toEqual('0'); expect(baggageHeader).toEqual( `sentry-environment=production,sentry-release=1.0.0,sentry-user_segment=segmentA,sentry-public_key=dogsarebadatkeepingsecrets,sentry-trace_id=${traceId}`, diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index b2dc2beebf71..0e208b050357 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -225,7 +225,7 @@ function updateSpanWithOtelData(sentrySpan: SentrySpan, otelSpan: OtelSpan): voi const { op, description, data } = parseSpanDescription(otelSpan); - const { data: additionalData, tags } = getOtelSpanData(otelSpan.spanContext().spanId); + const { data: additionalData, tags, origin } = getOtelSpanData(otelSpan.spanContext().spanId); sentrySpan.setStatus(mapOtelStatus(otelSpan)); sentrySpan.setData('otel.kind', SpanKind[kind]); @@ -245,11 +245,15 @@ function updateSpanWithOtelData(sentrySpan: SentrySpan, otelSpan: OtelSpan): voi sentrySpan.op = op; sentrySpan.description = description; + + if (origin) { + sentrySpan.origin = origin; + } } function updateTransactionWithOtelData(transaction: Transaction, otelSpan: OtelSpan): void { const { op, description, source, data } = parseSpanDescription(otelSpan); - const { data: additionalData, tags, contexts, metadata } = getOtelSpanData(otelSpan.spanContext().spanId); + const { data: additionalData, tags, contexts, metadata, origin } = getOtelSpanData(otelSpan.spanContext().spanId); transaction.setContext('otel', { attributes: otelSpan.attributes, @@ -283,6 +287,10 @@ function updateTransactionWithOtelData(transaction: Transaction, otelSpan: OtelS transaction.op = op; transaction.setName(description, source); + + if (origin) { + transaction.origin = origin; + } } function convertOtelTimeToSeconds([seconds, nano]: [number, number]): number { diff --git a/packages/opentelemetry-node/src/utils/spanData.ts b/packages/opentelemetry-node/src/utils/spanData.ts index ed83892301d9..74d20b964985 100644 --- a/packages/opentelemetry-node/src/utils/spanData.ts +++ b/packages/opentelemetry-node/src/utils/spanData.ts @@ -1,4 +1,4 @@ -import type { Context } from '@sentry/types'; +import type { Context, SpanOrigin } from '@sentry/types'; type SentryTags = Record; type SentryData = Record; @@ -9,13 +9,14 @@ export interface AdditionalOtelSpanData { tags: SentryTags; contexts: Contexts; metadata: unknown; + origin?: SpanOrigin; } const OTEL_SPAN_DATA_MAP: Map = new Map(); /** Add data that should be added to the sentry span resulting from the given otel span ID. */ -export function addOtelSpanData(otelSpanId: string, data: AdditionalOtelSpanData): void { - OTEL_SPAN_DATA_MAP.set(otelSpanId, data); +export function addOtelSpanData(otelSpanId: string, data: Partial): void { + OTEL_SPAN_DATA_MAP.set(otelSpanId, { data: {}, tags: {}, contexts: {}, metadata: {}, ...data }); } /** Get additional data for a Sentry span. */ diff --git a/packages/react/src/profiler.tsx b/packages/react/src/profiler.tsx index 4ada66266f26..34aa52092635 100644 --- a/packages/react/src/profiler.tsx +++ b/packages/react/src/profiler.tsx @@ -62,6 +62,7 @@ class Profiler extends React.Component { this._mountSpan = activeTransaction.startChild({ description: `<${name}>`, op: REACT_MOUNT_OP, + origin: 'auto.ui.react.profiler', }); } } @@ -89,6 +90,7 @@ class Profiler extends React.Component { }, description: `<${this.props.name}>`, op: REACT_UPDATE_OP, + origin: 'auto.ui.react.profiler', startTimestamp: now, }); } @@ -116,6 +118,7 @@ class Profiler extends React.Component { description: `<${name}>`, endTimestamp: timestampInSeconds(), op: REACT_RENDER_OP, + origin: 'auto.ui.react.profiler', startTimestamp: this._mountSpan.endTimestamp, }); } @@ -180,6 +183,7 @@ function useProfiler( return activeTransaction.startChild({ description: `<${name}>`, op: REACT_MOUNT_OP, + origin: 'auto.ui.react.profiler', }); } @@ -197,6 +201,7 @@ function useProfiler( description: `<${name}>`, endTimestamp: timestampInSeconds(), op: REACT_RENDER_OP, + origin: 'auto.ui.react.profiler', startTimestamp: mountSpan.endTimestamp, }); } diff --git a/packages/react/src/reactrouter.tsx b/packages/react/src/reactrouter.tsx index 95c507e3d07b..ab45d4e5f82e 100644 --- a/packages/react/src/reactrouter.tsx +++ b/packages/react/src/reactrouter.tsx @@ -93,6 +93,7 @@ function createReactRouterInstrumentation( activeTransaction = customStartTransaction({ name, op: 'pageload', + origin: 'auto.pageload.react.reactrouter', tags, metadata: { source, @@ -111,6 +112,7 @@ function createReactRouterInstrumentation( activeTransaction = customStartTransaction({ name, op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags, metadata: { source, diff --git a/packages/react/src/reactrouterv3.ts b/packages/react/src/reactrouterv3.ts index 4d12b3581e53..f185a025a649 100644 --- a/packages/react/src/reactrouterv3.ts +++ b/packages/react/src/reactrouterv3.ts @@ -53,6 +53,7 @@ export function reactRouterV3Instrumentation( activeTransaction = startTransaction({ name: prevName, op: 'pageload', + origin: 'auto.pageload.react.reactrouterv3', tags: { 'routing.instrumentation': 'react-router-v3', }, @@ -81,6 +82,7 @@ export function reactRouterV3Instrumentation( activeTransaction = startTransaction({ name: prevName, op: 'navigation', + origin: 'auto.navigation.react.reactrouterv3', tags, metadata: { source, diff --git a/packages/react/src/reactrouterv6.tsx b/packages/react/src/reactrouterv6.tsx index 161c7dd5fb3b..de356b05347b 100644 --- a/packages/react/src/reactrouterv6.tsx +++ b/packages/react/src/reactrouterv6.tsx @@ -55,6 +55,7 @@ export function reactRouterV6Instrumentation( activeTransaction = customStartTransaction({ name: initPathName, op: 'pageload', + origin: 'auto.pageload.react.reactrouterv6', tags: SENTRY_TAGS, metadata: { source: 'url', @@ -152,6 +153,7 @@ function handleNavigation( activeTransaction = _customStartTransaction({ name, op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: SENTRY_TAGS, metadata: { source, diff --git a/packages/react/test/profiler.test.tsx b/packages/react/test/profiler.test.tsx index 20dc1f2962c5..70eaff2d2c8b 100644 --- a/packages/react/test/profiler.test.tsx +++ b/packages/react/test/profiler.test.tsx @@ -79,6 +79,7 @@ describe('withProfiler', () => { expect(mockStartChild).toHaveBeenLastCalledWith({ description: `<${UNKNOWN_COMPONENT}>`, op: REACT_MOUNT_OP, + origin: 'auto.ui.react.profiler', }); }); }); @@ -96,6 +97,7 @@ describe('withProfiler', () => { description: `<${UNKNOWN_COMPONENT}>`, endTimestamp: expect.any(Number), op: REACT_RENDER_OP, + origin: 'auto.ui.react.profiler', startTimestamp: undefined, }); }); @@ -127,6 +129,7 @@ describe('withProfiler', () => { data: { changedProps: ['num'] }, description: `<${UNKNOWN_COMPONENT}>`, op: REACT_UPDATE_OP, + origin: 'auto.ui.react.profiler', startTimestamp: expect.any(Number), }); expect(mockFinish).toHaveBeenCalledTimes(2); @@ -137,6 +140,7 @@ describe('withProfiler', () => { data: { changedProps: ['num'] }, description: `<${UNKNOWN_COMPONENT}>`, op: REACT_UPDATE_OP, + origin: 'auto.ui.react.profiler', startTimestamp: expect.any(Number), }); expect(mockFinish).toHaveBeenCalledTimes(3); @@ -175,6 +179,7 @@ describe('useProfiler()', () => { expect(mockStartChild).toHaveBeenLastCalledWith({ description: '', op: REACT_MOUNT_OP, + origin: 'auto.ui.react.profiler', }); }); }); @@ -197,6 +202,7 @@ describe('useProfiler()', () => { expect.objectContaining({ description: '', op: REACT_RENDER_OP, + origin: 'auto.ui.react.profiler', }), ); }); diff --git a/packages/react/test/reactrouterv3.test.tsx b/packages/react/test/reactrouterv3.test.tsx index a4cbe463adbc..42d1b6992440 100644 --- a/packages/react/test/reactrouterv3.test.tsx +++ b/packages/react/test/reactrouterv3.test.tsx @@ -52,6 +52,7 @@ describe('React Router V3', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', op: 'pageload', + origin: 'auto.pageload.react.reactrouterv3', tags: { 'routing.instrumentation': 'react-router-v3' }, metadata: { source: 'route', @@ -77,6 +78,7 @@ describe('React Router V3', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv3', tags: { from: '/', 'routing.instrumentation': 'react-router-v3' }, metadata: { source: 'route', @@ -90,6 +92,7 @@ describe('React Router V3', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/features', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv3', tags: { from: '/about', 'routing.instrumentation': 'react-router-v3' }, metadata: { source: 'route', @@ -143,6 +146,7 @@ describe('React Router V3', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/users/:userid', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv3', tags: { from: '/', 'routing.instrumentation': 'react-router-v3' }, metadata: { source: 'route', @@ -164,6 +168,7 @@ describe('React Router V3', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/organizations/:orgid/v1/:teamid', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv3', tags: { from: '/', 'routing.instrumentation': 'react-router-v3' }, metadata: { source: 'route', @@ -179,6 +184,7 @@ describe('React Router V3', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/organizations/:orgid', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv3', tags: { from: '/organizations/:orgid/v1/:teamid', 'routing.instrumentation': 'react-router-v3' }, metadata: { source: 'route', @@ -199,6 +205,7 @@ describe('React Router V3', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/organizations/1234/some/other/route', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv3', tags: { from: '/', 'routing.instrumentation': 'react-router-v3' }, metadata: { source: 'url', @@ -219,6 +226,7 @@ describe('React Router V3', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', op: 'pageload', + origin: 'auto.pageload.react.reactrouterv3', tags: { 'routing.instrumentation': 'react-router-v3' }, metadata: { source: 'url', diff --git a/packages/react/test/reactrouterv4.test.tsx b/packages/react/test/reactrouterv4.test.tsx index 343f78c175e5..fb608a37726e 100644 --- a/packages/react/test/reactrouterv4.test.tsx +++ b/packages/react/test/reactrouterv4.test.tsx @@ -37,6 +37,7 @@ describe('React Router v4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', op: 'pageload', + origin: 'auto.pageload.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v4' }, metadata: { source: 'url' }, }); @@ -66,6 +67,7 @@ describe('React Router v4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v4' }, metadata: { source: 'url' }, }); @@ -77,6 +79,7 @@ describe('React Router v4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/features', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v4' }, metadata: { source: 'url' }, }); @@ -155,6 +158,7 @@ describe('React Router v4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/users/123', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v4' }, metadata: { source: 'url' }, }); @@ -182,6 +186,7 @@ describe('React Router v4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/users/123', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v4' }, metadata: { source: 'url' }, }); @@ -211,6 +216,7 @@ describe('React Router v4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/organizations/1234/v1/758', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v4' }, metadata: { source: 'url' }, }); @@ -226,6 +232,7 @@ describe('React Router v4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/organizations/543', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v4' }, metadata: { source: 'url' }, }); @@ -259,6 +266,7 @@ describe('React Router v4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/organizations/:orgid/v1/:teamid', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v4' }, metadata: { source: 'route' }, }); @@ -270,6 +278,7 @@ describe('React Router v4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/organizations/:orgid', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v4' }, metadata: { source: 'route' }, }); diff --git a/packages/react/test/reactrouterv5.test.tsx b/packages/react/test/reactrouterv5.test.tsx index 568e630ccc25..c4a6ae2f7d98 100644 --- a/packages/react/test/reactrouterv5.test.tsx +++ b/packages/react/test/reactrouterv5.test.tsx @@ -37,6 +37,7 @@ describe('React Router v5', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', op: 'pageload', + origin: 'auto.pageload.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v5' }, metadata: { source: 'url' }, }); @@ -66,6 +67,7 @@ describe('React Router v5', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v5' }, metadata: { source: 'url' }, }); @@ -77,6 +79,7 @@ describe('React Router v5', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/features', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v5' }, metadata: { source: 'url' }, }); @@ -155,6 +158,7 @@ describe('React Router v5', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/users/123', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v5' }, metadata: { source: 'url' }, }); @@ -182,6 +186,7 @@ describe('React Router v5', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/users/123', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v5' }, metadata: { source: 'url' }, }); @@ -212,6 +217,7 @@ describe('React Router v5', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/organizations/1234/v1/758', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v5' }, metadata: { source: 'url' }, }); @@ -227,6 +233,7 @@ describe('React Router v5', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/organizations/543', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v5' }, metadata: { source: 'url' }, }); @@ -260,6 +267,7 @@ describe('React Router v5', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/organizations/:orgid/v1/:teamid', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v5' }, metadata: { source: 'route' }, }); @@ -271,6 +279,7 @@ describe('React Router v5', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/organizations/:orgid', op: 'navigation', + origin: 'auto.navigation.react.reactrouter', tags: { 'routing.instrumentation': 'react-router-v5' }, metadata: { source: 'route' }, }); diff --git a/packages/react/test/reactrouterv6.4.test.tsx b/packages/react/test/reactrouterv6.4.test.tsx index e6bfc67b9bcf..913b6041e053 100644 --- a/packages/react/test/reactrouterv6.4.test.tsx +++ b/packages/react/test/reactrouterv6.4.test.tsx @@ -70,6 +70,7 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenCalledWith({ name: '/', op: 'pageload', + origin: 'auto.pageload.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6', }, @@ -106,6 +107,7 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -144,6 +146,7 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/us', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -182,6 +185,7 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/:page', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -232,6 +236,7 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/stores/:storeId/products/:productId', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -300,6 +305,7 @@ describe('React Router v6.4', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/app/about/us', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'url' }, }); diff --git a/packages/react/test/reactrouterv6.test.tsx b/packages/react/test/reactrouterv6.test.tsx index 6d0faa719f57..281999b95696 100644 --- a/packages/react/test/reactrouterv6.test.tsx +++ b/packages/react/test/reactrouterv6.test.tsx @@ -59,6 +59,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', op: 'pageload', + origin: 'auto.pageload.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'url' }, }); @@ -96,6 +97,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', op: 'pageload', + origin: 'auto.pageload.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'url' }, }); @@ -118,6 +120,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -142,6 +145,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/us', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -166,6 +170,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/:page', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -192,6 +197,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/stores/:storeId/products/:productId', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -226,6 +232,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/projects/:projectId/views/:viewId', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -255,6 +262,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', op: 'pageload', + origin: 'auto.pageload.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'url' }, }); @@ -307,6 +315,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/', op: 'pageload', + origin: 'auto.pageload.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'url' }, }); @@ -338,6 +347,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -375,6 +385,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/us', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -412,6 +423,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/about/:page', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -455,6 +467,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/stores/:storeId/products/:productId', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); @@ -522,6 +535,7 @@ describe('React Router v6', () => { expect(mockStartTransaction).toHaveBeenLastCalledWith({ name: '/projects/:projectId/views/:viewId', op: 'navigation', + origin: 'auto.navigation.react.reactrouterv6', tags: { 'routing.instrumentation': 'react-router-v6' }, metadata: { source: 'route' }, }); diff --git a/packages/remix/src/client/performance.tsx b/packages/remix/src/client/performance.tsx index a3f7815b7bdc..ed7dea13cfc3 100644 --- a/packages/remix/src/client/performance.tsx +++ b/packages/remix/src/client/performance.tsx @@ -65,6 +65,7 @@ export function remixRouterInstrumentation(useEffect: UseEffect, useLocation: Us activeTransaction = customStartTransaction({ name: initPathName, op: 'pageload', + origin: 'auto.pageload.remix', tags: DEFAULT_TAGS, metadata: { source: 'url', @@ -142,6 +143,7 @@ export function withSentry

, R extends React.FC activeTransaction = _customStartTransaction({ name: matches[matches.length - 1].id, op: 'navigation', + origin: 'auto.navigation.remix', tags: DEFAULT_TAGS, metadata: { source: 'route', diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index b0ba45f69c26..e588ae74ccad 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -140,6 +140,7 @@ function makeWrappedDocumentRequestFunction( try { const span = activeTransaction?.startChild({ op: 'function.remix.document_request', + origin: 'auto.function.remix', description: activeTransaction.name, tags: { method: request.method, @@ -182,6 +183,7 @@ function makeWrappedDataFunction(origFn: DataFunction, id: string, name: 'action try { const span = activeTransaction?.startChild({ op: `function.remix.${name}`, + origin: 'auto.ui.remix', description: id, tags: { name, @@ -325,6 +327,7 @@ export function startRequestHandlerTransaction( const transaction = hub.startTransaction({ name, op: 'http.server', + origin: 'auto.http.remix', tags: { method: request.method, }, diff --git a/packages/serverless/src/awslambda.ts b/packages/serverless/src/awslambda.ts index b5cf41af5e90..b5aa9200b524 100644 --- a/packages/serverless/src/awslambda.ts +++ b/packages/serverless/src/awslambda.ts @@ -292,6 +292,7 @@ export function wrapHandler( const transaction = hub.startTransaction({ name: context.functionName, op: 'function.aws.lambda', + origin: 'auto.function.serverless', ...traceparentData, metadata: { dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, diff --git a/packages/serverless/src/awsservices.ts b/packages/serverless/src/awsservices.ts index 55c6741ffb69..f5ba74fd52b2 100644 --- a/packages/serverless/src/awsservices.ts +++ b/packages/serverless/src/awsservices.ts @@ -68,6 +68,7 @@ function wrapMakeRequest( span = transaction.startChild({ description: describe(this, operation, params), op: 'http.client', + origin: 'auto.http.serverless', }); } }); diff --git a/packages/serverless/src/gcpfunction/cloud_events.ts b/packages/serverless/src/gcpfunction/cloud_events.ts index f20f7f941ee2..83725ffbb840 100644 --- a/packages/serverless/src/gcpfunction/cloud_events.ts +++ b/packages/serverless/src/gcpfunction/cloud_events.ts @@ -35,6 +35,7 @@ function _wrapCloudEventFunction( const transaction = hub.startTransaction({ name: context.type || '', op: 'function.gcp.cloud_event', + origin: 'auto.function.serverless.gcp_cloud_event', metadata: { source: 'component' }, }) as ReturnType | undefined; diff --git a/packages/serverless/src/gcpfunction/events.ts b/packages/serverless/src/gcpfunction/events.ts index f6485de054d7..e2342d1fe905 100644 --- a/packages/serverless/src/gcpfunction/events.ts +++ b/packages/serverless/src/gcpfunction/events.ts @@ -37,6 +37,7 @@ function _wrapEventFunction const transaction = hub.startTransaction({ name: context.eventType, op: 'function.gcp.event', + origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }) as ReturnType | undefined; diff --git a/packages/serverless/src/gcpfunction/http.ts b/packages/serverless/src/gcpfunction/http.ts index d3b24f5ba743..1c265fe9fb64 100644 --- a/packages/serverless/src/gcpfunction/http.ts +++ b/packages/serverless/src/gcpfunction/http.ts @@ -77,6 +77,7 @@ function _wrapHttpFunction(fn: HttpFunction, wrapOptions: Partial { diff --git a/packages/serverless/src/google-cloud-http.ts b/packages/serverless/src/google-cloud-http.ts index 0dd4f1f60ccb..b781a8c4c9c5 100644 --- a/packages/serverless/src/google-cloud-http.ts +++ b/packages/serverless/src/google-cloud-http.ts @@ -62,6 +62,7 @@ function wrapRequestFunction(orig: RequestFunction): RequestFunction { span = transaction.startChild({ description: `${httpMethod} ${reqOpts.uri}`, op: `http.client.${identifyService(this.apiEndpoint)}`, + origin: 'auto.http.serverless', }); } orig.call(this, reqOpts, (...args: Parameters) => { diff --git a/packages/serverless/test/awslambda.test.ts b/packages/serverless/test/awslambda.test.ts index f2cff6e9b9c4..53770927c4a5 100644 --- a/packages/serverless/test/awslambda.test.ts +++ b/packages/serverless/test/awslambda.test.ts @@ -194,6 +194,7 @@ describe('AWSLambda', () => { const fakeTransactionContext = { name: 'functionName', op: 'function.aws.lambda', + origin: 'auto.function.serverless', metadata: { source: 'component' }, }; @@ -221,6 +222,7 @@ describe('AWSLambda', () => { const fakeTransactionContext = { name: 'functionName', op: 'function.aws.lambda', + origin: 'auto.function.serverless', metadata: { source: 'component' }, }; @@ -261,6 +263,7 @@ describe('AWSLambda', () => { parentSpanId: '1121201211212012', parentSampled: false, op: 'function.aws.lambda', + origin: 'auto.function.serverless', name: 'functionName', traceId: '12312012123120121231201212312012', metadata: { @@ -295,6 +298,7 @@ describe('AWSLambda', () => { const fakeTransactionContext = { name: 'functionName', op: 'function.aws.lambda', + origin: 'auto.function.serverless', traceId: '12312012123120121231201212312012', parentSpanId: '1121201211212012', parentSampled: false, @@ -325,6 +329,7 @@ describe('AWSLambda', () => { const fakeTransactionContext = { name: 'functionName', op: 'function.aws.lambda', + origin: 'auto.function.serverless', metadata: { source: 'component' }, }; @@ -363,6 +368,7 @@ describe('AWSLambda', () => { const fakeTransactionContext = { name: 'functionName', op: 'function.aws.lambda', + origin: 'auto.function.serverless', metadata: { source: 'component' }, }; @@ -405,6 +411,7 @@ describe('AWSLambda', () => { const fakeTransactionContext = { name: 'functionName', op: 'function.aws.lambda', + origin: 'auto.function.serverless', metadata: { source: 'component' }, }; @@ -443,6 +450,7 @@ describe('AWSLambda', () => { const fakeTransactionContext = { name: 'functionName', op: 'function.aws.lambda', + origin: 'auto.function.serverless', metadata: { source: 'component' }, }; diff --git a/packages/serverless/test/awsservices.test.ts b/packages/serverless/test/awsservices.test.ts index 63051773ea04..b37f9aa527f0 100644 --- a/packages/serverless/test/awsservices.test.ts +++ b/packages/serverless/test/awsservices.test.ts @@ -33,6 +33,7 @@ describe('AWSServices', () => { // @ts-ignore see "Why @ts-ignore" note expect(SentryNode.fakeTransaction.startChild).toBeCalledWith({ op: 'http.client', + origin: 'auto.http.serverless', description: 'aws.s3.getObject foo', }); // @ts-ignore see "Why @ts-ignore" note @@ -50,6 +51,7 @@ describe('AWSServices', () => { // @ts-ignore see "Why @ts-ignore" note expect(SentryNode.fakeTransaction.startChild).toBeCalledWith({ op: 'http.client', + origin: 'auto.http.serverless', description: 'aws.s3.getObject foo', }); }); @@ -65,6 +67,7 @@ describe('AWSServices', () => { // @ts-ignore see "Why @ts-ignore" note expect(SentryNode.fakeTransaction.startChild).toBeCalledWith({ op: 'http.client', + origin: 'auto.http.serverless', description: 'aws.lambda.invoke foo', }); }); diff --git a/packages/serverless/test/gcpfunction.test.ts b/packages/serverless/test/gcpfunction.test.ts index a203ceef4797..74939f1f574a 100644 --- a/packages/serverless/test/gcpfunction.test.ts +++ b/packages/serverless/test/gcpfunction.test.ts @@ -114,6 +114,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'POST /path', op: 'function.gcp.http', + origin: 'auto.function.serverless.gcp_http', metadata: { source: 'route' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -147,6 +148,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'POST /path', op: 'function.gcp.http', + origin: 'auto.function.serverless.gcp_http', traceId: '12312012123120121231201212312012', parentSpanId: '1121201211212012', parentSampled: false, @@ -183,6 +185,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'POST /path', op: 'function.gcp.http', + origin: 'auto.function.serverless.gcp_http', traceId: '12312012123120121231201212312012', parentSpanId: '1121201211212012', parentSampled: false, @@ -276,6 +279,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.event', + origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -303,6 +307,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.event', + origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -335,6 +340,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.event', + origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -366,6 +372,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.event', + origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -395,6 +402,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.event', + origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -422,6 +430,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.event', + origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -450,6 +459,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.event', + origin: 'auto.function.serverless.gcp_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -489,6 +499,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.cloud_event', + origin: 'auto.function.serverless.gcp_cloud_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -516,6 +527,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.cloud_event', + origin: 'auto.function.serverless.gcp_cloud_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -545,6 +557,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.cloud_event', + origin: 'auto.function.serverless.gcp_cloud_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -572,6 +585,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.cloud_event', + origin: 'auto.function.serverless.gcp_cloud_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note @@ -600,6 +614,7 @@ describe('GCPFunction', () => { const fakeTransactionContext = { name: 'event.type', op: 'function.gcp.cloud_event', + origin: 'auto.function.serverless.gcp_cloud_event', metadata: { source: 'component' }, }; // @ts-ignore see "Why @ts-ignore" note diff --git a/packages/serverless/test/google-cloud-grpc.test.ts b/packages/serverless/test/google-cloud-grpc.test.ts index ec8dcfe8c77b..212faefa111f 100644 --- a/packages/serverless/test/google-cloud-grpc.test.ts +++ b/packages/serverless/test/google-cloud-grpc.test.ts @@ -129,6 +129,7 @@ describe('GoogleCloudGrpc tracing', () => { // @ts-ignore see "Why @ts-ignore" note expect(SentryNode.fakeTransaction.startChild).toBeCalledWith({ op: 'grpc.pubsub', + origin: 'auto.grpc.serverless', description: 'unary call publish', }); await pubsub.close(); diff --git a/packages/serverless/test/google-cloud-http.test.ts b/packages/serverless/test/google-cloud-http.test.ts index f1fb19bad9ac..b2201f1728d7 100644 --- a/packages/serverless/test/google-cloud-http.test.ts +++ b/packages/serverless/test/google-cloud-http.test.ts @@ -60,11 +60,13 @@ describe('GoogleCloudHttp tracing', () => { // @ts-ignore see "Why @ts-ignore" note expect(SentryNode.fakeTransaction.startChild).toBeCalledWith({ op: 'http.client.bigquery', + origin: 'auto.http.serverless', description: 'POST /jobs', }); // @ts-ignore see "Why @ts-ignore" note expect(SentryNode.fakeTransaction.startChild).toBeCalledWith({ op: 'http.client.bigquery', + origin: 'auto.http.serverless', description: expect.stringMatching(new RegExp('^GET /queries/.+')), }); }); diff --git a/packages/svelte/src/performance.ts b/packages/svelte/src/performance.ts index 8bcafb8d9ed8..2230db18f9a4 100644 --- a/packages/svelte/src/performance.ts +++ b/packages/svelte/src/performance.ts @@ -50,6 +50,7 @@ function recordInitSpan(transaction: Transaction, componentName: string): Span { const initSpan = transaction.startChild({ op: UI_SVELTE_INIT, description: componentName, + origin: 'auto.ui.svelte', }); onMount(() => { @@ -77,6 +78,7 @@ function recordUpdateSpans(componentName: string, initSpan?: Span): void { updateSpan = parentSpan.startChild({ op: UI_SVELTE_UPDATE, description: componentName, + origin: 'auto.ui.svelte', }); }); diff --git a/packages/svelte/test/performance.test.ts b/packages/svelte/test/performance.test.ts index 1e02eb549d5f..c92492d0d943 100644 --- a/packages/svelte/test/performance.test.ts +++ b/packages/svelte/test/performance.test.ts @@ -65,11 +65,13 @@ describe('Sentry.trackComponent()', () => { expect(testTransaction.startChild).toHaveBeenCalledWith({ description: '', op: 'ui.svelte.init', + origin: 'auto.ui.svelte', }); expect(testInitSpan.startChild).toHaveBeenCalledWith({ description: '', op: 'ui.svelte.update', + origin: 'auto.ui.svelte', }); expect(testInitSpan.finish).toHaveBeenCalledTimes(1); @@ -96,6 +98,7 @@ describe('Sentry.trackComponent()', () => { expect(testTransaction.startChild).toHaveBeenLastCalledWith({ description: '', op: 'ui.svelte.update', + origin: 'auto.ui.svelte', }); expect(testTransaction.spans.length).toEqual(3); }); @@ -106,6 +109,7 @@ describe('Sentry.trackComponent()', () => { expect(testTransaction.startChild).toHaveBeenCalledWith({ description: '', op: 'ui.svelte.init', + origin: 'auto.ui.svelte', }); expect(testInitSpan.startChild).not.toHaveBeenCalled(); @@ -120,6 +124,7 @@ describe('Sentry.trackComponent()', () => { expect(testTransaction.startChild).toHaveBeenCalledWith({ description: '', op: 'ui.svelte.update', + origin: 'auto.ui.svelte', }); expect(testInitSpan.startChild).not.toHaveBeenCalled(); @@ -144,11 +149,13 @@ describe('Sentry.trackComponent()', () => { expect(testTransaction.startChild).toHaveBeenCalledWith({ description: '', op: 'ui.svelte.init', + origin: 'auto.ui.svelte', }); expect(testInitSpan.startChild).toHaveBeenCalledWith({ description: '', op: 'ui.svelte.update', + origin: 'auto.ui.svelte', }); expect(testInitSpan.finish).toHaveBeenCalledTimes(1); @@ -187,6 +194,7 @@ describe('Sentry.trackComponent()', () => { expect(testTransaction.startChild).toHaveBeenLastCalledWith({ description: '', op: 'ui.svelte.init', + origin: 'auto.ui.svelte', }); expect(testTransaction.spans.length).toEqual(2); }); diff --git a/packages/sveltekit/src/client/load.ts b/packages/sveltekit/src/client/load.ts index 44430a3b9b1c..673941d76488 100644 --- a/packages/sveltekit/src/client/load.ts +++ b/packages/sveltekit/src/client/load.ts @@ -75,6 +75,7 @@ export function wrapLoadWithSentry any>(origLoad: T) return trace( { op: 'function.sveltekit.load', + origin: 'auto.function.sveltekit', name: routeId ? routeId : event.url.pathname, status: 'ok', metadata: { diff --git a/packages/sveltekit/src/client/router.ts b/packages/sveltekit/src/client/router.ts index 64125bdbdbd4..1883e5587c08 100644 --- a/packages/sveltekit/src/client/router.ts +++ b/packages/sveltekit/src/client/router.ts @@ -38,6 +38,7 @@ function instrumentPageload(startTransactionFn: (context: TransactionContext) => const pageloadTransaction = startTransactionFn({ name: initialPath, op: 'pageload', + origin: 'auto.pageload.sveltekit', description: initialPath, tags: { ...DEFAULT_TAGS, @@ -102,6 +103,7 @@ function instrumentNavigations(startTransactionFn: (context: TransactionContext) activeTransaction = startTransactionFn({ name: parameterizedRouteDestination || rawRouteDestination || 'unknown', op: 'navigation', + origin: 'auto.navigation.sveltekit', metadata: { source: parameterizedRouteDestination ? 'route' : 'url' }, tags: { ...DEFAULT_TAGS, @@ -117,6 +119,7 @@ function instrumentNavigations(startTransactionFn: (context: TransactionContext) routingSpan = activeTransaction.startChild({ op: 'ui.sveltekit.routing', description: 'SvelteKit Route Change', + origin: 'auto.ui.sveltekit', }); activeTransaction.setTag('from', parameterizedRouteOrigin); } diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index 332dea4925df..245fcee9658e 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -129,6 +129,7 @@ function instrumentHandle({ event, resolve }: Parameters[0], options: Se return trace( { op: 'http.server', + origin: 'auto.http.sveltekit', name: `${event.request.method} ${event.route?.id || event.url.pathname}`, status: 'ok', ...traceparentData, diff --git a/packages/sveltekit/src/server/load.ts b/packages/sveltekit/src/server/load.ts index 04d0137062c6..c286ad5e3834 100644 --- a/packages/sveltekit/src/server/load.ts +++ b/packages/sveltekit/src/server/load.ts @@ -72,6 +72,7 @@ export function wrapLoadWithSentry any>(origLoad: T) const traceLoadContext: TransactionContext = { op: 'function.sveltekit.load', + origin: 'auto.function.sveltekit', name: routeId ? routeId : event.url.pathname, status: 'ok', metadata: { @@ -130,6 +131,7 @@ export function wrapServerLoadWithSentry any>(origSe const traceLoadContext: TransactionContext = { op: 'function.sveltekit.server.load', + origin: 'auto.function.sveltekit', name: routeId ? routeId : event.url.pathname, status: 'ok', metadata: { diff --git a/packages/sveltekit/src/vite/autoInstrument.ts b/packages/sveltekit/src/vite/autoInstrument.ts index cabfc743db0c..07e8b7646124 100644 --- a/packages/sveltekit/src/vite/autoInstrument.ts +++ b/packages/sveltekit/src/vite/autoInstrument.ts @@ -40,7 +40,7 @@ type AutoInstrumentPluginOptions = AutoInstrumentSelection & { * @returns the plugin */ export function makeAutoInstrumentationPlugin(options: AutoInstrumentPluginOptions): Plugin { - const { load: shouldWrapLoad, serverLoad: shouldWrapServerLoad, debug } = options; + const { load: wrapLoadEnabled, serverLoad: wrapServerLoadEnabled, debug } = options; return { name: 'sentry-auto-instrumentation', @@ -49,7 +49,7 @@ export function makeAutoInstrumentationPlugin(options: AutoInstrumentPluginOptio async load(id) { const applyUniversalLoadWrapper = - shouldWrapLoad && + wrapLoadEnabled && /^\+(page|layout)\.(js|ts|mjs|mts)$/.test(path.basename(id)) && (await canWrapLoad(id, debug)); @@ -60,7 +60,7 @@ export function makeAutoInstrumentationPlugin(options: AutoInstrumentPluginOptio } const applyServerLoadWrapper = - shouldWrapServerLoad && + wrapServerLoadEnabled && /^\+(page|layout)\.server\.(js|ts|mjs|mts)$/.test(path.basename(id)) && (await canWrapLoad(id, debug)); @@ -90,7 +90,19 @@ export function makeAutoInstrumentationPlugin(options: AutoInstrumentPluginOptio * @returns `true` if we can wrap the given file, `false` otherwise */ export async function canWrapLoad(id: string, debug: boolean): Promise { + // Some 3rd party plugins add ids to the build that actually don't exist. + // We need to check for that here, otherwise users get get a build errors. + if (!fs.existsSync(id)) { + debug && + // eslint-disable-next-line no-console + console.log( + `Skipping wrapping ${id} because it doesn't exist. A 3rd party plugin might have added this as a virtual file to the build`, + ); + return false; + } + const code = (await fs.promises.readFile(id, 'utf8')).toString(); + const mod = parseModule(code); const program = mod.$ast.type === 'Program' && mod.$ast; diff --git a/packages/sveltekit/test/client/load.test.ts b/packages/sveltekit/test/client/load.test.ts index 01c2377ddbf2..64b5ef70fe3b 100644 --- a/packages/sveltekit/test/client/load.test.ts +++ b/packages/sveltekit/test/client/load.test.ts @@ -108,6 +108,7 @@ describe('wrapLoadWithSentry', () => { expect(mockTrace).toHaveBeenCalledWith( { op: 'function.sveltekit.load', + origin: 'auto.function.sveltekit', name: '/users/[id]', status: 'ok', metadata: { @@ -136,6 +137,7 @@ describe('wrapLoadWithSentry', () => { expect(mockTrace).toHaveBeenCalledWith( { op: 'function.sveltekit.load', + origin: 'auto.function.sveltekit', name: '/users/123', status: 'ok', metadata: { diff --git a/packages/sveltekit/test/client/router.test.ts b/packages/sveltekit/test/client/router.test.ts index 0b95a7195176..65bc39d5cdbb 100644 --- a/packages/sveltekit/test/client/router.test.ts +++ b/packages/sveltekit/test/client/router.test.ts @@ -53,6 +53,7 @@ describe('sveltekitRoutingInstrumentation', () => { expect(mockedStartTransaction).toHaveBeenCalledWith({ name: '/', op: 'pageload', + origin: 'auto.pageload.sveltekit', description: '/', tags: { 'routing.instrumentation': '@sentry/sveltekit', @@ -105,6 +106,7 @@ describe('sveltekitRoutingInstrumentation', () => { expect(mockedStartTransaction).toHaveBeenCalledWith({ name: '/users/[id]', op: 'navigation', + origin: 'auto.navigation.sveltekit', metadata: { source: 'route', }, @@ -115,6 +117,7 @@ describe('sveltekitRoutingInstrumentation', () => { expect(returnedTransaction?.startChild).toHaveBeenCalledWith({ op: 'ui.sveltekit.routing', + origin: 'auto.ui.sveltekit', description: 'SvelteKit Route Change', }); @@ -154,6 +157,7 @@ describe('sveltekitRoutingInstrumentation', () => { expect(mockedStartTransaction).toHaveBeenCalledWith({ name: '/users/[id]', op: 'navigation', + origin: 'auto.navigation.sveltekit', metadata: { source: 'route', }, @@ -164,6 +168,7 @@ describe('sveltekitRoutingInstrumentation', () => { expect(returnedTransaction?.startChild).toHaveBeenCalledWith({ op: 'ui.sveltekit.routing', + origin: 'auto.ui.sveltekit', description: 'SvelteKit Route Change', }); diff --git a/packages/sveltekit/test/server/load.test.ts b/packages/sveltekit/test/server/load.test.ts index 90980204cf68..6e27829f4004 100644 --- a/packages/sveltekit/test/server/load.test.ts +++ b/packages/sveltekit/test/server/load.test.ts @@ -230,6 +230,7 @@ describe('wrapLoadWithSentry calls trace', () => { expect(mockTrace).toHaveBeenCalledWith( { op: 'function.sveltekit.load', + origin: 'auto.function.sveltekit', name: '/users/[id]', status: 'ok', metadata: { @@ -249,6 +250,7 @@ describe('wrapLoadWithSentry calls trace', () => { expect(mockTrace).toHaveBeenCalledWith( { op: 'function.sveltekit.load', + origin: 'auto.function.sveltekit', name: '/users/123', status: 'ok', metadata: { @@ -283,6 +285,7 @@ describe('wrapServerLoadWithSentry calls trace', () => { expect(mockTrace).toHaveBeenCalledWith( { op: 'function.sveltekit.server.load', + origin: 'auto.function.sveltekit', name: '/users/[id]', parentSampled: true, parentSpanId: '1234567890abcdef', @@ -317,6 +320,7 @@ describe('wrapServerLoadWithSentry calls trace', () => { expect(mockTrace).toHaveBeenCalledWith( { op: 'function.sveltekit.server.load', + origin: 'auto.function.sveltekit', name: '/users/[id]', status: 'ok', data: { @@ -339,6 +343,7 @@ describe('wrapServerLoadWithSentry calls trace', () => { expect(mockTrace).toHaveBeenCalledWith( { op: 'function.sveltekit.server.load', + origin: 'auto.function.sveltekit', name: '/users/[id]', parentSampled: true, parentSpanId: '1234567890abcdef', @@ -368,6 +373,7 @@ describe('wrapServerLoadWithSentry calls trace', () => { expect(mockTrace).toHaveBeenCalledWith( { op: 'function.sveltekit.server.load', + origin: 'auto.function.sveltekit', name: '/users/123', parentSampled: true, parentSpanId: '1234567890abcdef', diff --git a/packages/sveltekit/test/vite/autoInstrument.test.ts b/packages/sveltekit/test/vite/autoInstrument.test.ts index 954138f017bf..13ee56eef3c6 100644 --- a/packages/sveltekit/test/vite/autoInstrument.test.ts +++ b/packages/sveltekit/test/vite/autoInstrument.test.ts @@ -21,6 +21,12 @@ vi.mock('fs', async () => { return fileContent || DEFAULT_CONTENT; }), }, + existsSync: vi.fn().mockImplementation(id => { + if (id === '+page.virtual.ts') { + return false; + } + return true; + }), }; }); @@ -198,15 +204,20 @@ describe('canWrapLoad', () => { 'export const loadNotLoad = () => {}; export const prerender = true;', 'export function aload(){}; export const prerender = true;', 'export function loader(){}; export const prerender = true;', - 'let loademe = false; export {loadme}', + 'let loadme = false; export {loadme}', 'const a = {load: true}; export {a}', 'if (s === "load") {}', 'const a = load ? load : false', '// const load = () => {}', '/* export const load = () => {} */ export const prerender = true;', '/* export const notLoad = () => { const a = getSomething() as load; } */ export const prerender = true;', - ])('returns `false` if no load declaration exists', async (_, code) => { + ])('returns `false` if no load declaration exists', async code => { fileContent = code; - expect(await canWrapLoad('+page.ts', false)).toEqual(true); + expect(await canWrapLoad('+page.ts', false)).toEqual(false); + }); + + it("returns `false` if the passed file id doesn't exist", async () => { + fileContent = DEFAULT_CONTENT; + expect(await canWrapLoad('+page.virtual.ts', false)).toEqual(false); }); }); diff --git a/packages/tracing-internal/src/browser/browsertracing.ts b/packages/tracing-internal/src/browser/browsertracing.ts index d01c837d26c2..a11ea7ff2cf2 100644 --- a/packages/tracing-internal/src/browser/browsertracing.ts +++ b/packages/tracing-internal/src/browser/browsertracing.ts @@ -364,7 +364,7 @@ export class BrowserTracing implements Integration { traceId: idleTransaction.traceId, spanId: idleTransaction.spanId, parentSpanId: idleTransaction.parentSpanId, - sampled: !!idleTransaction.sampled, + sampled: idleTransaction.sampled, }); } diff --git a/packages/tracing-internal/src/browser/metrics/index.ts b/packages/tracing-internal/src/browser/metrics/index.ts index f8cf44894d74..56fc793d21f7 100644 --- a/packages/tracing-internal/src/browser/metrics/index.ts +++ b/packages/tracing-internal/src/browser/metrics/index.ts @@ -77,6 +77,7 @@ export function startTrackingLongTasks(): void { transaction.startChild({ description: 'Main UI thread blocked', op: 'ui.long-task', + origin: 'auto.ui.browser.metrics', startTimestamp: startTime, endTimestamp: startTime + duration, }); @@ -104,6 +105,7 @@ export function startTrackingInteractions(): void { transaction.startChild({ description: htmlTreeAsString(entry.target), op: `ui.interaction.${entry.name}`, + origin: 'auto.ui.browser.metrics', startTimestamp: startTime, endTimestamp: startTime + duration, }); @@ -274,6 +276,7 @@ export function addPerformanceEntries(transaction: Transaction): void { description: 'first input delay', endTimestamp: fidMark.value + msToSec(_measurements['fid'].value), op: 'ui.action', + origin: 'auto.ui.browser.metrics', startTimestamp: fidMark.value, }); @@ -319,6 +322,7 @@ export function _addMeasureSpans( description: entry.name as string, endTimestamp: measureEndTimestamp, op: entry.entryType as string, + origin: 'auto.resource.browser.metrics', startTimestamp: measureStartTimestamp, }); @@ -354,6 +358,7 @@ function _addPerformanceNavigationTiming( } _startChild(transaction, { op: 'browser', + origin: 'auto.browser.browser.metrics', description: description || event, startTimestamp: timeOrigin + msToSec(start), endTimestamp: timeOrigin + msToSec(end), @@ -365,6 +370,7 @@ function _addPerformanceNavigationTiming( function _addRequest(transaction: Transaction, entry: Record, timeOrigin: number): void { _startChild(transaction, { op: 'browser', + origin: 'auto.browser.browser.metrics', description: 'request', startTimestamp: timeOrigin + msToSec(entry.requestStart as number), endTimestamp: timeOrigin + msToSec(entry.responseEnd as number), @@ -372,6 +378,7 @@ function _addRequest(transaction: Transaction, entry: Record, timeO _startChild(transaction, { op: 'browser', + origin: 'auto.browser.browser.metrics', description: 'response', startTimestamp: timeOrigin + msToSec(entry.responseStart as number), endTimestamp: timeOrigin + msToSec(entry.responseEnd as number), @@ -423,6 +430,7 @@ export function _addResourceSpans( description: resourceName, endTimestamp, op: entry.initiatorType ? `resource.${entry.initiatorType}` : 'resource.other', + origin: 'auto.resource.browser.metrics', startTimestamp, data, }); diff --git a/packages/tracing-internal/src/browser/request.ts b/packages/tracing-internal/src/browser/request.ts index 7c64484ce54b..a6ccfefd1508 100644 --- a/packages/tracing-internal/src/browser/request.ts +++ b/packages/tracing-internal/src/browser/request.ts @@ -335,6 +335,7 @@ export function fetchCallback( }, description: `${method} ${url}`, op: 'http.client', + origin: 'auto.http.browser', }) : undefined; @@ -491,6 +492,7 @@ export function xhrCallback( }, description: `${sentryXhrData.method} ${sentryXhrData.url}`, op: 'http.client', + origin: 'auto.http.browser', }) : undefined; diff --git a/packages/tracing-internal/src/browser/router.ts b/packages/tracing-internal/src/browser/router.ts index ef3c0b7a6257..c796e44d3783 100644 --- a/packages/tracing-internal/src/browser/router.ts +++ b/packages/tracing-internal/src/browser/router.ts @@ -25,6 +25,7 @@ export function instrumentRoutingWithDefaults( // pageload should always start at timeOrigin (and needs to be in s, not ms) startTimestamp: browserPerformanceTimeOrigin ? browserPerformanceTimeOrigin / 1000 : undefined, op: 'pageload', + origin: 'auto.pageload.browser', metadata: { source: 'url' }, }); } @@ -55,6 +56,7 @@ export function instrumentRoutingWithDefaults( activeTransaction = customStartTransaction({ name: WINDOW.location.pathname, op: 'navigation', + origin: 'auto.navigation.browser', metadata: { source: 'url' }, }); } diff --git a/packages/tracing-internal/src/node/integrations/apollo.ts b/packages/tracing-internal/src/node/integrations/apollo.ts index f004d6533c61..945cde0d4a7c 100644 --- a/packages/tracing-internal/src/node/integrations/apollo.ts +++ b/packages/tracing-internal/src/node/integrations/apollo.ts @@ -192,6 +192,7 @@ function wrapResolver( const span = parentSpan?.startChild({ description: `${resolverGroupName}.${resolverName}`, op: 'graphql.resolve', + origin: 'auto.graphql.apollo', }); const rv = orig.call(this, ...args); diff --git a/packages/tracing-internal/src/node/integrations/express.ts b/packages/tracing-internal/src/node/integrations/express.ts index 154ad1546b15..047236381269 100644 --- a/packages/tracing-internal/src/node/integrations/express.ts +++ b/packages/tracing-internal/src/node/integrations/express.ts @@ -156,6 +156,7 @@ function wrap(fn: Function, method: Method): (...args: any[]) => void { const span = transaction.startChild({ description: fn.name, op: `middleware.express.${method}`, + origin: 'auto.middleware.express', }); res.once('finish', () => { span.finish(); @@ -175,6 +176,7 @@ function wrap(fn: Function, method: Method): (...args: any[]) => void { const span = transaction?.startChild({ description: fn.name, op: `middleware.express.${method}`, + origin: 'auto.middleware.express', }); fn.call(this, req, res, function (this: NodeJS.Global, ...args: unknown[]): void { span?.finish(); @@ -194,6 +196,7 @@ function wrap(fn: Function, method: Method): (...args: any[]) => void { const span = transaction?.startChild({ description: fn.name, op: `middleware.express.${method}`, + origin: 'auto.middleware.express', }); fn.call(this, err, req, res, function (this: NodeJS.Global, ...args: unknown[]): void { span?.finish(); diff --git a/packages/tracing-internal/src/node/integrations/graphql.ts b/packages/tracing-internal/src/node/integrations/graphql.ts index fc9852afd58f..fc9bf5aece57 100644 --- a/packages/tracing-internal/src/node/integrations/graphql.ts +++ b/packages/tracing-internal/src/node/integrations/graphql.ts @@ -56,6 +56,7 @@ export class GraphQL implements LazyLoadedIntegration { const span = parentSpan?.startChild({ description: 'execute', op: 'graphql.execute', + origin: 'auto.graphql.graphql', }); scope?.setSpan(span); diff --git a/packages/tracing-internal/src/node/integrations/mongo.ts b/packages/tracing-internal/src/node/integrations/mongo.ts index c462ea4dcdf2..df460520b87b 100644 --- a/packages/tracing-internal/src/node/integrations/mongo.ts +++ b/packages/tracing-internal/src/node/integrations/mongo.ts @@ -237,6 +237,7 @@ export class Mongo implements LazyLoadedIntegration { const spanContext: SpanContext = { op: 'db', // TODO v8: Use `${collection.collectionName}.${operation}` + origin: 'auto.db.mongo', description: operation, data, }; diff --git a/packages/tracing-internal/src/node/integrations/mysql.ts b/packages/tracing-internal/src/node/integrations/mysql.ts index 77ce6a66cd32..9c11165c643e 100644 --- a/packages/tracing-internal/src/node/integrations/mysql.ts +++ b/packages/tracing-internal/src/node/integrations/mysql.ts @@ -94,6 +94,7 @@ export class Mysql implements LazyLoadedIntegration { const span = parentSpan?.startChild({ description: typeof options === 'string' ? options : (options as { sql: string }).sql, op: 'db', + origin: 'auto.db.mysql', data: { ...spanDataFromConfig(), 'db.system': 'mysql', diff --git a/packages/tracing-internal/src/node/integrations/postgres.ts b/packages/tracing-internal/src/node/integrations/postgres.ts index f226c9264938..6a92e76b059c 100644 --- a/packages/tracing-internal/src/node/integrations/postgres.ts +++ b/packages/tracing-internal/src/node/integrations/postgres.ts @@ -109,6 +109,7 @@ export class Postgres implements LazyLoadedIntegration { const span = parentSpan?.startChild({ description: typeof config === 'string' ? config : (config as { text: string }).text, op: 'db', + origin: 'auto.db.postgres', data, }); diff --git a/packages/tracing-internal/src/node/integrations/prisma.ts b/packages/tracing-internal/src/node/integrations/prisma.ts index b8cb6ebff7b3..bf98be00e163 100644 --- a/packages/tracing-internal/src/node/integrations/prisma.ts +++ b/packages/tracing-internal/src/node/integrations/prisma.ts @@ -102,6 +102,7 @@ export class Prisma implements Integration { { name: model ? `${model} ${action}` : action, op: 'db.sql.prisma', + origin: 'auto.db.prisma', data: { ...clientData, 'db.operation': action }, }, () => next(params), diff --git a/packages/tracing-internal/test/browser/metrics/index.test.ts b/packages/tracing-internal/test/browser/metrics/index.test.ts index c196fbd2467c..41b4cd9c1940 100644 --- a/packages/tracing-internal/test/browser/metrics/index.test.ts +++ b/packages/tracing-internal/test/browser/metrics/index.test.ts @@ -32,6 +32,7 @@ describe('_addMeasureSpans', () => { startTimestamp: timeOrigin + startTime, endTimestamp: timeOrigin + startTime + duration, op: 'measure', + origin: 'auto.resource.browser.metrics', }); }); }); @@ -99,6 +100,7 @@ describe('_addResourceSpans', () => { description: '/assets/to/css', endTimestamp: timeOrigin + startTime + duration, op: 'resource.css', + origin: 'auto.resource.browser.metrics', startTimestamp: timeOrigin + startTime, }); }); diff --git a/packages/tracing-internal/test/browser/router.test.ts b/packages/tracing-internal/test/browser/router.test.ts index 8ad414a97b80..aa123154caa3 100644 --- a/packages/tracing-internal/test/browser/router.test.ts +++ b/packages/tracing-internal/test/browser/router.test.ts @@ -46,6 +46,7 @@ conditionalTest({ min: 16 })('instrumentRoutingWithDefaults', () => { expect(customStartTransaction).toHaveBeenLastCalledWith({ name: 'blank', op: 'pageload', + origin: 'auto.pageload.browser', metadata: { source: 'url' }, startTimestamp: expect.any(Number), }); @@ -67,6 +68,7 @@ conditionalTest({ min: 16 })('instrumentRoutingWithDefaults', () => { expect(customStartTransaction).not.toHaveBeenLastCalledWith({ name: 'blank', op: 'navigation', + origin: 'auto.navigation.browser', metadata: { source: 'url' }, }); }); @@ -80,6 +82,7 @@ conditionalTest({ min: 16 })('instrumentRoutingWithDefaults', () => { expect(customStartTransaction).toHaveBeenLastCalledWith({ name: 'blank', op: 'navigation', + origin: 'auto.navigation.browser', metadata: { source: 'url' }, }); }); diff --git a/packages/tracing/test/integrations/apollo-nestjs.test.ts b/packages/tracing/test/integrations/apollo-nestjs.test.ts index cf5733ad979d..703edcaad23d 100644 --- a/packages/tracing/test/integrations/apollo-nestjs.test.ts +++ b/packages/tracing/test/integrations/apollo-nestjs.test.ts @@ -93,6 +93,7 @@ describe('setupOnce', () => { expect(parentSpan.startChild).toBeCalledWith({ description: 'Query.res_1', op: 'graphql.resolve', + origin: 'auto.graphql.apollo', }); expect(childSpan.finish).toBeCalled(); }); @@ -103,6 +104,7 @@ describe('setupOnce', () => { expect(parentSpan.startChild).toBeCalledWith({ description: 'Mutation.res_2', op: 'graphql.resolve', + origin: 'auto.graphql.apollo', }); expect(childSpan.finish).toBeCalled(); }); diff --git a/packages/tracing/test/integrations/apollo.test.ts b/packages/tracing/test/integrations/apollo.test.ts index 9b50fbaa1adc..90da1f8e0b21 100644 --- a/packages/tracing/test/integrations/apollo.test.ts +++ b/packages/tracing/test/integrations/apollo.test.ts @@ -93,6 +93,7 @@ describe('setupOnce', () => { expect(parentSpan.startChild).toBeCalledWith({ description: 'Query.res_1', op: 'graphql.resolve', + origin: 'auto.graphql.apollo', }); expect(childSpan.finish).toBeCalled(); }); @@ -103,6 +104,7 @@ describe('setupOnce', () => { expect(parentSpan.startChild).toBeCalledWith({ description: 'Mutation.res_2', op: 'graphql.resolve', + origin: 'auto.graphql.apollo', }); expect(childSpan.finish).toBeCalled(); }); diff --git a/packages/tracing/test/integrations/graphql.test.ts b/packages/tracing/test/integrations/graphql.test.ts index fa5a026d653a..c61f287c3e86 100644 --- a/packages/tracing/test/integrations/graphql.test.ts +++ b/packages/tracing/test/integrations/graphql.test.ts @@ -55,6 +55,7 @@ describe('setupOnce', () => { expect(parentSpan.startChild).toBeCalledWith({ description: 'execute', op: 'graphql.execute', + origin: 'auto.graphql.graphql', }); expect(childSpan.finish).toBeCalled(); expect(scope.setSpan).toHaveBeenCalledTimes(2); diff --git a/packages/tracing/test/integrations/node/mongo.test.ts b/packages/tracing/test/integrations/node/mongo.test.ts index 4e866f608cb8..30e5c76563bd 100644 --- a/packages/tracing/test/integrations/node/mongo.test.ts +++ b/packages/tracing/test/integrations/node/mongo.test.ts @@ -81,6 +81,7 @@ describe('patchOperation()', () => { 'db.system': 'mongodb', }, op: 'db', + origin: 'auto.db.mongo', description: 'insertOne', }); expect(childSpan.finish).toBeCalled(); @@ -100,6 +101,7 @@ describe('patchOperation()', () => { 'db.system': 'mongodb', }, op: 'db', + origin: 'auto.db.mongo', description: 'insertOne', }); expect(childSpan.finish).toBeCalled(); @@ -116,6 +118,7 @@ describe('patchOperation()', () => { 'db.system': 'mongodb', }, op: 'db', + origin: 'auto.db.mongo', description: 'initializeOrderedBulkOp', }); expect(childSpan.finish).toBeCalled(); diff --git a/packages/tracing/test/integrations/node/postgres.test.ts b/packages/tracing/test/integrations/node/postgres.test.ts index d1b1f03b7914..bb735ca40d8b 100644 --- a/packages/tracing/test/integrations/node/postgres.test.ts +++ b/packages/tracing/test/integrations/node/postgres.test.ts @@ -72,6 +72,7 @@ describe('setupOnce', () => { expect(parentSpan.startChild).toBeCalledWith({ description: 'SELECT NOW()', op: 'db', + origin: 'auto.db.postgres', data: { 'db.system': 'postgresql', }, @@ -87,6 +88,7 @@ describe('setupOnce', () => { expect(parentSpan.startChild).toBeCalledWith({ description: 'SELECT NOW()', op: 'db', + origin: 'auto.db.postgres', data: { 'db.system': 'postgresql', }, @@ -102,6 +104,7 @@ describe('setupOnce', () => { expect(parentSpan.startChild).toBeCalledWith({ description: 'SELECT NOW()', op: 'db', + origin: 'auto.db.postgres', data: { 'db.system': 'postgresql', }, diff --git a/packages/tracing/test/integrations/node/prisma.test.ts b/packages/tracing/test/integrations/node/prisma.test.ts index 43d7fe6b5570..4b2034f37a01 100644 --- a/packages/tracing/test/integrations/node/prisma.test.ts +++ b/packages/tracing/test/integrations/node/prisma.test.ts @@ -56,6 +56,7 @@ describe('setupOnce', function () { { name: 'user create', op: 'db.sql.prisma', + origin: 'auto.db.prisma', data: { 'db.system': 'postgresql', 'db.prisma.version': '3.1.2', 'db.operation': 'create' }, }, expect.any(Function), diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index e6e35e105508..ca16a1395e3e 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -164,6 +164,7 @@ describe('Span', () => { parent_span_id: 'b', span_id: 'd', trace_id: 'c', + origin: 'manual', }); }); }); diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 3b9c5835f8b8..65da5947ce69 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -176,6 +176,11 @@ export interface Client { */ on?(hook: 'beforeEnvelope', callback: (envelope: Envelope) => void): void; + /** + * Register a callback for before an event is sent. + */ + on?(hook: 'beforeSendEvent', callback: (event: Event, hint?: EventHint | void) => void): void; + /** * Register a callback for when an event has been sent. */ @@ -212,6 +217,12 @@ export interface Client { */ emit?(hook: 'beforeEnvelope', envelope: Envelope): void; + /* + * Fire a hook event before sending an event. Expects to be given an Event & EventHint as the + * second/third argument. + */ + emit?(hook: 'beforeSendEvent', event: Event, hint?: EventHint): void; + /* * Fire a hook event after sending an event. Expects to be given an Event as the * second argument. diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 5b9490045a02..8a93681aa938 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -88,7 +88,7 @@ export type { // eslint-disable-next-line deprecation/deprecation export type { Severity, SeverityLevel } from './severity'; -export type { Span, SpanContext } from './span'; +export type { Span, SpanContext, SpanOrigin } from './span'; export type { StackFrame } from './stackframe'; export type { Stacktrace, StackParser, StackLineParser, StackLineParserFn } from './stacktrace'; export type { TextEncoderInternal } from './textencoder'; diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index a689590c499e..7485bbf88d72 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -2,6 +2,16 @@ import type { Instrumenter } from './instrumenter'; import type { Primitive } from './misc'; import type { Transaction } from './transaction'; +type SpanOriginType = 'manual' | 'auto'; +type SpanOriginCategory = string; // e.g. http, db, ui, .... +type SpanOriginIntegrationName = string; +type SpanOriginIntegrationPart = string; +export type SpanOrigin = + | SpanOriginType + | `${SpanOriginType}.${SpanOriginCategory}` + | `${SpanOriginType}.${SpanOriginCategory}.${SpanOriginIntegrationName}` + | `${SpanOriginType}.${SpanOriginCategory}.${SpanOriginIntegrationName}.${SpanOriginIntegrationPart}`; + /** Interface holding all properties that can be set on a Span on creation. */ export interface SpanContext { /** @@ -69,6 +79,11 @@ export interface SpanContext { * The instrumenter that created this span. */ instrumenter?: Instrumenter; + + /** + * The origin of the span, giving context about what created the span. + */ + origin?: SpanOrigin; } /** Span holding trace_id, span_id */ diff --git a/packages/types/src/tracing.ts b/packages/types/src/tracing.ts index 11c4a1658d50..7c5b02c45596 100644 --- a/packages/types/src/tracing.ts +++ b/packages/types/src/tracing.ts @@ -5,7 +5,7 @@ export type TracePropagationTargets = (string | RegExp)[]; export interface PropagationContext { traceId: string; spanId: string; - sampled: boolean; + sampled?: boolean; parentSpanId?: string; dsc?: DynamicSamplingContext; } diff --git a/packages/utils/src/instrument.ts b/packages/utils/src/instrument.ts index be0f8f49cedc..94812f47b252 100644 --- a/packages/utils/src/instrument.ts +++ b/packages/utils/src/instrument.ts @@ -10,11 +10,12 @@ import type { } from '@sentry/types'; import { isString } from './is'; +import type { ConsoleLevel } from './logger'; import { CONSOLE_LEVELS, logger } from './logger'; import { fill } from './object'; import { getFunctionName } from './stacktrace'; import { supportsHistory, supportsNativeFetch } from './supports'; -import { getGlobalObject } from './worldwide'; +import { getGlobalObject, GLOBAL_OBJ } from './worldwide'; // eslint-disable-next-line deprecation/deprecation const WINDOW = getGlobalObject(); @@ -112,25 +113,30 @@ function triggerHandlers(type: InstrumentHandlerType, data: any): void { } } +/** Only exported for testing & debugging. */ +export const originalConsoleMethods: { + [key in ConsoleLevel]?: (...args: any[]) => void; +} = {}; + /** JSDoc */ function instrumentConsole(): void { - if (!('console' in WINDOW)) { + if (!('console' in GLOBAL_OBJ)) { return; } - CONSOLE_LEVELS.forEach(function (level: string): void { - if (!(level in WINDOW.console)) { + CONSOLE_LEVELS.forEach(function (level: ConsoleLevel): void { + if (!(level in GLOBAL_OBJ.console)) { return; } - fill(WINDOW.console, level, function (originalConsoleMethod: () => any): Function { + fill(GLOBAL_OBJ.console, level, function (originalConsoleMethod: () => any): Function { + originalConsoleMethods[level] = originalConsoleMethod; + return function (...args: any[]): void { triggerHandlers('console', { args, level }); - // this fails for some browsers. :( - if (originalConsoleMethod) { - originalConsoleMethod.apply(WINDOW.console, args); - } + const log = originalConsoleMethods[level]; + log && log.apply(GLOBAL_OBJ.console, args); }; }); }); @@ -142,7 +148,7 @@ function instrumentFetch(): void { return; } - fill(WINDOW, 'fetch', function (originalFetch: () => void): () => void { + fill(GLOBAL_OBJ, 'fetch', function (originalFetch: () => void): () => void { return function (...args: any[]): void { const { method, url } = parseFetchArgs(args); @@ -160,7 +166,7 @@ function instrumentFetch(): void { }); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return originalFetch.apply(WINDOW, args).then( + return originalFetch.apply(GLOBAL_OBJ, args).then( (response: Response) => { triggerHandlers('fetch', { ...handlerData, diff --git a/packages/utils/src/tracing.ts b/packages/utils/src/tracing.ts index f879b856f9f2..6ad839f2b920 100644 --- a/packages/utils/src/tracing.ts +++ b/packages/utils/src/tracing.ts @@ -61,7 +61,7 @@ export function tracingContextFromHeaders( const propagationContext: PropagationContext = { traceId: traceId || uuid4(), spanId: uuid4().substring(16), - sampled: parentSampled === undefined ? false : parentSampled, + sampled: parentSampled, }; if (parentSpanId) { diff --git a/packages/vue/src/router.ts b/packages/vue/src/router.ts index 25408f24f6b6..641a4344fd4c 100644 --- a/packages/vue/src/router.ts +++ b/packages/vue/src/router.ts @@ -70,6 +70,7 @@ export function vueRouterInstrumentation( startTransaction({ name: WINDOW.location.pathname, op: 'pageload', + origin: 'auto.pageload.vue', tags, metadata: { source: 'url', @@ -121,6 +122,7 @@ export function vueRouterInstrumentation( startTransaction({ name: transactionName, op: 'navigation', + origin: 'auto.navigation.vue', tags, data, metadata: { diff --git a/packages/vue/src/tracing.ts b/packages/vue/src/tracing.ts index 1be68b26b61e..100ff4aeb126 100644 --- a/packages/vue/src/tracing.ts +++ b/packages/vue/src/tracing.ts @@ -76,6 +76,7 @@ export const createTracingMixins = (options: TracingOptions): Mixins => { activeTransaction.startChild({ description: 'Application Render', op: `${VUE_OP}.render`, + origin: 'auto.ui.vue', }); } } @@ -109,6 +110,7 @@ export const createTracingMixins = (options: TracingOptions): Mixins => { this.$_sentrySpans[operation] = activeTransaction.startChild({ description: `Vue <${name}>`, op: `${VUE_OP}.${operation}`, + origin: 'auto.ui.vue', }); } } else { diff --git a/packages/vue/test/router.test.ts b/packages/vue/test/router.test.ts index 044228181f50..a1d6dd4a6713 100644 --- a/packages/vue/test/router.test.ts +++ b/packages/vue/test/router.test.ts @@ -108,6 +108,7 @@ describe('vueRouterInstrumentation()', () => { query: to.query, }, op: 'navigation', + origin: 'auto.navigation.vue', tags: { 'routing.instrumentation': 'vue-router', }, @@ -148,6 +149,7 @@ describe('vueRouterInstrumentation()', () => { source: 'url', }, op: 'pageload', + origin: 'auto.pageload.vue', tags: { 'routing.instrumentation': 'vue-router', }, @@ -194,6 +196,7 @@ describe('vueRouterInstrumentation()', () => { query: to.query, }, op: 'navigation', + origin: 'auto.navigation.vue', tags: { 'routing.instrumentation': 'vue-router', }, @@ -225,6 +228,7 @@ describe('vueRouterInstrumentation()', () => { query: to.query, }, op: 'navigation', + origin: 'auto.navigation.vue', tags: { 'routing.instrumentation': 'vue-router', }, @@ -259,6 +263,7 @@ describe('vueRouterInstrumentation()', () => { source: 'url', }, op: 'pageload', + origin: 'auto.pageload.vue', tags: { 'routing.instrumentation': 'vue-router', }, @@ -345,6 +350,7 @@ describe('vueRouterInstrumentation()', () => { query: to.query, }, op: 'navigation', + origin: 'auto.navigation.vue', tags: { 'routing.instrumentation': 'vue-router', },