Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abhi twp propagation context #8418

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions packages/core/src/baseclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ import type {
EventHint,
Integration,
IntegrationClass,
Options,
Outcome,
PropagationContext,
SdkMetadata,
Session,
SessionAggregates,
Severity,
SeverityLevel,
TraceContext,
Transaction,
TransactionEvent,
Transport,
Expand Down Expand Up @@ -585,6 +588,8 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
throw new SentryError('An event processor returned `null`, will not send event.', 'log');
}

addPropagationContextToEvent(prepared, options, this.getDsn());

const isInternalException = hint.data && (hint.data as { __sentry__: boolean }).__sentry__ === true;
if (isInternalException) {
return prepared;
Expand Down Expand Up @@ -755,3 +760,36 @@ function isErrorEvent(event: Event): event is ErrorEvent {
function isTransactionEvent(event: Event): event is TransactionEvent {
return event.type === 'transaction';
}

function addPropagationContextToEvent(event: Event, options: Options, dsn: DsnComponents | undefined): void {
const { propagationContext } = event.sdkProcessingMetadata || {};
if (!propagationContext) {
return;
}

const {
traceId: trace_id,
spanId: span_id,
parentSpanId: parent_span_id,
dsc,
} = propagationContext as PropagationContext;

const dynamicSamplingContext = dsc ? dsc : { trace_id };

const trace = event.contexts && event.contexts.trace;
if (!trace) {
event.contexts = {
trace: {
trace_id: traceId,
span_id: spanId,
parent_span_id: parentSpanId,
},
...event.contexts,
};

event.sdkProcessingMetadata = {
...event.sdkProcessingMetadata,
dynamicSamplingContext: dsc as DynamicSamplingContext,
};
}
}
30 changes: 26 additions & 4 deletions packages/core/src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
Extra,
Extras,
Primitive,
PropagationContext,
RequestSession,
Scope as ScopeInterface,
ScopeContext,
Expand All @@ -29,6 +30,7 @@ import {
isThenable,
logger,
SyncPromise,
uuid4,
} from '@sentry/utils';

import { updateSession } from './session';
Expand Down Expand Up @@ -70,6 +72,9 @@ export class Scope implements ScopeInterface {
/** Attachments */
protected _attachments: Attachment[];

/** Propagation Context for distributed tracing */
protected _propagationContext: PropagationContext;

/**
* 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
Expand Down Expand Up @@ -108,6 +113,11 @@ export class Scope implements ScopeInterface {
this._extra = {};
this._contexts = {};
this._sdkProcessingMetadata = {};
this._propagationContext = {
traceId: uuid4(),
spanId: uuid4().substring(16),
sampled: false,
};
}

/**
Expand All @@ -131,6 +141,7 @@ export class Scope implements ScopeInterface {
newScope._requestSession = scope._requestSession;
newScope._attachments = [...scope._attachments];
newScope._sdkProcessingMetadata = { ...scope._sdkProcessingMetadata };
newScope._propagationContext = { ...scope._propagationContext };
}
return newScope;
}
Expand Down Expand Up @@ -347,6 +358,9 @@ export class Scope implements ScopeInterface {
if (captureContext._requestSession) {
this._requestSession = captureContext._requestSession;
}
if (captureContext._propagationContext) {
this._propagationContext = captureContext._propagationContext;
}
} else if (isPlainObject(captureContext)) {
// eslint-disable-next-line no-param-reassign
captureContext = captureContext as ScopeContext;
Expand All @@ -365,6 +379,9 @@ export class Scope implements ScopeInterface {
if (captureContext.requestSession) {
this._requestSession = captureContext.requestSession;
}
if (captureContext.propagationContext) {
this._propagationContext = captureContext.propagationContext;
}
}

return this;
Expand Down Expand Up @@ -480,9 +497,10 @@ export class Scope implements ScopeInterface {
// We want to set the trace context for normal events only if there isn't already
// a trace context on the event. There is a product feature in place where we link
// errors with transaction and it relies on that.
if (this._span) {
event.contexts = { trace: this._span.getTraceContext(), ...event.contexts };
const transaction = this._span.transaction;
const span = this._span;
if (span) {
event.contexts = { trace: span.getTraceContext(), ...event.contexts };
const transaction = span.transaction;
if (transaction) {
event.sdkProcessingMetadata = {
dynamicSamplingContext: transaction.getDynamicSamplingContext(),
Expand All @@ -500,7 +518,11 @@ export class Scope implements ScopeInterface {
event.breadcrumbs = [...(event.breadcrumbs || []), ...this._breadcrumbs];
event.breadcrumbs = event.breadcrumbs.length > 0 ? event.breadcrumbs : undefined;

event.sdkProcessingMetadata = { ...event.sdkProcessingMetadata, ...this._sdkProcessingMetadata };
event.sdkProcessingMetadata = {
...event.sdkProcessingMetadata,
...this._sdkProcessingMetadata,
propagationContext: this._propagationContext,
};

return this._notifyEventProcessors([...getGlobalEventProcessors(), ...this._eventProcessors], event, hint);
}
Expand Down
25 changes: 9 additions & 16 deletions packages/core/src/tracing/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import type {
} from '@sentry/types';
import { dropUndefinedKeys, logger } from '@sentry/utils';

import { DEFAULT_ENVIRONMENT } from '../constants';
import type { Hub } from '../hub';
import { getCurrentHub } from '../hub';
import { getDynamicSamplingContextFromHub } from '../utils/dynamicSamplingContext';
import { Span as SpanClass, SpanRecorder } from './span';

/** JSDoc */
Expand Down Expand Up @@ -245,38 +245,31 @@ export class Transaction extends SpanClass implements TransactionInterface {
return this._frozenDynamicSamplingContext;
}

const hub: Hub = this._hub || getCurrentHub();
const client = hub && hub.getClient();

if (!client) return {};

const { environment, release } = client.getOptions() || {};
const { publicKey: public_key } = client.getDsn() || {};
const hub = this._hub || getCurrentHub();
const partialDsc = getDynamicSamplingContextFromHub(hub);

const maybeSampleRate = this.metadata.sampleRate;
const sample_rate = maybeSampleRate !== undefined ? maybeSampleRate.toString() : undefined;

const { segment: user_segment } = hub.getScope().getUser() || {};

const source = this.metadata.source;

// We don't want to have a transaction name in the DSC if the source is "url" because URLs might contain PII
const transaction = source && source !== 'url' ? this.name : undefined;

const dsc = dropUndefinedKeys({
environment: environment || DEFAULT_ENVIRONMENT,
release,
...partialDsc,
transaction,
user_segment,
public_key,
trace_id: this.traceId,
sample_rate,
});
}) as DynamicSamplingContext;

// Uncomment if we want to make DSC immutable
// this._frozenDynamicSamplingContext = dsc;

client.emit && client.emit('createDsc', dsc);
const client = hub.getClient();
if (client && client.emit) {
client.emit('createDsc', dsc);
}

return dsc;
}
Expand Down
26 changes: 26 additions & 0 deletions packages/core/src/utils/dynamicSamplingContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import type { DynamicSamplingContext } from '@sentry/types';

