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

experiment with using propagation context (Part 3 of TwP) #8433

Closed
wants to merge 1 commit 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
7 changes: 7 additions & 0 deletions packages/core/src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,13 @@ export class Scope implements ScopeInterface {
return this;
}

/**
* @inheritdoc
*/
public getPropagationContext(): PropagationContext {
return this._propagationContext;
}

/**
* This will be called after {@link applyToEvent} is finished.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/tracing/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export { startIdleTransaction, addTracingExtensions } from './hubextensions';
export { IdleTransaction, TRACING_DEFAULTS } from './idletransaction';
export { Span, spanStatusfromHttpCode } from './span';
export { Span, spanStatusfromHttpCode, spanContextToTraceparent } from './span';
export { Transaction } from './transaction';
export { extractTraceparentData, getActiveTransaction } from './utils';
// eslint-disable-next-line deprecation/deprecation
Expand Down
16 changes: 11 additions & 5 deletions packages/core/src/tracing/span.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,7 @@ export class Span implements SpanInterface {
* @inheritDoc
*/
public toTraceparent(): string {
let sampledString = '';
if (this.sampled !== undefined) {
sampledString = this.sampled ? '-1' : '-0';
}
return `${this.traceId}-${this.spanId}${sampledString}`;
return spanContextToTraceparent(this.traceId, this.spanId, this.sampled);
}

