Skip to content

Commit 45408a4

Browse files
committed
wip
1 parent 7473fb2 commit 45408a4

File tree

8 files changed

+98
-64
lines changed

8 files changed

+98
-64
lines changed

dev-packages/node-integration-tests/suites/tracing/meta-tags-twp-errors/test.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { afterAll, describe, expect, test } from 'vitest';
22
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';
3-
import { run } from 'node:test';
43

54
describe('errors in TwP mode have same trace in trace context and getTraceData()', () => {
65
afterAll(() => {
@@ -9,16 +8,11 @@ describe('errors in TwP mode have same trace in trace context and getTraceData()
98

109
// In a request handler, the spanId is consistent inside of the request
1110
test('in incoming request', async () => {
12-
let firstTraceId: string | undefined;
13-
1411
const runner = createRunner(__dirname, 'server.js')
1512
.expect({
1613
event: event => {
1714
const { contexts } = event;
1815
const { trace_id, span_id } = contexts?.trace || {};
19-
if (!firstTraceId) {
20-
firstTraceId = trace_id;
21-
}
2216
expect(trace_id).toMatch(/^[a-f\d]{32}$/);
2317
expect(span_id).toMatch(/^[a-f\d]{16}$/);
2418

@@ -34,15 +28,8 @@ describe('errors in TwP mode have same trace in trace context and getTraceData()
3428
expect(traceData.metaTags).not.toContain('sentry-sampled=');
3529
},
3630
})
37-
.expect({
38-
event: event => {
39-
expect(event.contexts?.trace?.trace_id).toBeDefined();
40-
expect(event.contexts?.trace?.trace_id).toBe(firstTraceId);
41-
},
42-
})
4331
.start();
4432
runner.makeRequest('get', '/test');
45-
runner.makeRequest('get', '/test');
4633
await runner.completed();
4734
});
4835

packages/browser/src/integrations/spanstreaming.ts

Lines changed: 2 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
defineIntegration,
77
getDynamicSamplingContextFromSpan,
88
isV2BeforeSendSpanCallback,
9-
showSpanDropWarning,
109
} from '@sentry/core';
1110
import { DEBUG_BUILD } from '../debug-build';
1211

@@ -73,10 +72,9 @@ export const spanStreamingIntegration = defineIntegration(((userOptions?: Partia
7372
// TODO: This will change once we have more concrete ideas about a universal SDK data buffer.
7473
client.on('afterSegmentSpanEnd', segmentSpan => {
7574
sendSegment(segmentSpan, {
76-
spanTreeMap: spanTreeMap,
75+
spanTreeMap,
7776
client,
7877
batchLimit: options.batchLimit,
79-
beforeSendSpan,
8078
});
8179
});
8280
},
@@ -87,7 +85,6 @@ interface SpanProcessingOptions {
8785
client: Client;
8886
spanTreeMap: Map<string, Set<SpanV2JSONWithSegmentRef>>;
8987
batchLimit: number;
90-
beforeSendSpan: ((span: SpanV2JSON) => SpanV2JSON) | undefined;
9188
}
9289

9390
/**
@@ -97,10 +94,7 @@ function getSpanTreeMapKey(spanJSON: SpanV2JSONWithSegmentRef): string {
9794
return `${spanJSON.trace_id}-${spanJSON._segmentSpan?.spanContext().spanId || spanJSON.span_id}`;
9895
}
9996

100-
function sendSegment(
101-
segmentSpan: Span,
102-
{ client, spanTreeMap, batchLimit, beforeSendSpan }: SpanProcessingOptions,
103-
): void {
97+
function sendSegment(segmentSpan: Span, { client, spanTreeMap, batchLimit }: SpanProcessingOptions): void {
10498
const traceId = segmentSpan.spanContext().traceId;
10599
const segmentSpanId = segmentSpan.spanContext().spanId;
106100
const spanTreeMapKey = `${traceId}-${segmentSpanId}`;
@@ -116,10 +110,6 @@ function sendSegment(
116110
// Remove the segment span reference before processing
117111
// eslint-disable-next-line @typescript-eslint/no-unused-vars
118112
const { _segmentSpan, ...cleanSpanJSON } = spanJSON;
119-
120-
if (beforeSendSpan) {
121-
return applyBeforeSendSpanCallback(cleanSpanJSON, beforeSendSpan);
122-
}
123113
return cleanSpanJSON;
124114
});
125115

@@ -145,12 +135,3 @@ function sendSegment(
145135

146136
spanTreeMap.delete(spanTreeMapKey);
147137
}
148-
149-
function applyBeforeSendSpanCallback(span: SpanV2JSON, beforeSendSpan: (span: SpanV2JSON) => SpanV2JSON): SpanV2JSON {
150-
const modifedSpan = beforeSendSpan(span);
151-
if (!modifedSpan) {
152-
showSpanDropWarning();
153-
return span;
154-
}
155-
return modifedSpan;
156-
}

packages/core/src/client.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,14 @@ import type { RequestEventData } from './types-hoist/request';
3131
import type { SdkMetadata } from './types-hoist/sdkmetadata';
3232
import type { Session, SessionAggregates } from './types-hoist/session';
3333
import type { SeverityLevel } from './types-hoist/severity';
34-
import type { Span, SpanAttributes, SpanContextData, SpanJSON, SpanV2JSON } from './types-hoist/span';
34+
import type {
35+
Span,
36+
SpanAttributes,
37+
SpanContextData,
38+
SpanJSON,
39+
SpanV2JSON,
40+
SpanV2JSONWithSegmentRef,
41+
} from './types-hoist/span';
3542
import type { StartSpanOptions } from './types-hoist/startSpanOptions';
3643
import type { Transport, TransportMakeRequestResponse } from './types-hoist/transport';
3744
import { isV2BeforeSendSpanCallback } from './utils/beforeSendSpan';
@@ -620,7 +627,7 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
620627
/**
621628
* Register a callback for when the span JSON is ready to be enqueued into the span buffer.
622629
*/
623-
public on(hook: 'enqueueSpan', callback: (spanJSON: SpanV2JSON) => void): () => void;
630+
public on(hook: 'enqueueSpan', callback: (spanJSON: SpanV2JSONWithSegmentRef) => void): () => void;
624631
/**
625632
* Register a callback for when a span JSON is processed, to add some attributes to the span JSON.
626633
*/
@@ -906,7 +913,7 @@ export abstract class Client<O extends ClientOptions = ClientOptions> {
906913
/** Fire a hook after the `segmentSpanEnd` hook is fired. */
907914
public emit(hook: 'afterSegmentSpanEnd', span: Span): void;
908915
/** Fire a hook after a span ready to be enqueued into the span buffer. */
909-
public emit(hook: 'enqueueSpan', spanJSON: SpanV2JSON): void;
916+
public emit(hook: 'enqueueSpan', spanJSON: SpanV2JSONWithSegmentRef): void;
910917

911918
/**
912919
* Fire a hook indicating that an idle span is allowed to auto finish.

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ export {
9090
showSpanDropWarning,
9191
} from './utils/spanUtils';
9292
export { captureSpan } from './spans/captureSpan';
93-
export type { SpanV2JSONWithSegmentRef } from './spans/captureSpan';
9493
export { safeSetSpanAttributes, safeSetSpanJSONAttributes } from './spans/spanFirstUtils';
9594
export { attributesFromObject } from './utils/attributes';
9695
export { _setSpanForScope as _INTERNAL_setSpanForScope } from './utils/spanOnScope';
@@ -450,6 +449,7 @@ export type {
450449
SpanContextData,
451450
TraceFlag,
452451
SpanV2JSON,
452+
SpanV2JSONWithSegmentRef,
453453
} from './types-hoist/span';
454454
export type { SpanStatus } from './types-hoist/spanStatus';
455455
export type { Log, LogSeverityLevel } from './types-hoist/log';

packages/core/src/spans/captureSpan.ts

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Attributes, RawAttribute, RawAttributes } from '../attributes';
12
import type { Client } from '../client';
23
import { getClient, getGlobalScope } from '../currentScopes';
34
import { DEBUG_BUILD } from '../debug-build';
@@ -16,35 +17,27 @@ import {
1617
} from '../semanticAttributes';
1718
import { getCapturedScopesOnSpan } from '../tracing/utils';
1819
import type { SerializedAttributes } from '../types-hoist/attributes';
20+
import { Contexts } from '../types-hoist/context';
1921
import type { Span, SpanV2JSON } from '../types-hoist/span';
2022
import { mergeScopeData } from '../utils/applyScopeDataToEvent';
23+
import { isV2BeforeSendSpanCallback } from '../utils/beforeSendSpan';
2124
import { debug } from '../utils/debug-logger';
2225
import { INTERNAL_getSegmentSpan, spanToV2JSON } from '../utils/spanUtils';
23-
import { safeSetSpanJSONAttributes } from './spanFirstUtils';
24-
25-
/**
26-
* A SpanV2JSON with an attached reference to the segment span.
27-
* This reference is used to compute dynamic sampling context before sending.
28-
* The reference MUST be removed before sending the span envelope.
29-
*/
30-
export interface SpanV2JSONWithSegmentRef extends SpanV2JSON {
31-
_segmentSpan: Span;
32-
}
33-
26+
import { applyBeforeSendSpanCallback, safeSetSpanJSONAttributes } from './spanFirstUtils';
3427
/**
3528
* Captures a span and returns a JSON representation to be enqueued for sending.
3629
*
3730
* IMPORTANT: This function converts the span to JSON immediately to avoid writing
3831
* to an already-ended OTel span instance (which is blocked by the OTel Span class).
3932
*/
40-
export function captureSpan(span: Span, client = getClient()): SpanV2JSONWithSegmentRef | void {
33+
export function captureSpan(span: Span, client = getClient()): void {
4134
if (!client) {
4235
DEBUG_BUILD && debug.warn('No client available to capture span.');
4336
return;
4437
}
4538

4639
// Convert to JSON FIRST - we cannot write to an already-ended span
47-
const spanJSON = spanToV2JSON(span) as SpanV2JSONWithSegmentRef;
40+
const spanJSON = spanToV2JSON(span);
4841

4942
const segmentSpan = INTERNAL_getSegmentSpan(span);
5043
const serializedSegmentSpan = spanToV2JSON(segmentSpan);
@@ -60,29 +53,31 @@ export function captureSpan(span: Span, client = getClient()): SpanV2JSONWithSeg
6053
applyScopeToSegmentSpan(spanJSON, finalScopeData, originalAttributes);
6154
}
6255

63-
// Attach segment span reference for DSC generation at send time
64-
spanJSON._segmentSpan = segmentSpan;
65-
6656
// Allow integrations to add additional data to the span JSON
6757
client.emit('processSpan', spanJSON, { readOnlySpan: span });
6858

69-
// Enqueue the JSON representation for sending
70-
// Note: We now enqueue JSON instead of the span instance to avoid mutating ended spans
71-
client.emit('enqueueSpan', spanJSON);
59+
const beforeSendSpan = client.getOptions().beforeSendSpan;
60+
const processedSpan = isV2BeforeSendSpanCallback(beforeSendSpan)
61+
? applyBeforeSendSpanCallback(spanJSON, beforeSendSpan)
62+
: spanJSON;
7263

73-
return spanJSON;
64+
const spanWithRef = {
65+
...processedSpan,
66+
_segmentSpan: segmentSpan,
67+
};
68+
69+
client.emit('enqueueSpan', spanWithRef);
7470
}
7571

7672
function applyScopeToSegmentSpan(
7773
segmentSpanJSON: SpanV2JSON,
7874
scopeData: ScopeData,
7975
originalAttributes: SerializedAttributes,
8076
): void {
81-
// TODO: Apply all scope data from auto instrumentation (contexts, request) to segment span
82-
const { attributes } = scopeData;
83-
if (attributes) {
84-
safeSetSpanJSONAttributes(segmentSpanJSON, attributes, originalAttributes);
85-
}
77+
// TODO: Apply all scope and request data from auto instrumentation (contexts, request) to segment span
78+
const { contexts } = scopeData;
79+
80+
safeSetSpanJSONAttributes(segmentSpanJSON, contextsToAttributes(contexts), originalAttributes);
8681
}
8782

8883
function applyCommonSpanAttributes(
@@ -113,6 +108,7 @@ function applyCommonSpanAttributes(
113108
[SEMANTIC_ATTRIBUTE_USER_USERNAME]: scopeData.user?.username,
114109
}
115110
: {}),
111+
...scopeData.attributes,
116112
},
117113
originalAttributes,
118114
);
@@ -129,3 +125,13 @@ function getFinalScopeData(isolationScope: Scope | undefined, scope: Scope | und
129125
}
130126
return finalScopeData;
131127
}
128+
129+
function contextsToAttributes(contexts: Contexts): RawAttributes<Record<string, unknown>> {
130+
return {
131+
'os.build_id': contexts.os?.build,
132+
'os.name': contexts.os?.name,
133+
'os.version': contexts.os?.version,
134+
// TODO: Add to Sentry SemConv
135+
'os.kernel_version': contexts.os?.kernel_version,
136+
};
137+
}

packages/core/src/spans/spanFirstUtils.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { isAttributeObject } from '../attributes';
33
import type { SerializedAttributes } from '../types-hoist/attributes';
44
import type { Span, SpanV2JSON } from '../types-hoist/span';
55
import { attributeValueToSerializedAttribute } from '../utils/attributes';
6+
import { showSpanDropWarning } from '../utils/spanUtils';
67

78
/**
89
* Only set a span attribute if it is not already set.
@@ -55,6 +56,21 @@ function setAttributeOnSpanWithMaybeUnit(span: Span, attributeKey: string, attri
5556
}
5657
}
5758

59+
/**
60+
* Apply a user-provided beforeSendSpan callback to a span JSON.
61+
*/
62+
export function applyBeforeSendSpanCallback(
63+
span: SpanV2JSON,
64+
beforeSendSpan: (span: SpanV2JSON) => SpanV2JSON,
65+
): SpanV2JSON {
66+
const modifedSpan = beforeSendSpan(span);
67+
if (!modifedSpan) {
68+
showSpanDropWarning();
69+
return span;
70+
}
71+
return modifedSpan;
72+
}
73+
5874
function setAttributeOnSpanJSONWithMaybeUnit(
5975
spanJSON: SpanV2JSON,
6076
attributeKey: string,

packages/core/src/types-hoist/span.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ export type SpanAttributes = Partial<{
3535
/** This type is aligned with the OpenTelemetry TimeInput type. */
3636
export type SpanTimeInput = HrTime | number | Date;
3737

38+
/**
39+
* JSON representation of a v2 span, as it should be sent to Sentry.
40+
*/
3841
export interface SpanV2JSON {
3942
trace_id: string;
4043
parent_span_id?: string;
@@ -48,6 +51,15 @@ export interface SpanV2JSON {
4851
links?: SpanLinkJSON<SerializedAttributes>[];
4952
}
5053

54+
/**
55+
* A SpanV2JSON with an attached reference to the segment span.
56+
* This reference is used to compute dynamic sampling context before sending.
57+
* The reference MUST be removed before sending the span envelope.
58+
*/
59+
export interface SpanV2JSONWithSegmentRef extends SpanV2JSON {
60+
_segmentSpan: Span;
61+
}
62+
5163
export type SerializedSpanContainer = {
5264
items: Array<SpanV2JSON>;
5365
};

packages/node-core/src/integrations/context.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type {
1515
IntegrationFn,
1616
OsContext,
1717
} from '@sentry/core';
18-
import { defineIntegration } from '@sentry/core';
18+
import { debug, defineIntegration, getGlobalScope } from '@sentry/core';
1919

2020
export const readFileAsync = promisify(readFile);
2121
export const readDirAsync = promisify(readdir);
@@ -107,6 +107,31 @@ const _nodeContextIntegration = ((options: ContextOptions = {}) => {
107107

108108
return {
109109
name: INTEGRATION_NAME,
110+
setupOnce() {
111+
console.log('xx setupOnce');
112+
_getContexts()
113+
.then(updatedContext => {
114+
const globalScope = getGlobalScope();
115+
const previousContexts = globalScope.getScopeData().contexts;
116+
117+
const contexts = {
118+
app: { ...updatedContext.app, ...previousContexts?.app },
119+
os: { ...updatedContext.os, ...previousContexts?.os },
120+
device: { ...updatedContext.device, ...previousContexts?.device },
121+
culture: { ...updatedContext.culture, ...previousContexts?.culture },
122+
cloud_resource: { ...updatedContext.cloud_resource, ...previousContexts?.cloud_resource },
123+
};
124+
125+
Object.keys(contexts).forEach(key => {
126+
globalScope.setContext(key, contexts[key as keyof Event['contexts']]);
127+
});
128+
129+
console.log('xx set contexts to global scope', contexts);
130+
})
131+
.catch(() => {
132+
debug.warn(`[${INTEGRATION_NAME}] Failed to get contexts from Node`);
133+
});
134+
},
110135
// TODO (span-streaming): we probably need to apply this to spans via a hook IF we decide to apply contexts to (segment) spans
111136
processEvent(event) {
112137
return addContext(event);

0 commit comments

Comments
 (0)