Skip to content
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

fix(profiling): Don't put require, __filename and __dirname on global object #14470

Open
wants to merge 25 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 9 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1205,7 +1205,15 @@ jobs:
- name: Run E2E test
working-directory: dev-packages/e2e-tests/test-applications/${{ matrix.test-application }}
timeout-minutes: 10
run: xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:assert
run: |
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:assert

# Skipped for now because I don't know what this test actually tests, couldn't get it to work.
# - name: Run E2E ESM test
# working-directory: dev-packages/e2e-tests/test-applications/node-profiling
# run: |
# npm pkg set type=module
# node index.mjs

job_required_jobs_passed:
name: All required jobs passed or were skipped
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
// only runs integration and unit tests, this change would be missed and could end up in a release.
// Therefor, once all binaries are precompiled in CI and tests pass, run esbuild with bundle:true
// which will copy all binaries to the outfile folder and throw if any of them are missing.
import esbuild from 'esbuild';
import esbuild from "esbuild";

console.log('Running build using esbuild version', esbuild.version);
console.log("Running build using esbuild version", esbuild.version);

esbuild.buildSync({
platform: 'node',
entryPoints: ['./index.ts'],
outdir: './dist',
target: 'esnext',
format: 'cjs',
bundle: true,
loader: { '.node': 'copy' },
platform: "node",
entryPoints: ["./index.ts"],
outdir: "./dist",
target: "esnext",
format: "cjs",
bundle: true,
loader: { ".node": "copy" },
});
34 changes: 34 additions & 0 deletions dev-packages/e2e-tests/test-applications/node-profiling/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// This tests asserts that @sentr/profiling-node is not patching globalThis values, which
// breaks our runtime detection and can break instrumentation
// https://github.com/getsentry/sentry-javascript/issues/14525#issuecomment-2511208064
import * as Sentry from '@sentry/node';
import { nodeProfilingIntegration } from '@sentry/profiling-node';

console.log('🧵 Starting ESM test');

const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

function assertUnpatchedRequire() {
if (typeof require !== 'undefined') {
// Test that globalThis.require is not defined by any side effects of the profiling
// https://github.com/getsentry/sentry-javascript/issues/13662
throw new Error(
`globalThis.require should not be defined, check that profiling integration is not defining it, received: ` +
typeof require,
);
}
}

Sentry.init({
dsn: 'https://[email protected]/6625302',
integrations: [nodeProfilingIntegration()],
tracesSampleRate: 1.0,
profilesSampleRate: 1.0,
});

Sentry.startSpan({ name: 'Precompile test' }, async () => {
await wait(500);
});

assertUnpatchedRequire();
console.log('✅ Require is not patched');
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const Sentry = require('@sentry/node');
const { nodeProfilingIntegration } = require('@sentry/profiling-node');
import * as Sentry from '@sentry/node';
import { nodeProfilingIntegration } from '@sentry/profiling-node';

const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
"clean": "npx rimraf node_modules dist",
"test:electron": "$(pnpm bin)/electron-rebuild && playwright test",
"test:build": "pnpm run typecheck && pnpm run build",
"test:assert": "pnpm run test && pnpm run test:electron"
"test:assert": "pnpm run test && pnpm run test:electron",
"test:mjs": "node index.mjs"
},
"dependencies": {
"@electron/rebuild": "^3.7.0",
Expand All @@ -19,7 +20,9 @@
"@sentry/profiling-node": "latest || *",
"electron": "^33.2.0"
},
"devDependencies": {},
"devDependencies": {
"esbuild": "0.24.0"
},
"volta": {
"extends": "../../package.json"
},
Expand Down
5 changes: 4 additions & 1 deletion packages/node/src/utils/commonjs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { GLOBAL_OBJ } from '@sentry/core';