/**
Expand Down Expand Up @@ -438,3 +434,13 @@ export function spanStatusfromHttpCode(httpStatus: number): SpanStatusType {

return 'unknown_error';
}

/** Generate sentry-trace header from span context */
export function spanContextToTraceparent(traceId: string, maybeSpanId?: string, sampled?: boolean): string {
let sampledString = '';
if (sampled !== undefined) {
sampledString = sampled ? '-1' : '-0';
}
const spanId = maybeSpanId || uuid4().substring(16);
return `${traceId}-${spanId}${sampledString}`;
}
100 changes: 60 additions & 40 deletions packages/node/src/integrations/undici/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Hub } from '@sentry/core';
import type { EventProcessor, Integration } from '@sentry/types';
import { spanContextToTraceparent } from '@sentry/core';
import type { EventProcessor, Integration, Span } from '@sentry/types';
import {
dynamicRequire,
dynamicSamplingContextToSentryBaggageHeader,
Expand All @@ -12,7 +13,13 @@ import { LRUMap } from 'lru_map';
import type { NodeClient } from '../../client';
import { NODE_VERSION } from '../../nodeVersion';
import { isSentryRequest } from '../utils/http';
import type { DiagnosticsChannel, RequestCreateMessage, RequestEndMessage, RequestErrorMessage } from './types';
import type {
DiagnosticsChannel,
RequestCreateMessage,
RequestEndMessage,
RequestErrorMessage,
RequestWithSentry,
} from './types';

export enum ChannelName {
// https://github.com/nodejs/undici/blob/e6fc80f809d1217814c044f52ed40ef13f21e43c/docs/api/DiagnosticsChannel.md#undicirequestcreate
Expand Down Expand Up @@ -124,7 +131,6 @@ export class Undici implements Integration {
const { request } = message as RequestCreateMessage;

const stringUrl = request.origin ? request.origin.toString() + request.path : request.path;
const url = parseUrl(stringUrl);

if (isSentryRequest(stringUrl) || request.__sentry__ !== undefined) {
return;
Expand All @@ -133,52 +139,45 @@ export class Undici implements Integration {
const client = hub.getClient<NodeClient>();
const scope = hub.getScope();

const activeSpan = scope.getSpan();
const parentSpan = scope.getSpan();

if (activeSpan && client) {
if (client) {
const clientOptions = client.getOptions();

if (shouldCreateSpan(stringUrl)) {
const method = request.method || 'GET';
const data: Record<string, unknown> = {
'http.method': method,
};
if (url.search) {
data['http.query'] = url.search;
const shouldAttachTraceData = (url: string): boolean => {
if (clientOptions.tracePropagationTargets === undefined) {
return true;
}
if (url.hash) {
data['http.fragment'] = url.hash;
}
const span = activeSpan.startChild({
op: 'http.client',
description: `${method} ${getSanitizedUrlString(url)}`,
data,
});
request.__sentry__ = span;

const shouldAttachTraceData = (url: string): boolean => {
if (clientOptions.tracePropagationTargets === undefined) {
return true;
}
const cachedDecision = this._headersUrlMap.get(url);
if (cachedDecision !== undefined) {
return cachedDecision;
}

const cachedDecision = this._headersUrlMap.get(url);
if (cachedDecision !== undefined) {
return cachedDecision;
}
const decision = stringMatchesSomePattern(url, clientOptions.tracePropagationTargets);
this._headersUrlMap.set(url, decision);
return decision;
};

const decision = stringMatchesSomePattern(url, clientOptions.tracePropagationTargets);
this._headersUrlMap.set(url, decision);
return decision;
};
const span = shouldCreateSpan(stringUrl) ? createRequestSpan(request, stringUrl, parentSpan) : undefined;
if (span) {
request.__sentry__ = span;
}

if (shouldAttachTraceData(stringUrl)) {
if (shouldAttachTraceData(stringUrl)) {
if (span) {
request.addHeader('sentry-trace', span.toTraceparent());
if (span.transaction) {
const dynamicSamplingContext = span.transaction.getDynamicSamplingContext();
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
if (sentryBaggageHeader) {
request.addHeader('baggage', sentryBaggageHeader);
}
const dynamicSamplingContext = span.transaction?.getDynamicSamplingContext();
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dynamicSamplingContext);
if (sentryBaggageHeader) {
request.addHeader('baggage', sentryBaggageHeader);
}
} else {
const { traceId, sampled, dsc } = scope.getPropagationContext();
request.addHeader('sentry-trace', spanContextToTraceparent(traceId, undefined, sampled));
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(dsc);
if (sentryBaggageHeader) {
request.addHeader('baggage', sentryBaggageHeader);
}
}
}
Expand Down Expand Up @@ -265,3 +264,24 @@ export class Undici implements Integration {
});
}
}

/** */
function createRequestSpan(request: RequestWithSentry, stringUrl: string, parentSpan?: Span): Span | undefined {
const url = parseUrl(stringUrl);
const method = request.method || 'GET';
const data: Record<string, unknown> = {
'http.method': method,
};
if (url.search) {
data['http.query'] = url.search;
}
if (url.hash) {
data['http.fragment'] = url.hash;
}
const span = parentSpan?.startChild({
op: 'http.client',
description: `${method} ${getSanitizedUrlString(url)}`,
data,
});
return span;
}
5 changes: 5 additions & 0 deletions packages/types/src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,11 @@ export interface Scope {
*/
setSDKProcessingMetadata(newData: { [key: string]: unknown }): this;

/**
* Returns propagation context on the scope.
*/
getPropagationContext(): PropagationContext;

/**
* Add propagation context to the scope, used for distributed tracing
*/
Expand Down
5 changes: 4 additions & 1 deletion packages/utils/src/baggage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,11 @@ export function baggageHeaderToDynamicSamplingContext(
*/
export function dynamicSamplingContextToSentryBaggageHeader(
// this also takes undefined for convenience and bundle size in other places
dynamicSamplingContext: Partial<DynamicSamplingContext>,
dynamicSamplingContext?: Partial<DynamicSamplingContext>,
): string | undefined {
if (!dynamicSamplingContext) {
return undefined;
}
// Prefix all DSC keys with "sentry-" and put them into a new object
const sentryPrefixedDSC = Object.entries(dynamicSamplingContext).reduce<Record<string, string>>(
(acc, [dscKey, dscValue]) => {
Expand Down
Loading