Skip to content

feat: Add sentry to bundler plugins #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 24 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4822616
create sentry util for tracking traces, issues, and metrics
nsdeschenes Feb 13, 2024
e6c5c29
add sentry node
nsdeschenes Feb 13, 2024
b9f202c
setup unbuild to inline replace __PACKAGE_VERSION with the current re…
nsdeschenes Feb 13, 2024
1f7d135
add telemetry option to the plugin config
nsdeschenes Feb 13, 2024
b963fbe
configure sentry client to be used across bundler plugins
nsdeschenes Feb 13, 2024
7ce2799
add guage metric to fetchWithRetry function
nsdeschenes Feb 13, 2024
62cab8f
pass through sentry client in getPreSignedURL and uploadStats
nsdeschenes Feb 13, 2024
f3ad016
add bundler plugin metrics to plugin factory
nsdeschenes Feb 13, 2024
e4ae897
use new normalizeOptions function and create sentry client if conditi…
nsdeschenes Feb 13, 2024
22e2fe1
add in changeset
nsdeschenes Feb 13, 2024
8167a07
swap to using gauge factory function for fetchWithRetry, and add tags…
nsdeschenes Feb 13, 2024
84ebd39
fix up a few things with sentry setup
nsdeschenes Feb 13, 2024
9486589
fix up bundle analysis type error in test
nsdeschenes Feb 13, 2024
6ca1025
rework how sentry metrics are collected
nsdeschenes Feb 16, 2024
a50562f
fix type error
nsdeschenes Feb 16, 2024
ab9d98d
disable telemetry in integration tests
nsdeschenes Feb 20, 2024
f042bc9
checking sentrt debug logs
nsdeschenes Feb 21, 2024
f97223d
create telem plugin
nsdeschenes Feb 21, 2024
22afbd4
use telem plugin
nsdeschenes Feb 21, 2024
49d8b7a
inline replace in all plugin build configs
nsdeschenes Feb 23, 2024
1c997de
maybe we just need to reassign the options?
nsdeschenes Feb 23, 2024
90582e6
that didn't work
nsdeschenes Feb 23, 2024
6799747
small tweaks post rebase
nsdeschenes Mar 5, 2024
9b08536
small fix for telemetry type issue
nsdeschenes Mar 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/rare-cats-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@codecov/bundler-plugin-core": patch
"@codecov/rollup-plugin": patch
"@codecov/vite-plugin": patch
"@codecov/webpack-plugin": patch
---

