-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
feat: Streamline sentry-trace
, baggage
and DSC handling
#14364
Changes from 21 commits
464d725
b31203e
07c8fcf
81d87ff
8bd3ef8
5201fc8
1608c4c
f6208e0
04227f2
a5f8c1e
12c9f45
e2de55d
d0c92b8
112ca89
c47f0ac
58bb2ac
ec4aa58
ad67471
7b423e8
7a557f4
e026314
7d5865e
0d5c32d
f8a5d82
a2e4c8f
d43ceae
b8432f9
5ae3b58
aa3846a
ae0e8ec
9f0b1ea
d5f08b6
a20fc43
c3963be
57bfd0e
80ecf6b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,24 +10,18 @@ import { | |
SentryNonRecordingSpan, | ||
getActiveSpan, | ||
getClient, | ||
getCurrentScope, | ||
getDynamicSamplingContextFromClient, | ||
getDynamicSamplingContextFromSpan, | ||
getIsolationScope, | ||
getTraceData, | ||
hasTracingEnabled, | ||
instrumentFetchRequest, | ||
setHttpStatus, | ||
spanToJSON, | ||
spanToTraceHeader, | ||
startInactiveSpan, | ||
} from '@sentry/core'; | ||
import { | ||
BAGGAGE_HEADER_NAME, | ||
addFetchEndInstrumentationHandler, | ||
addFetchInstrumentationHandler, | ||
browserPerformanceTimeOrigin, | ||
dynamicSamplingContextToSentryBaggageHeader, | ||
generateSentryTraceHeader, | ||
parseUrl, | ||
stringMatchesSomePattern, | ||
} from '@sentry/core'; | ||
|
@@ -402,12 +396,9 @@ export function xhrCallback( | |
xhr.__sentry_xhr_span_id__ = span.spanContext().spanId; | ||
spans[xhr.__sentry_xhr_span_id__] = span; | ||
|
||
const client = getClient(); | ||
|
||
if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url) && client) { | ||
if (xhr.setRequestHeader && shouldAttachHeaders(sentryXhrData.url) && getClient()) { | ||
addTracingHeadersToXhrRequest( | ||
xhr, | ||
client, | ||
// If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), | ||
// we do not want to use the span as base for the trace headers, | ||
// which means that the headers will be generated from the scope and the sampling decision is deferred | ||
|
@@ -418,22 +409,12 @@ export function xhrCallback( | |
return span; | ||
} | ||
|
||
function addTracingHeadersToXhrRequest(xhr: SentryWrappedXMLHttpRequest, client: Client, span?: Span): void { | ||
const scope = getCurrentScope(); | ||
const isolationScope = getIsolationScope(); | ||
const { traceId, spanId, sampled, dsc } = { | ||
...isolationScope.getPropagationContext(), | ||
...scope.getPropagationContext(), | ||
}; | ||
|
||
const sentryTraceHeader = | ||
span && hasTracingEnabled() ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, we have checked for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For browser, I think that's fine! |
||
function addTracingHeadersToXhrRequest(xhr: SentryWrappedXMLHttpRequest, span?: Span): void { | ||
const { 'sentry-trace': sentryTrace, baggage } = getTraceData({ span }); | ||
|
||
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader( | ||
dsc || (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client)), | ||
); | ||
|
||
setHeaderOnXhr(xhr, sentryTraceHeader, sentryBaggageHeader); | ||
if (sentryTrace) { | ||
setHeaderOnXhr(xhr, sentryTrace, baggage); | ||
} | ||
} | ||
|
||
function setHeaderOnXhr( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,23 +32,22 @@ import type { | |
} from '@sentry/types'; | ||
|
||
import { getEnvelopeEndpointWithUrlEncodedAuth } from './api'; | ||
import { getIsolationScope } from './currentScopes'; | ||
import { getCurrentScope, getIsolationScope, getTraceContextFromScope } from './currentScopes'; | ||
import { DEBUG_BUILD } from './debug-build'; | ||
import { createEventEnvelope, createSessionEnvelope } from './envelope'; | ||
import type { IntegrationIndex } from './integration'; | ||
import { afterSetupIntegrations } from './integration'; | ||
import { setupIntegration, setupIntegrations } from './integration'; | ||
import type { Scope } from './scope'; | ||
import { updateSession } from './session'; | ||
import { getDynamicSamplingContextFromClient } from './tracing/dynamicSamplingContext'; | ||
import { getDynamicSamplingContextFromScope } from './tracing/dynamicSamplingContext'; | ||
import { createClientReportEnvelope } from './utils-hoist/clientreport'; | ||
import { dsnToString, makeDsn } from './utils-hoist/dsn'; | ||
import { addItemToEnvelope, createAttachmentEnvelopeItem } from './utils-hoist/envelope'; | ||
import { SentryError } from './utils-hoist/error'; | ||
import { isParameterizedString, isPlainObject, isPrimitive, isThenable } from './utils-hoist/is'; | ||
import { logger } from './utils-hoist/logger'; | ||
import { checkOrSetAlreadyCaught, uuid4 } from './utils-hoist/misc'; | ||
import { dropUndefinedKeys } from './utils-hoist/object'; | ||
import { SyncPromise, rejectedSyncPromise, resolvedSyncPromise } from './utils-hoist/syncpromise'; | ||
import { parseSampleRate } from './utils/parseSampleRate'; | ||
import { prepareEvent } from './utils/prepareEvent'; | ||
|
@@ -659,7 +658,7 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> { | |
protected _prepareEvent( | ||
event: Event, | ||
hint: EventHint, | ||
currentScope?: Scope, | ||
currentScope = getCurrentScope(), | ||
isolationScope = getIsolationScope(), | ||
): PromiseLike<Event | null> { | ||
const options = this.getOptions(); | ||
|
@@ -679,30 +678,18 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> { | |
return evt; | ||
} | ||
|
||
const propagationContext = { | ||
...isolationScope.getPropagationContext(), | ||
...(currentScope ? currentScope.getPropagationContext() : undefined), | ||
evt.contexts = { | ||
trace: getTraceContextFromScope(currentScope), | ||
...evt.contexts, | ||
}; | ||
|
||
const trace = evt.contexts && evt.contexts.trace; | ||
if (!trace && propagationContext) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These checks were not really necessary, I believe - first, |
||
const { traceId: trace_id, spanId, parentSpanId, dsc } = propagationContext; | ||
evt.contexts = { | ||
trace: dropUndefinedKeys({ | ||
trace_id, | ||
span_id: spanId, | ||
parent_span_id: parentSpanId, | ||
}), | ||
...evt.contexts, | ||
}; | ||
const dynamicSamplingContext = getDynamicSamplingContextFromScope(this, currentScope); | ||
|
||
const dynamicSamplingContext = dsc ? dsc : getDynamicSamplingContextFromClient(trace_id, this); | ||
evt.sdkProcessingMetadata = { | ||
dynamicSamplingContext, | ||
...evt.sdkProcessingMetadata, | ||
}; | ||
|
||
evt.sdkProcessingMetadata = { | ||
dynamicSamplingContext, | ||
...evt.sdkProcessingMetadata, | ||
}; | ||
} | ||
return evt; | ||
}); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,24 +1,14 @@ | ||
import type { Client, HandlerDataFetch, Scope, Span, SpanOrigin } from '@sentry/types'; | ||
import { getClient, getCurrentScope, getIsolationScope } from './currentScopes'; | ||
import { getClient } from './currentScopes'; | ||
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from './semanticAttributes'; | ||
import { | ||
SPAN_STATUS_ERROR, | ||
getDynamicSamplingContextFromClient, | ||
getDynamicSamplingContextFromSpan, | ||
setHttpStatus, | ||
startInactiveSpan, | ||
} from './tracing'; | ||
import { SPAN_STATUS_ERROR, setHttpStatus, startInactiveSpan } from './tracing'; | ||
import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan'; | ||
import { | ||
BAGGAGE_HEADER_NAME, | ||
SENTRY_BAGGAGE_KEY_PREFIX, | ||
dynamicSamplingContextToSentryBaggageHeader, | ||
} from './utils-hoist/baggage'; | ||
import { BAGGAGE_HEADER_NAME, SENTRY_BAGGAGE_KEY_PREFIX } from './utils-hoist/baggage'; | ||
import { isInstanceOf } from './utils-hoist/is'; | ||
import { generateSentryTraceHeader } from './utils-hoist/tracing'; | ||
import { parseUrl } from './utils-hoist/url'; | ||
import { hasTracingEnabled } from './utils/hasTracingEnabled'; | ||
import { getActiveSpan, spanToTraceHeader } from './utils/spanUtils'; | ||
import { getActiveSpan } from './utils/spanUtils'; | ||
import { getTraceData } from './utils/traceData'; | ||
|
||
type PolymorphicRequestHeaders = | ||
| Record<string, string | undefined> | ||
|
@@ -63,9 +53,6 @@ export function instrumentFetchRequest( | |
return undefined; | ||
} | ||
|
||
const scope = getCurrentScope(); | ||
const client = getClient(); | ||
|
||
const { method, url } = handlerData.fetchData; | ||
|
||
const fullUrl = getFullURL(url); | ||
|
@@ -92,7 +79,7 @@ export function instrumentFetchRequest( | |
handlerData.fetchData.__span = span.spanContext().spanId; | ||
spans[span.spanContext().spanId] = span; | ||
|
||
if (shouldAttachHeaders(handlerData.fetchData.url) && client) { | ||
if (shouldAttachHeaders(handlerData.fetchData.url) && getClient()) { | ||
const request: string | Request = handlerData.args[0]; | ||
|
||
// In case the user hasn't set the second argument of a fetch call we default it to `{}`. | ||
|
@@ -101,10 +88,10 @@ export function instrumentFetchRequest( | |
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
const options: { [key: string]: any } = handlerData.args[1]; | ||
|
||
options.headers = addTracingHeadersToFetchRequest( | ||
options.headers = _addTracingHeadersToFetchRequest( | ||
request, | ||
client, | ||
scope, | ||
undefined, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. these just remain for backwards compatibility, because |
||
undefined, | ||
options, | ||
// If performance is disabled (TWP) or there's no active root span (pageload/navigation/interaction), | ||
// we do not want to use the span as base for the trace headers, | ||
|
@@ -117,12 +104,19 @@ export function instrumentFetchRequest( | |
} | ||
|
||
/** | ||
* Adds sentry-trace and baggage headers to the various forms of fetch headers | ||
* Adds sentry-trace and baggage headers to the various forms of fetch headers. | ||
* | ||
* @deprecated This function will not be exported anymore in v9. | ||
*/ | ||
export const addTracingHeadersToFetchRequest = _addTracingHeadersToFetchRequest; | ||
|
||
/** | ||
* Adds sentry-trace and baggage headers to the various forms of fetch headers. | ||
*/ | ||
export function addTracingHeadersToFetchRequest( | ||
function _addTracingHeadersToFetchRequest( | ||
request: string | unknown, // unknown is actually type Request but we can't export DOM types from this package, | ||
client: Client, | ||
scope: Scope, | ||
_client: Client | undefined, | ||
_scope: Scope | undefined, | ||
fetchOptionsObj: { | ||
headers?: | ||
| { | ||
|
@@ -132,44 +126,37 @@ export function addTracingHeadersToFetchRequest( | |
}, | ||
span?: Span, | ||
): PolymorphicRequestHeaders | undefined { | ||
const isolationScope = getIsolationScope(); | ||
const traceHeaders = getTraceData({ span }); | ||
const sentryTrace = traceHeaders['sentry-trace']; | ||
const baggage = traceHeaders.baggage; | ||
|
||
const { traceId, spanId, sampled, dsc } = { | ||
...isolationScope.getPropagationContext(), | ||
...scope.getPropagationContext(), | ||
}; | ||
|
||
const sentryTraceHeader = span ? spanToTraceHeader(span) : generateSentryTraceHeader(traceId, spanId, sampled); | ||
|
||
const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader( | ||
dsc || (span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromClient(traceId, client)), | ||
); | ||
// Nothing to do, we just return the existing headers untouched | ||
if (!sentryTrace) { | ||
return fetchOptionsObj && (fetchOptionsObj.headers as PolymorphicRequestHeaders); | ||
} | ||
|
||
const headers = | ||
fetchOptionsObj.headers || | ||
(typeof Request !== 'undefined' && isInstanceOf(request, Request) ? (request as Request).headers : undefined); | ||
|
||
if (!headers) { | ||
return { 'sentry-trace': sentryTraceHeader, baggage: sentryBaggageHeader }; | ||
return { ...traceHeaders }; | ||
} else if (typeof Headers !== 'undefined' && isInstanceOf(headers, Headers)) { | ||
const newHeaders = new Headers(headers as Headers); | ||
newHeaders.set('sentry-trace', sentryTrace); | ||
|
||
newHeaders.set('sentry-trace', sentryTraceHeader); | ||
|
||
if (sentryBaggageHeader) { | ||
if (baggage) { | ||
const prevBaggageHeader = newHeaders.get(BAGGAGE_HEADER_NAME); | ||
if (prevBaggageHeader) { | ||
const prevHeaderStrippedFromSentryBaggage = stripBaggageHeaderOfSentryBaggageValues(prevBaggageHeader); | ||
newHeaders.set( | ||
BAGGAGE_HEADER_NAME, | ||
// If there are non-sentry entries (i.e. if the stripped string is non-empty/truthy) combine the stripped header and sentry baggage header | ||
// otherwise just set the sentry baggage header | ||
prevHeaderStrippedFromSentryBaggage | ||
? `${prevHeaderStrippedFromSentryBaggage},${sentryBaggageHeader}` | ||
: sentryBaggageHeader, | ||
prevHeaderStrippedFromSentryBaggage ? `${prevHeaderStrippedFromSentryBaggage},${baggage}` : baggage, | ||
); | ||
} else { | ||
newHeaders.set(BAGGAGE_HEADER_NAME, sentryBaggageHeader); | ||
newHeaders.set(BAGGAGE_HEADER_NAME, baggage); | ||
} | ||
} | ||
|
||
|
@@ -191,13 +178,13 @@ export function addTracingHeadersToFetchRequest( | |
} | ||
}), | ||
// Attach the new sentry-trace header | ||
['sentry-trace', sentryTraceHeader], | ||
['sentry-trace', sentryTrace], | ||
]; | ||
|
||
if (sentryBaggageHeader) { | ||
if (baggage) { | ||
// If there are multiple entries with the same key, the browser will merge the values into a single request header. | ||
// Its therefore safe to simply push a "baggage" entry, even though there might already be another baggage header. | ||
newHeaders.push([BAGGAGE_HEADER_NAME, sentryBaggageHeader]); | ||
newHeaders.push([BAGGAGE_HEADER_NAME, baggage]); | ||
} | ||
|
||
return newHeaders as PolymorphicRequestHeaders; | ||
|
@@ -215,13 +202,13 @@ export function addTracingHeadersToFetchRequest( | |
newBaggageHeaders.push(stripBaggageHeaderOfSentryBaggageValues(existingBaggageHeader)); | ||
} | ||
|
||
if (sentryBaggageHeader) { | ||
newBaggageHeaders.push(sentryBaggageHeader); | ||
if (baggage) { | ||
newBaggageHeaders.push(baggage); | ||
} | ||
|
||
return { | ||
...(headers as Exclude<typeof headers, Headers>), | ||
'sentry-trace': sentryTraceHeader, | ||
'sentry-trace': sentryTrace, | ||
baggage: newBaggageHeaders.length > 0 ? newBaggageHeaders.join(',') : undefined, | ||
}; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
kind of unrelated but this kept flaking every now and then, just fixing this here...