import { DEFAULT_ENVIRONMENT } from '../constants';
import { getCurrentHub } from '../hub';

type PartialDsc = Partial<Pick<DynamicSamplingContext, 'environment' | 'release' | 'user_segment' | 'public_key'>>;

/** */
export function getDynamicSamplingContextFromHub(hub = getCurrentHub()): PartialDsc {
const client = hub.getClient();
if (!client) {
return {};
}

const { environment = DEFAULT_ENVIRONMENT, release } = client.getOptions() || {};
const { publicKey: public_key } = client.getDsn() || {};

const { segment: user_segment } = hub.getScope().getUser() || {};

return {
environment,
release,
user_segment,
public_key,
};
}
42 changes: 42 additions & 0 deletions packages/hub/test/scope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ describe('Scope', () => {
GLOBAL_OBJ.__SENTRY__.globalEventProcessors = undefined;
});

describe('init', () => {
test('it creates a propagation context', () => {
const scope = new Scope();

// @ts-ignore asserting on private properties
expect(scope._propagationContext).toEqual({
traceId: expect.any(String),
spanId: expect.any(String),
sampled: false,
dsc: undefined,
parentSpanId: undefined,
});
});
});

describe('attributes modification', () => {
test('setFingerprint', () => {
const scope = new Scope();
Expand Down Expand Up @@ -193,6 +208,14 @@ describe('Scope', () => {
expect(parentScope.getRequestSession()).toEqual({ status: 'ok' });
expect(scope.getRequestSession()).toEqual({ status: 'ok' });
});

test('should clone propagation context', () => {
const parentScope = new Scope();
const scope = Scope.clone(parentScope);

// @ts-ignore accessing private property for test
expect(scope._propagationContext).toEqual(parentScope._propagationContext);
});
});

describe('applyToEvent', () => {
Expand Down Expand Up @@ -393,6 +416,12 @@ describe('Scope', () => {
scope.clear();
expect((scope as any)._extra).toEqual({});
expect((scope as any)._requestSession).toEqual(undefined);
// @ts-ignore acessing private property for test
expect(scope._propagationContext).toEqual({
traceId: expect.any(String),
spanId: expect.any(String),
sampled: false,
});
});

test('clearBreadcrumbs', () => {
Expand Down Expand Up @@ -486,6 +515,8 @@ describe('Scope', () => {
expect(updatedScope._level).toEqual('warning');
expect(updatedScope._fingerprint).toEqual(['bar']);
expect(updatedScope._requestSession.status).toEqual('ok');
// @ts-ignore accessing private property for test
expect(updatedScope._propagationContext).toEqual(localScope._propagationContext);
});

test('given an empty instance of Scope, it should preserve all the original scope data', () => {
Expand Down Expand Up @@ -518,7 +549,13 @@ describe('Scope', () => {
tags: { bar: '3', baz: '4' },
user: { id: '42' },
requestSession: { status: 'errored' as RequestSessionStatus },
propagationContext: {
traceId: '8949daf83f4a4a70bee4c1eb9ab242ed',
spanId: 'a024ad8fea82680e',
sampled: true,
},
};

const updatedScope = scope.update(localAttributes) as any;

expect(updatedScope._tags).toEqual({
Expand All @@ -540,6 +577,11 @@ describe('Scope', () => {
expect(updatedScope._level).toEqual('warning');
expect(updatedScope._fingerprint).toEqual(['bar']);
expect(updatedScope._requestSession).toEqual({ status: 'errored' });
expect(updatedScope._propagationContext).toEqual({
traceId: '8949daf83f4a4a70bee4c1eb9ab242ed',
spanId: 'a024ad8fea82680e',
sampled: true,
});
});
});

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface Contexts extends Record<string, Context | undefined> {
os?: OsContext;
culture?: CultureContext;
response?: ResponseContext;
trace?: TraceContext;
}

export interface AppContext extends Record<string, unknown> {
Expand Down
3 changes: 3 additions & 0 deletions packages/types/src/hub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ export interface Hub {
/** Returns the client of the top stack. */
getClient(): Client | undefined;

/** Returns the scope of the top stack */
getScope(): Scope;

/**
* Captures an exception event and sends it to Sentry.
*
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export type { Span, SpanContext } from './span';
export type { StackFrame } from './stackframe';
export type { Stacktrace, StackParser, StackLineParser, StackLineParserFn } from './stacktrace';
export type { TextEncoderInternal } from './textencoder';
export type { TracePropagationTargets } from './tracing';
export type { TracePropagationTargets, PropagationContext } from './tracing';
export type {
CustomSamplingContext,
SamplingContext,
Expand Down
2 changes: 2 additions & 0 deletions packages/types/src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { Primitive } from './misc';
import type { RequestSession, Session } from './session';
import type { Severity, SeverityLevel } from './severity';
import type { Span } from './span';
import type { PropagationContext } from './tracing';
import type { Transaction } from './transaction';
import type { User } from './user';

Expand All @@ -23,6 +24,7 @@ export interface ScopeContext {
tags: { [key: string]: Primitive };
fingerprint: string[];
requestSession: RequestSession;
propagationContext: PropagationContext;
}

/**
Expand Down
10 changes: 10 additions & 0 deletions packages/types/src/tracing.ts
Original file line number Diff line number Diff line change
@@ -1 +1,11 @@
import type { DynamicSamplingContext } from './envelope';

export type TracePropagationTargets = (string | RegExp)[];

export interface PropagationContext {
traceId: string;
spanId: string;
sampled: boolean;
parentSpanId?: string;
dsc?: DynamicSamplingContext;
}