Skip to content

Commit ae86c48

Browse files
committed
feat(core): Add spanGetMetadata() & spanSetMetadata() APIs
To replace `transaction.setMetadata()`.
1 parent 3acb7e4 commit ae86c48

File tree

38 files changed

+253
-114
lines changed

38 files changed

+253
-114
lines changed

MIGRATION.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ In v8, the Span class is heavily reworked. The following properties & methods ar
1717
* `span.setName(newName)`: Use `span.updateName(newName)` instead.
1818
* `span.toTraceparent()`: use `spanToTraceHeader(span)` util instead.
1919
* `span.getTraceContext()`: Use `spanToTraceContext(span)` utility function instead.
20+
* `transaction.setMetadata(metadata)`: Use `spanSetMetadata(span, metadata)` utility function instead.
21+
* `transaction.metadata`: Use `spanGetMetadata(span)` utility function instead.
2022

2123
## Deprecate `pushScope` & `popScope` in favor of `withScope`
2224

dev-packages/node-integration-tests/suites/express/sentry-trace/baggage-transaction-name/server.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import http from 'http';
2+
import { spanSetMetadata } from '@sentry/core';
23
import * as Sentry from '@sentry/node';
34
import * as Tracing from '@sentry/tracing';
45
import cors from 'cors';
@@ -32,7 +33,7 @@ app.get('/test/express', (_req, res) => {
3233
const transaction = Sentry.getCurrentHub().getScope().getTransaction();
3334
if (transaction) {
3435
transaction.traceId = '86f39e84263a4de99c326acab3bfe3bd';
35-
transaction.setMetadata({ source: 'route' });
36+
spanSetMetadata(transaction, { source: 'route' });
3637
}
3738
const headers = http.get('http://somewhere.not.sentry/').getHeaders();
3839

packages/angular-ivy/ng-package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
"entryFile": "src/index.ts",
66
"umdModuleIds": {
77
"@sentry/browser": "Sentry",
8-
"@sentry/utils": "Sentry.util"
8+
"@sentry/utils": "Sentry.util",
9+
"@sentry/core": "Sentry.core"
910
}
1011
},
11-
"allowedNonPeerDependencies": ["@sentry/browser", "@sentry/utils", "@sentry/types", "tslib"],
12+
"allowedNonPeerDependencies": ["@sentry/browser", "@sentry/core", "@sentry/utils", "@sentry/types", "tslib"],
1213
"assets": ["README.md", "LICENSE"]
1314
}

packages/angular-ivy/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
},
2323
"dependencies": {
2424
"@sentry/browser": "7.91.0",
25+
"@sentry/core": "7.91.0",
2526
"@sentry/types": "7.91.0",
2627
"@sentry/utils": "7.91.0",
2728
"tslib": "^2.4.1"

packages/angular/ng-package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
"entryFile": "src/index.ts",
66
"umdModuleIds": {
77
"@sentry/browser": "Sentry",
8-
"@sentry/utils": "Sentry.util"
8+
"@sentry/utils": "Sentry.util",
9+
"@sentry/core": "Sentry.core"
910
}
1011
},
11-
"whitelistedNonPeerDependencies": ["@sentry/browser", "@sentry/utils", "@sentry/types", "tslib"],
12+
"whitelistedNonPeerDependencies": ["@sentry/browser", "@sentry/core", "@sentry/utils", "@sentry/types", "tslib"],
1213
"assets": ["README.md", "LICENSE"]
1314
}

packages/angular/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
},
2323
"dependencies": {
2424
"@sentry/browser": "7.91.0",
25+
"@sentry/core": "7.91.0",
2526
"@sentry/types": "7.91.0",
2627
"@sentry/utils": "7.91.0",
2728
"tslib": "^2.4.1"

packages/angular/src/tracing.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import type { ActivatedRouteSnapshot, Event, RouterState } from '@angular/router
88
import { NavigationCancel, NavigationError, Router } from '@angular/router';
99
import { NavigationEnd, NavigationStart, ResolveEnd } from '@angular/router';
1010
import { WINDOW, getCurrentScope } from '@sentry/browser';
11+
import { spanSetMetadata } from '@sentry/core';
1112
import type { Span, Transaction, TransactionContext } from '@sentry/types';
1213
import { logger, stripUrlQueryAndFragment, timestampInSeconds } from '@sentry/utils';
1314
import type { Observable } from 'rxjs';
@@ -119,7 +120,7 @@ export class TraceService implements OnDestroy {
119120
// TODO (v8 / #5416): revisit the source condition. Do we want to make the parameterized route the default?
120121
if (transaction && transaction.metadata.source === 'url') {
121122
transaction.updateName(route);
122-
transaction.setMetadata({ source: 'route' });
123+
spanSetMetadata(transaction, { source: 'route' });
123124
}
124125
}),
125126
);

