@sentry/browser 10.26.0 → 10.28.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/npm/cjs/dev/client.js +0 -1
- package/build/npm/cjs/dev/client.js.map +1 -1
- package/build/npm/cjs/dev/index.js +5 -0
- package/build/npm/cjs/dev/index.js.map +1 -1
- package/build/npm/cjs/dev/integrations/httpcontext.js +2 -0
- package/build/npm/cjs/dev/integrations/httpcontext.js.map +1 -1
- package/build/npm/cjs/dev/integrations/spanstreaming.js +217 -0
- package/build/npm/cjs/dev/integrations/spanstreaming.js.map +1 -0
- package/build/npm/cjs/dev/profiling/UIProfiler.js +98 -41
- package/build/npm/cjs/dev/profiling/UIProfiler.js.map +1 -1
- package/build/npm/cjs/dev/profiling/index.js +59 -0
- package/build/npm/cjs/dev/profiling/index.js.map +1 -0
- package/build/npm/cjs/dev/profiling/integration.js +12 -10
- package/build/npm/cjs/dev/profiling/integration.js.map +1 -1
- package/build/npm/cjs/dev/profiling/utils.js +11 -5
- package/build/npm/cjs/dev/profiling/utils.js.map +1 -1
- package/build/npm/cjs/dev/tracing/request.js +1 -0
- package/build/npm/cjs/dev/tracing/request.js.map +1 -1
- package/build/npm/cjs/prod/client.js +0 -1
- package/build/npm/cjs/prod/client.js.map +1 -1
- package/build/npm/cjs/prod/index.js +5 -0
- package/build/npm/cjs/prod/index.js.map +1 -1
- package/build/npm/cjs/prod/integrations/httpcontext.js +2 -0
- package/build/npm/cjs/prod/integrations/httpcontext.js.map +1 -1
- package/build/npm/cjs/prod/integrations/spanstreaming.js +217 -0
- package/build/npm/cjs/prod/integrations/spanstreaming.js.map +1 -0
- package/build/npm/cjs/prod/profiling/UIProfiler.js +98 -41
- package/build/npm/cjs/prod/profiling/UIProfiler.js.map +1 -1
- package/build/npm/cjs/prod/profiling/index.js +59 -0
- package/build/npm/cjs/prod/profiling/index.js.map +1 -0
- package/build/npm/cjs/prod/profiling/integration.js +12 -10
- package/build/npm/cjs/prod/profiling/integration.js.map +1 -1
- package/build/npm/cjs/prod/profiling/utils.js +11 -5
- package/build/npm/cjs/prod/profiling/utils.js.map +1 -1
- package/build/npm/cjs/prod/tracing/request.js +1 -0
- package/build/npm/cjs/prod/tracing/request.js.map +1 -1
- package/build/npm/esm/dev/client.js +0 -1
- package/build/npm/esm/dev/client.js.map +1 -1
- package/build/npm/esm/dev/index.js +3 -1
- package/build/npm/esm/dev/index.js.map +1 -1
- package/build/npm/esm/dev/integrations/httpcontext.js +2 -0
- package/build/npm/esm/dev/integrations/httpcontext.js.map +1 -1
- package/build/npm/esm/dev/integrations/spanstreaming.js +215 -0
- package/build/npm/esm/dev/integrations/spanstreaming.js.map +1 -0
- package/build/npm/esm/dev/package.json +1 -1
- package/build/npm/esm/dev/profiling/UIProfiler.js +100 -43
- package/build/npm/esm/dev/profiling/UIProfiler.js.map +1 -1
- package/build/npm/esm/dev/profiling/index.js +57 -0
- package/build/npm/esm/dev/profiling/index.js.map +1 -0
- package/build/npm/esm/dev/profiling/integration.js +13 -11
- package/build/npm/esm/dev/profiling/integration.js.map +1 -1
- package/build/npm/esm/dev/profiling/utils.js +12 -6
- package/build/npm/esm/dev/profiling/utils.js.map +1 -1
- package/build/npm/esm/dev/tracing/request.js +1 -0
- package/build/npm/esm/dev/tracing/request.js.map +1 -1
- package/build/npm/esm/prod/client.js +0 -1
- package/build/npm/esm/prod/client.js.map +1 -1
- package/build/npm/esm/prod/index.js +3 -1
- package/build/npm/esm/prod/index.js.map +1 -1
- package/build/npm/esm/prod/integrations/httpcontext.js +2 -0
- package/build/npm/esm/prod/integrations/httpcontext.js.map +1 -1
- package/build/npm/esm/prod/integrations/spanstreaming.js +215 -0
- package/build/npm/esm/prod/integrations/spanstreaming.js.map +1 -0
- package/build/npm/esm/prod/package.json +1 -1
- package/build/npm/esm/prod/profiling/UIProfiler.js +100 -43
- package/build/npm/esm/prod/profiling/UIProfiler.js.map +1 -1
- package/build/npm/esm/prod/profiling/index.js +57 -0
- package/build/npm/esm/prod/profiling/index.js.map +1 -0
- package/build/npm/esm/prod/profiling/integration.js +13 -11
- package/build/npm/esm/prod/profiling/integration.js.map +1 -1
- package/build/npm/esm/prod/profiling/utils.js +12 -6
- package/build/npm/esm/prod/profiling/utils.js.map +1 -1
- package/build/npm/esm/prod/tracing/request.js +1 -0
- package/build/npm/esm/prod/tracing/request.js.map +1 -1
- package/build/npm/types/client.d.ts.map +1 -1
- package/build/npm/types/exports.d.ts +1 -0
- package/build/npm/types/exports.d.ts.map +1 -1
- package/build/npm/types/index.d.ts +2 -1
- package/build/npm/types/index.d.ts.map +1 -1
- package/build/npm/types/integrations/httpcontext.d.ts.map +1 -1
- package/build/npm/types/integrations/spanstreaming.d.ts +5 -0
- package/build/npm/types/integrations/spanstreaming.d.ts.map +1 -0
- package/build/npm/types/profiling/UIProfiler.d.ts +23 -22
- package/build/npm/types/profiling/UIProfiler.d.ts.map +1 -1
- package/build/npm/types/profiling/index.d.ts +8 -0
- package/build/npm/types/profiling/index.d.ts.map +1 -0
- package/build/npm/types/profiling/integration.d.ts.map +1 -1
- package/build/npm/types/profiling/utils.d.ts +1 -1
- package/build/npm/types/profiling/utils.d.ts.map +1 -1
- package/build/npm/types/tracing/request.d.ts.map +1 -1
- package/build/npm/types-ts3.8/exports.d.ts +1 -0
- package/build/npm/types-ts3.8/index.d.ts +2 -1
- package/build/npm/types-ts3.8/integrations/spanstreaming.d.ts +5 -0
- package/build/npm/types-ts3.8/profiling/UIProfiler.d.ts +23 -22
- package/build/npm/types-ts3.8/profiling/index.d.ts +8 -0
- package/build/npm/types-ts3.8/profiling/utils.d.ts +1 -1
- package/package.json +7 -7
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { defineIntegration, debug, isV2BeforeSendSpanCallback, INTERNAL_getSegmentSpan, spanToV2JSON, shouldIgnoreSpan, SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME, reparentChildSpans, getDynamicSamplingContextFromSpan, createSpanV2Envelope, getCapturedScopesOnSpan, SEMANTIC_ATTRIBUTE_USER_USERNAME, SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS, SEMANTIC_ATTRIBUTE_USER_EMAIL, SEMANTIC_ATTRIBUTE_USER_ID, SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION, SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME, SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID, SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME, showSpanDropWarning, getGlobalScope, mergeScopeData, SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT, SEMANTIC_ATTRIBUTE_SENTRY_RELEASE } from '@sentry/core';
|
|
2
|
+
import { DEBUG_BUILD } from '../debug-build.js';
|
|
3
|
+
|
|
4
|
+
const spanStreamingIntegration = defineIntegration(((userOptions) => {
|
|
5
|
+
const validatedUserProvidedBatchLimit =
|
|
6
|
+
userOptions?.batchLimit && userOptions.batchLimit <= 1000 && userOptions.batchLimit >= 1
|
|
7
|
+
? userOptions.batchLimit
|
|
8
|
+
: undefined;
|
|
9
|
+
|
|
10
|
+
if (DEBUG_BUILD && userOptions?.batchLimit && !validatedUserProvidedBatchLimit) {
|
|
11
|
+
debug.warn('SpanStreaming batchLimit must be between 1 and 1000, defaulting to 1000');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const options = {
|
|
15
|
+
...userOptions,
|
|
16
|
+
batchLimit:
|
|
17
|
+
userOptions?.batchLimit && userOptions.batchLimit <= 1000 && userOptions.batchLimit >= 1
|
|
18
|
+
? userOptions.batchLimit
|
|
19
|
+
: 1000,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// key: traceId-segmentSpanId
|
|
23
|
+
const spanTreeMap = new Map();
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
name: 'SpanStreaming',
|
|
27
|
+
setup(client) {
|
|
28
|
+
const clientOptions = client.getOptions();
|
|
29
|
+
const beforeSendSpan = clientOptions.beforeSendSpan;
|
|
30
|
+
|
|
31
|
+
const initialMessage = 'spanStreamingIntegration requires';
|
|
32
|
+
const fallbackMsg = 'Falling back to static trace lifecycle.';
|
|
33
|
+
|
|
34
|
+
if (clientOptions.traceLifecycle !== 'stream') {
|
|
35
|
+
DEBUG_BUILD && debug.warn(`${initialMessage} \`traceLifecycle\` to be set to "stream"! ${fallbackMsg}`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (beforeSendSpan && !isV2BeforeSendSpanCallback(beforeSendSpan)) {
|
|
40
|
+
client.getOptions().traceLifecycle = 'static';
|
|
41
|
+
debug.warn(`${initialMessage} a beforeSendSpan callback using \`withStreamSpan\`! ${fallbackMsg}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
client.on('spanEnd', span => {
|
|
46
|
+
const spanTreeMapKey = getSpanTreeMapKey(span);
|
|
47
|
+
const spanBuffer = spanTreeMap.get(spanTreeMapKey);
|
|
48
|
+
if (spanBuffer) {
|
|
49
|
+
spanBuffer.add(span);
|
|
50
|
+
} else {
|
|
51
|
+
spanTreeMap.set(spanTreeMapKey, new Set([span]));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// For now, we send all spans on local segment (root) span end.
|
|
56
|
+
// TODO: This will change once we have more concrete ideas about a universal SDK data buffer.
|
|
57
|
+
client.on('segmentSpanEnd', segmentSpan => {
|
|
58
|
+
processAndSendSpans(segmentSpan, {
|
|
59
|
+
spanTreeMap: spanTreeMap,
|
|
60
|
+
client,
|
|
61
|
+
batchLimit: options.batchLimit,
|
|
62
|
+
beforeSendSpan,
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}) );
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Just the traceid alone isn't enough because there can be multiple span trees with the same traceid.
|
|
71
|
+
*/
|
|
72
|
+
function getSpanTreeMapKey(span) {
|
|
73
|
+
return `${span.spanContext().traceId}-${INTERNAL_getSegmentSpan(span).spanContext().spanId}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function processAndSendSpans(
|
|
77
|
+
segmentSpan,
|
|
78
|
+
{ client, spanTreeMap, batchLimit, beforeSendSpan },
|
|
79
|
+
) {
|
|
80
|
+
const traceId = segmentSpan.spanContext().traceId;
|
|
81
|
+
const spanTreeMapKey = getSpanTreeMapKey(segmentSpan);
|
|
82
|
+
const spansOfTrace = spanTreeMap.get(spanTreeMapKey);
|
|
83
|
+
|
|
84
|
+
if (!spansOfTrace?.size) {
|
|
85
|
+
spanTreeMap.delete(spanTreeMapKey);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const segmentSpanJson = spanToV2JSON(segmentSpan);
|
|
90
|
+
|
|
91
|
+
for (const span of spansOfTrace) {
|
|
92
|
+
applyCommonSpanAttributes(span, segmentSpanJson, client);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const { ignoreSpans } = client.getOptions();
|
|
96
|
+
|
|
97
|
+
// 1. Check if the entire span tree is ignored by ignoreSpans
|
|
98
|
+
if (ignoreSpans?.length && shouldIgnoreSpan(segmentSpanJson, ignoreSpans)) {
|
|
99
|
+
client.recordDroppedEvent('before_send', 'span', spansOfTrace.size);
|
|
100
|
+
spanTreeMap.delete(spanTreeMapKey);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const serializedSpans = Array.from(spansOfTrace ?? []).map(s => {
|
|
105
|
+
const serialized = spanToV2JSON(s);
|
|
106
|
+
// remove internal span attributes we don't need to send.
|
|
107
|
+
delete serialized.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];
|
|
108
|
+
return serialized;
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const processedSpans = [];
|
|
112
|
+
let ignoredSpanCount = 0;
|
|
113
|
+
|
|
114
|
+
for (const span of serializedSpans) {
|
|
115
|
+
// 2. Check if child spans should be ignored
|
|
116
|
+
const isChildSpan = span.span_id !== segmentSpan.spanContext().spanId;
|
|
117
|
+
if (ignoreSpans?.length && isChildSpan && shouldIgnoreSpan(span, ignoreSpans)) {
|
|
118
|
+
reparentChildSpans(serializedSpans, span);
|
|
119
|
+
ignoredSpanCount++;
|
|
120
|
+
// drop this span by not adding it to the processedSpans array
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 3. Apply beforeSendSpan callback
|
|
125
|
+
// TODO: validate beforeSendSpan result/pass in a copy and merge afterwards
|
|
126
|
+
const processedSpan = beforeSendSpan ? applyBeforeSendSpanCallback(span, beforeSendSpan) : span;
|
|
127
|
+
processedSpans.push(processedSpan);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (ignoredSpanCount) {
|
|
131
|
+
client.recordDroppedEvent('before_send', 'span', ignoredSpanCount);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const batches = [];
|
|
135
|
+
for (let i = 0; i < processedSpans.length; i += batchLimit) {
|
|
136
|
+
batches.push(processedSpans.slice(i, i + batchLimit));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
DEBUG_BUILD && debug.log(`Sending trace ${traceId} in ${batches.length} batch${batches.length === 1 ? '' : 'es'}`);
|
|
140
|
+
|
|
141
|
+
const dsc = getDynamicSamplingContextFromSpan(segmentSpan);
|
|
142
|
+
|
|
143
|
+
for (const batch of batches) {
|
|
144
|
+
const envelope = createSpanV2Envelope(batch, dsc, client);
|
|
145
|
+
// no need to handle client reports for network errors,
|
|
146
|
+
// buffer overflows or rate limiting here. All of this is handled
|
|
147
|
+
// by client and transport.
|
|
148
|
+
client.sendEnvelope(envelope).then(null, reason => {
|
|
149
|
+
DEBUG_BUILD && debug.error('Error while sending span stream envelope:', reason);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
spanTreeMap.delete(spanTreeMapKey);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function applyCommonSpanAttributes(span, serializedSegmentSpan, client) {
|
|
157
|
+
const sdk = client.getSdkMetadata();
|
|
158
|
+
const { release, environment, sendDefaultPii } = client.getOptions();
|
|
159
|
+
|
|
160
|
+
const { isolationScope: spanIsolationScope, scope: spanScope } = getCapturedScopesOnSpan(span);
|
|
161
|
+
|
|
162
|
+
const originalAttributeKeys = Object.keys(spanToV2JSON(span).attributes ?? {});
|
|
163
|
+
|
|
164
|
+
const finalScopeData = getFinalScopeData(spanIsolationScope, spanScope);
|
|
165
|
+
|
|
166
|
+
// avoid overwriting any previously set attributes (from users or potentially our SDK instrumentation)
|
|
167
|
+
setAttributesIfNotPresent(span, originalAttributeKeys, {
|
|
168
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_RELEASE]: release,
|
|
169
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT]: environment,
|
|
170
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: serializedSegmentSpan.name,
|
|
171
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: serializedSegmentSpan.span_id,
|
|
172
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: sdk?.sdk?.name,
|
|
173
|
+
[SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: sdk?.sdk?.version,
|
|
174
|
+
...(sendDefaultPii
|
|
175
|
+
? {
|
|
176
|
+
[SEMANTIC_ATTRIBUTE_USER_ID]: finalScopeData.user?.id,
|
|
177
|
+
[SEMANTIC_ATTRIBUTE_USER_EMAIL]: finalScopeData.user?.email,
|
|
178
|
+
[SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS]: finalScopeData.user?.ip_address ?? undefined,
|
|
179
|
+
[SEMANTIC_ATTRIBUTE_USER_USERNAME]: finalScopeData.user?.username,
|
|
180
|
+
}
|
|
181
|
+
: {}),
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function applyBeforeSendSpanCallback(span, beforeSendSpan) {
|
|
186
|
+
const modifedSpan = beforeSendSpan(span);
|
|
187
|
+
if (!modifedSpan) {
|
|
188
|
+
showSpanDropWarning();
|
|
189
|
+
return span;
|
|
190
|
+
}
|
|
191
|
+
return modifedSpan;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function setAttributesIfNotPresent(span, originalAttributeKeys, newAttributes) {
|
|
195
|
+
Object.keys(newAttributes).forEach(key => {
|
|
196
|
+
if (!originalAttributeKeys.includes(key)) {
|
|
197
|
+
span.setAttribute(key, newAttributes[key]);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// TODO: Extract this to a helper in core. It's used in multiple places.
|
|
203
|
+
function getFinalScopeData(isolationScope, scope) {
|
|
204
|
+
const finalScopeData = getGlobalScope().getScopeData();
|
|
205
|
+
if (isolationScope) {
|
|
206
|
+
mergeScopeData(finalScopeData, isolationScope.getScopeData());
|
|
207
|
+
}
|
|
208
|
+
if (scope) {
|
|
209
|
+
mergeScopeData(finalScopeData, scope.getScopeData());
|
|
210
|
+
}
|
|
211
|
+
return finalScopeData;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { spanStreamingIntegration };
|
|
215
|
+
//# sourceMappingURL=spanstreaming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spanstreaming.js","sources":["../../../../../src/integrations/spanstreaming.ts"],"sourcesContent":["import type { Client, IntegrationFn, Scope, ScopeData, Span, SpanAttributes, SpanV2JSON } from '@sentry/core';\nimport {\n createSpanV2Envelope,\n debug,\n defineIntegration,\n getCapturedScopesOnSpan,\n getDynamicSamplingContextFromSpan,\n getGlobalScope,\n INTERNAL_getSegmentSpan,\n isV2BeforeSendSpanCallback,\n mergeScopeData,\n reparentChildSpans,\n SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME,\n SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT,\n SEMANTIC_ATTRIBUTE_SENTRY_RELEASE,\n SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME,\n SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION,\n SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID,\n SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME,\n SEMANTIC_ATTRIBUTE_USER_EMAIL,\n SEMANTIC_ATTRIBUTE_USER_ID,\n SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS,\n SEMANTIC_ATTRIBUTE_USER_USERNAME,\n shouldIgnoreSpan,\n showSpanDropWarning,\n spanToV2JSON,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from '../debug-build';\n\nexport interface SpanStreamingOptions {\n batchLimit: number;\n}\n\nexport const spanStreamingIntegration = defineIntegration(((userOptions?: Partial<SpanStreamingOptions>) => {\n const validatedUserProvidedBatchLimit =\n userOptions?.batchLimit && userOptions.batchLimit <= 1000 && userOptions.batchLimit >= 1\n ? userOptions.batchLimit\n : undefined;\n\n if (DEBUG_BUILD && userOptions?.batchLimit && !validatedUserProvidedBatchLimit) {\n debug.warn('SpanStreaming batchLimit must be between 1 and 1000, defaulting to 1000');\n }\n\n const options: SpanStreamingOptions = {\n ...userOptions,\n batchLimit:\n userOptions?.batchLimit && userOptions.batchLimit <= 1000 && userOptions.batchLimit >= 1\n ? userOptions.batchLimit\n : 1000,\n };\n\n // key: traceId-segmentSpanId\n const spanTreeMap = new Map<string, Set<Span>>();\n\n return {\n name: 'SpanStreaming',\n setup(client) {\n const clientOptions = client.getOptions();\n const beforeSendSpan = clientOptions.beforeSendSpan;\n\n const initialMessage = 'spanStreamingIntegration requires';\n const fallbackMsg = 'Falling back to static trace lifecycle.';\n\n if (clientOptions.traceLifecycle !== 'stream') {\n DEBUG_BUILD && debug.warn(`${initialMessage} \\`traceLifecycle\\` to be set to \"stream\"! ${fallbackMsg}`);\n return;\n }\n\n if (beforeSendSpan && !isV2BeforeSendSpanCallback(beforeSendSpan)) {\n client.getOptions().traceLifecycle = 'static';\n debug.warn(`${initialMessage} a beforeSendSpan callback using \\`withStreamSpan\\`! ${fallbackMsg}`);\n return;\n }\n\n client.on('spanEnd', span => {\n const spanTreeMapKey = getSpanTreeMapKey(span);\n const spanBuffer = spanTreeMap.get(spanTreeMapKey);\n if (spanBuffer) {\n spanBuffer.add(span);\n } else {\n spanTreeMap.set(spanTreeMapKey, new Set([span]));\n }\n });\n\n // For now, we send all spans on local segment (root) span end.\n // TODO: This will change once we have more concrete ideas about a universal SDK data buffer.\n client.on('segmentSpanEnd', segmentSpan => {\n processAndSendSpans(segmentSpan, {\n spanTreeMap: spanTreeMap,\n client,\n batchLimit: options.batchLimit,\n beforeSendSpan,\n });\n });\n },\n };\n}) satisfies IntegrationFn);\n\ninterface SpanProcessingOptions {\n client: Client;\n spanTreeMap: Map<string, Set<Span>>;\n batchLimit: number;\n beforeSendSpan: ((span: SpanV2JSON) => SpanV2JSON) | undefined;\n}\n\n/**\n * Just the traceid alone isn't enough because there can be multiple span trees with the same traceid.\n */\nfunction getSpanTreeMapKey(span: Span): string {\n return `${span.spanContext().traceId}-${INTERNAL_getSegmentSpan(span).spanContext().spanId}`;\n}\n\nfunction processAndSendSpans(\n segmentSpan: Span,\n { client, spanTreeMap, batchLimit, beforeSendSpan }: SpanProcessingOptions,\n): void {\n const traceId = segmentSpan.spanContext().traceId;\n const spanTreeMapKey = getSpanTreeMapKey(segmentSpan);\n const spansOfTrace = spanTreeMap.get(spanTreeMapKey);\n\n if (!spansOfTrace?.size) {\n spanTreeMap.delete(spanTreeMapKey);\n return;\n }\n\n const segmentSpanJson = spanToV2JSON(segmentSpan);\n\n for (const span of spansOfTrace) {\n applyCommonSpanAttributes(span, segmentSpanJson, client);\n }\n\n const { ignoreSpans } = client.getOptions();\n\n // 1. Check if the entire span tree is ignored by ignoreSpans\n if (ignoreSpans?.length && shouldIgnoreSpan(segmentSpanJson, ignoreSpans)) {\n client.recordDroppedEvent('before_send', 'span', spansOfTrace.size);\n spanTreeMap.delete(spanTreeMapKey);\n return;\n }\n\n const serializedSpans = Array.from(spansOfTrace ?? []).map(s => {\n const serialized = spanToV2JSON(s);\n // remove internal span attributes we don't need to send.\n delete serialized.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_CUSTOM_SPAN_NAME];\n return serialized;\n });\n\n const processedSpans = [];\n let ignoredSpanCount = 0;\n\n for (const span of serializedSpans) {\n // 2. Check if child spans should be ignored\n const isChildSpan = span.span_id !== segmentSpan.spanContext().spanId;\n if (ignoreSpans?.length && isChildSpan && shouldIgnoreSpan(span, ignoreSpans)) {\n reparentChildSpans(serializedSpans, span);\n ignoredSpanCount++;\n // drop this span by not adding it to the processedSpans array\n continue;\n }\n\n // 3. Apply beforeSendSpan callback\n // TODO: validate beforeSendSpan result/pass in a copy and merge afterwards\n const processedSpan = beforeSendSpan ? applyBeforeSendSpanCallback(span, beforeSendSpan) : span;\n processedSpans.push(processedSpan);\n }\n\n if (ignoredSpanCount) {\n client.recordDroppedEvent('before_send', 'span', ignoredSpanCount);\n }\n\n const batches: SpanV2JSON[][] = [];\n for (let i = 0; i < processedSpans.length; i += batchLimit) {\n batches.push(processedSpans.slice(i, i + batchLimit));\n }\n\n DEBUG_BUILD && debug.log(`Sending trace ${traceId} in ${batches.length} batch${batches.length === 1 ? '' : 'es'}`);\n\n const dsc = getDynamicSamplingContextFromSpan(segmentSpan);\n\n for (const batch of batches) {\n const envelope = createSpanV2Envelope(batch, dsc, client);\n // no need to handle client reports for network errors,\n // buffer overflows or rate limiting here. All of this is handled\n // by client and transport.\n client.sendEnvelope(envelope).then(null, reason => {\n DEBUG_BUILD && debug.error('Error while sending span stream envelope:', reason);\n });\n }\n\n spanTreeMap.delete(spanTreeMapKey);\n}\n\nfunction applyCommonSpanAttributes(span: Span, serializedSegmentSpan: SpanV2JSON, client: Client): void {\n const sdk = client.getSdkMetadata();\n const { release, environment, sendDefaultPii } = client.getOptions();\n\n const { isolationScope: spanIsolationScope, scope: spanScope } = getCapturedScopesOnSpan(span);\n\n const originalAttributeKeys = Object.keys(spanToV2JSON(span).attributes ?? {});\n\n const finalScopeData = getFinalScopeData(spanIsolationScope, spanScope);\n\n // avoid overwriting any previously set attributes (from users or potentially our SDK instrumentation)\n setAttributesIfNotPresent(span, originalAttributeKeys, {\n [SEMANTIC_ATTRIBUTE_SENTRY_RELEASE]: release,\n [SEMANTIC_ATTRIBUTE_SENTRY_ENVIRONMENT]: environment,\n [SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_NAME]: serializedSegmentSpan.name,\n [SEMANTIC_ATTRIBUTE_SENTRY_SEGMENT_ID]: serializedSegmentSpan.span_id,\n [SEMANTIC_ATTRIBUTE_SENTRY_SDK_NAME]: sdk?.sdk?.name,\n [SEMANTIC_ATTRIBUTE_SENTRY_SDK_VERSION]: sdk?.sdk?.version,\n ...(sendDefaultPii\n ? {\n [SEMANTIC_ATTRIBUTE_USER_ID]: finalScopeData.user?.id,\n [SEMANTIC_ATTRIBUTE_USER_EMAIL]: finalScopeData.user?.email,\n [SEMANTIC_ATTRIBUTE_USER_IP_ADDRESS]: finalScopeData.user?.ip_address ?? undefined,\n [SEMANTIC_ATTRIBUTE_USER_USERNAME]: finalScopeData.user?.username,\n }\n : {}),\n });\n}\n\nfunction applyBeforeSendSpanCallback(span: SpanV2JSON, beforeSendSpan: (span: SpanV2JSON) => SpanV2JSON): SpanV2JSON {\n const modifedSpan = beforeSendSpan(span);\n if (!modifedSpan) {\n showSpanDropWarning();\n return span;\n }\n return modifedSpan;\n}\n\nfunction setAttributesIfNotPresent(span: Span, originalAttributeKeys: string[], newAttributes: SpanAttributes): void {\n Object.keys(newAttributes).forEach(key => {\n if (!originalAttributeKeys.includes(key)) {\n span.setAttribute(key, newAttributes[key]);\n }\n });\n}\n\n// TODO: Extract this to a helper in core. It's used in multiple places.\nfunction getFinalScopeData(isolationScope: Scope | undefined, scope: Scope | undefined): ScopeData {\n const finalScopeData = getGlobalScope().getScopeData();\n if (isolationScope) {\n mergeScopeData(finalScopeData, isolationScope.getScopeData());\n }\n if (scope) {\n mergeScopeData(finalScopeData, scope.getScopeData());\n }\n return finalScopeData;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"type":"module","version":"10.
|
|
1
|
+
{"type":"module","version":"10.28.0-alpha.0","sideEffects":false}
|
|
@@ -1,58 +1,112 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { debug, uuid4, getGlobalScope, getRootSpan, getSdkMetadataForEnvelopeHeader, createEnvelope, dsnToString } from '@sentry/core';
|
|
2
2
|
import { DEBUG_BUILD } from '../debug-build.js';
|
|
3
|
-
import { startJSSelfProfile, createProfileChunkPayload, validateProfileChunk } from './utils.js';
|
|
3
|
+
import { shouldProfileSession, startJSSelfProfile, createProfileChunkPayload, validateProfileChunk } from './utils.js';
|
|
4
4
|
|
|
5
5
|
const CHUNK_INTERVAL_MS = 60000; // 1 minute
|
|
6
6
|
// Maximum length for trace lifecycle profiling per root span (e.g. if spanEnd never fires)
|
|
7
|
-
const MAX_ROOT_SPAN_PROFILE_MS = 300000; // 5 minutes
|
|
7
|
+
const MAX_ROOT_SPAN_PROFILE_MS = 300000; // 5 minutes max per root span in trace mode
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
10
|
+
* UIProfiler (Profiling V2):
|
|
11
|
+
* Supports two lifecycle modes:
|
|
12
|
+
* - 'manual': controlled explicitly via start()/stop()
|
|
13
|
+
* - 'trace': automatically runs while there are active sampled root spans
|
|
14
14
|
*
|
|
15
15
|
* Profiles are emitted as standalone `profile_chunk` envelopes either when:
|
|
16
16
|
* - there are no more sampled root spans, or
|
|
17
17
|
* - the 60s chunk timer elapses while profiling is running.
|
|
18
18
|
*/
|
|
19
|
-
class UIProfiler
|
|
19
|
+
class UIProfiler {
|
|
20
20
|
|
|
21
|
-
//
|
|
21
|
+
// Manual + Trace
|
|
22
|
+
// one per Profiler session
|
|
23
|
+
// current profiler instance active flag
|
|
24
|
+
// sampling decision for entire session
|
|
22
25
|
|
|
23
|
-
//
|
|
26
|
+
// Trace-only
|
|
24
27
|
|
|
25
28
|
constructor() {
|
|
26
29
|
this._client = undefined;
|
|
27
30
|
this._profiler = undefined;
|
|
28
31
|
this._chunkTimer = undefined;
|
|
29
|
-
|
|
30
|
-
this._rootSpanTimeouts = new Map();
|
|
32
|
+
|
|
31
33
|
this._profilerId = undefined;
|
|
32
34
|
this._isRunning = false;
|
|
33
35
|
this._sessionSampled = false;
|
|
36
|
+
this._lifecycleMode = undefined;
|
|
37
|
+
|
|
38
|
+
this._activeRootSpanIds = new Set();
|
|
39
|
+
this._rootSpanTimeouts = new Map();
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
/**
|
|
37
|
-
* Initialize the profiler with client
|
|
43
|
+
* Initialize the profiler with client, session sampling and lifecycle mode.
|
|
38
44
|
*/
|
|
39
|
-
initialize(client
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
initialize(client) {
|
|
46
|
+
const lifecycleMode = (client.getOptions() ).profileLifecycle;
|
|
47
|
+
const sessionSampled = shouldProfileSession(client.getOptions());
|
|
48
|
+
|
|
49
|
+
DEBUG_BUILD && debug.log(`[Profiling] Initializing profiler (lifecycle='${lifecycleMode}').`);
|
|
42
50
|
|
|
43
|
-
|
|
51
|
+
if (!sessionSampled) {
|
|
52
|
+
DEBUG_BUILD && debug.log('[Profiling] Session not sampled. Skipping lifecycle profiler initialization.');
|
|
53
|
+
}
|
|
44
54
|
|
|
55
|
+
// One Profiler ID per profiling session (user session)
|
|
56
|
+
this._profilerId = uuid4();
|
|
45
57
|
this._client = client;
|
|
46
58
|
this._sessionSampled = sessionSampled;
|
|
59
|
+
this._lifecycleMode = lifecycleMode;
|
|
47
60
|
|
|
48
|
-
|
|
61
|
+
if (lifecycleMode === 'trace') {
|
|
62
|
+
this._setupTraceLifecycleListeners(client);
|
|
63
|
+
}
|
|
49
64
|
}
|
|
50
65
|
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
66
|
+
/** Starts UI profiling (only effective in 'manual' mode and when sampled). */
|
|
67
|
+
start() {
|
|
68
|
+
if (this._lifecycleMode === 'trace') {
|
|
69
|
+
DEBUG_BUILD &&
|
|
70
|
+
debug.warn(
|
|
71
|
+
'[Profiling] `profileLifecycle` is set to "trace". Calls to `uiProfiler.start()` are ignored in trace mode.',
|
|
72
|
+
);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (this._isRunning) {
|
|
77
|
+
DEBUG_BUILD && debug.warn('[Profiling] Profile session is already running, `uiProfiler.start()` is a no-op.');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
55
81
|
if (!this._sessionSampled) {
|
|
82
|
+
DEBUG_BUILD && debug.warn('[Profiling] Session is not sampled, `uiProfiler.start()` is a no-op.');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this._beginProfiling();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Stops UI profiling (only effective in 'manual' mode). */
|
|
90
|
+
stop() {
|
|
91
|
+
if (this._lifecycleMode === 'trace') {
|
|
92
|
+
DEBUG_BUILD &&
|
|
93
|
+
debug.warn(
|
|
94
|
+
'[Profiling] `profileLifecycle` is set to "trace". Calls to `uiProfiler.stop()` are ignored in trace mode.',
|
|
95
|
+
);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!this._isRunning) {
|
|
100
|
+
DEBUG_BUILD && debug.warn('[Profiling] Profiler is not running, `uiProfiler.stop()` is a no-op.');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
this._endProfiling();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** Handle an already-active root span at integration setup time (used only in trace mode). */
|
|
108
|
+
notifyRootSpanActive(rootSpan) {
|
|
109
|
+
if (this._lifecycleMode !== 'trace' || !this._sessionSampled) {
|
|
56
110
|
return;
|
|
57
111
|
}
|
|
58
112
|
|
|
@@ -61,7 +115,7 @@ class UIProfiler {
|
|
|
61
115
|
return;
|
|
62
116
|
}
|
|
63
117
|
|
|
64
|
-
this.
|
|
118
|
+
this._registerTraceRootSpan(spanId);
|
|
65
119
|
|
|
66
120
|
const rootSpanCount = this._activeRootSpanIds.size;
|
|
67
121
|
|
|
@@ -69,20 +123,20 @@ class UIProfiler {
|
|
|
69
123
|
DEBUG_BUILD &&
|
|
70
124
|
debug.log('[Profiling] Detected already active root span during setup. Active root spans now:', rootSpanCount);
|
|
71
125
|
|
|
72
|
-
this.
|
|
126
|
+
this._beginProfiling();
|
|
73
127
|
}
|
|
74
128
|
}
|
|
75
129
|
|
|
76
130
|
/**
|
|
77
|
-
*
|
|
131
|
+
* Begin profiling if not already running.
|
|
78
132
|
*/
|
|
79
|
-
|
|
133
|
+
_beginProfiling() {
|
|
80
134
|
if (this._isRunning) {
|
|
81
135
|
return;
|
|
82
136
|
}
|
|
83
137
|
this._isRunning = true;
|
|
84
138
|
|
|
85
|
-
DEBUG_BUILD && debug.log('[Profiling] Started profiling with
|
|
139
|
+
DEBUG_BUILD && debug.log('[Profiling] Started profiling with profiler ID:', this._profilerId);
|
|
86
140
|
|
|
87
141
|
// Expose profiler_id to match root spans with profiles
|
|
88
142
|
getGlobalScope().setContext('profile', { profiler_id: this._profilerId });
|
|
@@ -90,7 +144,7 @@ class UIProfiler {
|
|
|
90
144
|
this._startProfilerInstance();
|
|
91
145
|
|
|
92
146
|
if (!this._profiler) {
|
|
93
|
-
DEBUG_BUILD && debug.log('[Profiling]
|
|
147
|
+
DEBUG_BUILD && debug.log('[Profiling] Failed to start JS Profiler; stopping.');
|
|
94
148
|
this._resetProfilerInfo();
|
|
95
149
|
return;
|
|
96
150
|
}
|
|
@@ -98,15 +152,13 @@ class UIProfiler {
|
|
|
98
152
|
this._startPeriodicChunking();
|
|
99
153
|
}
|
|
100
154
|
|
|
101
|
-
/**
|
|
102
|
-
|
|
103
|
-
*/
|
|
104
|
-
stop() {
|
|
155
|
+
/** End profiling session; final chunk will be collected and sent. */
|
|
156
|
+
_endProfiling() {
|
|
105
157
|
if (!this._isRunning) {
|
|
106
158
|
return;
|
|
107
159
|
}
|
|
108
|
-
|
|
109
160
|
this._isRunning = false;
|
|
161
|
+
|
|
110
162
|
if (this._chunkTimer) {
|
|
111
163
|
clearTimeout(this._chunkTimer);
|
|
112
164
|
this._chunkTimer = undefined;
|
|
@@ -118,6 +170,12 @@ class UIProfiler {
|
|
|
118
170
|
this._collectCurrentChunk().catch(e => {
|
|
119
171
|
DEBUG_BUILD && debug.error('[Profiling] Failed to collect current profile chunk on `stop()`:', e);
|
|
120
172
|
});
|
|
173
|
+
|
|
174
|
+
// Manual: Clear profiling context so spans outside start()/stop() aren't marked as profiled
|
|
175
|
+
// Trace: Profile context is kept for the whole session duration
|
|
176
|
+
if (this._lifecycleMode === 'manual') {
|
|
177
|
+
getGlobalScope().setContext('profile', {});
|
|
178
|
+
}
|
|
121
179
|
}
|
|
122
180
|
|
|
123
181
|
/** Trace-mode: attach spanStart/spanEnd listeners. */
|
|
@@ -149,7 +207,7 @@ class UIProfiler {
|
|
|
149
207
|
debug.log(
|
|
150
208
|
`[Profiling] Root span ${spanId} started. Profiling active while there are active root spans (count=${rootSpanCount}).`,
|
|
151
209
|
);
|
|
152
|
-
this.
|
|
210
|
+
this._beginProfiling();
|
|
153
211
|
}
|
|
154
212
|
});
|
|
155
213
|
|
|
@@ -172,13 +230,13 @@ class UIProfiler {
|
|
|
172
230
|
this._collectCurrentChunk().catch(e => {
|
|
173
231
|
DEBUG_BUILD && debug.error('[Profiling] Failed to collect current profile chunk on last `spanEnd`:', e);
|
|
174
232
|
});
|
|
175
|
-
this.
|
|
233
|
+
this._endProfiling();
|
|
176
234
|
}
|
|
177
235
|
});
|
|
178
236
|
}
|
|
179
237
|
|
|
180
238
|
/**
|
|
181
|
-
* Resets profiling information from scope and resets running state
|
|
239
|
+
* Resets profiling information from scope and resets running state (used on failure)
|
|
182
240
|
*/
|
|
183
241
|
_resetProfilerInfo() {
|
|
184
242
|
this._isRunning = false;
|
|
@@ -193,7 +251,7 @@ class UIProfiler {
|
|
|
193
251
|
this._rootSpanTimeouts.clear();
|
|
194
252
|
}
|
|
195
253
|
|
|
196
|
-
/**
|
|
254
|
+
/** Keep track of root spans and schedule safeguard timeout (trace mode). */
|
|
197
255
|
_registerTraceRootSpan(spanId) {
|
|
198
256
|
this._activeRootSpanIds.add(spanId);
|
|
199
257
|
const timeout = setTimeout(() => this._onRootSpanTimeout(spanId), MAX_ROOT_SPAN_PROFILE_MS);
|
|
@@ -205,11 +263,11 @@ class UIProfiler {
|
|
|
205
263
|
*/
|
|
206
264
|
_startProfilerInstance() {
|
|
207
265
|
if (this._profiler?.stopped === false) {
|
|
208
|
-
return;
|
|
266
|
+
return; // already running
|
|
209
267
|
}
|
|
210
268
|
const profiler = startJSSelfProfile();
|
|
211
269
|
if (!profiler) {
|
|
212
|
-
DEBUG_BUILD && debug.log('[Profiling] Failed to start JS Profiler
|
|
270
|
+
DEBUG_BUILD && debug.log('[Profiling] Failed to start JS Profiler.');
|
|
213
271
|
return;
|
|
214
272
|
}
|
|
215
273
|
this._profiler = profiler;
|
|
@@ -266,14 +324,13 @@ class UIProfiler {
|
|
|
266
324
|
|
|
267
325
|
this._activeRootSpanIds.delete(rootSpanId);
|
|
268
326
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
this.stop();
|
|
327
|
+
if (this._activeRootSpanIds.size === 0) {
|
|
328
|
+
this._endProfiling();
|
|
272
329
|
}
|
|
273
330
|
}
|
|
274
331
|
|
|
275
332
|
/**
|
|
276
|
-
* Stop
|
|
333
|
+
* Stop current profiler instance, convert profile to chunk & send.
|
|
277
334
|
*/
|
|
278
335
|
async _collectCurrentChunk() {
|
|
279
336
|
const prevProfiler = this._profiler;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UIProfiler.js","sources":["../../../../../src/profiling/UIProfiler.ts"],"sourcesContent":["import type { Client, ProfileChunk, Span } from '@sentry/core';\nimport {\n type ProfileChunkEnvelope,\n createEnvelope,\n debug,\n dsnToString,\n getGlobalScope,\n getRootSpan,\n getSdkMetadataForEnvelopeHeader,\n uuid4,\n} from '@sentry/core';\nimport { DEBUG_BUILD } from './../debug-build';\nimport type { JSSelfProfiler } from './jsSelfProfiling';\nimport { createProfileChunkPayload, startJSSelfProfile, validateProfileChunk } from './utils';\n\nconst CHUNK_INTERVAL_MS = 60_000; // 1 minute\n// Maximum length for trace lifecycle profiling per root span (e.g. if spanEnd never fires)\nconst MAX_ROOT_SPAN_PROFILE_MS = 300_000; // 5 minutes\n\n/**\n * Browser trace-lifecycle profiler (UI Profiling / Profiling V2):\n * - Starts when the first sampled root span starts\n * - Stops when the last sampled root span ends\n * - While running, periodically stops and restarts the JS self-profiling API to collect chunks\n *\n * Profiles are emitted as standalone `profile_chunk` envelopes either when:\n * - there are no more sampled root spans, or\n * - the 60s chunk timer elapses while profiling is running.\n */\nexport class UIProfiler {\n private _client: Client | undefined;\n private _profiler: JSSelfProfiler | undefined;\n private _chunkTimer: ReturnType<typeof setTimeout> | undefined;\n // For keeping track of active root spans\n private _activeRootSpanIds: Set<string>;\n private _rootSpanTimeouts: Map<string, ReturnType<typeof setTimeout>>;\n // ID for Profiler session\n private _profilerId: string | undefined;\n private _isRunning: boolean;\n private _sessionSampled: boolean;\n\n public constructor() {\n this._client = undefined;\n this._profiler = undefined;\n this._chunkTimer = undefined;\n this._activeRootSpanIds = new Set<string>();\n this._rootSpanTimeouts = new Map<string, ReturnType<typeof setTimeout>>();\n this._profilerId = undefined;\n this._isRunning = false;\n this._sessionSampled = false;\n }\n\n /**\n * Initialize the profiler with client and session sampling decision computed by the integration.\n */\n public initialize(client: Client, sessionSampled: boolean): void {\n // One Profiler ID per profiling session (user session)\n this._profilerId = uuid4();\n\n DEBUG_BUILD && debug.log(\"[Profiling] Initializing profiler (lifecycle='trace').\");\n\n this._client = client;\n this._sessionSampled = sessionSampled;\n\n this._setupTraceLifecycleListeners(client);\n }\n\n /**\n * Handle an already-active root span at integration setup time.\n */\n public notifyRootSpanActive(rootSpan: Span): void {\n if (!this._sessionSampled) {\n return;\n }\n\n const spanId = rootSpan.spanContext().spanId;\n if (!spanId || this._activeRootSpanIds.has(spanId)) {\n return;\n }\n\n this._activeRootSpanIds.add(spanId);\n\n const rootSpanCount = this._activeRootSpanIds.size;\n\n if (rootSpanCount === 1) {\n DEBUG_BUILD &&\n debug.log('[Profiling] Detected already active root span during setup. Active root spans now:', rootSpanCount);\n\n this.start();\n }\n }\n\n /**\n * Start profiling if not already running.\n */\n public start(): void {\n if (this._isRunning) {\n return;\n }\n this._isRunning = true;\n\n DEBUG_BUILD && debug.log('[Profiling] Started profiling with profile ID:', this._profilerId);\n\n // Expose profiler_id to match root spans with profiles\n getGlobalScope().setContext('profile', { profiler_id: this._profilerId });\n\n this._startProfilerInstance();\n\n if (!this._profiler) {\n DEBUG_BUILD && debug.log('[Profiling] Stopping trace lifecycle profiling.');\n this._resetProfilerInfo();\n return;\n }\n\n this._startPeriodicChunking();\n }\n\n /**\n * Stop profiling; final chunk will be collected and sent.\n */\n public stop(): void {\n if (!this._isRunning) {\n return;\n }\n\n this._isRunning = false;\n if (this._chunkTimer) {\n clearTimeout(this._chunkTimer);\n this._chunkTimer = undefined;\n }\n\n this._clearAllRootSpanTimeouts();\n\n // Collect whatever was currently recording\n this._collectCurrentChunk().catch(e => {\n DEBUG_BUILD && debug.error('[Profiling] Failed to collect current profile chunk on `stop()`:', e);\n });\n }\n\n /** Trace-mode: attach spanStart/spanEnd listeners. */\n private _setupTraceLifecycleListeners(client: Client): void {\n client.on('spanStart', span => {\n if (!this._sessionSampled) {\n DEBUG_BUILD && debug.log('[Profiling] Session not sampled because of negative sampling decision.');\n return;\n }\n if (span !== getRootSpan(span)) {\n return; // only care about root spans\n }\n // Only count sampled root spans\n if (!span.isRecording()) {\n DEBUG_BUILD && debug.log('[Profiling] Discarding profile because root span was not sampled.');\n return;\n }\n\n const spanId = span.spanContext().spanId;\n if (!spanId || this._activeRootSpanIds.has(spanId)) {\n return;\n }\n\n this._registerTraceRootSpan(spanId);\n\n const rootSpanCount = this._activeRootSpanIds.size;\n if (rootSpanCount === 1) {\n DEBUG_BUILD &&\n debug.log(\n `[Profiling] Root span ${spanId} started. Profiling active while there are active root spans (count=${rootSpanCount}).`,\n );\n this.start();\n }\n });\n\n client.on('spanEnd', span => {\n if (!this._sessionSampled) {\n return;\n }\n const spanId = span.spanContext().spanId;\n if (!spanId || !this._activeRootSpanIds.has(spanId)) {\n return;\n }\n this._activeRootSpanIds.delete(spanId);\n const rootSpanCount = this._activeRootSpanIds.size;\n\n DEBUG_BUILD &&\n debug.log(\n `[Profiling] Root span with ID ${spanId} ended. Will continue profiling for as long as there are active root spans (currently: ${rootSpanCount}).`,\n );\n if (rootSpanCount === 0) {\n this._collectCurrentChunk().catch(e => {\n DEBUG_BUILD && debug.error('[Profiling] Failed to collect current profile chunk on last `spanEnd`:', e);\n });\n this.stop();\n }\n });\n }\n\n /**\n * Resets profiling information from scope and resets running state\n */\n private _resetProfilerInfo(): void {\n this._isRunning = false;\n getGlobalScope().setContext('profile', {});\n }\n\n /**\n * Clear and reset all per-root-span timeouts.\n */\n private _clearAllRootSpanTimeouts(): void {\n this._rootSpanTimeouts.forEach(timeout => clearTimeout(timeout));\n this._rootSpanTimeouts.clear();\n }\n\n /** Register root span and schedule safeguard timeout (trace mode). */\n private _registerTraceRootSpan(spanId: string): void {\n this._activeRootSpanIds.add(spanId);\n const timeout = setTimeout(() => this._onRootSpanTimeout(spanId), MAX_ROOT_SPAN_PROFILE_MS);\n this._rootSpanTimeouts.set(spanId, timeout);\n }\n\n /**\n * Start a profiler instance if needed.\n */\n private _startProfilerInstance(): void {\n if (this._profiler?.stopped === false) {\n return;\n }\n const profiler = startJSSelfProfile();\n if (!profiler) {\n DEBUG_BUILD && debug.log('[Profiling] Failed to start JS Profiler in trace lifecycle.');\n return;\n }\n this._profiler = profiler;\n }\n\n /**\n * Schedule the next 60s chunk while running.\n * Each tick collects a chunk and restarts the profiler.\n * A chunk should be closed when there are no active root spans anymore OR when the maximum chunk interval is reached.\n */\n private _startPeriodicChunking(): void {\n if (!this._isRunning) {\n return;\n }\n\n this._chunkTimer = setTimeout(() => {\n this._collectCurrentChunk().catch(e => {\n DEBUG_BUILD && debug.error('[Profiling] Failed to collect current profile chunk during periodic chunking:', e);\n });\n\n if (this._isRunning) {\n this._startProfilerInstance();\n\n if (!this._profiler) {\n // If restart failed, stop scheduling further chunks and reset context.\n this._resetProfilerInfo();\n return;\n }\n\n this._startPeriodicChunking();\n }\n }, CHUNK_INTERVAL_MS);\n }\n\n /**\n * Handle timeout for a specific root span ID to avoid indefinitely running profiler if `spanEnd` never fires.\n * If this was the last active root span, collect the current chunk and stop profiling.\n */\n private _onRootSpanTimeout(rootSpanId: string): void {\n // If span already ended, ignore\n if (!this._rootSpanTimeouts.has(rootSpanId)) {\n return;\n }\n this._rootSpanTimeouts.delete(rootSpanId);\n\n if (!this._activeRootSpanIds.has(rootSpanId)) {\n return;\n }\n\n DEBUG_BUILD &&\n debug.log(\n `[Profiling] Reached 5-minute timeout for root span ${rootSpanId}. You likely started a manual root span that never called \\`.end()\\`.`,\n );\n\n this._activeRootSpanIds.delete(rootSpanId);\n\n const rootSpanCount = this._activeRootSpanIds.size;\n if (rootSpanCount === 0) {\n this.stop();\n }\n }\n\n /**\n * Stop the current profiler, convert and send a profile chunk.\n */\n private async _collectCurrentChunk(): Promise<void> {\n const prevProfiler = this._profiler;\n this._profiler = undefined;\n\n if (!prevProfiler) {\n return;\n }\n\n try {\n const profile = await prevProfiler.stop();\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const chunk = createProfileChunkPayload(profile, this._client!, this._profilerId);\n\n // Validate chunk before sending\n const validationReturn = validateProfileChunk(chunk);\n if ('reason' in validationReturn) {\n DEBUG_BUILD &&\n debug.log(\n '[Profiling] Discarding invalid profile chunk (this is probably a bug in the SDK):',\n validationReturn.reason,\n );\n return;\n }\n\n this._sendProfileChunk(chunk);\n\n DEBUG_BUILD && debug.log('[Profiling] Collected browser profile chunk.');\n } catch (e) {\n DEBUG_BUILD && debug.log('[Profiling] Error while stopping JS Profiler for chunk:', e);\n }\n }\n\n /**\n * Send a profile chunk as a standalone envelope.\n */\n private _sendProfileChunk(chunk: ProfileChunk): void {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const client = this._client!;\n\n const sdkInfo = getSdkMetadataForEnvelopeHeader(client.getSdkMetadata?.());\n const dsn = client.getDsn();\n const tunnel = client.getOptions().tunnel;\n\n const envelope = createEnvelope<ProfileChunkEnvelope>(\n {\n event_id: uuid4(),\n sent_at: new Date().toISOString(),\n ...(sdkInfo && { sdk: sdkInfo }),\n ...(!!tunnel && dsn && { dsn: dsnToString(dsn) }),\n },\n [[{ type: 'profile_chunk' }, chunk]],\n );\n\n client.sendEnvelope(envelope).then(null, reason => {\n DEBUG_BUILD && debug.error('Error while sending profile chunk envelope:', reason);\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"UIProfiler.js","sources":["../../../../../src/profiling/UIProfiler.ts"],"sourcesContent":["import type { Client, ContinuousProfiler, ProfileChunk, Span } from '@sentry/core';\nimport {\n type ProfileChunkEnvelope,\n createEnvelope,\n debug,\n dsnToString,\n getGlobalScope,\n getRootSpan,\n getSdkMetadataForEnvelopeHeader,\n uuid4,\n} from '@sentry/core';\nimport type { BrowserOptions } from '../client';\nimport { DEBUG_BUILD } from './../debug-build';\nimport type { JSSelfProfiler } from './jsSelfProfiling';\nimport { createProfileChunkPayload, shouldProfileSession, startJSSelfProfile, validateProfileChunk } from './utils';\n\nconst CHUNK_INTERVAL_MS = 60_000; // 1 minute\n// Maximum length for trace lifecycle profiling per root span (e.g. if spanEnd never fires)\nconst MAX_ROOT_SPAN_PROFILE_MS = 300_000; // 5 minutes max per root span in trace mode\n\n/**\n * UIProfiler (Profiling V2):\n * Supports two lifecycle modes:\n * - 'manual': controlled explicitly via start()/stop()\n * - 'trace': automatically runs while there are active sampled root spans\n *\n * Profiles are emitted as standalone `profile_chunk` envelopes either when:\n * - there are no more sampled root spans, or\n * - the 60s chunk timer elapses while profiling is running.\n */\nexport class UIProfiler implements ContinuousProfiler<Client> {\n private _client: Client | undefined;\n private _profiler: JSSelfProfiler | undefined;\n private _chunkTimer: ReturnType<typeof setTimeout> | undefined;\n\n // Manual + Trace\n private _profilerId: string | undefined; // one per Profiler session\n private _isRunning: boolean; // current profiler instance active flag\n private _sessionSampled: boolean; // sampling decision for entire session\n private _lifecycleMode: 'manual' | 'trace' | undefined;\n\n // Trace-only\n private _activeRootSpanIds: Set<string>;\n private _rootSpanTimeouts: Map<string, ReturnType<typeof setTimeout>>;\n\n public constructor() {\n this._client = undefined;\n this._profiler = undefined;\n this._chunkTimer = undefined;\n\n this._profilerId = undefined;\n this._isRunning = false;\n this._sessionSampled = false;\n this._lifecycleMode = undefined;\n\n this._activeRootSpanIds = new Set();\n this._rootSpanTimeouts = new Map();\n }\n\n /**\n * Initialize the profiler with client, session sampling and lifecycle mode.\n */\n public initialize(client: Client): void {\n const lifecycleMode = (client.getOptions() as BrowserOptions).profileLifecycle;\n const sessionSampled = shouldProfileSession(client.getOptions());\n\n DEBUG_BUILD && debug.log(`[Profiling] Initializing profiler (lifecycle='${lifecycleMode}').`);\n\n if (!sessionSampled) {\n DEBUG_BUILD && debug.log('[Profiling] Session not sampled. Skipping lifecycle profiler initialization.');\n }\n\n // One Profiler ID per profiling session (user session)\n this._profilerId = uuid4();\n this._client = client;\n this._sessionSampled = sessionSampled;\n this._lifecycleMode = lifecycleMode;\n\n if (lifecycleMode === 'trace') {\n this._setupTraceLifecycleListeners(client);\n }\n }\n\n /** Starts UI profiling (only effective in 'manual' mode and when sampled). */\n public start(): void {\n if (this._lifecycleMode === 'trace') {\n DEBUG_BUILD &&\n debug.warn(\n '[Profiling] `profileLifecycle` is set to \"trace\". Calls to `uiProfiler.start()` are ignored in trace mode.',\n );\n return;\n }\n\n if (this._isRunning) {\n DEBUG_BUILD && debug.warn('[Profiling] Profile session is already running, `uiProfiler.start()` is a no-op.');\n return;\n }\n\n if (!this._sessionSampled) {\n DEBUG_BUILD && debug.warn('[Profiling] Session is not sampled, `uiProfiler.start()` is a no-op.');\n return;\n }\n\n this._beginProfiling();\n }\n\n /** Stops UI profiling (only effective in 'manual' mode). */\n public stop(): void {\n if (this._lifecycleMode === 'trace') {\n DEBUG_BUILD &&\n debug.warn(\n '[Profiling] `profileLifecycle` is set to \"trace\". Calls to `uiProfiler.stop()` are ignored in trace mode.',\n );\n return;\n }\n\n if (!this._isRunning) {\n DEBUG_BUILD && debug.warn('[Profiling] Profiler is not running, `uiProfiler.stop()` is a no-op.');\n return;\n }\n\n this._endProfiling();\n }\n\n /** Handle an already-active root span at integration setup time (used only in trace mode). */\n public notifyRootSpanActive(rootSpan: Span): void {\n if (this._lifecycleMode !== 'trace' || !this._sessionSampled) {\n return;\n }\n\n const spanId = rootSpan.spanContext().spanId;\n if (!spanId || this._activeRootSpanIds.has(spanId)) {\n return;\n }\n\n this._registerTraceRootSpan(spanId);\n\n const rootSpanCount = this._activeRootSpanIds.size;\n\n if (rootSpanCount === 1) {\n DEBUG_BUILD &&\n debug.log('[Profiling] Detected already active root span during setup. Active root spans now:', rootSpanCount);\n\n this._beginProfiling();\n }\n }\n\n /**\n * Begin profiling if not already running.\n */\n private _beginProfiling(): void {\n if (this._isRunning) {\n return;\n }\n this._isRunning = true;\n\n DEBUG_BUILD && debug.log('[Profiling] Started profiling with profiler ID:', this._profilerId);\n\n // Expose profiler_id to match root spans with profiles\n getGlobalScope().setContext('profile', { profiler_id: this._profilerId });\n\n this._startProfilerInstance();\n\n if (!this._profiler) {\n DEBUG_BUILD && debug.log('[Profiling] Failed to start JS Profiler; stopping.');\n this._resetProfilerInfo();\n return;\n }\n\n this._startPeriodicChunking();\n }\n\n /** End profiling session; final chunk will be collected and sent. */\n private _endProfiling(): void {\n if (!this._isRunning) {\n return;\n }\n this._isRunning = false;\n\n if (this._chunkTimer) {\n clearTimeout(this._chunkTimer);\n this._chunkTimer = undefined;\n }\n\n this._clearAllRootSpanTimeouts();\n\n // Collect whatever was currently recording\n this._collectCurrentChunk().catch(e => {\n DEBUG_BUILD && debug.error('[Profiling] Failed to collect current profile chunk on `stop()`:', e);\n });\n\n // Manual: Clear profiling context so spans outside start()/stop() aren't marked as profiled\n // Trace: Profile context is kept for the whole session duration\n if (this._lifecycleMode === 'manual') {\n getGlobalScope().setContext('profile', {});\n }\n }\n\n /** Trace-mode: attach spanStart/spanEnd listeners. */\n private _setupTraceLifecycleListeners(client: Client): void {\n client.on('spanStart', span => {\n if (!this._sessionSampled) {\n DEBUG_BUILD && debug.log('[Profiling] Session not sampled because of negative sampling decision.');\n return;\n }\n if (span !== getRootSpan(span)) {\n return; // only care about root spans\n }\n // Only count sampled root spans\n if (!span.isRecording()) {\n DEBUG_BUILD && debug.log('[Profiling] Discarding profile because root span was not sampled.');\n return;\n }\n\n const spanId = span.spanContext().spanId;\n if (!spanId || this._activeRootSpanIds.has(spanId)) {\n return;\n }\n\n this._registerTraceRootSpan(spanId);\n\n const rootSpanCount = this._activeRootSpanIds.size;\n if (rootSpanCount === 1) {\n DEBUG_BUILD &&\n debug.log(\n `[Profiling] Root span ${spanId} started. Profiling active while there are active root spans (count=${rootSpanCount}).`,\n );\n this._beginProfiling();\n }\n });\n\n client.on('spanEnd', span => {\n if (!this._sessionSampled) {\n return;\n }\n const spanId = span.spanContext().spanId;\n if (!spanId || !this._activeRootSpanIds.has(spanId)) {\n return;\n }\n this._activeRootSpanIds.delete(spanId);\n const rootSpanCount = this._activeRootSpanIds.size;\n\n DEBUG_BUILD &&\n debug.log(\n `[Profiling] Root span with ID ${spanId} ended. Will continue profiling for as long as there are active root spans (currently: ${rootSpanCount}).`,\n );\n if (rootSpanCount === 0) {\n this._collectCurrentChunk().catch(e => {\n DEBUG_BUILD && debug.error('[Profiling] Failed to collect current profile chunk on last `spanEnd`:', e);\n });\n this._endProfiling();\n }\n });\n }\n\n /**\n * Resets profiling information from scope and resets running state (used on failure)\n */\n private _resetProfilerInfo(): void {\n this._isRunning = false;\n getGlobalScope().setContext('profile', {});\n }\n\n /**\n * Clear and reset all per-root-span timeouts.\n */\n private _clearAllRootSpanTimeouts(): void {\n this._rootSpanTimeouts.forEach(timeout => clearTimeout(timeout));\n this._rootSpanTimeouts.clear();\n }\n\n /** Keep track of root spans and schedule safeguard timeout (trace mode). */\n private _registerTraceRootSpan(spanId: string): void {\n this._activeRootSpanIds.add(spanId);\n const timeout = setTimeout(() => this._onRootSpanTimeout(spanId), MAX_ROOT_SPAN_PROFILE_MS);\n this._rootSpanTimeouts.set(spanId, timeout);\n }\n\n /**\n * Start a profiler instance if needed.\n */\n private _startProfilerInstance(): void {\n if (this._profiler?.stopped === false) {\n return; // already running\n }\n const profiler = startJSSelfProfile();\n if (!profiler) {\n DEBUG_BUILD && debug.log('[Profiling] Failed to start JS Profiler.');\n return;\n }\n this._profiler = profiler;\n }\n\n /**\n * Schedule the next 60s chunk while running.\n * Each tick collects a chunk and restarts the profiler.\n * A chunk should be closed when there are no active root spans anymore OR when the maximum chunk interval is reached.\n */\n private _startPeriodicChunking(): void {\n if (!this._isRunning) {\n return;\n }\n\n this._chunkTimer = setTimeout(() => {\n this._collectCurrentChunk().catch(e => {\n DEBUG_BUILD && debug.error('[Profiling] Failed to collect current profile chunk during periodic chunking:', e);\n });\n\n if (this._isRunning) {\n this._startProfilerInstance();\n\n if (!this._profiler) {\n // If restart failed, stop scheduling further chunks and reset context.\n this._resetProfilerInfo();\n return;\n }\n\n this._startPeriodicChunking();\n }\n }, CHUNK_INTERVAL_MS);\n }\n\n /**\n * Handle timeout for a specific root span ID to avoid indefinitely running profiler if `spanEnd` never fires.\n * If this was the last active root span, collect the current chunk and stop profiling.\n */\n private _onRootSpanTimeout(rootSpanId: string): void {\n // If span already ended, ignore\n if (!this._rootSpanTimeouts.has(rootSpanId)) {\n return;\n }\n this._rootSpanTimeouts.delete(rootSpanId);\n\n if (!this._activeRootSpanIds.has(rootSpanId)) {\n return;\n }\n\n DEBUG_BUILD &&\n debug.log(\n `[Profiling] Reached 5-minute timeout for root span ${rootSpanId}. You likely started a manual root span that never called \\`.end()\\`.`,\n );\n\n this._activeRootSpanIds.delete(rootSpanId);\n\n if (this._activeRootSpanIds.size === 0) {\n this._endProfiling();\n }\n }\n\n /**\n * Stop current profiler instance, convert profile to chunk & send.\n */\n private async _collectCurrentChunk(): Promise<void> {\n const prevProfiler = this._profiler;\n this._profiler = undefined;\n\n if (!prevProfiler) {\n return;\n }\n\n try {\n const profile = await prevProfiler.stop();\n\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const chunk = createProfileChunkPayload(profile, this._client!, this._profilerId);\n\n // Validate chunk before sending\n const validationReturn = validateProfileChunk(chunk);\n if ('reason' in validationReturn) {\n DEBUG_BUILD &&\n debug.log(\n '[Profiling] Discarding invalid profile chunk (this is probably a bug in the SDK):',\n validationReturn.reason,\n );\n return;\n }\n\n this._sendProfileChunk(chunk);\n\n DEBUG_BUILD && debug.log('[Profiling] Collected browser profile chunk.');\n } catch (e) {\n DEBUG_BUILD && debug.log('[Profiling] Error while stopping JS Profiler for chunk:', e);\n }\n }\n\n /**\n * Send a profile chunk as a standalone envelope.\n */\n private _sendProfileChunk(chunk: ProfileChunk): void {\n // eslint-disable-next-line @typescript-eslint/no-non-null-assertion\n const client = this._client!;\n\n const sdkInfo = getSdkMetadataForEnvelopeHeader(client.getSdkMetadata?.());\n const dsn = client.getDsn();\n const tunnel = client.getOptions().tunnel;\n\n const envelope = createEnvelope<ProfileChunkEnvelope>(\n {\n event_id: uuid4(),\n sent_at: new Date().toISOString(),\n ...(sdkInfo && { sdk: sdkInfo }),\n ...(!!tunnel && dsn && { dsn: dsnToString(dsn) }),\n },\n [[{ type: 'profile_chunk' }, chunk]],\n );\n\n client.sendEnvelope(envelope).then(null, reason => {\n DEBUG_BUILD && debug.error('Error while sending profile chunk envelope:', reason);\n });\n }\n}\n"],"names":[],"mappings":"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"}
|