Skip to content

Commit 1ec9937

Browse files
committed
Add external propagation context support and export SENTRY_API_VERSION
1 parent c5ce81d commit 1ec9937

6 files changed

Lines changed: 177 additions & 3 deletions

File tree

‎packages/core/src/api.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { DsnComponents, DsnLike } from './types-hoist/dsn';
33
importtype{SdkInfo}from'./types-hoist/sdkinfo';
44
import{dsnToString,makeDsn}from'./utils/dsn';
55

6-
constSENTRY_API_VERSION='7';
6+
exportconstSENTRY_API_VERSION='7';
77

88
/** Returns the prefix to construct Sentry ingestion API endpoints. */
99
functiongetBaseApiEndpoint(dsn: DsnComponents): string{

‎packages/core/src/currentScopes.ts‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,31 @@ import { Scope } from './scope';
55
importtype{TraceContext}from'./types-hoist/context';
66
import{generateSpanId}from'./utils/propagationContext';
77

8+
let_externalPropagationContextProvider: (()=>{traceId: string;spanId: string}|undefined)|undefined;
9+
10+
/**
11+
* Register an external propagation context provider function.
12+
* When registered, trace context will be read from the external source (e.g. OpenTelemetry)
13+
* instead of from the Sentry scope's propagation context.
14+
*/
15+
exportfunctionregisterExternalPropagationContext(fn: ()=>{traceId: string;spanId: string}|undefined): void{
16+
_externalPropagationContextProvider=fn;
17+
}
18+
19+
/**
20+
* Get the external propagation context, if a provider has been registered.
21+
*/
22+
exportfunctiongetExternalPropagationContext(): {traceId: string;spanId: string}|undefined{
23+
return_externalPropagationContextProvider?.();
24+
}
25+
26+
/**
27+
* Check if an external propagation context provider has been registered.
28+
*/
29+
exportfunctionhasExternalPropagationContext(): boolean{
30+
return_externalPropagationContextProvider!==undefined;
31+
}
32+
833
/**
934
* Get the currently active scope.
1035
*/
@@ -125,6 +150,11 @@ export function getClient<C extends Client>(): C | undefined {
125150
* Get a trace context for the given scope.
126151
*/
127152
exportfunctiongetTraceContextFromScope(scope: Scope): TraceContext{
153+
constexternalContext=getExternalPropagationContext();
154+
if(externalContext){
155+
return{trace_id: externalContext.traceId,span_id: externalContext.spanId};
156+
}
157+
128158
constpropagationContext=scope.getPropagationContext();
129159

130160
const{ traceId, parentSpanId, propagationSpanId }=propagationContext;

‎packages/core/src/index.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ export {
4141
withIsolationScope,
4242
getClient,
4343
getTraceContextFromScope,
44+
registerExternalPropagationContext,
45+
getExternalPropagationContext,
46+
hasExternalPropagationContext,
4447
}from'./currentScopes';
4548
export{getDefaultCurrentScope,getDefaultIsolationScope}from'./defaultScopes';
4649
export{setAsyncContextStrategy}from'./asyncContext';
@@ -49,7 +52,7 @@ export { makeSession, closeSession, updateSession } from './session';
4952
export{Scope}from'./scope';
5053
exporttype{CaptureContext,ScopeContext,ScopeData}from'./scope';
5154
export{notifyEventProcessors}from'./eventProcessors';
52-
export{getEnvelopeEndpointWithUrlEncodedAuth,getReportDialogEndpoint}from'./api';
55+
export{getEnvelopeEndpointWithUrlEncodedAuth,getReportDialogEndpoint,SENTRY_API_VERSION}from'./api';
5356
export{Client}from'./client';
5457
export{ServerRuntimeClient}from'./server-runtime-client';
5558
export{initAndBind,setCurrentClient}from'./sdk';

‎packages/core/src/utils/traceData.ts‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import{getAsyncContextStrategy}from'../asyncContext';
22
import{getMainCarrier}from'../carrier';
33
importtype{Client}from'../client';
4-
import{getClient,getCurrentScope}from'../currentScopes';
4+
import{getClient,getCurrentScope,hasExternalPropagationContext}from'../currentScopes';
55
import{isEnabled}from'../exports';
66
importtype{Scope}from'../scope';
77
import{getDynamicSamplingContextFromScope,getDynamicSamplingContextFromSpan}from'../tracing';
@@ -20,6 +20,10 @@ import { generateSentryTraceHeader, generateTraceparentHeader, TRACEPARENT_REGEX
2020
* This function also applies some validation to the generated sentry-trace and baggage values to ensure that
2121
* only valid strings are returned.
2222
*
23+
* When an external propagation context is registered (e.g. via the OTLP integration) and there is no active
24+
* Sentry span, this function returns an empty object to defer outgoing request propagation to the external
25+
* propagator (e.g. an OpenTelemetry propagator).
26+
*
2327
* If (@param options.propagateTraceparent) is `true`, the function will also generate a `traceparent` value,
2428
* following the W3C traceparent header format.
2529
*
@@ -42,6 +46,13 @@ export function getTraceData(
4246

4347
constscope=options.scope||getCurrentScope();
4448
constspan=options.span||getActiveSpan();
49+
50+
// When no active span and external propagation context is registered (e.g. OTLP integration),
51+
// return empty to let the OTel propagator handle outgoing request propagation.
52+
if(!span&&hasExternalPropagationContext()){
53+
return{};
54+
}
55+
4556
constsentryTrace=span ? spanToTraceHeader(span) : scopeToTraceHeader(scope);
4657
constdsc=span ? getDynamicSamplingContextFromSpan(span) : getDynamicSamplingContextFromScope(client,scope);
4758
constbaggage=dynamicSamplingContextToSentryBaggageHeader(dsc);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import{afterEach,describe,expect,it}from'vitest';
2+
import{
3+
getExternalPropagationContext,
4+
getTraceContextFromScope,
5+
hasExternalPropagationContext,
6+
registerExternalPropagationContext,
7+
}from'../../src/currentScopes';
8+
import{Scope}from'../../src/scope';
9+
10+
describe('External Propagation Context',()=>{
11+
afterEach(()=>{
12+
// Reset by registering a provider that returns undefined
13+
registerExternalPropagationContext(()=>undefined);
14+
});
15+
16+
describe('registerExternalPropagationContext',()=>{
17+
it('registers a provider function',()=>{
18+
registerExternalPropagationContext(()=>({
19+
traceId: 'abc123',
20+
spanId: 'def456',
21+
}));
22+
23+
expect(hasExternalPropagationContext()).toBe(true);
24+
});
25+
});
26+
27+
describe('getExternalPropagationContext',()=>{
28+
it('returns undefined when provider returns undefined',()=>{
29+
registerExternalPropagationContext(()=>undefined);
30+
expect(getExternalPropagationContext()).toBeUndefined();
31+
});
32+
33+
it('returns trace context from provider',()=>{
34+
registerExternalPropagationContext(()=>({
35+
traceId: '12345678901234567890123456789012',
36+
spanId: '1234567890123456',
37+
}));
38+
39+
constresult=getExternalPropagationContext();
40+
expect(result).toEqual({
41+
traceId: '12345678901234567890123456789012',
42+
spanId: '1234567890123456',
43+
});
44+
});
45+
});
46+
47+
describe('hasExternalPropagationContext',()=>{
48+
it('returns true after registration',()=>{
49+
registerExternalPropagationContext(()=>undefined);
50+
expect(hasExternalPropagationContext()).toBe(true);
51+
});
52+
});
53+
54+
describe('getTraceContextFromScope with external propagation context',()=>{
55+
it('uses external propagation context when available',()=>{
56+
registerExternalPropagationContext(()=>({
57+
traceId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1',
58+
spanId: 'bbbbbbbbbbbbbb01',
59+
}));
60+
61+
constscope=newScope();
62+
scope.setPropagationContext({
63+
traceId: 'cccccccccccccccccccccccccccccc01',
64+
sampleRand: 0.5,
65+
});
66+
67+
consttraceContext=getTraceContextFromScope(scope);
68+
expect(traceContext.trace_id).toBe('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1');
69+
expect(traceContext.span_id).toBe('bbbbbbbbbbbbbb01');
70+
expect(traceContext.parent_span_id).toBeUndefined();
71+
});
72+
73+
it('falls back to scope propagation context when provider returns undefined',()=>{
74+
registerExternalPropagationContext(()=>undefined);
75+
76+
constscope=newScope();
77+
scope.setPropagationContext({
78+
traceId: 'cccccccccccccccccccccccccccccc01',
79+
sampleRand: 0.5,
80+
});
81+
82+
consttraceContext=getTraceContextFromScope(scope);
83+
expect(traceContext.trace_id).toBe('cccccccccccccccccccccccccccccc01');
84+
});
85+
});
86+
});

‎packages/core/test/lib/utils/traceData.test.ts‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getIsolationScope,
77
getMainCarrier,
88
getTraceData,
9+
registerExternalPropagationContext,
910
Scope,
1011
SentrySpan,
1112
setAsyncContextStrategy,
@@ -347,4 +348,47 @@ describe('getTraceData', () => {
347348
expect(traceData.traceparent).toBeDefined();
348349
expect(traceData.traceparent).toMatch(/00-12345678901234567890123456789099-[0-9a-f]{16}-00/);
349350
});
351+
352+
it('returns empty object when no span and external propagation context is registered',()=>{
353+
setupClient();
354+
355+
registerExternalPropagationContext(()=>({
356+
traceId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1',
357+
spanId: 'bbbbbbbbbbbbbb01',
358+
}));
359+
360+
consttraceData=getTraceData();
361+
expect(traceData).toEqual({});
362+
363+
// Clean up
364+
registerExternalPropagationContext(()=>undefined);
365+
});
366+
367+
it('still returns trace data from span even when external propagation context is registered',()=>{
368+
setupClient();
369+
370+
registerExternalPropagationContext(()=>({
371+
traceId: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1',
372+
spanId: 'bbbbbbbbbbbbbb01',
373+
}));
374+
375+
constspan=newSentrySpan({
376+
traceId: '12345678901234567890123456789012',
377+
spanId: '1234567890123456',
378+
sampled: true,
379+
});
380+
381+
withActiveSpan(span,()=>{
382+
constdata=getTraceData();
383+
384+
expect(data).toEqual({
385+
'sentry-trace': '12345678901234567890123456789012-1234567890123456-1',
386+
baggage:
387+
'sentry-environment=production,sentry-public_key=123,sentry-trace_id=12345678901234567890123456789012,sentry-sampled=true',
388+
});
389+
});
390+
391+
// Clean up
392+
registerExternalPropagationContext(()=>undefined);
393+
});
350394
});

0 commit comments

Comments
 (0)