From 399393582fabd0b164851cf9bd08cf14e332187d Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Tue, 5 Jul 2022 14:42:49 -0400 Subject: [PATCH] feat(tracing): Add transaction source field This patch adds `source` information to `Transaction`, which is typed by `TransactionSource` in `@sentry/tracing`. This helps track how the name of a transaction was determined, which will be used by the server for server-side controls. For now, we are placing the `source` field under transaction metadata. In the future, we can move this up into a top level API (an argument to `startTransaction` or `transaction.setSource`) if needed, but this should be fine to get us started. For next steps, after this patch gets merged, we will start going through various routing instrumentation frameworks and adding transaction source. --- packages/tracing/src/transaction.ts | 12 ++++++--- packages/tracing/test/span.test.ts | 38 +++++++++++++++++++++++++++++ packages/types/src/event.ts | 4 +++ packages/types/src/index.ts | 1 + packages/types/src/transaction.ts | 29 ++++++++++++++++++++++ 5 files changed, 81 insertions(+), 3 deletions(-) diff --git a/packages/tracing/src/transaction.ts b/packages/tracing/src/transaction.ts index 61e8195ee2d8..3c1df5358066 100644 --- a/packages/tracing/src/transaction.ts +++ b/packages/tracing/src/transaction.ts @@ -75,8 +75,7 @@ export class Transaction extends SpanClass implements TransactionInterface { } /** - * Set metadata for this transaction. - * @hidden + * @inheritDoc */ public setMetadata(newMetadata: TransactionMetadata): void { this.metadata = { ...this.metadata, ...newMetadata }; @@ -122,6 +121,8 @@ export class Transaction extends SpanClass implements TransactionInterface { }).endTimestamp; } + const metadata = this.metadata; + const transaction: Event = { contexts: { trace: this.getTraceContext(), @@ -133,9 +134,14 @@ export class Transaction extends SpanClass implements TransactionInterface { transaction: this.name, type: 'transaction', sdkProcessingMetadata: { - ...this.metadata, + ...metadata, baggage: this.getBaggage(), }, + ...(metadata.source && { + transaction_info: { + source: metadata.source, + }, + }), }; const hasMeasurements = Object.keys(this._measurements).length > 0; diff --git a/packages/tracing/test/span.test.ts b/packages/tracing/test/span.test.ts index 8315eb242ed6..c9d8be6b17f0 100644 --- a/packages/tracing/test/span.test.ts +++ b/packages/tracing/test/span.test.ts @@ -475,4 +475,42 @@ describe('Span', () => { expect(baggage && getThirdPartyBaggage(baggage)).toStrictEqual(''); }); }); + + describe('Transaction source', () => { + test('is not included by default', () => { + const spy = jest.spyOn(hub as any, 'captureEvent') as any; + const transaction = hub.startTransaction({ name: 'test', sampled: true }); + expect(spy).toHaveBeenCalledTimes(0); + + transaction.finish(); + + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenLastCalledWith( + expect.not.objectContaining({ + transaction_info: { + source: expect.any(String), + }, + }), + ); + }); + + test('is included when transaction metadata is set', () => { + const spy = jest.spyOn(hub as any, 'captureEvent') as any; + const transaction = hub.startTransaction({ name: 'test', sampled: true }); + transaction.setMetadata({ + source: 'url', + }); + expect(spy).toHaveBeenCalledTimes(0); + + transaction.finish(); + expect(spy).toHaveBeenCalledTimes(1); + expect(spy).toHaveBeenLastCalledWith( + expect.objectContaining({ + transaction_info: { + source: 'url', + }, + }), + ); + }); + }); }); diff --git a/packages/types/src/event.ts b/packages/types/src/event.ts index 14056d9d7b07..0d3897734956 100644 --- a/packages/types/src/event.ts +++ b/packages/types/src/event.ts @@ -11,6 +11,7 @@ import { CaptureContext } from './scope'; import { SdkInfo } from './sdkinfo'; import { Severity, SeverityLevel } from './severity'; import { Span } from './span'; +import { TransactionSource } from './transaction'; import { User } from './user'; /** JSDoc */ @@ -46,6 +47,9 @@ export interface Event { debug_meta?: DebugMeta; // A place to stash data which is needed at some point in the SDK's event processing pipeline but which shouldn't get sent to Sentry sdkProcessingMetadata?: { [key: string]: any }; + transaction_info?: { + source: TransactionSource; + }; } /** JSDoc */ diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 0bf2caaebe6b..32b2d077a74c 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -71,6 +71,7 @@ export type { TransactionContext, TransactionMetadata, TransactionSamplingMethod, + TransactionSource, } from './transaction'; export type { DurationUnit, diff --git a/packages/types/src/transaction.ts b/packages/types/src/transaction.ts index a596f720f713..beb01ce3e7d6 100644 --- a/packages/types/src/transaction.ts +++ b/packages/types/src/transaction.ts @@ -89,6 +89,12 @@ export interface Transaction extends TransactionContext, Span { /** Updates the current transaction with a new `TransactionContext` */ updateWithContext(transactionContext: TransactionContext): this; + /** + * Set metadata for this transaction. + * @hidden + */ + setMetadata(newMetadata: TransactionMetadata): void; + /** return the baggage for dynamic sampling and trace propagation */ getBaggage(): Baggage; } @@ -138,4 +144,27 @@ export interface TransactionMetadata { /** For transactions tracing server-side request handling, the path of the request being tracked. */ requestPath?: string; + + /** Information on how a transaction name was generated. */ + source?: TransactionSource; } + +/** + * Contains information about how the name of the transaction was determined. This will be used by the server to decide + * whether or not to scrub identifiers from the transaction name, or replace the entire name with a placeholder. + */ +export type TransactionSource = + /** User-defined name */ + | 'custom' + /** Raw URL, potentially containing identifiers */ + | 'url' + /** Parametrized URL / route */ + | 'route' + /** Name of the view handling the request */ + | 'view' + /** This is the default value set by Relay for legacy SDKs. */ + | 'unknown' + /** Named after a software component, such as a function or class name. */ + | 'component' + /** Name of a background task (e.g. a Celery task) */ + | 'task';