Skip to content

Commit abb5ddc

Browse files
committed
apply common attributes
1 parent c13d2f2 commit abb5ddc

File tree

3 files changed

+103
-4
lines changed

3 files changed

+103
-4
lines changed

.size-limit.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@ module.exports = [
4040
gzip: true,
4141
limit: '41 KB',
4242
},
43+
{
44+
name: '@sentry/browser (incl. Tracing with Span Streaming)',
45+
path: 'packages/browser/build/npm/esm/index.js',
46+
import: createImport('init', 'browserTracingIntegration', 'spanStreamingIntegration'),
47+
gzip: true,
48+
limit: '41.5 KB',
49+
},
4350
{
4451
name: '@sentry/browser (incl. Tracing, Replay)',
4552
path: 'packages/browser/build/npm/esm/index.js',

packages/browser/src/integrations/spanstreaming.ts

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
1-
import type { Client, IntegrationFn, Span, SpanV2JSON } from '@sentry/core';
1+
import type { Client, IntegrationFn, Span, SpanAttributes, SpanAttributeValue, SpanV2JSON } from '@sentry/core';
22
import {
33
createSpanV2Envelope,
44
debug,
55
defineIntegration,
6+
getCapturedScopesOnSpan,
67
getDynamicSamplingContextFromSpan,
8+
getGlobalScope,
79
getRootSpan as getSegmentSpan,
810
isV2BeforeSendSpanCallback,
11+
mergeScopeData,
912
reparentChildSpans,
13+
SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT,
14+
SEMANTIC_ATTRIBUTE_SENTRY_RELEASE,
15+
SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,
16+
SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,
17+
SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME,
18+
SEMANTIC_ATTRIBUTE_USER_EMAIL,
19+
SEMANTIC_ATTRIBUTE_USER_ID,
20+
SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS,
21+
SEMANTIC_ATTRIBUTE_USER_USERNAME,
1022
shouldIgnoreSpan,
1123
showSpanDropWarning,
1224
spanToV2JSON,
@@ -91,6 +103,9 @@ interface SpanProcessingOptions {
91103
beforeSendSpan: ((span: SpanV2JSON) => SpanV2JSON) | undefined;
92104
}
93105

106+
/**
107+
* Just the traceid alone isn't enough because there can be multiple span trees with the same traceid.
108+
*/
94109
function getSpanTreeMapKey(span: Span): string {
95110
return `${span.spanContext().traceId}-${getSegmentSpan(span).spanContext().spanId}`;
96111
}
@@ -107,13 +122,17 @@ function processAndSendSpans(
107122
spanTreeMap.delete(spanTreeMapKey);
108123
return;
109124
}
125+
const segmentSpanJson = spanToV2JSON(segmentSpan);
110126

111-
const { ignoreSpans } = client.getOptions();
127+
for (const span of spansOfTrace) {
128+
applyCommonSpanAttributes(span, segmentSpanJson, client);
129+
}
112130

113-
// TODO: Apply scopes to spans
131+
// TODO: Apply scope data and contexts to segment span
132+
133+
const { ignoreSpans } = client.getOptions();
114134

115135
// 1. Check if the entire span tree is ignored by ignoreSpans
116-
const segmentSpanJson = spanToV2JSON(segmentSpan);
117136
if (ignoreSpans?.length && shouldIgnoreSpan(segmentSpanJson, ignoreSpans)) {
118137
client.recordDroppedEvent('before_send', 'span', spansOfTrace.size);
119138
spanTreeMap.delete(spanTreeMapKey);
@@ -166,6 +185,41 @@ function processAndSendSpans(
166185
spanTreeMap.delete(spanTreeMapKey);
167186
}
168187

188+
function applyCommonSpanAttributes(span: Span, serializedSegmentSpan: SpanV2JSON, client: Client): void {
189+
const sdk = client.getSdkMetadata();
190+
const { release, environment, sendDefaultPii } = client.getOptions();
191+
192+
const { isolationScope: spanIsolationScope, scope: spanScope } = getCapturedScopesOnSpan(span);
193+
194+
const originalAttributeKeys = Object.keys(spanToV2JSON(span).attributes ?? {});
195+
196+
// TODO: Extract this scope data merge to a helper in core. It's used in multiple places.
197+
const finalScopeData = getGlobalScope().getScopeData();
198+
if (spanIsolationScope) {
199+
mergeScopeData(finalScopeData, spanIsolationScope.getScopeData());
200+
}
201+
if (spanScope) {
202+
mergeScopeData(finalScopeData, spanScope.getScopeData());
203+
}
204+
205+
// avoid overwriting any previously set attributes (from users or potentially our SDK instrumentation)
206+
setAttributesIfNotPresent(span, originalAttributeKeys, {
207+
[SEMANTIC_ATTRIBUTE_SENTRY_RELEASE]: release,
208+
[SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT]: environment,
209+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: serializedSegmentSpan.name,
210+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: sdk?.sdk?.name,
211+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: sdk?.sdk?.version,
212+
...(sendDefaultPii
213+
? {
214+
[SEMANTIC_ATTRIBUTE_USER_ID]: finalScopeData.user?.id,
215+
[SEMANTIC_ATTRIBUTE_USER_EMAIL]: finalScopeData.user?.email,
216+
[SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS]: finalScopeData.user?.ip_address ?? undefined,
217+
[SEMANTIC_ATTRIBUTE_USER_USERNAME]: finalScopeData.user?.username,
218+
}
219+
: {}),
220+
});
221+
}
222+
169223
function applyBeforeSendSpanCallback(span: SpanV2JSON, beforeSendSpan: (span: SpanV2JSON) => SpanV2JSON): SpanV2JSON {
170224
const modifedSpan = beforeSendSpan(span);
171225
if (!modifedSpan) {
@@ -174,3 +228,11 @@ function applyBeforeSendSpanCallback(span: SpanV2JSON, beforeSendSpan: (span: Sp
174228
}
175229
return modifedSpan;
176230
}
231+
232+
function setAttributesIfNotPresent(span: Span, originalAttributeKeys: string[], newAttributes: SpanAttributes): void {
233+
Object.keys(newAttributes).forEach(key => {
234+
if (!originalAttributeKeys.includes(key)) {
235+
span.setAttribute(key, newAttributes[key]);
236+
}
237+
});
238+
}

packages/core/src/semanticAttributes.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,33 @@ export const SEMANTIC_ATTRIBUTE_URL_FULL = 'url.full';
7777
* @see https://develop.sentry.dev/sdk/telemetry/traces/span-links/#link-types
7878
*/
7979
export const SEMANTIC_LINK_ATTRIBUTE_LINK_TYPE = 'sentry.link.type';
80+
81+
// some attributes for now exclusively used for span streaming
82+
// @see https://develop.sentry.dev/sdk/telemetry/spans/span-protocol/#common-attribute-keys
83+
84+
/** The release version of the application */
85+
export const SEMANTIC_ATTRIBUTE_SENTRY_RELEASE = 'sentry.release';
86+
/** The environment name (e.g., "production", "staging", "development") */
87+
export const SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT = 'sentry.environment';
88+
/** The segment name (e.g., "GET /users") */
89+
export const SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME = 'sentry.segment_name';
90+
/** The operating system name (e.g., "Linux", "Windows", "macOS") */
91+
export const SEMANTIC_ATTRIBUTE_OS_NAME = 'os.name';
92+
/** The browser name (e.g., "Chrome", "Firefox", "Safari") */
93+
export const SEMANTIC_ATTRIBUTE_BROWSER_VERSION = 'browser.name';
94+
/** The user ID (gated by sendDefaultPii) */
95+
export const SEMANTIC_ATTRIBUTE_USER_ID = 'user.id';
96+
/** The user email (gated by sendDefaultPii) */
97+
export const SEMANTIC_ATTRIBUTE_USER_EMAIL = 'user.email';
98+
/** The user IP address (gated by sendDefaultPii) */
99+
export const SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS = 'user.ip_address';
100+
/** The user username (gated by sendDefaultPii) */
101+
export const SEMANTIC_ATTRIBUTE_USER_USERNAME = 'user.username';
102+
/** The thread ID */
103+
export const SEMANTIC_ATTRIBUTE_THREAD_ID = 'thread.id';
104+
/** The thread name */
105+
export const SEMANTIC_ATTRIBUTE_THREAD_NAME = 'thread.name';
106+
/** The name of the Sentry SDK (e.g., "sentry.php", "sentry.javascript") */
107+
export const SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME = 'sentry.sdk.name';
108+
/** The version of the Sentry SDK */
109+
export const SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION = 'sentry.sdk.version';

0 commit comments

Comments
 (0)