/** Detect CommonJS. */
export function isCjs(): boolean {
return typeof require !== 'undefined';
// @ts-expect-error Require is not defined on global obj
return !!GLOBAL_OBJ.require;
}
1 change: 1 addition & 0 deletions packages/profiling-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"author": "Sentry",
"license": "MIT",
"main": "lib/cjs/index.js",
"module": "lib/esm/index.js",
"types": "lib/types/index.d.ts",
"exports": {
"./package.json": "./package.json",
Expand Down
50 changes: 34 additions & 16 deletions packages/profiling-node/rollup.npm.config.mjs
Original file line number Diff line number Diff line change
@@ -1,28 +1,42 @@
import commonjs from '@rollup/plugin-commonjs';
import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils';

export const ESMShim = `
import cjsUrl from 'node:url';
import cjsPath from 'node:path';
import cjsModule from 'node:module';
export const ESMImportShim = `
import {createRequire} from 'node:module';
import {fileURLToPath} from 'node:url';
import {dirname } from 'node:path';
`;

if(typeof __filename === 'undefined'){
globalThis.__filename = cjsUrl.fileURLToPath(import.meta.url);
}
const ESMRequireShim = `
const require = createRequire(import.meta.url);
`;

const ESMDirnameShim = `
const __dirname = dirname(fileURLToPath(import.meta.url));
`;

if(typeof __dirname === 'undefined'){
globalThis.__dirname = cjsPath.dirname(__filename);
function makeESMImportShimPlugin(shim) {
return {
transform(code) {
const SHIM_REGEXP = /\/\/ #START_SENTRY_ESM_IMPORT_SHIM[\s\S]*?\/\/ #END_SENTRY_ESM_IMPORT_SHIM/;
return code.replace(SHIM_REGEXP, shim);
},
};
}

if(typeof require === 'undefined'){
globalThis.require = cjsModule.createRequire(import.meta.url);
function makeESMRequireShimPlugin(shim) {
return {
transform(code) {
const SHIM_REGEXP = /\/\/ #START_SENTRY_ESM_REQUIRE_SHIM[\s\S]*?\/\/ #END_SENTRY_ESM_REQUIRE_SHIM/;
return code.replace(SHIM_REGEXP, shim);
},
};
}
`;

function makeESMShimPlugin(shim) {
function makeESMDirnameShimPlugin(shim) {
return {
transform(code) {
const SHIM_REGEXP = /\/\/ #START_SENTRY_ESM_SHIM[\s\S]*?\/\/ #END_SENTRY_ESM_SHIM/;
const SHIM_REGEXP = /\/\/ #START_SENTRY_ESM_DIRNAME_SHIM[\s\S]*?\/\/ #END_SENTRY_ESM_DIRNAME_SHIM/;
return code.replace(SHIM_REGEXP, shim);
},
};
Expand All @@ -39,10 +53,14 @@ const variants = makeNPMConfigVariants(

for (const variant of variants) {
if (variant.output.format === 'esm') {
variant.plugins.push(makeESMShimPlugin(ESMShim));
variant.plugins.push(makeESMImportShimPlugin(ESMImportShim));
variant.plugins.push(makeESMRequireShimPlugin(ESMRequireShim));
variant.plugins.push(makeESMDirnameShimPlugin(ESMDirnameShim));
} else {
// Remove the ESM shim comment
variant.plugins.push(makeESMShimPlugin(''));
variant.plugins.push(makeESMImportShimPlugin(''));
variant.plugins.push(makeESMRequireShimPlugin(''));
variant.plugins.push(makeESMDirnameShimPlugin(''));
}
}

Expand Down
15 changes: 11 additions & 4 deletions packages/profiling-node/src/cpu_profiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,25 +15,27 @@ import type {
} from './types';
import type { ProfileFormat } from './types';

// #START_SENTRY_ESM_SHIM
// #START_SENTRY_ESM_IMPORT_SHIM
// When building for ESM, we shim require to use createRequire and __dirname.
// We need to do this because .node extensions in esm are not supported.
// The comment below this line exists as a placeholder for where to insert the shim.
// #END_SENTRY_ESM_SHIM
// #END_SENTRY_ESM_IMPORT_SHIM

const stdlib = familySync();
const platform = process.env['BUILD_PLATFORM'] || _platform();
const arch = process.env['BUILD_ARCH'] || _arch();
const abi = getAbi(versions.node, 'node');
const identifier = [platform, arch, stdlib, abi].filter(c => c !== undefined && c !== null).join('-');

const built_from_source_path = resolve(__dirname, '..', `./sentry_cpu_profiler-${identifier}`);

/**
* Imports cpp bindings based on the current platform and architecture.
*/
// eslint-disable-next-line complexity
export function importCppBindingsModule(): PrivateV8CpuProfilerBindings {
// #START_SENTRY_ESM_REQUIRE_SHIM
// When building for ESM, we shim require to use createRequire because .node extensions in esm are not supported.
// #END_SENTRY_ESM_REQUIRE_SHIM

// If a binary path is specified, use that.
if (env['SENTRY_PROFILER_BINARY_PATH']) {
const envPath = env['SENTRY_PROFILER_BINARY_PATH'];
Expand Down Expand Up @@ -160,6 +162,11 @@ export function importCppBindingsModule(): PrivateV8CpuProfilerBindings {
}
}
}

// #START_SENTRY_ESM_DIRNAME_SHIM
// const __dirname = dirname(fileURLToPath(import.meta.url));
// #END_SENTRY_ESM_DIRNAME_SHIM
const built_from_source_path = resolve(__dirname, '..', `sentry_cpu_profiler-${identifier}`);
return require(`${built_from_source_path}.node`);
}

Expand Down
Loading