Add Sentry in to collect traces and metrics"
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = defineConfig({
resolve(), // tells Rollup how to find date-fns in node_modules
commonjs(), // converts date-fns to ES modules
codecovRollupPlugin({
telemetry: false,
enableBundleAnalysis: true,
bundleName: "test-rollup-v3",
uploadToken: "test-token",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = defineConfig({
resolve(), // tells Rollup how to find date-fns in node_modules
commonjs(), // converts date-fns to ES modules
codecovRollupPlugin({
telemetry: false,
enableBundleAnalysis: true,
bundleName: "test-rollup-v4",
uploadToken: "test-token",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default defineConfig({
},
plugins: [
codecovVitePlugin({
telemetry: false,
enableBundleAnalysis: true,
bundleName: "test-vite-v4",
uploadToken: "test-token",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default defineConfig({
},
plugins: [
codecovVitePlugin({
telemetry: false,
enableBundleAnalysis: true,
bundleName: "test-vite-v5",
uploadToken: "test-token",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
mode: "production",
plugins: [
codecovWebpackPlugin({
telemetry: false,
enableBundleAnalysis: true,
bundleName: "test-webpack-v5",
uploadToken: "test-token",
Expand Down
9 changes: 8 additions & 1 deletion packages/bundler-plugin-core/build.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { defineBuildConfig } from "unbuild";
import { codecovRollupPlugin } from "codecovProdRollupPlugin";
import packageJson from "./package.json";

export default defineBuildConfig({
entries: ["./src/index"],
Expand All @@ -16,13 +17,19 @@ export default defineBuildConfig({
esbuild: {
minify: true,
},
replace: {
preventAssignment: true,
values: {
__PACKAGE_VERSION__: JSON.stringify(packageJson.version),
},
},
},
hooks: {
"rollup:options": (_ctx, opts) => {
if (process.env.PLUGIN_CODECOV_TOKEN && Array.isArray(opts.plugins)) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
opts.plugins = [
...opts.plugins,
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
codecovRollupPlugin({
enableBundleAnalysis:
typeof process.env.PLUGIN_CODECOV_TOKEN === "string",
Expand Down
1 change: 1 addition & 0 deletions packages/bundler-plugin-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"test:unit:ci": "jest --coverage"
},
"dependencies": {
"@sentry/node": "^7.100.1",
"chalk": "4.1.2",
"semver": "^7.5.4",
"unplugin": "^1.6.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { type UnpluginContextMeta } from "unplugin";
import { bundleAnalysisPluginFactory } from "../bundleAnalysisPluginFactory";

describe("bundleAnalysisPluginFactory", () => {
Expand All @@ -10,12 +11,18 @@ describe("bundleAnalysisPluginFactory", () => {
enableBundleAnalysis: true,
retryCount: 3,
uploadToken: "test-token",
telemetry: false,
},
bundleAnalysisUploadPlugin: () => ({
version: "1",
name: "plugin-name",
pluginVersion: "1.0.0",
}),
unpluginMetaContext: {} as UnpluginContextMeta,
sentryMetrics: undefined,
handleRecoverableError() {
return;
},
});

expect(plugin).toMatchSnapshot();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
import { type UnpluginOptions } from "unplugin";
import {
type BundleAnalysisUploadPlugin,
type Output,
type ProviderUtilInputs,
type UploadOverrides,
} from "../types.ts";
import { type UnpluginContextMeta, type UnpluginOptions } from "unplugin";
import { getPreSignedURL } from "../utils/getPreSignedURL.ts";
import { uploadStats } from "../utils/uploadStats.ts";
import { type SentryMetrics } from "../sentry.ts";
import { type NormalizedOptions } from "../utils/normalizeOptions.ts";
import { detectProvider } from "../utils/provider.ts";
import { uploadStats } from "../utils/uploadStats.ts";
import { sendSentryBundleStats } from "../utils/sentryUtils.ts";
import { createGauge } from "../utils/fetchWithRetry.ts";

interface BundleAnalysisUploadPluginArgs {
options: NormalizedOptions;
unpluginMetaContext: UnpluginContextMeta;
bundleAnalysisUploadPlugin: BundleAnalysisUploadPlugin;
sentryMetrics: SentryMetrics;
handleRecoverableError: (error: unknown) => void;
}

export const bundleAnalysisPluginFactory = ({
options,
unpluginMetaContext,
bundleAnalysisUploadPlugin,
sentryMetrics,
handleRecoverableError,
}: BundleAnalysisUploadPluginArgs): UnpluginOptions => {
const output: Output = {
version: "1",
Expand Down Expand Up @@ -67,25 +75,62 @@ export const bundleAnalysisPluginFactory = ({
const provider = await detectProvider(inputs);

let url = "";
const gauge = createGauge({
bundler: unpluginMetaContext.framework,
sentryMetrics,
});
const getPreSignedURLStart = Date.now();
try {
url = await getPreSignedURL({
apiURL: options?.apiUrl ?? "https://api.codecov.io",
apiURL: options?.apiUrl,
uploadToken: options?.uploadToken,
serviceParams: provider,
retryCount: options?.retryCount,
gauge,
});
sentryMetrics?.increment("request_presigned_url.success", 1, "none", {
bundler: unpluginMetaContext.framework,
});
} catch (error) {
sentryMetrics?.increment("request_presigned_url.error", 1, "none", {
bundler: unpluginMetaContext.framework,
});

handleRecoverableError(error);
return;
} finally {
sentryMetrics?.distribution(
"request_presigned_url",
Date.now() - getPreSignedURLStart,
"millisecond",
{ bundler: unpluginMetaContext.framework },
);
}

const uploadStart = Date.now();
try {
await uploadStats({
preSignedUrl: url,
bundleName: output.bundleName,
message: JSON.stringify(output),
retryCount: options?.retryCount,
gauge,
});
} catch {}
sentryMetrics?.increment("upload_bundle_stats.success", 1, "none", {
bundler: unpluginMetaContext.framework,
});
} catch (error) {
sentryMetrics?.increment("upload_bundle_stats.error", 1);
handleRecoverableError(error);
return;
} finally {
sentryMetrics?.distribution(
"upload_bundle_stats",
Date.now() - uploadStart,
"millisecond",
{ bundler: unpluginMetaContext.framework },
);
}
},
};
};
64 changes: 62 additions & 2 deletions packages/bundler-plugin-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import {
import { red } from "./utils/logging.ts";
import { normalizePath } from "./utils/normalizePath.ts";
import { bundleAnalysisPluginFactory } from "./bundle-analysis/bundleAnalysisPluginFactory.ts";
import { normalizeOptions } from "./utils/normalizeOptions.ts";
import {
normalizeOptions,
type NormalizedOptions,
} from "./utils/normalizeOptions.ts";
import { createSentryInstance } from "./sentry.ts";
import { telemetryPlugin } from "./plugins/telemetry.ts";

const NODE_VERSION_RANGE = ">=18.18.0";

Expand All @@ -37,6 +42,8 @@ function codecovUnpluginFactory({
return [];
}

const options = normalizedOptions.options;

if (!satisfies(process.version, NODE_VERSION_RANGE)) {
red(
`Codecov ${unpluginMetaContext.framework} bundler plugin requires Node.js ${NODE_VERSION_RANGE}. You are using Node.js ${process.version}. Please upgrade your Node.js version.`,
Expand All @@ -45,12 +52,64 @@ function codecovUnpluginFactory({
return plugins;
}

const options = normalizedOptions.options;
const { sentryHub, sentryMetrics, sentryClient } = createSentryInstance(
options,
unpluginMetaContext.framework,
);

const sentrySession = sentryHub?.startSession();
sentryHub?.captureSession();

let sentEndSession = false; // Just to prevent infinite loops with beforeExit, which is called whenever the event loop empties out
// We also need to manually end sessions on errors because beforeExit is not called on crashes
process.on("beforeExit", () => {
if (!sentEndSession) {
sentryHub?.endSession();
sentEndSession = true;
}
});

function handleRecoverableError(unknownError: unknown) {
if (sentrySession) {
sentrySession.status = "abnormal";
try {
if (options.errorHandler) {
try {
if (unknownError instanceof Error) {
options.errorHandler(unknownError);
} else {
options.errorHandler(new Error("An unknown error occurred"));
}
} catch (e) {
sentrySession.status = "crashed";
throw e;
}
} else {
sentrySession.status = "crashed";
throw unknownError;
}
} finally {
sentryHub?.endSession();
}
}
}

plugins.push(
telemetryPlugin({
sentryClient,
sentryHub,
shouldSendTelemetry: options.telemetry,
}),
);

if (options?.enableBundleAnalysis) {
plugins.push(
bundleAnalysisPluginFactory({
options,
unpluginMetaContext,
bundleAnalysisUploadPlugin,
sentryMetrics,
handleRecoverableError,
}),
);
}
Expand All @@ -68,6 +127,7 @@ export type {
ProviderUtilInputs,
UploadOverrides,
Output,
NormalizedOptions,
};

export { normalizePath, codecovUnpluginFactory, red };
31 changes: 31 additions & 0 deletions packages/bundler-plugin-core/src/plugins/telemetry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { type Hub, type NodeClient } from "@sentry/node";
import { type UnpluginOptions } from "unplugin";
import { dim } from "../utils/logging";
import { safeFlushTelemetry } from "../sentry";

interface TelemetryPluginOptions {
sentryHub?: Hub;
sentryClient?: NodeClient;
shouldSendTelemetry: boolean;
}

export function telemetryPlugin({
sentryHub,
sentryClient,
shouldSendTelemetry,
}: TelemetryPluginOptions): UnpluginOptions {
return {
name: "codecov-telemetry-plugin",
async buildStart() {
if (shouldSendTelemetry && sentryHub && sentryClient) {
dim(
"Sending error and performance telemetry data to Sentry. To disable telemetry, set `options.telemetry` to `false`.",
);
sentryHub
.startTransaction({ name: "Codecov Bundler Plugin execution" })
.finish();
await safeFlushTelemetry(sentryClient);
}
},
};
}
Loading