Skip to content

Commit be799ee

Browse files
authored
feat: Update Sentry telemetry to v8 (#604)
1 parent 7959c7b commit be799ee

File tree

13 files changed

+566
-284
lines changed

13 files changed

+566
-284
lines changed

packages/bundler-plugin-core/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,9 @@
7070
"@rollup/plugin-replace": "^4.0.0",
7171
"@sentry-internal/eslint-config": "2.22.4",
7272
"@sentry-internal/sentry-bundler-plugin-tsconfig": "2.22.4",
73-
"@sentry/node": "7.102.0",
74-
"@sentry/utils": "7.102.0",
73+
"@sentry/core": "8.30.0",
74+
"@sentry/types": "8.30.0",
75+
"@sentry/utils": "8.30.0",
7576
"@swc/core": "^1.2.205",
7677
"@swc/jest": "^0.2.21",
7778
"@types/jest": "^28.1.3",

packages/bundler-plugin-core/src/debug-id-upload.ts

Lines changed: 144 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import path from "path";
55
import * as util from "util";
66
import { Logger } from "./sentry/logger";
77
import { promisify } from "util";
8-
import { Hub, NodeClient } from "@sentry/node";
98
import SentryCli from "@sentry/cli";
109
import { dynamicSamplingContextToSentryBaggageHeader } from "@sentry/utils";
1110
import { safeFlushTelemetry } from "./sentry/telemetry";
1211
import { stripQueryAndHashFromPath } from "./utils";
12+
import { setMeasurement, spanToTraceHeader, startSpan } from "@sentry/core";
13+
import { getDynamicSamplingContextFromSpan, Scope } from "@sentry/core";
14+
import { Client } from "@sentry/types";
1315

1416
interface RewriteSourcesHook {
17+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1518
(source: string, map: any): string;
1619
}
1720

@@ -23,8 +26,8 @@ interface DebugIdUploadPluginOptions {
2326
dist?: string;
2427
rewriteSourcesHook?: RewriteSourcesHook;
2528
handleRecoverableError: (error: unknown) => void;
26-
sentryHub: Hub;
27-
sentryClient: NodeClient;
29+
sentryScope: Scope;
30+
sentryClient: Client;
2831
sentryCliOptions: {
2932
url: string;
3033
authToken: string;
@@ -44,7 +47,7 @@ export function createDebugIdUploadFunction({
4447
releaseName,
4548
dist,
4649
handleRecoverableError,
47-
sentryHub,
50+
sentryScope,
4851
sentryClient,
4952
sentryCliOptions,
5053
rewriteSourcesHook,
@@ -53,155 +56,152 @@ export function createDebugIdUploadFunction({
5356
const freeGlobalDependencyOnSourcemapFiles = createDependencyOnSourcemapFiles();
5457

5558
return async (buildArtifactPaths: string[]) => {
56-
const artifactBundleUploadTransaction = sentryHub.startTransaction({
57-
name: "debug-id-sourcemap-upload",
58-
});
59+
await startSpan(
60+
// This is `forceTransaction`ed because this span is used in dashboards in the form of indexed transactions.
61+
{ name: "debug-id-sourcemap-upload", scope: sentryScope, forceTransaction: true },
62+
async () => {
63+
let folderToCleanUp: string | undefined;
64+
65+
// It is possible that this writeBundle hook (which calls this function) is called multiple times in one build (for example when reusing the plugin, or when using build tooling like `@vitejs/plugin-legacy`)
66+
// Therefore we need to actually register the execution of this hook as dependency on the sourcemap files.
67+
const freeUploadDependencyOnSourcemapFiles = createDependencyOnSourcemapFiles();
68+
69+
try {
70+
const tmpUploadFolder = await startSpan(
71+
{ name: "mkdtemp", scope: sentryScope },
72+
async () => {
73+
return await fs.promises.mkdtemp(
74+
path.join(os.tmpdir(), "sentry-bundler-plugin-upload-")
75+
);
76+
}
77+
);
5978

60-
let folderToCleanUp: string | undefined;
79+
folderToCleanUp = tmpUploadFolder;
6180

62-
// It is possible that this writeBundle hook (which calls this function) is called multiple times in one build (for example when reusing the plugin, or when using build tooling like `@vitejs/plugin-legacy`)
63-
// Therefore we need to actually register the execution of this hook as dependency on the sourcemap files.
64-
const freeUploadDependencyOnSourcemapFiles = createDependencyOnSourcemapFiles();
81+
let globAssets: string | string[];
82+
if (assets) {
83+
globAssets = assets;
84+
} else {
85+
logger.debug(
86+
"No `sourcemaps.assets` option provided, falling back to uploading detected build artifacts."
87+
);
88+
globAssets = buildArtifactPaths;
89+
}
6590

66-
try {
67-
const mkdtempSpan = artifactBundleUploadTransaction.startChild({ description: "mkdtemp" });
68-
const tmpUploadFolder = await fs.promises.mkdtemp(
69-
path.join(os.tmpdir(), "sentry-bundler-plugin-upload-")
70-
);
71-
mkdtempSpan.finish();
72-
73-
folderToCleanUp = tmpUploadFolder;
74-
75-
let globAssets;
76-
if (assets) {
77-
globAssets = assets;
78-
} else {
79-
logger.debug(
80-
"No `sourcemaps.assets` option provided, falling back to uploading detected build artifacts."
81-
);
82-
globAssets = buildArtifactPaths;
83-
}
91+
const globResult = await startSpan(
92+
{ name: "glob", scope: sentryScope },
93+
async () => await glob(globAssets, { absolute: true, nodir: true, ignore: ignore })
94+
);
95+
96+
const debugIdChunkFilePaths = globResult.filter((debugIdChunkFilePath) => {
97+
return !!stripQueryAndHashFromPath(debugIdChunkFilePath).match(/\.(js|mjs|cjs)$/);
98+
});
99+
100+
// The order of the files output by glob() is not deterministic
101+
// Ensure order within the files so that {debug-id}-{chunkIndex} coupling is consistent
102+
debugIdChunkFilePaths.sort();
84103

85-
const globSpan = artifactBundleUploadTransaction.startChild({ description: "glob" });
86-
const globResult = await glob(globAssets, {
87-
absolute: true,
88-
nodir: true,
89-
ignore: ignore,
90-
});
91-
globSpan.finish();
92-
93-
const debugIdChunkFilePaths = globResult.filter((debugIdChunkFilePath) => {
94-
return !!stripQueryAndHashFromPath(debugIdChunkFilePath).match(/\.(js|mjs|cjs)$/);
95-
});
96-
97-
// The order of the files output by glob() is not deterministic
98-
// Ensure order within the files so that {debug-id}-{chunkIndex} coupling is consistent
99-
debugIdChunkFilePaths.sort();
100-
101-
if (Array.isArray(assets) && assets.length === 0) {
102-
logger.debug(
103-
"Empty `sourcemaps.assets` option provided. Will not upload sourcemaps with debug ID."
104-
);
105-
} else if (debugIdChunkFilePaths.length === 0) {
106-
logger.warn(
107-
"Didn't find any matching sources for debug ID upload. Please check the `sourcemaps.assets` option."
108-
);
109-
} else {
110-
const prepareSpan = artifactBundleUploadTransaction.startChild({
111-
description: "prepare-bundles",
112-
});
113-
114-
// Preparing the bundles can be a lot of work and doing it all at once has the potential of nuking the heap so
115-
// instead we do it with a maximum of 16 concurrent workers
116-
const preparationTasks = debugIdChunkFilePaths.map(
117-
(chunkFilePath, chunkIndex) => async () => {
118-
await prepareBundleForDebugIdUpload(
119-
chunkFilePath,
120-
tmpUploadFolder,
121-
chunkIndex,
122-
logger,
123-
rewriteSourcesHook ?? defaultRewriteSourcesHook
104+
if (Array.isArray(assets) && assets.length === 0) {
105+
logger.debug(
106+
"Empty `sourcemaps.assets` option provided. Will not upload sourcemaps with debug ID."
124107
);
108+
} else if (debugIdChunkFilePaths.length === 0) {
109+
logger.warn(
110+
"Didn't find any matching sources for debug ID upload. Please check the `sourcemaps.assets` option."
111+
);
112+
} else {
113+
await startSpan(
114+
{ name: "prepare-bundles", scope: sentryScope },
115+
async (prepBundlesSpan) => {
116+
// Preparing the bundles can be a lot of work and doing it all at once has the potential of nuking the heap so
117+
// instead we do it with a maximum of 16 concurrent workers
118+
const preparationTasks = debugIdChunkFilePaths.map(
119+
(chunkFilePath, chunkIndex) => async () => {
120+
await prepareBundleForDebugIdUpload(
121+
chunkFilePath,
122+
tmpUploadFolder,
123+
chunkIndex,
124+
logger,
125+
rewriteSourcesHook ?? defaultRewriteSourcesHook
126+
);
127+
}
128+
);
129+
const workers: Promise<void>[] = [];
130+
const worker = async () => {
131+
while (preparationTasks.length > 0) {
132+
const task = preparationTasks.shift();
133+
if (task) {
134+
await task();
135+
}
136+
}
137+
};
138+
for (let workerIndex = 0; workerIndex < 16; workerIndex++) {
139+
workers.push(worker());
140+
}
141+
142+
await Promise.all(workers);
143+
144+
const files = await fs.promises.readdir(tmpUploadFolder);
145+
const stats = files.map((file) =>
146+
fs.promises.stat(path.join(tmpUploadFolder, file))
147+
);
148+
const uploadSize = (await Promise.all(stats)).reduce(
149+
(accumulator, { size }) => accumulator + size,
150+
0
151+
);
152+
153+
setMeasurement("files", files.length, "none", prepBundlesSpan);
154+
setMeasurement("upload_size", uploadSize, "byte", prepBundlesSpan);
155+
156+
await startSpan({ name: "upload", scope: sentryScope }, async (uploadSpan) => {
157+
const cliInstance = new SentryCli(null, {
158+
...sentryCliOptions,
159+
headers: {
160+
"sentry-trace": spanToTraceHeader(uploadSpan),
161+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
162+
baggage: dynamicSamplingContextToSentryBaggageHeader(
163+
getDynamicSamplingContextFromSpan(uploadSpan)
164+
)!,
165+
...sentryCliOptions.headers,
166+
},
167+
});
168+
169+
await cliInstance.releases.uploadSourceMaps(
170+
releaseName ?? "undefined", // unfortunetly this needs a value for now but it will not matter since debug IDs overpower releases anyhow
171+
{
172+
include: [
173+
{
174+
paths: [tmpUploadFolder],
175+
rewrite: false,
176+
dist: dist,
177+
},
178+
],
179+
useArtifactBundle: true,
180+
}
181+
);
182+
});
183+
}
184+
);
185+
186+
logger.info("Successfully uploaded source maps to Sentry");
125187
}
126-
);
127-
const workers: Promise<void>[] = [];
128-
const worker = async () => {
129-
while (preparationTasks.length > 0) {
130-
const task = preparationTasks.shift();
131-
if (task) {
132-
await task();
133-
}
188+
} catch (e) {
189+
sentryScope.captureException('Error in "debugIdUploadPlugin" writeBundle hook');
190+
handleRecoverableError(e);
191+
} finally {
192+
if (folderToCleanUp) {
193+
void startSpan({ name: "cleanup", scope: sentryScope }, async () => {
194+
if (folderToCleanUp) {
195+
await fs.promises.rm(folderToCleanUp, { recursive: true, force: true });
196+
}
197+
});
134198
}
135-
};
136-
for (let workerIndex = 0; workerIndex < 16; workerIndex++) {
137-
workers.push(worker());
199+
freeGlobalDependencyOnSourcemapFiles();
200+
freeUploadDependencyOnSourcemapFiles();
201+
await safeFlushTelemetry(sentryClient);
138202
}
139-
await Promise.all(workers);
140-
141-
prepareSpan.finish();
142-
143-
const files = await fs.promises.readdir(tmpUploadFolder);
144-
const stats = files.map((file) => fs.promises.stat(path.join(tmpUploadFolder, file)));
145-
const uploadSize = (await Promise.all(stats)).reduce(
146-
(accumulator, { size }) => accumulator + size,
147-
0
148-
);
149-
150-
artifactBundleUploadTransaction.setMeasurement("files", files.length, "none");
151-
artifactBundleUploadTransaction.setMeasurement("upload_size", uploadSize, "byte");
152-
153-
const uploadSpan = artifactBundleUploadTransaction.startChild({
154-
description: "upload",
155-
});
156-
157-
const cliInstance = new SentryCli(null, {
158-
...sentryCliOptions,
159-
headers: {
160-
"sentry-trace": uploadSpan.toTraceparent(),
161-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
162-
baggage: dynamicSamplingContextToSentryBaggageHeader(
163-
artifactBundleUploadTransaction.getDynamicSamplingContext()
164-
)!,
165-
...sentryCliOptions.headers,
166-
},
167-
});
168-
169-
await cliInstance.releases.uploadSourceMaps(
170-
releaseName ?? "undefined", // unfortunetly this needs a value for now but it will not matter since debug IDs overpower releases anyhow
171-
{
172-
include: [
173-
{
174-
paths: [tmpUploadFolder],
175-
rewrite: false,
176-
dist: dist,
177-
},
178-
],
179-
useArtifactBundle: true,
180-
}
181-
);
182-
183-
uploadSpan.finish();
184-
logger.info("Successfully uploaded source maps to Sentry");
185-
}
186-
} catch (e) {
187-
sentryHub.withScope((scope) => {
188-
scope.setSpan(artifactBundleUploadTransaction);
189-
sentryHub.captureException('Error in "debugIdUploadPlugin" writeBundle hook');
190-
});
191-
handleRecoverableError(e);
192-
} finally {
193-
if (folderToCleanUp) {
194-
const cleanupSpan = artifactBundleUploadTransaction.startChild({
195-
description: "cleanup",
196-
});
197-
void fs.promises.rm(folderToCleanUp, { recursive: true, force: true });
198-
cleanupSpan.finish();
199203
}
200-
artifactBundleUploadTransaction.finish();
201-
freeGlobalDependencyOnSourcemapFiles();
202-
freeUploadDependencyOnSourcemapFiles();
203-
await safeFlushTelemetry(sentryClient);
204-
}
204+
);
205205
};
206206
}
207207

0 commit comments

Comments
 (0)