packages/angular/test/tracing.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Component } from '@angular/core';
22
import type { ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot } from '@angular/router';
33

4+
import * as SentryCore from '@sentry/core';
45
import { TraceClassDecorator, TraceDirective, TraceMethodDecorator, instrumentAngularRouting } from '../src';
56
import { getParameterizedRouteFromSnapshot } from '../src/tracing';
67
import { AppComponent, TestEnv } from './utils/index';
@@ -11,7 +12,6 @@ const defaultStartTransaction = (ctx: any) => {
1112
transaction = {
1213
...ctx,
1314
updateName: jest.fn(name => (transaction.name = name)),
14-
setMetadata: jest.fn(),
1515
};
1616

1717
return transaction;
@@ -31,9 +31,12 @@ jest.mock('@sentry/browser', () => {
3131
};
3232
});
3333

34+
const mockSpanSetMetadata = jest.spyOn(SentryCore, 'spanSetMetadata');
35+
3436
describe('Angular Tracing', () => {
3537
beforeEach(() => {
3638
transaction = undefined;
39+
mockSpanSetMetadata.mockClear();
3740
});
3841

3942
describe('instrumentAngularRouting', () => {
@@ -329,7 +332,7 @@ describe('Angular Tracing', () => {
329332
metadata: { source: 'url' },
330333
});
331334
expect(transaction.updateName).toHaveBeenCalledWith(result);
332-
expect(transaction.setMetadata).toHaveBeenCalledWith({ source: 'route' });
335+
expect(mockSpanSetMetadata).toHaveBeenCalledWith(expect.anything(), { source: 'route' });
333336

334337
env.destroy();
335338
});

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export { prepareEvent } from './utils/prepareEvent';
6969
export { createCheckInEnvelope } from './checkin';
7070
export { hasTracingEnabled } from './utils/hasTracingEnabled';
7171
export { isSentryRequestUrl } from './utils/isSentryRequestUrl';
72-
export { spanToTraceHeader } from './utils/spanUtils';
72+
export { spanToTraceHeader, spanSetMetadata, spanGetMetadata } from './utils/spanUtils';
7373
export { DEFAULT_ENVIRONMENT } from './constants';
7474
export { ModuleMetadata } from './integrations/metadata';
7575
export { RequestData } from './integrations/requestdata';

packages/core/src/tracing/sampling.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { isNaN, logger } from '@sentry/utils';
33

44
import { DEBUG_BUILD } from '../debug-build';
55
import { hasTracingEnabled } from '../utils/hasTracingEnabled';
6+
import { spanSetMetadata } from '../utils/spanUtils';
67
import type { Transaction } from './transaction';
78

89
/**
@@ -27,7 +28,7 @@ export function sampleTransaction<T extends Transaction>(
2728

2829
// if the user has forced a sampling decision by passing a `sampled` value in their transaction context, go with that
2930
if (transaction.sampled !== undefined) {
30-
transaction.setMetadata({
31+
spanSetMetadata(transaction, {
3132
sampleRate: Number(transaction.sampled),
3233
});
3334
return transaction;
@@ -38,20 +39,20 @@ export function sampleTransaction<T extends Transaction>(
3839
let sampleRate;
3940
if (typeof options.tracesSampler === 'function') {
4041
sampleRate = options.tracesSampler(samplingContext);
41-
transaction.setMetadata({
42+
spanSetMetadata(transaction, {
4243
sampleRate: Number(sampleRate),
4344
});
4445
} else if (samplingContext.parentSampled !== undefined) {
4546
sampleRate = samplingContext.parentSampled;
4647
} else if (typeof options.tracesSampleRate !== 'undefined') {
4748
sampleRate = options.tracesSampleRate;
48-
transaction.setMetadata({
49-
sampleRate: Number(sampleRate),
49+
spanSetMetadata(transaction, {
50+
sampleRate,
5051
});
5152
} else {
5253
// When `enableTracing === true`, we use a sample rate of 100%
5354
sampleRate = 1;
54-
transaction.setMetadata({
55+
spanSetMetadata(transaction, {
5556
sampleRate,
5657
});
5758
}

packages/core/src/tracing/transaction.ts

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,13 @@ import { dropUndefinedKeys, logger, timestampInSeconds } from '@sentry/utils';
1414
import { DEBUG_BUILD } from '../debug-build';
1515
import type { Hub } from '../hub';
1616
import { getCurrentHub } from '../hub';
17-
import { spanToTraceContext } from '../utils/spanUtils';
17+
import { spanGetMetadata, spanSetMetadata, spanToTraceContext } from '../utils/spanUtils';
1818
import { getDynamicSamplingContextFromClient } from './dynamicSamplingContext';
1919
import { Span as SpanClass, SpanRecorder } from './span';
2020
import { ensureTimestampInSeconds } from './utils';
2121

2222
/** JSDoc */
2323
export class Transaction extends SpanClass implements TransactionInterface {
24-
public metadata: TransactionMetadata;
25-
2624
/**
2725
* The reference to the current hub.
2826
*/
@@ -58,11 +56,11 @@ export class Transaction extends SpanClass implements TransactionInterface {
5856

5957
this._name = transactionContext.name || '';
6058

61-
this.metadata = {
59+
spanSetMetadata(this, {
6260
source: 'custom',
6361
...transactionContext.metadata,
6462
spanMetadata: {},
65-
};
63+
});
6664

6765
this._trimEnd = transactionContext.trimEnd;
6866

@@ -71,13 +69,18 @@ export class Transaction extends SpanClass implements TransactionInterface {
7169

7270
// If Dynamic Sampling Context is provided during the creation of the transaction, we freeze it as it usually means
7371
// there is incoming Dynamic Sampling Context. (Either through an incoming request, a baggage meta-tag, or other means)
74-
const incomingDynamicSamplingContext = this.metadata.dynamicSamplingContext;
72+
const incomingDynamicSamplingContext = (
73+
spanGetMetadata(this) as { dynamicSamplingContext: DynamicSamplingContext | undefined }
74+
).dynamicSamplingContext;
7575
if (incomingDynamicSamplingContext) {
7676
// We shallow copy this in case anything writes to the original reference of the passed in `dynamicSamplingContext`
7777
this._frozenDynamicSamplingContext = { ...incomingDynamicSamplingContext };
7878
}
7979
}
8080

81+
// This sadly conflicts with the getter/setter ordering :(
82+
/* eslint-disable @typescript-eslint/member-ordering */
83+
8184
/** Getter for `name` property */
8285
public get name(): string {
8386
return this._name;
@@ -91,14 +94,32 @@ export class Transaction extends SpanClass implements TransactionInterface {
9194
this.setName(newName);
9295
}
9396

97+
/**
98+
* Get the metadata for this transaction.
99+
* @deprecated Use `spanGetMetadata(transaction)` instead.
100+
*/
101+
public get metadata(): TransactionMetadata {
102+
return spanGetMetadata(this) as TransactionMetadata;
103+
}
104+
105+
/**
106+
* Update the metadata for this transaction.
107+
* @deprecated Use `spanGetMetadata(transaction)` instead.
108+
*/
109+
public set metadata(metadata: TransactionMetadata) {
110+
spanSetMetadata(this, metadata);
111+
}
112+
113+
/* eslint-enable @typescript-eslint/member-ordering */
114+
94115
/**
95116
* Setter for `name` property, which also sets `source` on the metadata.
96117
*
97118
* @deprecated Use `updateName()` and `setMetadata()` instead.
98119
*/
99120
public setName(name: string, source: TransactionMetadata['source'] = 'custom'): void {
100121
this._name = name;
101-
this.metadata.source = source;
122+
spanSetMetadata(this, { source });
102123
}
103124

104125
/** @inheritdoc */
@@ -138,10 +159,11 @@ export class Transaction extends SpanClass implements TransactionInterface {
138159
}
139160

140161
/**
141-
* @inheritDoc
162+
* Set metadata for this transaction.
163+
* @deprecated Use `spanSetMetadata(span, metadata)` instead.
142164
*/
143165
public setMetadata(newMetadata: Partial<TransactionMetadata>): void {
144-
this.metadata = { ...this.metadata, ...newMetadata };
166+
spanSetMetadata(this, newMetadata);
145167
}
146168

147169
/**
@@ -203,13 +225,15 @@ export class Transaction extends SpanClass implements TransactionInterface {
203225
const scope = hub.getScope();
204226
const dsc = getDynamicSamplingContextFromClient(this.traceId, client, scope);
205227

206-
const maybeSampleRate = this.metadata.sampleRate;
228+
const metadata = spanGetMetadata(this) as TransactionMetadata;
229+
230+
const maybeSampleRate = metadata.sampleRate;
207231
if (maybeSampleRate !== undefined) {
208232
dsc.sample_rate = `${maybeSampleRate}`;
209233
}
210234

211235
// We don't want to have a transaction name in the DSC if the source is "url" because URLs might contain PII
212-
const source = this.metadata.source;
236+
const source = metadata.source;
213237
if (source && source !== 'url') {
214238
dsc.transaction = this.name;
215239
}
@@ -278,7 +302,7 @@ export class Transaction extends SpanClass implements TransactionInterface {
278302
}).endTimestamp;
279303
}
280304

281-
const metadata = this.metadata;
305+
const metadata = spanGetMetadata(this) as TransactionMetadata;
282306

283307
const transaction: TransactionEvent = {
284308
contexts: {

packages/core/src/utils/spanUtils.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import type { Span, TraceContext } from '@sentry/types';
1+
import type { Span, SpanMetadata, TraceContext } from '@sentry/types';
22
import { dropUndefinedKeys, generateSentryTraceHeader } from '@sentry/utils';
33

4+
const SPAN_METADATA = new WeakMap<Span, SpanMetadata>();
5+
46
/**
57
* Convert a span to a trace context, which can be sent as the `trace` context in an event.
68
*/
@@ -26,3 +28,19 @@ export function spanToTraceContext(span: Span): TraceContext {
2628
export function spanToTraceHeader(span: Span): string {
2729
return generateSentryTraceHeader(span.traceId, span.spanId, span.sampled);
2830
}
31+
32+
/**
33+
* Get the metadata for a span.
34+
*/
35+
export function spanGetMetadata(span: Span): SpanMetadata {
36+
return SPAN_METADATA.get(span) || {};
37+
}
38+
39+
/**
40+
* Update metadata for a span.
41+
* This will merge the given new metadata with existing metadata.
42+
*/
43+
export function spanSetMetadata(span: Span, newMetadata: SpanMetadata): void {
44+
const existingMetadata = spanGetMetadata(span);
45+
SPAN_METADATA.set(span, { ...existingMetadata, ...newMetadata });
46+
}
Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Transaction } from '../../../src';
2+
import { spanSetMetadata } from '../../../src/utils/spanUtils';
23

34
describe('transaction', () => {
45
it('works with name', () => {
@@ -8,37 +9,38 @@ describe('transaction', () => {
89

910
it('allows to update the name via setter', () => {
1011
const transaction = new Transaction({ name: 'span name' });
11-
transaction.setMetadata({ source: 'route' });
12+
spanSetMetadata(transaction, { source: 'route' });
1213
expect(transaction.name).toEqual('span name');
1314

1415
transaction.name = 'new name';
1516

1617
expect(transaction.name).toEqual('new name');
18+
// eslint-disable-next-line deprecation/deprecation
1719
expect(transaction.metadata.source).toEqual('custom');
1820
});
1921

2022
it('allows to update the name via setName', () => {
2123
const transaction = new Transaction({ name: 'span name' });
22-
transaction.setMetadata({ source: 'route' });
24+
spanSetMetadata(transaction, { source: 'route' });
2325
expect(transaction.name).toEqual('span name');
2426

25-
transaction.setMetadata({ source: 'route' });
26-
2727
// eslint-disable-next-line deprecation/deprecation
2828
transaction.setName('new name');
2929

3030
expect(transaction.name).toEqual('new name');
31+
// eslint-disable-next-line deprecation/deprecation
3132
expect(transaction.metadata.source).toEqual('custom');
3233
});
3334

3435
it('allows to update the name via updateName', () => {
3536
const transaction = new Transaction({ name: 'span name' });
36-
transaction.setMetadata({ source: 'route' });
37+
spanSetMetadata(transaction, { source: 'route' });
3738
expect(transaction.name).toEqual('span name');
3839

3940
transaction.updateName('new name');
4041

4142
expect(transaction.name).toEqual('new name');
43+
// eslint-disable-next-line deprecation/deprecation
4244
expect(transaction.metadata.source).toEqual('route');
4345
});
4446
});

0 commit comments

Comments
 (0)