diff --git a/packages/nextjs/test/integration/package.json b/packages/nextjs/test/integration/package.json
index ea62b01992af..8ac72bc27f13 100644
--- a/packages/nextjs/test/integration/package.json
+++ b/packages/nextjs/test/integration/package.json
@@ -27,7 +27,7 @@
"resolutions": {
"@sentry/browser": "file:../../../browser",
"@sentry/core": "file:../../../core",
- "@sentry/node": "file:../../../node-experimental",
+ "@sentry/node": "file:../../../node",
"@sentry/opentelemetry": "file:../../../opentelemetry",
"@sentry/react": "file:../../../react",
"@sentry-internal/replay": "file:../../../replay-internal",
diff --git a/packages/node-experimental/LICENSE b/packages/node-experimental/LICENSE
index d11896ba1181..535ef0561e1b 100644
--- a/packages/node-experimental/LICENSE
+++ b/packages/node-experimental/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2023 Sentry (https://sentry.io) and individual contributors. All rights reserved.
+Copyright (c) 2019 Sentry (https://sentry.io) and individual contributors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
diff --git a/packages/node-experimental/README.md b/packages/node-experimental/README.md
index 324700199b53..af6819c9a4ce 100644
--- a/packages/node-experimental/README.md
+++ b/packages/node-experimental/README.md
@@ -4,28 +4,36 @@
-# Official Sentry SDK for Node
+# Legacy Sentry SDK for NodeJS
-[](https://www.npmjs.com/package/@sentry/node)
-[](https://www.npmjs.com/package/@sentry/node)
-[](https://www.npmjs.com/package/@sentry/node)
+[](https://www.npmjs.com/package/@sentry/node-experimental)
+[](https://www.npmjs.com/package/@sentry/node-experimental)
+[](https://www.npmjs.com/package/@sentry/node-experimental)
-## Installation
+## Links
-```bash
-npm install @sentry/node
+- [Official SDK Docs](https://docs.sentry.io/quickstart/)
+- [TypeDoc](http://getsentry.github.io/sentry-javascript/)
-# Or yarn
-yarn add @sentry/node
-```
+## Status
+
+Since v8, this is the _legacy_ SDK, and it will most likely be completely removed before v8 is fully stable. It only
+exists so that Meta-SDKs like `@sentry/nextjs` or `@sentry/sveltekit` can be migrated to the new `@sentry/node`
+step-by-step.
+
+You should instead use [@sentry/node](./../node-experimental/).
## Usage
-```js
-// CJS Syntax
-const Sentry = require('@sentry/node');
-// ESM Syntax
-import * as Sentry from '@sentry/node';
+To use this SDK, call `init(options)` as early as possible in the main entry module. This will initialize the SDK and
+hook into the environment. Note that you can turn off almost all side effects using the respective options. Minimum
+supported Node version is Node 14.
+
+```javascript
+// CJS syntax
+const Sentry = require('@sentry/node-experimental');
+// ESM syntax
+import * as Sentry from '@sentry/node-experimental';
Sentry.init({
dsn: '__DSN__',
@@ -33,27 +41,28 @@ Sentry.init({
});
```
-Note that it is necessary to initialize Sentry **before you import any package that may be instrumented by us**.
-
-[More information on how to set up Sentry for Node in v8.](./../../docs/v8-node.md)
+To set context information or send manual events, use the exported functions of `@sentry/node-experimental`. Note that
+these functions will not perform any action before you have called `init()`:
-### ESM Support
+```javascript
+// Set user information, as well as tags and further extras
+Sentry.setExtra('battery', 0.7);
+Sentry.setTag('user_mode', 'admin');
+Sentry.setUser({ id: '4711' });
-Due to the way OpenTelemetry handles instrumentation, this only works out of the box for CommonJS (`require`)
-applications.
-
-There is experimental support for running OpenTelemetry with ESM (`"type": "module"`):
+// Add a breadcrumb for future events
+Sentry.addBreadcrumb({
+ message: 'My Breadcrumb',
+ // ...
+});
-```bash
-node --experimental-loader=@opentelemetry/instrumentation/hook.mjs ./app.js
+// Capture exceptions, messages or manual events
+Sentry.captureMessage('Hello, world!');
+Sentry.captureException(new Error('Good bye'));
+Sentry.captureEvent({
+ message: 'Manual',
+ stacktrace: [
+ // ...
+ ],
+});
```
-
-You'll need to install `@opentelemetry/instrumentation` in your app to ensure this works.
-
-See
-[OpenTelemetry Instrumentation Docs](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation#instrumentation-for-es-modules-in-nodejs-experimental)
-for details on this - but note that this is a) experimental, and b) does not work with all integrations.
-
-## Links
-
-- [Official SDK Docs](https://docs.sentry.io/quickstart/)
diff --git a/packages/node-experimental/package.json b/packages/node-experimental/package.json
index c23a6ffa7ca9..6a124a4725e8 100644
--- a/packages/node-experimental/package.json
+++ b/packages/node-experimental/package.json
@@ -1,7 +1,7 @@
{
- "name": "@sentry/node",
+ "name": "@sentry/node-experimental",
"version": "8.0.0-alpha.7",
- "description": "Sentry Node SDK using OpenTelemetry for performance instrumentation",
+ "description": "The old version of Sentry SDK for Node.js, without OpenTelemetry support.",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node-experimental",
"author": "Sentry",
@@ -42,39 +42,22 @@
"access": "public"
},
"dependencies": {
- "@opentelemetry/api": "1.7.0",
- "@opentelemetry/context-async-hooks": "1.21.0",
- "@opentelemetry/core": "1.21.0",
- "@opentelemetry/instrumentation": "0.48.0",
- "@opentelemetry/instrumentation-express": "0.35.0",
- "@opentelemetry/instrumentation-fastify": "0.33.0",
- "@opentelemetry/instrumentation-graphql": "0.37.0",
- "@opentelemetry/instrumentation-hapi": "0.34.0",
- "@opentelemetry/instrumentation-http": "0.48.0",
- "@opentelemetry/instrumentation-koa": "0.37.0",
- "@opentelemetry/instrumentation-mongodb": "0.39.0",
- "@opentelemetry/instrumentation-mongoose": "0.35.0",
- "@opentelemetry/instrumentation-mysql": "0.35.0",
- "@opentelemetry/instrumentation-mysql2": "0.35.0",
- "@opentelemetry/instrumentation-nestjs-core": "0.34.0",
- "@opentelemetry/instrumentation-pg": "0.38.0",
- "@opentelemetry/resources": "1.21.0",
- "@opentelemetry/sdk-trace-base": "1.21.0",
- "@opentelemetry/semantic-conventions": "1.21.0",
- "@prisma/instrumentation": "5.9.0",
+ "@sentry-internal/tracing": "8.0.0-alpha.7",
"@sentry/core": "8.0.0-alpha.7",
- "@sentry/opentelemetry": "8.0.0-alpha.7",
"@sentry/types": "8.0.0-alpha.7",
"@sentry/utils": "8.0.0-alpha.7"
},
"devDependencies": {
- "@types/node": "^14.18.0"
- },
- "optionalDependencies": {
- "opentelemetry-instrumentation-fetch-node": "1.1.2"
+ "@types/cookie": "0.5.2",
+ "@types/express": "^4.17.14",
+ "@types/lru-cache": "^5.1.0",
+ "@types/node": "14.18.63",
+ "express": "^4.17.1",
+ "nock": "^13.0.5",
+ "undici": "^5.21.0"
},
"scripts": {
- "build": "run-p build:transpile build:types",
+ "build": "run-s build:transpile build:types",
"build:dev": "yarn build",
"build:transpile": "rollup -c rollup.npm.config.mjs",
"build:types": "run-s build:types:core build:types:downlevel",
@@ -89,13 +72,23 @@
"clean": "rimraf build coverage sentry-node-experimental-*.tgz",
"fix": "eslint . --format stylish --fix",
"lint": "eslint . --format stylish",
- "test": "yarn test:jest",
+ "test": "run-s test:jest test:express test:webpack test:release-health",
+ "test:express": "node test/manual/express-scope-separation/start.js",
"test:jest": "jest",
+ "test:release-health": "node test/manual/release-health/runner.js",
+ "test:webpack": "cd test/manual/webpack-async-context/ && yarn --silent --ignore-engines && node npm-build.js",
"test:watch": "jest --watch",
"yalc:publish": "ts-node ../../scripts/prepack.ts && yalc publish build --push --sig"
},
"volta": {
"extends": "../../package.json"
},
+ "madge": {
+ "detectiveOptions": {
+ "ts": {
+ "skipTypeImports": true
+ }
+ }
+ },
"sideEffects": false
}
diff --git a/packages/node-experimental/rollup.anr-worker.config.mjs b/packages/node-experimental/rollup.anr-worker.config.mjs
index bd3c1d4b825c..48463d5763ee 100644
--- a/packages/node-experimental/rollup.anr-worker.config.mjs
+++ b/packages/node-experimental/rollup.anr-worker.config.mjs
@@ -1,31 +1,34 @@
import { makeBaseBundleConfig } from '@sentry-internal/rollup-utils';
-export function createAnrWorkerCode() {
- let base64Code;
-
- return {
- workerRollupConfig: makeBaseBundleConfig({
- bundleType: 'node-worker',
- entrypoints: ['src/integrations/anr/worker.ts'],
- licenseTitle: '@sentry/node',
- outputFileBase: () => 'worker-script.js',
- packageSpecificConfig: {
- output: {
- dir: 'build/esm/integrations/anr',
- sourcemap: false,
- },
- plugins: [
- {
- name: 'output-base64-worker-script',
- renderChunk(code) {
- base64Code = Buffer.from(code).toString('base64');
- },
- },
- ],
+function createAnrWorkerConfig(destDir, esm) {
+ return makeBaseBundleConfig({
+ bundleType: 'node-worker',
+ entrypoints: ['src/integrations/anr/worker.ts'],
+ licenseTitle: '@sentry/node',
+ outputFileBase: () => 'worker-script.js',
+ packageSpecificConfig: {
+ output: {
+ dir: destDir,
+ sourcemap: false,
},
- }),
- getBase64Code() {
- return base64Code;
+ plugins: [
+ {
+ name: 'output-base64-worker-script',
+ renderChunk(code) {
+ const base64Code = Buffer.from(code).toString('base64');
+ if (esm) {
+ return `export const base64WorkerScript = '${base64Code}';`;
+ } else {
+ return `exports.base64WorkerScript = '${base64Code}';`;
+ }
+ },
+ },
+ ],
},
- };
+ });
}
+
+export const anrWorkerConfigs = [
+ createAnrWorkerConfig('build/esm/integrations/anr', true),
+ createAnrWorkerConfig('build/cjs/integrations/anr', false),
+];
diff --git a/packages/node-experimental/rollup.npm.config.mjs b/packages/node-experimental/rollup.npm.config.mjs
index 17c0727d7eff..88c90de4825f 100644
--- a/packages/node-experimental/rollup.npm.config.mjs
+++ b/packages/node-experimental/rollup.npm.config.mjs
@@ -1,32 +1,8 @@
-import replace from '@rollup/plugin-replace';
import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils';
-import { createAnrWorkerCode } from './rollup.anr-worker.config.mjs';
-
-const { workerRollupConfig, getBase64Code } = createAnrWorkerCode();
+import { anrWorkerConfigs } from './rollup.anr-worker.config.mjs';
export default [
- // The worker needs to be built first since it's output is used in the main bundle.
- workerRollupConfig,
- ...makeNPMConfigVariants(
- makeBaseNPMConfig({
- packageSpecificConfig: {
- output: {
- // set exports to 'named' or 'auto' so that rollup doesn't warn
- exports: 'named',
- // set preserveModules to false because we want to bundle everything into one file.
- preserveModules: false,
- },
- plugins: [
- replace({
- delimiters: ['###', '###'],
- // removes some webpack warnings
- preventAssignment: true,
- values: {
- base64WorkerScript: () => getBase64Code(),
- },
- }),
- ],
- },
- }),
- ),
+ ...makeNPMConfigVariants(makeBaseNPMConfig()),
+ // The ANR worker builds must come after the main build because they overwrite the worker-script.js file
+ ...anrWorkerConfigs,
];
diff --git a/packages/node/src/_setSpanForScope.ts b/packages/node-experimental/src/_setSpanForScope.ts
similarity index 100%
rename from packages/node/src/_setSpanForScope.ts
rename to packages/node-experimental/src/_setSpanForScope.ts
diff --git a/packages/node/src/async/domain.ts b/packages/node-experimental/src/async/domain.ts
similarity index 100%
rename from packages/node/src/async/domain.ts
rename to packages/node-experimental/src/async/domain.ts
diff --git a/packages/node/src/async/hooks.ts b/packages/node-experimental/src/async/hooks.ts
similarity index 100%
rename from packages/node/src/async/hooks.ts
rename to packages/node-experimental/src/async/hooks.ts
diff --git a/packages/node/src/async/index.ts b/packages/node-experimental/src/async/index.ts
similarity index 100%
rename from packages/node/src/async/index.ts
rename to packages/node-experimental/src/async/index.ts
diff --git a/packages/node/src/client.ts b/packages/node-experimental/src/client.ts
similarity index 100%
rename from packages/node/src/client.ts
rename to packages/node-experimental/src/client.ts
diff --git a/packages/node/src/handlers.ts b/packages/node-experimental/src/handlers.ts
similarity index 100%
rename from packages/node/src/handlers.ts
rename to packages/node-experimental/src/handlers.ts
diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts
index 33bbcca54213..36b27086ef08 100644
--- a/packages/node-experimental/src/index.ts
+++ b/packages/node-experimental/src/index.ts
@@ -1,127 +1,150 @@
-export { httpIntegration } from './integrations/http';
-export { nativeNodeFetchIntegration } from './integrations/node-fetch';
-
-export { consoleIntegration } from './integrations/console';
-export { nodeContextIntegration } from './integrations/context';
-export { contextLinesIntegration } from './integrations/contextlines';
-export { localVariablesIntegration } from './integrations/local-variables';
-export { modulesIntegration } from './integrations/modules';
-export { onUncaughtExceptionIntegration } from './integrations/onuncaughtexception';
-export { onUnhandledRejectionIntegration } from './integrations/onunhandledrejection';
-export { anrIntegration } from './integrations/anr';
-
-export { expressIntegration, expressErrorHandler, setupExpressErrorHandler } from './integrations/tracing/express';
-export { fastifyIntegration, setupFastifyErrorHandler } from './integrations/tracing/fastify';
-export { graphqlIntegration } from './integrations/tracing/graphql';
-export { mongoIntegration } from './integrations/tracing/mongo';
-export { mongooseIntegration } from './integrations/tracing/mongoose';
-export { mysqlIntegration } from './integrations/tracing/mysql';
-export { mysql2Integration } from './integrations/tracing/mysql2';
-export { nestIntegration } from './integrations/tracing/nest';
-export { postgresIntegration } from './integrations/tracing/postgres';
-export { prismaIntegration } from './integrations/tracing/prisma';
-export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/hapi';
-export { spotlightIntegration } from './integrations/spotlight';
-
-export { init, getDefaultIntegrations } from './sdk/init';
-export { initOpenTelemetry } from './sdk/initOtel';
-export { getAutoPerformanceIntegrations } from './integrations/tracing';
-export { getSentryRelease, defaultStackParser } from './sdk/api';
-export { createGetModuleFromFilename } from './utils/module';
-export { makeNodeTransport } from './transports';
-export { NodeClient } from './sdk/client';
-export { cron } from './cron';
+export type {
+ Breadcrumb,
+ BreadcrumbHint,
+ PolymorphicRequest,
+ Request,
+ SdkInfo,
+ Event,
+ EventHint,
+ Exception,
+ Session,
+ SeverityLevel,
+ Span,
+ StackFrame,
+ Stacktrace,
+ Thread,
+ Transaction,
+ User,
+} from '@sentry/types';
+export type { AddRequestDataToEventOptions, TransactionNamingScheme } from '@sentry/utils';
export type { NodeOptions } from './types';
export {
- addRequestDataToEvent,
- DEFAULT_USER_INCLUDES,
- extractRequestData,
-} from '@sentry/utils';
-
-// These are custom variants that need to be used instead of the core one
-// As they have slightly different implementations
-export { continueTrace } from '@sentry/opentelemetry';
-
-export {
+ addEventProcessor,
addBreadcrumb,
- isInitialized,
- getGlobalScope,
+ addIntegration,
+ captureException,
+ captureEvent,
+ captureMessage,
close,
createTransport,
flush,
+ // eslint-disable-next-line deprecation/deprecation
+ getCurrentHub,
+ getClient,
+ isInitialized,
+ getCurrentScope,
+ getGlobalScope,
+ getIsolationScope,
Hub,
+ setCurrentClient,
+ Scope,
SDK_VERSION,
+ setContext,
+ setExtra,
+ setExtras,
+ setTag,
+ setTags,
+ setUser,
getSpanStatusFromHttpCode,
setHttpStatus,
+ withScope,
+ withIsolationScope,
captureCheckIn,
withMonitor,
- requestDataIntegration,
+ setMeasurement,
+ getActiveSpan,
+ getRootSpan,
+ startSpan,
+ startInactiveSpan,
+ startSpanManual,
+ withActiveSpan,
+ getSpanDescendants,
+ continueTrace,
+ parameterize,
functionToStringIntegration,
inboundFiltersIntegration,
linkedErrorsIntegration,
- addEventProcessor,
- setContext,
- setExtra,
- setExtras,
- setTag,
- setTags,
- setUser,
+ requestDataIntegration,
+ metricsDefault as metrics,
+ startSession,
+ captureSession,
+ endSession,
+} from '@sentry/core';
+
+export {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
- setCurrentClient,
- Scope,
- setMeasurement,
- getSpanDescendants,
- parameterize,
- getClient,
- // eslint-disable-next-line deprecation/deprecation
- getCurrentHub,
- getCurrentScope,
- getIsolationScope,
- withScope,
- withIsolationScope,
- captureException,
- captureEvent,
- captureMessage,
+} from '@sentry/core';
+
+export { autoDiscoverNodePerformanceMonitoringIntegrations } from './tracing';
+
+export { NodeClient } from './client';
+export { makeNodeTransport } from './transports';
+export {
+ getDefaultIntegrations,
+ init,
+ defaultStackParser,
+ getSentryRelease,
+} from './sdk';
+export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from '@sentry/utils';
+
+export { createGetModuleFromFilename } from './module';
+
+import * as Handlers from './handlers';
+import * as NodeIntegrations from './integrations';
+import * as TracingIntegrations from './tracing/integrations';
+
+// TODO: Deprecate this once we migrated tracing integrations
+export const Integrations = {
+ ...NodeIntegrations,
+ ...TracingIntegrations,
+};
+
+export {
captureConsoleIntegration,
debugIntegration,
dedupeIntegration,
extraErrorDataIntegration,
rewriteFramesIntegration,
sessionTimingIntegration,
- metricsDefault as metrics,
- startSession,
- captureSession,
- endSession,
- addIntegration,
- startSpan,
- startSpanManual,
- startInactiveSpan,
- getActiveSpan,
- withActiveSpan,
- getRootSpan,
- spanToJSON,
} from '@sentry/core';
-export type {
- Breadcrumb,
- BreadcrumbHint,
- PolymorphicRequest,
- Request,
- SdkInfo,
- Event,
- EventHint,
- Exception,
- Session,
- SeverityLevel,
- StackFrame,
- Stacktrace,
- Thread,
- Transaction,
- User,
- Span,
-} from '@sentry/types';
+export { consoleIntegration } from './integrations/console';
+export { onUncaughtExceptionIntegration } from './integrations/onuncaughtexception';
+export { onUnhandledRejectionIntegration } from './integrations/onunhandledrejection';
+export { modulesIntegration } from './integrations/modules';
+export { contextLinesIntegration } from './integrations/contextlines';
+export { nodeContextIntegration } from './integrations/context';
+export { localVariablesIntegration } from './integrations/local-variables';
+export { spotlightIntegration } from './integrations/spotlight';
+export { anrIntegration } from './integrations/anr';
+export { hapiIntegration } from './integrations/hapi';
+// eslint-disable-next-line deprecation/deprecation
+export { Undici, nativeNodeFetchintegration } from './integrations/undici';
+// eslint-disable-next-line deprecation/deprecation
+export { Http, httpIntegration } from './integrations/http';
+
+// TODO(v8): Remove all of these exports. They were part of a hotfix #10339 where we produced wrong .d.ts files because we were packing packages inside the /build folder.
+export type { LocalVariablesIntegrationOptions } from './integrations/local-variables/common';
+export type { DebugSession } from './integrations/local-variables/local-variables-sync';
+export type { AnrIntegrationOptions } from './integrations/anr/common';
+// ---
+
+export { Handlers };
+
+export { hapiErrorPlugin } from './integrations/hapi';
+
+import { instrumentCron } from './cron/cron';
+import { instrumentNodeCron } from './cron/node-cron';
+import { instrumentNodeSchedule } from './cron/node-schedule';
+
+/** Methods to instrument cron libraries for Sentry check-ins */
+export const cron = {
+ instrumentCron,
+ instrumentNodeCron,
+ instrumentNodeSchedule,
+};
diff --git a/packages/node-experimental/src/integrations/anr/common.ts b/packages/node-experimental/src/integrations/anr/common.ts
index e2e50fae4179..5617871ccb24 100644
--- a/packages/node-experimental/src/integrations/anr/common.ts
+++ b/packages/node-experimental/src/integrations/anr/common.ts
@@ -37,7 +37,6 @@ export interface WorkerStartData extends AnrIntegrationOptions {
debug: boolean;
sdkMetadata: SdkMetadata;
dsn: DsnComponents;
- tunnel: string | undefined;
release: string | undefined;
environment: string;
dist: string | undefined;
diff --git a/packages/node-experimental/src/integrations/anr/index.ts b/packages/node-experimental/src/integrations/anr/index.ts
index 7dbe9e905cb4..7e0de1d0badc 100644
--- a/packages/node-experimental/src/integrations/anr/index.ts
+++ b/packages/node-experimental/src/integrations/anr/index.ts
@@ -1,41 +1,37 @@
-import { defineIntegration, mergeScopeData } from '@sentry/core';
-import type { Contexts, Event, EventHint, Integration, IntegrationFn, ScopeData } from '@sentry/types';
-import { GLOBAL_OBJ, logger } from '@sentry/utils';
-import * as inspector from 'inspector';
-import { Worker } from 'worker_threads';
-import { getCurrentScope, getGlobalScope, getIsolationScope } from '../..';
+// TODO (v8): This import can be removed once we only support Node with global URL
+import { URL } from 'url';
+import { defineIntegration, getCurrentScope } from '@sentry/core';
+import type { Contexts, Event, EventHint, IntegrationFn } from '@sentry/types';
+import { dynamicRequire, logger } from '@sentry/utils';
+import type { Worker, WorkerOptions } from 'worker_threads';
+import type { NodeClient } from '../../client';
import { NODE_VERSION } from '../../nodeVersion';
-import type { NodeClient } from '../../sdk/client';
import type { AnrIntegrationOptions, WorkerStartData } from './common';
import { base64WorkerScript } from './worker-script';
const DEFAULT_INTERVAL = 50;
const DEFAULT_HANG_THRESHOLD = 5000;
+type WorkerNodeV14 = Worker & { new (filename: string | URL, options?: WorkerOptions): Worker };
+
+type WorkerThreads = {
+ Worker: WorkerNodeV14;
+};
+
function log(message: string, ...args: unknown[]): void {
logger.log(`[ANR] ${message}`, ...args);
}
-function globalWithScopeFetchFn(): typeof GLOBAL_OBJ & { __SENTRY_GET_SCOPES__?: () => ScopeData } {
- return GLOBAL_OBJ;
-}
-
-/** Fetches merged scope data */
-function getScopeData(): ScopeData {
- const scope = getGlobalScope().getScopeData();
- mergeScopeData(scope, getIsolationScope().getScopeData());
- mergeScopeData(scope, getCurrentScope().getScopeData());
-
- // We remove attachments because they likely won't serialize well as json
- scope.attachments = [];
- // We can't serialize event processor functions
- scope.eventProcessors = [];
-
- return scope;
+/**
+ * We need to use dynamicRequire because worker_threads is not available in node < v12 and webpack error will when
+ * targeting those versions
+ */
+function getWorkerThreads(): WorkerThreads {
+ return dynamicRequire(module, 'worker_threads');
}
/**
- * Gets contexts by calling all event processors. This shouldn't be called until all integrations are setup
+ * Gets contexts by calling all event processors. This relies on being called after all integrations are setup
*/
async function getContexts(client: NodeClient): Promise {
let event: Event | null = { message: 'ANR' };
@@ -49,76 +45,40 @@ async function getContexts(client: NodeClient): Promise {
return event?.contexts || {};
}
-const INTEGRATION_NAME = 'Anr';
+interface InspectorApi {
+ open: (port: number) => void;
+ url: () => string | undefined;
+}
-type AnrInternal = { startWorker: () => void; stopWorker: () => void };
+const INTEGRATION_NAME = 'Anr';
const _anrIntegration = ((options: Partial = {}) => {
- if (NODE_VERSION.major < 16 || (NODE_VERSION.major === 16 && NODE_VERSION.minor < 17)) {
- throw new Error('ANR detection requires Node 16.17.0 or later');
- }
-
- let worker: Promise<() => void> | undefined;
- let client: NodeClient | undefined;
-
- // Hookup the scope fetch function to the global object so that it can be called from the worker thread via the
- // debugger when it pauses
- const gbl = globalWithScopeFetchFn();
- gbl.__SENTRY_GET_SCOPES__ = getScopeData;
-
return {
name: INTEGRATION_NAME,
- startWorker: () => {
- if (worker) {
- return;
- }
-
- if (client) {
- worker = _startWorker(client, options);
- }
- },
- stopWorker: () => {
- if (worker) {
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- worker.then(stop => {
- stop();
- worker = undefined;
- });
+ setup(client: NodeClient) {
+ if (NODE_VERSION.major < 16 || (NODE_VERSION.major === 16 && NODE_VERSION.minor < 17)) {
+ throw new Error('ANR detection requires Node 16.17.0 or later');
}
- },
- setup(initClient: NodeClient) {
- client = initClient;
- // setImmediate is used to ensure that all other integrations have had their setup called first.
- // This allows us to call into all integrations to fetch the full context
- setImmediate(() => this.startWorker());
+ // setImmediate is used to ensure that all other integrations have been setup
+ setImmediate(() => _startWorker(client, options));
},
- } as Integration & AnrInternal;
+ };
}) satisfies IntegrationFn;
-type AnrReturn = (options?: Partial) => Integration & AnrInternal;
-
-export const anrIntegration = defineIntegration(_anrIntegration) as AnrReturn;
+export const anrIntegration = defineIntegration(_anrIntegration);
/**
* Starts the ANR worker thread
- *
- * @returns A function to stop the worker
*/
-async function _startWorker(
- client: NodeClient,
- integrationOptions: Partial,
-): Promise<() => void> {
+async function _startWorker(client: NodeClient, _options: Partial): Promise {
+ const contexts = await getContexts(client);
const dsn = client.getDsn();
if (!dsn) {
- return () => {
- //
- };
+ return;
}
- const contexts = await getContexts(client);
-
// These will not be accurate if sent later from the worker thread
delete contexts.app?.app_memory;
delete contexts.device?.free_memory;
@@ -133,25 +93,28 @@ async function _startWorker(
const options: WorkerStartData = {
debug: logger.isEnabled(),
dsn,
- tunnel: initOptions.tunnel,
environment: initOptions.environment || 'production',
release: initOptions.release,
dist: initOptions.dist,
sdkMetadata,
- appRootPath: integrationOptions.appRootPath,
- pollInterval: integrationOptions.pollInterval || DEFAULT_INTERVAL,
- anrThreshold: integrationOptions.anrThreshold || DEFAULT_HANG_THRESHOLD,
- captureStackTrace: !!integrationOptions.captureStackTrace,
- staticTags: integrationOptions.staticTags || {},
+ appRootPath: _options.appRootPath,
+ pollInterval: _options.pollInterval || DEFAULT_INTERVAL,
+ anrThreshold: _options.anrThreshold || DEFAULT_HANG_THRESHOLD,
+ captureStackTrace: !!_options.captureStackTrace,
+ staticTags: _options.staticTags || {},
contexts,
};
if (options.captureStackTrace) {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const inspector: InspectorApi = require('inspector');
if (!inspector.url()) {
inspector.open(0);
}
}
+ const { Worker } = getWorkerThreads();
+
const worker = new Worker(new URL(`data:application/javascript;base64,${base64WorkerScript}`), {
workerData: options,
});
@@ -195,10 +158,4 @@ async function _startWorker(
// Ensure this thread can't block app exit
worker.unref();
-
- return () => {
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
- worker.terminate();
- clearInterval(timer);
- };
}
diff --git a/packages/node-experimental/src/integrations/anr/worker-script.ts b/packages/node-experimental/src/integrations/anr/worker-script.ts
index c70323e0fc50..16394eaacfe1 100644
--- a/packages/node-experimental/src/integrations/anr/worker-script.ts
+++ b/packages/node-experimental/src/integrations/anr/worker-script.ts
@@ -1,2 +1,2 @@
-// This string is a placeholder that gets overwritten with the worker code.
-export const base64WorkerScript = '###base64WorkerScript###';
+// This file is a placeholder that gets overwritten in the build directory.
+export const base64WorkerScript = '';
diff --git a/packages/node-experimental/src/integrations/anr/worker.ts b/packages/node-experimental/src/integrations/anr/worker.ts
index 21bdcbbb0631..a8b984b48379 100644
--- a/packages/node-experimental/src/integrations/anr/worker.ts
+++ b/packages/node-experimental/src/integrations/anr/worker.ts
@@ -1,12 +1,11 @@
import {
- applyScopeDataToEvent,
createEventEnvelope,
createSessionEnvelope,
getEnvelopeEndpointWithUrlEncodedAuth,
makeSession,
updateSession,
} from '@sentry/core';
-import type { Event, ScopeData, Session, StackFrame } from '@sentry/types';
+import type { Event, Session, StackFrame, TraceContext } from '@sentry/types';
import {
callFrameToStackFrame,
normalizeUrlToBase,
@@ -17,11 +16,12 @@ import {
import { Session as InspectorSession } from 'inspector';
import { parentPort, workerData } from 'worker_threads';
+import { createGetModuleFromFilename } from '../../module';
import { makeNodeTransport } from '../../transports';
-import { createGetModuleFromFilename } from '../../utils/module';
import type { WorkerStartData } from './common';
type VoidFunction = () => void;
+type InspectorSessionNodeV12 = InspectorSession & { connectToMainThread: VoidFunction };
const options: WorkerStartData = workerData;
let session: Session | undefined;
@@ -34,7 +34,7 @@ function log(msg: string): void {
}
}
-const url = getEnvelopeEndpointWithUrlEncodedAuth(options.dsn, options.tunnel, options.sdkMetadata.sdk);
+const url = getEnvelopeEndpointWithUrlEncodedAuth(options.dsn);
const transport = makeNodeTransport({
url,
recordDroppedEvent: () => {
@@ -48,7 +48,7 @@ async function sendAbnormalSession(): Promise {
log('Sending abnormal session');
updateSession(session, { status: 'abnormal', abnormal_mechanism: 'anr_foreground' });
- const envelope = createSessionEnvelope(session, options.dsn, options.sdkMetadata, options.tunnel);
+ const envelope = createSessionEnvelope(session, options.dsn, options.sdkMetadata);
// Log the envelope so to aid in testing
log(JSON.stringify(envelope));
@@ -87,23 +87,7 @@ function prepareStackFrames(stackFrames: StackFrame[] | undefined): StackFrame[]
return strippedFrames;
}
-function applyScopeToEvent(event: Event, scope: ScopeData): void {
- applyScopeDataToEvent(event, scope);
-
- if (!event.contexts?.trace) {
- const { traceId, spanId, parentSpanId } = scope.propagationContext;
- event.contexts = {
- trace: {
- trace_id: traceId,
- span_id: spanId,
- parent_span_id: parentSpanId,
- },
- ...event.contexts,
- };
- }
-}
-
-async function sendAnrEvent(frames?: StackFrame[], scope?: ScopeData): Promise {
+async function sendAnrEvent(frames?: StackFrame[], traceContext?: TraceContext): Promise {
if (hasSentAnrEvent) {
return;
}
@@ -116,7 +100,7 @@ async function sendAnrEvent(frames?: StackFrame[], scope?: ScopeData): Promise {
process.exit(0);
}, 5_000);
@@ -159,7 +139,7 @@ let debuggerPause: VoidFunction | undefined;
if (options.captureStackTrace) {
log('Connecting to debugger');
- const session = new InspectorSession();
+ const session = new InspectorSession() as InspectorSessionNodeV12;
session.connectToMainThread();
log('Connected to debugger');
@@ -192,23 +172,20 @@ if (options.captureStackTrace) {
'Runtime.evaluate',
{
// Grab the trace context from the current scope
- expression: 'global.__SENTRY_GET_SCOPES__();',
+ expression:
+ 'const ctx = __SENTRY__.acs?.getCurrentScope().getPropagationContext() || {}; ctx.traceId + "-" + ctx.spanId + "-" + ctx.parentSpanId',
// Don't re-trigger the debugger if this causes an error
silent: true,
- // Serialize the result to json otherwise only primitives are supported
- returnByValue: true,
},
- (err, param) => {
- if (err) {
- log(`Error executing script: '${err.message}'`);
- }
-
- const scopes = param && param.result ? (param.result.value as ScopeData) : undefined;
+ (_, param) => {
+ const traceId = param && param.result ? (param.result.value as string) : '--';
+ const [trace_id, span_id, parent_span_id] = traceId.split('-') as (string | undefined)[];
session.post('Debugger.resume');
session.post('Debugger.disable');
- sendAnrEvent(stackFrames, scopes).then(null, () => {
+ const context = trace_id?.length && span_id?.length ? { trace_id, span_id, parent_span_id } : undefined;
+ sendAnrEvent(stackFrames, context).then(null, () => {
log('Sending ANR event failed.');
});
},
diff --git a/packages/node-experimental/src/integrations/console.ts b/packages/node-experimental/src/integrations/console.ts
index 0b3d27fe8510..5a185b8fcee1 100644
--- a/packages/node-experimental/src/integrations/console.ts
+++ b/packages/node-experimental/src/integrations/console.ts
@@ -1,6 +1,6 @@
import * as util from 'util';
-import { addBreadcrumb, defineIntegration, getClient } from '@sentry/core';
-import type { IntegrationFn } from '@sentry/types';
+import { addBreadcrumb, convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core';
+import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
import { addConsoleInstrumentationHandler, severityLevelFromString } from '@sentry/utils';
const INTEGRATION_NAME = 'Console';
@@ -30,7 +30,16 @@ const _consoleIntegration = (() => {
};
}) satisfies IntegrationFn;
+export const consoleIntegration = defineIntegration(_consoleIntegration);
+
/**
- * Capture console logs as breadcrumbs.
+ * Console module integration.
+ * @deprecated Use `consoleIntegration()` instead.
*/
-export const consoleIntegration = defineIntegration(_consoleIntegration);
+// eslint-disable-next-line deprecation/deprecation
+export const Console = convertIntegrationFnToClass(INTEGRATION_NAME, consoleIntegration) as IntegrationClass<
+ Integration & { setup: (client: Client) => void }
+>;
+
+// eslint-disable-next-line deprecation/deprecation
+export type Console = typeof Console;
diff --git a/packages/node-experimental/src/integrations/context.ts b/packages/node-experimental/src/integrations/context.ts
index c33d97e79044..fa5184204bf2 100644
--- a/packages/node-experimental/src/integrations/context.ts
+++ b/packages/node-experimental/src/integrations/context.ts
@@ -1,9 +1,10 @@
+/* eslint-disable max-lines */
import { execFile } from 'child_process';
import { readFile, readdir } from 'fs';
import * as os from 'os';
import { join } from 'path';
import { promisify } from 'util';
-import { defineIntegration } from '@sentry/core';
+import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
import type {
AppContext,
CloudResourceContext,
@@ -11,10 +12,13 @@ import type {
CultureContext,
DeviceContext,
Event,
+ Integration,
+ IntegrationClass,
IntegrationFn,
OsContext,
} from '@sentry/types';
+// TODO: Required until we drop support for Node v8
export const readFileAsync = promisify(readFile);
export const readDirAsync = promisify(readdir);
@@ -104,10 +108,27 @@ const _nodeContextIntegration = ((options: ContextOptions = {}) => {
};
}) satisfies IntegrationFn;
+export const nodeContextIntegration = defineIntegration(_nodeContextIntegration);
+
/**
- * Capture context about the environment and the device that the client is running on, to events.
+ * Add node modules / packages to the event.
+ * @deprecated Use `nodeContextIntegration()` instead.
*/
-export const nodeContextIntegration = defineIntegration(_nodeContextIntegration);
+// eslint-disable-next-line deprecation/deprecation
+export const Context = convertIntegrationFnToClass(INTEGRATION_NAME, nodeContextIntegration) as IntegrationClass<
+ Integration & { processEvent: (event: Event) => Promise }
+> & {
+ new (options?: {
+ app?: boolean;
+ os?: boolean;
+ device?: { cpu?: boolean; memory?: boolean } | boolean;
+ culture?: boolean;
+ cloudResource?: boolean;
+ }): Integration;
+};
+
+// eslint-disable-next-line deprecation/deprecation
+export type Context = typeof Context;
/**
* Updates the context with dynamic values that can change
diff --git a/packages/node-experimental/src/integrations/contextlines.ts b/packages/node-experimental/src/integrations/contextlines.ts
index 3755e164e5ea..7c367f51a970 100644
--- a/packages/node-experimental/src/integrations/contextlines.ts
+++ b/packages/node-experimental/src/integrations/contextlines.ts
@@ -1,4 +1,4 @@
-import { promises } from 'fs';
+import { readFile } from 'fs';
import { defineIntegration } from '@sentry/core';
import type { Event, IntegrationFn, StackFrame } from '@sentry/types';
import { LRUMap, addContextToFrame } from '@sentry/utils';
@@ -7,7 +7,15 @@ const FILE_CONTENT_CACHE = new LRUMap(100);
const DEFAULT_LINES_OF_CONTEXT = 7;
const INTEGRATION_NAME = 'ContextLines';
-const readFileAsync = promises.readFile;
+// TODO: Replace with promisify when minimum supported node >= v8
+function readTextFileAsync(path: string): Promise {
+ return new Promise((resolve, reject) => {
+ readFile(path, 'utf8', (err, data) => {
+ if (err) reject(err);
+ else resolve(data);
+ });
+ });
+}
/**
* Resets the file cache. Exists for testing purposes.
@@ -27,8 +35,7 @@ interface ContextLinesOptions {
frameContextLines?: number;
}
-/** Exported only for tests, as a type-safe variant. */
-export const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => {
+const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => {
const contextLines = options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT;
return {
@@ -39,9 +46,6 @@ export const _contextLinesIntegration = ((options: ContextLinesOptions = {}) =>
};
}) satisfies IntegrationFn;
-/**
- * Capture the lines before and after the frame's context.
- */
export const contextLinesIntegration = defineIntegration(_contextLinesIntegration);
async function addSourceContext(event: Event, contextLines: number): Promise {
@@ -135,7 +139,7 @@ async function _readSourceFile(filename: string): Promise {
// If we made it to here, it means that our file is not cache nor marked as failed, so attempt to read it
let content: string[] | null = null;
try {
- const rawFileContents = await readFileAsync(filename, 'utf-8');
+ const rawFileContents = await readTextFileAsync(filename);
content = rawFileContents.split('\n');
} catch (_) {
// if we fail, we will mark the file as null in the cache and short circuit next time we try to read it
diff --git a/packages/node/src/integrations/hapi/index.ts b/packages/node-experimental/src/integrations/hapi/index.ts
similarity index 100%
rename from packages/node/src/integrations/hapi/index.ts
rename to packages/node-experimental/src/integrations/hapi/index.ts
diff --git a/packages/node/src/integrations/hapi/types.ts b/packages/node-experimental/src/integrations/hapi/types.ts
similarity index 100%
rename from packages/node/src/integrations/hapi/types.ts
rename to packages/node-experimental/src/integrations/hapi/types.ts
diff --git a/packages/node-experimental/src/integrations/http.ts b/packages/node-experimental/src/integrations/http.ts
index ed93ebacdaa5..9eb7deab5318 100644
--- a/packages/node-experimental/src/integrations/http.ts
+++ b/packages/node-experimental/src/integrations/http.ts
@@ -1,155 +1,468 @@
-import type { ServerResponse } from 'http';
-import type { Span } from '@opentelemetry/api';
-import { SpanKind } from '@opentelemetry/api';
-import { registerInstrumentations } from '@opentelemetry/instrumentation';
-import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
-
-import { addBreadcrumb, defineIntegration, getIsolationScope, isSentryRequestUrl } from '@sentry/core';
-import { _INTERNAL, getClient, getSpanKind } from '@sentry/opentelemetry';
-import type { IntegrationFn } from '@sentry/types';
-
-import type { NodeClient } from '../sdk/client';
-import { setIsolationScope } from '../sdk/scope';
-import type { HTTPModuleRequestIncomingMessage } from '../transports/http-module';
-import { addOriginToSpan } from '../utils/addOriginToSpan';
-import { getRequestUrl } from '../utils/getRequestUrl';
+/* eslint-disable max-lines */
+import type * as http from 'http';
+import type * as https from 'https';
+import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startInactiveSpan } from '@sentry/core';
+import { defineIntegration, getIsolationScope, hasTracingEnabled } from '@sentry/core';
+import {
+ addBreadcrumb,
+ getClient,
+ getCurrentScope,
+ getDynamicSamplingContextFromClient,
+ getDynamicSamplingContextFromSpan,
+ isSentryRequestUrl,
+ setHttpStatus,
+ spanToJSON,
+ spanToTraceHeader,
+} from '@sentry/core';
+import type {
+ ClientOptions,
+ Integration,
+ IntegrationFn,
+ SanitizedRequestData,
+ TracePropagationTargets,
+} from '@sentry/types';
+import {
+ LRUMap,
+ dropUndefinedKeys,
+ dynamicSamplingContextToSentryBaggageHeader,
+ fill,
+ generateSentryTraceHeader,
+ logger,
+ stringMatchesSomePattern,
+} from '@sentry/utils';
+
+import type { NodeClient } from '../client';
+import { DEBUG_BUILD } from '../debug-build';
+import { NODE_VERSION } from '../nodeVersion';
+import type { NodeClientOptions } from '../types';
+import type { RequestMethod, RequestMethodArgs, RequestOptions } from './utils/http';
+import { cleanSpanName, extractRawUrl, extractUrl, normalizeRequestArgs } from './utils/http';
+
+interface TracingOptions {
+ /**
+ * List of strings/regex controlling to which outgoing requests
+ * the SDK will attach tracing headers.
+ *
+ * By default the SDK will attach those headers to all outgoing
+ * requests. If this option is provided, the SDK will match the
+ * request URL of outgoing requests against the items in this
+ * array, and only attach tracing headers if a match was found.
+ *
+ * @deprecated Use top level `tracePropagationTargets` option instead.
+ * This option will be removed in v8.
+ *
+ * ```
+ * Sentry.init({
+ * tracePropagationTargets: ['api.site.com'],
+ * })
+ */
+ tracePropagationTargets?: TracePropagationTargets;
+
+ /**
+ * Function determining whether or not to create spans to track outgoing requests to the given URL.
+ * By default, spans will be created for all outgoing requests.
+ */
+ shouldCreateSpanForRequest?: (url: string) => boolean;
+
+ /**
+ * This option is just for compatibility with v7.
+ * In v8, this will be the default behavior.
+ */
+ enableIfHasTracingEnabled?: boolean;
+}
interface HttpOptions {
/**
- * Whether breadcrumbs should be recorded for requests.
+ * Whether breadcrumbs should be recorded for requests
* Defaults to true
*/
breadcrumbs?: boolean;
/**
- * Do not capture spans or breadcrumbs for outgoing HTTP requests to URLs where the given callback returns `true`.
- * This controls both span & breadcrumb creation - spans will be non recording if tracing is disabled.
+ * Whether tracing spans should be created for requests
+ * Defaults to false
*/
- ignoreOutgoingRequests?: (url: string) => boolean;
+ tracing?: TracingOptions | boolean;
+}
+/* These are the newer options for `httpIntegration`. */
+interface HttpIntegrationOptions {
/**
- * Do not capture spans or breadcrumbs for incoming HTTP requests to URLs where the given callback returns `true`.
- * This controls both span & breadcrumb creation - spans will be non recording if tracing is disabled.
+ * Whether breadcrumbs should be recorded for requests
+ * Defaults to true.
*/
- ignoreIncomingRequests?: (url: string) => boolean;
+ breadcrumbs?: boolean;
+
+ /**
+ * Whether tracing spans should be created for requests
+ * If not set, this will be enabled/disabled based on if tracing is enabled.
+ */
+ tracing?: boolean;
+
+ /**
+ * Function determining whether or not to create spans to track outgoing requests to the given URL.
+ * By default, spans will be created for all outgoing requests.
+ */
+ shouldCreateSpanForRequest?: (url: string) => boolean;
}
-const _httpIntegration = ((options: HttpOptions = {}) => {
- const _breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
- const _ignoreOutgoingRequests = options.ignoreOutgoingRequests;
- const _ignoreIncomingRequests = options.ignoreIncomingRequests;
-
- return {
- name: 'Http',
- setupOnce() {
- const instrumentations = [
- new HttpInstrumentation({
- ignoreOutgoingRequestHook: request => {
- const url = getRequestUrl(request);
-
- if (!url) {
- return false;
- }
+const _httpIntegration = ((options: HttpIntegrationOptions = {}) => {
+ const { breadcrumbs, tracing, shouldCreateSpanForRequest } = options;
- if (isSentryRequestUrl(url, getClient())) {
- return true;
- }
+ const convertedOptions: HttpOptions = {
+ breadcrumbs,
+ tracing:
+ tracing === false
+ ? false
+ : dropUndefinedKeys({
+ // If tracing is forced to `true`, we don't want to set `enableIfHasTracingEnabled`
+ enableIfHasTracingEnabled: tracing === true ? undefined : true,
+ shouldCreateSpanForRequest,
+ }),
+ };
+ // eslint-disable-next-line deprecation/deprecation
+ return new Http(convertedOptions) as unknown as Integration;
+}) satisfies IntegrationFn;
- if (_ignoreOutgoingRequests && _ignoreOutgoingRequests(url)) {
- return true;
- }
+/**
+ * The http module integration instruments Node's internal http module. It creates breadcrumbs, spans for outgoing
+ * http requests, and attaches trace data when tracing is enabled via its `tracing` option.
+ *
+ * By default, this will always create breadcrumbs, and will create spans if tracing is enabled.
+ */
+export const httpIntegration = defineIntegration(_httpIntegration);
- return false;
- },
+/**
+ * The http integration instruments Node's internal http and https modules.
+ * It creates breadcrumbs and spans for outgoing HTTP requests which will be attached to the currently active span.
+ *
+ * @deprecated Use `httpIntegration()` instead.
+ */
+export class Http implements Integration {
+ /**
+ * @inheritDoc
+ */
+ public static id: string = 'Http';
- ignoreIncomingRequestHook: request => {
- const url = getRequestUrl(request);
+ /**
+ * @inheritDoc
+ */
+ // eslint-disable-next-line deprecation/deprecation
+ public name: string = Http.id;
- const method = request.method?.toUpperCase();
- // We do not capture OPTIONS/HEAD requests as transactions
- if (method === 'OPTIONS' || method === 'HEAD') {
- return true;
- }
+ private readonly _breadcrumbs: boolean;
+ private readonly _tracing: TracingOptions | undefined;
- if (_ignoreIncomingRequests && _ignoreIncomingRequests(url)) {
- return true;
- }
+ /**
+ * @inheritDoc
+ */
+ public constructor(options: HttpOptions = {}) {
+ this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
+ this._tracing = !options.tracing ? undefined : options.tracing === true ? {} : options.tracing;
+ }
- return false;
- },
+ /**
+ * @inheritDoc
+ */
+ public setupOnce(): void {
+ const clientOptions = getClient()?.getOptions();
- requireParentforOutgoingSpans: true,
- requireParentforIncomingSpans: false,
- requestHook: (span, req) => {
- _updateSpan(span);
+ // If `tracing` is not explicitly set, we default this based on whether or not tracing is enabled.
+ // But for compatibility, we only do that if `enableIfHasTracingEnabled` is set.
+ const shouldCreateSpans = _shouldCreateSpans(this._tracing, clientOptions);
- // Update the isolation scope, isolate this request
- if (getSpanKind(span) === SpanKind.SERVER) {
- const isolationScope = getIsolationScope().clone();
- isolationScope.setSDKProcessingMetadata({ request: req });
+ // No need to instrument if we don't want to track anything
+ if (!this._breadcrumbs && !shouldCreateSpans) {
+ return;
+ }
- const client = getClient();
- if (client && client.getOptions().autoSessionTracking) {
- isolationScope.setRequestSession({ status: 'ok' });
- }
- setIsolationScope(isolationScope);
- }
- },
- responseHook: (span, res) => {
- if (_breadcrumbs) {
- _addRequestBreadcrumb(span, res);
- }
+ const shouldCreateSpanForRequest = _getShouldCreateSpanForRequest(shouldCreateSpans, this._tracing, clientOptions);
- const client = getClient();
- if (client && client.getOptions().autoSessionTracking) {
- setImmediate(() => {
- client['_captureRequestSession']();
- });
- }
- },
- }),
- ];
-
- registerInstrumentations({
- instrumentations,
- });
- },
- };
-}) satisfies IntegrationFn;
+ // eslint-disable-next-line deprecation/deprecation
+ const tracePropagationTargets = clientOptions?.tracePropagationTargets || this._tracing?.tracePropagationTargets;
+
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const httpModule = require('http');
+ const wrappedHttpHandlerMaker = _createWrappedRequestMethodFactory(
+ httpModule,
+ this._breadcrumbs,
+ shouldCreateSpanForRequest,
+ tracePropagationTargets,
+ );
+ fill(httpModule, 'get', wrappedHttpHandlerMaker);
+ fill(httpModule, 'request', wrappedHttpHandlerMaker);
+
+ // NOTE: Prior to Node 9, `https` used internals of `http` module, thus we don't patch it.
+ // If we do, we'd get double breadcrumbs and double spans for `https` calls.
+ // It has been changed in Node 9, so for all versions equal and above, we patch `https` separately.
+ if (NODE_VERSION.major > 8) {
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const httpsModule = require('node:https');
+ const wrappedHttpsHandlerMaker = _createWrappedRequestMethodFactory(
+ httpsModule,
+ this._breadcrumbs,
+ shouldCreateSpanForRequest,
+ tracePropagationTargets,
+ );
+ fill(httpsModule, 'get', wrappedHttpsHandlerMaker);
+ fill(httpsModule, 'request', wrappedHttpsHandlerMaker);
+ }
+ }
+}
+
+// for ease of reading below
+type OriginalRequestMethod = RequestMethod;
+type WrappedRequestMethod = RequestMethod;
+type WrappedRequestMethodFactory = (original: OriginalRequestMethod) => WrappedRequestMethod;
/**
- * The http integration instruments Node's internal http and https modules.
- * It creates breadcrumbs and spans for outgoing HTTP requests which will be attached to the currently active span.
+ * Function which creates a function which creates wrapped versions of internal `request` and `get` calls within `http`
+ * and `https` modules. (NB: Not a typo - this is a creator^2!)
+ *
+ * @param breadcrumbsEnabled Whether or not to record outgoing requests as breadcrumbs
+ * @param tracingEnabled Whether or not to record outgoing requests as tracing spans
+ *
+ * @returns A function which accepts the exiting handler and returns a wrapped handler
*/
-export const httpIntegration = defineIntegration(_httpIntegration);
+function _createWrappedRequestMethodFactory(
+ httpModule: typeof http | typeof https,
+ breadcrumbsEnabled: boolean,
+ shouldCreateSpanForRequest: ((url: string) => boolean) | undefined,
+ tracePropagationTargets: TracePropagationTargets | undefined,
+): WrappedRequestMethodFactory {
+ // We're caching results so we don't have to recompute regexp every time we create a request.
+ const createSpanUrlMap = new LRUMap(100);
+ const headersUrlMap = new LRUMap(100);
+
+ const shouldCreateSpan = (url: string): boolean => {
+ if (shouldCreateSpanForRequest === undefined) {
+ return true;
+ }
+
+ const cachedDecision = createSpanUrlMap.get(url);
+ if (cachedDecision !== undefined) {
+ return cachedDecision;
+ }
+
+ const decision = shouldCreateSpanForRequest(url);
+ createSpanUrlMap.set(url, decision);
+ return decision;
+ };
+
+ const shouldAttachTraceData = (url: string): boolean => {
+ if (tracePropagationTargets === undefined) {
+ return true;
+ }
+
+ const cachedDecision = headersUrlMap.get(url);
+ if (cachedDecision !== undefined) {
+ return cachedDecision;
+ }
+
+ const decision = stringMatchesSomePattern(url, tracePropagationTargets);
+ headersUrlMap.set(url, decision);
+ return decision;
+ };
+
+ /**
+ * Captures Breadcrumb based on provided request/response pair
+ */
+ function addRequestBreadcrumb(
+ event: string,
+ requestSpanData: SanitizedRequestData,
+ req: http.ClientRequest,
+ res?: http.IncomingMessage,
+ ): void {
+ if (!getClient()?.getIntegrationByName('Http')) {
+ return;
+ }
+
+ addBreadcrumb(
+ {
+ category: 'http',
+ data: {
+ status_code: res && res.statusCode,
+ ...requestSpanData,
+ },
+ type: 'http',
+ },
+ {
+ event,
+ request: req,
+ response: res,
+ },
+ );
+ }
+
+ return function wrappedRequestMethodFactory(originalRequestMethod: OriginalRequestMethod): WrappedRequestMethod {
+ return function wrappedMethod(this: unknown, ...args: RequestMethodArgs): http.ClientRequest {
+ const requestArgs = normalizeRequestArgs(httpModule, args);
+ const requestOptions = requestArgs[0];
+ const rawRequestUrl = extractRawUrl(requestOptions);
+ const requestUrl = extractUrl(requestOptions);
+ const client = getClient();
+
+ // we don't want to record requests to Sentry as either breadcrumbs or spans, so just use the original method
+ if (isSentryRequestUrl(requestUrl, client)) {
+ return originalRequestMethod.apply(httpModule, requestArgs);
+ }
+
+ const scope = getCurrentScope();
+ const isolationScope = getIsolationScope();
+
+ const attributes = getRequestSpanData(requestUrl, requestOptions);
-/** Update the span with data we need. */
-function _updateSpan(span: Span): void {
- addOriginToSpan(span, 'auto.http.otel.http');
+ const requestSpan = shouldCreateSpan(rawRequestUrl)
+ ? startInactiveSpan({
+ onlyIfParent: true,
+ op: 'http.client',
+ name: `${attributes['http.method']} ${attributes.url}`,
+ attributes: {
+ ...attributes,
+ [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.node.http',
+ },
+ })
+ : undefined;
+
+ if (client && shouldAttachTraceData(rawRequestUrl)) {
+ const { traceId, spanId, sampled, dsc } = {
+ ...isolationScope.getPropagationContext(),
+ ...scope.getPropagationContext(),
+ };
+
+ const sentryTraceHeader = requestSpan
+ ? spanToTraceHeader(requestSpan)
+ : generateSentryTraceHeader(traceId, spanId, sampled);
+
+ const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(
+ dsc ||
+ (requestSpan
+ ? getDynamicSamplingContextFromSpan(requestSpan)
+ : getDynamicSamplingContextFromClient(traceId, client)),
+ );
+
+ addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, sentryBaggageHeader);
+ } else {
+ DEBUG_BUILD &&
+ logger.log(
+ `[Tracing] Not adding sentry-trace header to outgoing request (${requestUrl}) due to mismatching tracePropagationTargets option.`,
+ );
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
+ return originalRequestMethod
+ .apply(httpModule, requestArgs)
+ .once('response', function (this: http.ClientRequest, res: http.IncomingMessage): void {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const req = this;
+ if (breadcrumbsEnabled) {
+ addRequestBreadcrumb('response', attributes, req, res);
+ }
+ if (requestSpan) {
+ if (res.statusCode) {
+ setHttpStatus(requestSpan, res.statusCode);
+ }
+ requestSpan.updateName(cleanSpanName(spanToJSON(requestSpan).description || '', requestOptions, req) || '');
+ requestSpan.end();
+ }
+ })
+ .once('error', function (this: http.ClientRequest): void {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const req = this;
+
+ if (breadcrumbsEnabled) {
+ addRequestBreadcrumb('error', attributes, req);
+ }
+ if (requestSpan) {
+ setHttpStatus(requestSpan, 500);
+ requestSpan.updateName(cleanSpanName(spanToJSON(requestSpan).description || '', requestOptions, req) || '');
+ requestSpan.end();
+ }
+ });
+ };
+ };
}
-/** Add a breadcrumb for outgoing requests. */
-function _addRequestBreadcrumb(span: Span, response: HTTPModuleRequestIncomingMessage | ServerResponse): void {
- if (getSpanKind(span) !== SpanKind.CLIENT) {
+function addHeadersToRequestOptions(
+ requestOptions: RequestOptions,
+ requestUrl: string,
+ sentryTraceHeader: string,
+ sentryBaggageHeader: string | undefined,
+): void {
+ // Don't overwrite sentry-trace and baggage header if it's already set.
+ const headers = requestOptions.headers || {};
+ if (headers['sentry-trace']) {
return;
}
- const data = _INTERNAL.getRequestSpanData(span);
- addBreadcrumb(
- {
- category: 'http',
- data: {
- status_code: response.statusCode,
- ...data,
- },
- type: 'http',
- },
- {
- event: 'response',
- // TODO FN: Do we need access to `request` here?
- // If we do, we'll have to use the `applyCustomAttributesOnSpan` hook instead,
- // but this has worse context semantics than request/responseHook.
- response,
- },
- );
+ DEBUG_BUILD &&
+ logger.log(`[Tracing] Adding sentry-trace header ${sentryTraceHeader} to outgoing request to "${requestUrl}": `);
+
+ requestOptions.headers = {
+ ...requestOptions.headers,
+ 'sentry-trace': sentryTraceHeader,
+ // Setting a header to `undefined` will crash in node so we only set the baggage header when it's defined
+ ...(sentryBaggageHeader &&
+ sentryBaggageHeader.length > 0 && { baggage: normalizeBaggageHeader(requestOptions, sentryBaggageHeader) }),
+ };
+}
+
+function getRequestSpanData(requestUrl: string, requestOptions: RequestOptions): SanitizedRequestData {
+ const method = requestOptions.method || 'GET';
+ const data: SanitizedRequestData = {
+ url: requestUrl,
+ 'http.method': method,
+ };
+ if (requestOptions.hash) {
+ // strip leading "#"
+ data['http.fragment'] = requestOptions.hash.substring(1);
+ }
+ if (requestOptions.search) {
+ // strip leading "?"
+ data['http.query'] = requestOptions.search.substring(1);
+ }
+ return data;
+}
+
+function normalizeBaggageHeader(
+ requestOptions: RequestOptions,
+ sentryBaggageHeader: string | undefined,
+): string | string[] | undefined {
+ if (!requestOptions.headers || !requestOptions.headers.baggage) {
+ return sentryBaggageHeader;
+ } else if (!sentryBaggageHeader) {
+ return requestOptions.headers.baggage as string | string[];
+ } else if (Array.isArray(requestOptions.headers.baggage)) {
+ return [...requestOptions.headers.baggage, sentryBaggageHeader];
+ }
+ // Type-cast explanation:
+ // Technically this the following could be of type `(number | string)[]` but for the sake of simplicity
+ // we say this is undefined behaviour, since it would not be baggage spec conform if the user did this.
+ return [requestOptions.headers.baggage, sentryBaggageHeader] as string[];
+}
+
+/** Exported for tests only. */
+export function _shouldCreateSpans(
+ tracingOptions: TracingOptions | undefined,
+ clientOptions: Partial | undefined,
+): boolean {
+ return tracingOptions === undefined
+ ? false
+ : tracingOptions.enableIfHasTracingEnabled
+ ? hasTracingEnabled(clientOptions)
+ : true;
+}
+
+/** Exported for tests only. */
+export function _getShouldCreateSpanForRequest(
+ shouldCreateSpans: boolean,
+ tracingOptions: TracingOptions | undefined,
+ clientOptions: Partial | undefined,
+): undefined | ((url: string) => boolean) {
+ const handler = shouldCreateSpans
+ ? // eslint-disable-next-line deprecation/deprecation
+ tracingOptions?.shouldCreateSpanForRequest || clientOptions?.shouldCreateSpanForRequest
+ : () => false;
+
+ return handler;
}
diff --git a/packages/node/src/integrations/index.ts b/packages/node-experimental/src/integrations/index.ts
similarity index 100%
rename from packages/node/src/integrations/index.ts
rename to packages/node-experimental/src/integrations/index.ts
diff --git a/packages/node/src/integrations/local-variables/inspector.d.ts b/packages/node-experimental/src/integrations/local-variables/inspector.d.ts
similarity index 100%
rename from packages/node/src/integrations/local-variables/inspector.d.ts
rename to packages/node-experimental/src/integrations/local-variables/inspector.d.ts
diff --git a/packages/node/src/integrations/local-variables/local-variables-async.ts b/packages/node-experimental/src/integrations/local-variables/local-variables-async.ts
similarity index 100%
rename from packages/node/src/integrations/local-variables/local-variables-async.ts
rename to packages/node-experimental/src/integrations/local-variables/local-variables-async.ts
diff --git a/packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts b/packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts
index 91fb9005b4c3..e1ec4b57023c 100644
--- a/packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts
+++ b/packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts
@@ -1,11 +1,11 @@
-import { defineIntegration, getClient } from '@sentry/core';
-import type { Event, Exception, IntegrationFn, StackParser } from '@sentry/types';
+/* eslint-disable max-lines */
+import { convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core';
+import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackParser } from '@sentry/types';
import { LRUMap, logger } from '@sentry/utils';
-import type { Debugger, InspectorNotification, Runtime } from 'inspector';
-import { Session } from 'inspector';
+import type { Debugger, InspectorNotification, Runtime, Session } from 'inspector';
+import type { NodeClient } from '../../client';
-import { NODE_MAJOR } from '../../nodeVersion';
-import type { NodeClient } from '../../sdk/client';
+import { NODE_VERSION } from '../../nodeVersion';
import type {
FrameVariables,
LocalVariablesIntegrationOptions,
@@ -79,6 +79,22 @@ class AsyncSession implements DebugSession {
/** Throws if inspector API is not available */
public constructor() {
+ /*
+ TODO: We really should get rid of this require statement below for a couple of reasons:
+ 1. It makes the integration unusable in the SvelteKit SDK, as it's not possible to use `require`
+ in SvelteKit server code (at least not by default).
+ 2. Throwing in a constructor is bad practice
+
+ More context for a future attempt to fix this:
+ We already tried replacing it with import but didn't get it to work because of async problems.
+ We still called import in the constructor but assigned to a promise which we "awaited" in
+ `configureAndConnect`. However, this broke the Node integration tests as no local variables
+ were reported any more. We probably missed a place where we need to await the promise, too.
+ */
+
+ // Node can be built without inspector support so this can throw
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
+ const { Session } = require('inspector');
this._session = new Session();
}
@@ -288,16 +304,14 @@ const _localVariablesSyncIntegration = ((
return;
}
- // Filter out frames where the function name is `new Promise` since these are in the error.stack frames
- // but do not appear in the debugger call frames
- const frames = (exception.stacktrace?.frames || []).filter(frame => frame.function !== 'new Promise');
+ const frameCount = exception.stacktrace?.frames?.length || 0;
- for (let i = 0; i < frames.length; i++) {
+ for (let i = 0; i < frameCount; i++) {
// Sentry frames are in reverse order
- const frameIndex = frames.length - i - 1;
+ const frameIndex = frameCount - i - 1;
// Drop out if we run out of frames to match up
- if (!frames[frameIndex] || !cachedFrame[i]) {
+ if (!exception?.stacktrace?.frames?.[frameIndex] || !cachedFrame[i]) {
break;
}
@@ -305,14 +319,14 @@ const _localVariablesSyncIntegration = ((
// We need to have vars to add
cachedFrame[i].vars === undefined ||
// We're not interested in frames that are not in_app because the vars are not relevant
- frames[frameIndex].in_app === false ||
+ exception.stacktrace.frames[frameIndex].in_app === false ||
// The function names need to match
- !functionNamesMatch(frames[frameIndex].function, cachedFrame[i].function)
+ !functionNamesMatch(exception.stacktrace.frames[frameIndex].function, cachedFrame[i].function)
) {
continue;
}
- frames[frameIndex].vars = cachedFrame[i].vars;
+ exception.stacktrace.frames[frameIndex].vars = cachedFrame[i].vars;
}
}
@@ -333,7 +347,7 @@ const _localVariablesSyncIntegration = ((
if (session && clientOptions?.includeLocalVariables) {
// Only setup this integration if the Node version is >= v18
// https://github.com/getsentry/sentry-javascript/issues/7697
- const unsupportedNodeVersion = NODE_MAJOR < 18;
+ const unsupportedNodeVersion = NODE_VERSION.major < 18;
if (unsupportedNodeVersion) {
logger.log('The `LocalVariables` integration is only supported on Node >= v18.');
@@ -386,7 +400,19 @@ const _localVariablesSyncIntegration = ((
};
}) satisfies IntegrationFn;
+export const localVariablesSyncIntegration = defineIntegration(_localVariablesSyncIntegration);
+
/**
* Adds local variables to exception frames.
+ * @deprecated Use `localVariablesSyncIntegration()` instead.
*/
-export const localVariablesSyncIntegration = defineIntegration(_localVariablesSyncIntegration);
+// eslint-disable-next-line deprecation/deprecation
+export const LocalVariablesSync = convertIntegrationFnToClass(
+ INTEGRATION_NAME,
+ localVariablesSyncIntegration,
+) as IntegrationClass Event; setup: (client: NodeClient) => void }> & {
+ new (options?: LocalVariablesIntegrationOptions, session?: DebugSession): Integration;
+};
+
+// eslint-disable-next-line deprecation/deprecation
+export type LocalVariablesSync = typeof LocalVariablesSync;
diff --git a/packages/node-experimental/src/integrations/modules.ts b/packages/node-experimental/src/integrations/modules.ts
index ad30bb4d7a3b..1f9aff7303e3 100644
--- a/packages/node-experimental/src/integrations/modules.ts
+++ b/packages/node-experimental/src/integrations/modules.ts
@@ -1,31 +1,12 @@
import { existsSync, readFileSync } from 'fs';
import { dirname, join } from 'path';
-import { defineIntegration } from '@sentry/core';
-import type { IntegrationFn } from '@sentry/types';
+import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
+import type { Event, Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
let moduleCache: { [key: string]: string };
const INTEGRATION_NAME = 'Modules';
-const _modulesIntegration = (() => {
- return {
- name: INTEGRATION_NAME,
- processEvent(event) {
- event.modules = {
- ...event.modules,
- ..._getModules(),
- };
-
- return event;
- },
- };
-}) satisfies IntegrationFn;
-
-/**
- * Add node modules / packages to the event.
- */
-export const modulesIntegration = defineIntegration(_modulesIntegration);
-
/** Extract information about paths */
function getPaths(): string[] {
try {
@@ -94,3 +75,31 @@ function _getModules(): { [key: string]: string } {
}
return moduleCache;
}
+
+const _modulesIntegration = (() => {
+ return {
+ name: INTEGRATION_NAME,
+ processEvent(event) {
+ event.modules = {
+ ...event.modules,
+ ..._getModules(),
+ };
+
+ return event;
+ },
+ };
+}) satisfies IntegrationFn;
+
+export const modulesIntegration = defineIntegration(_modulesIntegration);
+
+/**
+ * Add node modules / packages to the event.
+ * @deprecated Use `modulesIntegration()` instead.
+ */
+// eslint-disable-next-line deprecation/deprecation
+export const Modules = convertIntegrationFnToClass(INTEGRATION_NAME, modulesIntegration) as IntegrationClass<
+ Integration & { processEvent: (event: Event) => Event }
+>;
+
+// eslint-disable-next-line deprecation/deprecation
+export type Modules = typeof Modules;
diff --git a/packages/node-experimental/src/integrations/onuncaughtexception.ts b/packages/node-experimental/src/integrations/onuncaughtexception.ts
index e56c3c0801d7..68be68a6d6cc 100644
--- a/packages/node-experimental/src/integrations/onuncaughtexception.ts
+++ b/packages/node-experimental/src/integrations/onuncaughtexception.ts
@@ -1,11 +1,11 @@
-import { captureException, defineIntegration } from '@sentry/core';
+import { captureException, convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
import { getClient } from '@sentry/core';
-import type { IntegrationFn } from '@sentry/types';
+import type { Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
import { logger } from '@sentry/utils';
+import type { NodeClient } from '../client';
import { DEBUG_BUILD } from '../debug-build';
-import type { NodeClient } from '../sdk/client';
-import { logAndExitProcess } from '../utils/errorhandling';
+import { logAndExitProcess } from './utils/errorhandling';
type OnFatalErrorHandler = (firstError: Error, secondError?: Error) => void;
@@ -54,10 +54,27 @@ const _onUncaughtExceptionIntegration = ((options: Partial void }> & {
+ new (
+ options?: Partial<{
+ exitEvenIfOtherHandlersAreRegistered: boolean;
+ onFatalError?(this: void, firstError: Error, secondError?: Error): void;
+ }>,
+ ): Integration;
+};
+
+// eslint-disable-next-line deprecation/deprecation
+export type OnUncaughtException = typeof OnUncaughtException;
type ErrorHandler = { _errorHandler: boolean } & ((error: Error) => void);
@@ -86,19 +103,20 @@ export function makeErrorHandler(client: NodeClient, options: OnUncaughtExceptio
// exit behaviour of the SDK accordingly:
// - If other listeners are attached, do not exit.
// - If the only listener attached is ours, exit.
- const userProvidedListenersCount = (global.process.listeners('uncaughtException') as TaggedListener[]).filter(
- listener => {
+ const userProvidedListenersCount = (
+ global.process.listeners('uncaughtException') as TaggedListener[]
+ ).reduce((acc, listener) => {
+ if (
// There are 3 listeners we ignore:
- return (
- // as soon as we're using domains this listener is attached by node itself
- listener.name !== 'domainUncaughtExceptionClear' &&
- // the handler we register for tracing
- listener.tag !== 'sentry_tracingErrorCallback' &&
- // the handler we register in this integration
- (listener as ErrorHandler)._errorHandler !== true
- );
- },
- ).length;
+ listener.name === 'domainUncaughtExceptionClear' || // as soon as we're using domains this listener is attached by node itself
+ (listener.tag && listener.tag === 'sentry_tracingErrorCallback') || // the handler we register for tracing
+ (listener as ErrorHandler)._errorHandler // the handler we register in this integration
+ ) {
+ return acc;
+ } else {
+ return acc + 1;
+ }
+ }, 0);
const processWouldExit = userProvidedListenersCount === 0;
const shouldApplyFatalHandlingLogic = options.exitEvenIfOtherHandlersAreRegistered || processWouldExit;
diff --git a/packages/node-experimental/src/integrations/onunhandledrejection.ts b/packages/node-experimental/src/integrations/onunhandledrejection.ts
index e1bc0b4145cf..9f3801b7a0cf 100644
--- a/packages/node-experimental/src/integrations/onunhandledrejection.ts
+++ b/packages/node-experimental/src/integrations/onunhandledrejection.ts
@@ -1,7 +1,8 @@
-import { captureException, defineIntegration, getClient } from '@sentry/core';
-import type { Client, IntegrationFn } from '@sentry/types';
+import { captureException, convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core';
+import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
import { consoleSandbox } from '@sentry/utils';
-import { logAndExitProcess } from '../utils/errorhandling';
+
+import { logAndExitProcess } from './utils/errorhandling';
type UnhandledRejectionMode = 'none' | 'warn' | 'strict';
@@ -26,10 +27,22 @@ const _onUnhandledRejectionIntegration = ((options: Partial void }> & {
+ new (options?: Partial<{ mode: UnhandledRejectionMode }>): Integration;
+};
+
+// eslint-disable-next-line deprecation/deprecation
+export type OnUnhandledRejection = typeof OnUnhandledRejection;
/**
* Send an exception with reason
diff --git a/packages/node-experimental/src/integrations/spotlight.ts b/packages/node-experimental/src/integrations/spotlight.ts
index 21629ad340ac..eb9c34260b61 100644
--- a/packages/node-experimental/src/integrations/spotlight.ts
+++ b/packages/node-experimental/src/integrations/spotlight.ts
@@ -1,6 +1,7 @@
import * as http from 'http';
-import { defineIntegration } from '@sentry/core';
-import type { Client, Envelope, IntegrationFn } from '@sentry/types';
+import { URL } from 'url';
+import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
+import type { Client, Envelope, Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
import { logger, serializeEnvelope } from '@sentry/utils';
type SpotlightConnectionOptions = {
@@ -29,14 +30,30 @@ const _spotlightIntegration = ((options: Partial = {
};
}) satisfies IntegrationFn;
+export const spotlightIntegration = defineIntegration(_spotlightIntegration);
+
/**
* Use this integration to send errors and transactions to Spotlight.
*
* Learn more about spotlight at https://spotlightjs.com
*
* Important: This integration only works with Node 18 or newer.
+ *
+ * @deprecated Use `spotlightIntegration()` instead.
*/
-export const spotlightIntegration = defineIntegration(_spotlightIntegration);
+// eslint-disable-next-line deprecation/deprecation
+export const Spotlight = convertIntegrationFnToClass(INTEGRATION_NAME, spotlightIntegration) as IntegrationClass<
+ Integration & { setup: (client: Client) => void }
+> & {
+ new (
+ options?: Partial<{
+ sidecarUrl?: string;
+ }>,
+ ): Integration;
+};
+
+// eslint-disable-next-line deprecation/deprecation
+export type Spotlight = typeof Spotlight;
function connectToSpotlight(client: Client, options: Required): void {
const spotlightUrl = parseSidecarUrl(options.sidecarUrl);
diff --git a/packages/node/src/integrations/undici/index.ts b/packages/node-experimental/src/integrations/undici/index.ts
similarity index 100%
rename from packages/node/src/integrations/undici/index.ts
rename to packages/node-experimental/src/integrations/undici/index.ts
diff --git a/packages/node/src/integrations/undici/types.ts b/packages/node-experimental/src/integrations/undici/types.ts
similarity index 100%
rename from packages/node/src/integrations/undici/types.ts
rename to packages/node-experimental/src/integrations/undici/types.ts
diff --git a/packages/node/src/integrations/utils/errorhandling.ts b/packages/node-experimental/src/integrations/utils/errorhandling.ts
similarity index 100%
rename from packages/node/src/integrations/utils/errorhandling.ts
rename to packages/node-experimental/src/integrations/utils/errorhandling.ts
diff --git a/packages/node/src/integrations/utils/http.ts b/packages/node-experimental/src/integrations/utils/http.ts
similarity index 100%
rename from packages/node/src/integrations/utils/http.ts
rename to packages/node-experimental/src/integrations/utils/http.ts
diff --git a/packages/node-experimental/src/utils/module.ts b/packages/node-experimental/src/module.ts
similarity index 100%
rename from packages/node-experimental/src/utils/module.ts
rename to packages/node-experimental/src/module.ts
diff --git a/packages/node-experimental/src/nodeVersion.ts b/packages/node-experimental/src/nodeVersion.ts
index 1f07883b771b..1574237f3fb4 100644
--- a/packages/node-experimental/src/nodeVersion.ts
+++ b/packages/node-experimental/src/nodeVersion.ts
@@ -1,4 +1,3 @@
import { parseSemver } from '@sentry/utils';
export const NODE_VERSION = parseSemver(process.versions.node) as { major: number; minor: number; patch: number };
-export const NODE_MAJOR = NODE_VERSION.major;
diff --git a/packages/node-experimental/src/proxy/helpers.ts b/packages/node-experimental/src/proxy/helpers.ts
index 031878511f6c..a5064408855d 100644
--- a/packages/node-experimental/src/proxy/helpers.ts
+++ b/packages/node-experimental/src/proxy/helpers.ts
@@ -30,6 +30,8 @@
import * as http from 'node:http';
import * as https from 'node:https';
import type { Readable } from 'stream';
+// TODO (v8): Remove this when Node < 12 is no longer supported
+import type { URL } from 'url';
export type ThenableRequest = http.ClientRequest & {
then: Promise['then'];
diff --git a/packages/node-experimental/src/proxy/index.ts b/packages/node-experimental/src/proxy/index.ts
index 83f72d56fb4e..15c700ed3e62 100644
--- a/packages/node-experimental/src/proxy/index.ts
+++ b/packages/node-experimental/src/proxy/index.ts
@@ -32,6 +32,8 @@ import type * as http from 'http';
import type { OutgoingHttpHeaders } from 'http';
import * as net from 'net';
import * as tls from 'tls';
+// TODO (v8): Remove this when Node < 12 is no longer supported
+import { URL } from 'url';
import { logger } from '@sentry/utils';
import { Agent } from './base';
import type { AgentConnectOpts } from './base';
diff --git a/packages/node/src/sdk.ts b/packages/node-experimental/src/sdk.ts
similarity index 100%
rename from packages/node/src/sdk.ts
rename to packages/node-experimental/src/sdk.ts
diff --git a/packages/node/src/tracing/index.ts b/packages/node-experimental/src/tracing/index.ts
similarity index 100%
rename from packages/node/src/tracing/index.ts
rename to packages/node-experimental/src/tracing/index.ts
diff --git a/packages/node/src/tracing/integrations.ts b/packages/node-experimental/src/tracing/integrations.ts
similarity index 100%
rename from packages/node/src/tracing/integrations.ts
rename to packages/node-experimental/src/tracing/integrations.ts
diff --git a/packages/node-experimental/src/transports/http-module.ts b/packages/node-experimental/src/transports/http-module.ts
index f5cbe6fd35f9..64b255cc869c 100644
--- a/packages/node-experimental/src/transports/http-module.ts
+++ b/packages/node-experimental/src/transports/http-module.ts
@@ -1,5 +1,7 @@
-import type { ClientRequest, IncomingHttpHeaders, RequestOptions as HTTPRequestOptions } from 'node:http';
-import type { RequestOptions as HTTPSRequestOptions } from 'node:https';
+import type { IncomingHttpHeaders, RequestOptions as HTTPRequestOptions } from 'http';
+import type { RequestOptions as HTTPSRequestOptions } from 'https';
+import type { Writable } from 'stream';
+import type { URL } from 'url';
export type HTTPModuleRequestOptions = HTTPRequestOptions | HTTPSRequestOptions | string | URL;
@@ -24,5 +26,15 @@ export interface HTTPModule {
* @param options These are {@see TransportOptions}
* @param callback Callback when request is finished
*/
- request(options: HTTPModuleRequestOptions, callback?: (res: HTTPModuleRequestIncomingMessage) => void): ClientRequest;
+ request(options: HTTPModuleRequestOptions, callback?: (res: HTTPModuleRequestIncomingMessage) => void): Writable;
+
+ // This is the type for nodejs versions that handle the URL argument
+ // (v10.9.0+), but we do not use it just yet because we support older node
+ // versions:
+
+ // request(
+ // url: string | URL,
+ // options: http.RequestOptions | https.RequestOptions,
+ // callback?: (res: http.IncomingMessage) => void,
+ // ): http.ClientRequest;
}
diff --git a/packages/node-experimental/src/transports/http.ts b/packages/node-experimental/src/transports/http.ts
index 4cbe7ece1f60..a6a05fc07c95 100644
--- a/packages/node-experimental/src/transports/http.ts
+++ b/packages/node-experimental/src/transports/http.ts
@@ -1,9 +1,8 @@
import * as http from 'node:http';
import * as https from 'node:https';
import { Readable } from 'stream';
+import { URL } from 'url';
import { createGzip } from 'zlib';
-import { context } from '@opentelemetry/api';
-import { suppressTracing } from '@opentelemetry/core';
import { createTransport } from '@sentry/core';
import type {
BaseTransportOptions,
@@ -14,6 +13,7 @@ import type {
} from '@sentry/types';
import { consoleSandbox } from '@sentry/utils';
import { HttpsProxyAgent } from '../proxy';
+
import type { HTTPModule } from './http-module';
export interface NodeTransportOptions extends BaseTransportOptions {
@@ -81,11 +81,8 @@ export function makeNodeTransport(options: NodeTransportOptions): Transport {
? (new HttpsProxyAgent(proxy) as http.Agent)
: new nativeHttpModule.Agent({ keepAlive, maxSockets: 30, timeout: 2000 });
- // This ensures we do not generate any spans in OpenTelemetry for the transport
- return context.with(suppressTracing(context.active()), () => {
- const requestExecutor = createRequestExecutor(options, options.httpModule ?? nativeHttpModule, agent);
- return createTransport(options, requestExecutor);
- });
+ const requestExecutor = createRequestExecutor(options, options.httpModule ?? nativeHttpModule, agent);
+ return createTransport(options, requestExecutor);
}
/**
diff --git a/packages/node-experimental/src/types.ts b/packages/node-experimental/src/types.ts
index d78e1761fd79..01f91fb46cbe 100644
--- a/packages/node-experimental/src/types.ts
+++ b/packages/node-experimental/src/types.ts
@@ -1,7 +1,6 @@
-import type { Span as WriteableSpan } from '@opentelemetry/api';
-import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
-import type { ClientOptions, Options, SamplingContext, Scope, Span, TracePropagationTargets } from '@sentry/types';
+import type { ClientOptions, Options, SamplingContext, TracePropagationTargets } from '@sentry/types';
+import type { NodeClient } from './client';
import type { NodeTransportOptions } from './transports';
export interface BaseNodeOptions {
@@ -52,6 +51,14 @@ export interface BaseNodeOptions {
*/
includeLocalVariables?: boolean;
+ /**
+ * Specify a custom NodeClient to be used. Must extend NodeClient!
+ * This is not a public, supported API, but used internally only.
+ *
+ * @hidden
+ * */
+ clientClass?: typeof NodeClient;
+
/**
* If you use Spotlight by Sentry during development, use
* this option to forward captured Sentry events to Spotlight.
@@ -64,15 +71,23 @@ export interface BaseNodeOptions {
*/
spotlight?: boolean | string;
+ // TODO (v8): Remove this in v8
/**
- * If this is set to true, the SDK will not set up OpenTelemetry automatically.
- * In this case, you _have_ to ensure to set it up correctly yourself, including:
- * * The `SentrySpanProcessor`
- * * The `SentryPropagator`
- * * The `SentryContextManager`
- * * The `SentrySampler`
+ * @deprecated Moved to constructor options of the `Http` and `Undici` integration.
+ * @example
+ * ```js
+ * Sentry.init({
+ * integrations: [
+ * new Sentry.Integrations.Http({
+ * tracing: {
+ * shouldCreateSpanForRequest: (url: string) => false,
+ * }
+ * });
+ * ],
+ * });
+ * ```
*/
- skipOpenTelemetrySetup?: boolean;
+ shouldCreateSpanForRequest?(this: void, url: string): boolean;
/** Callback that is executed when a fatal global error occurs. */
onFatalError?(this: void, error: Error): void;
@@ -89,19 +104,3 @@ export interface NodeOptions extends Options, BaseNodeOpti
* @see NodeClient for more information.
*/
export interface NodeClientOptions extends ClientOptions, BaseNodeOptions {}
-
-export interface CurrentScopes {
- scope: Scope;
- isolationScope: Scope;
-}
-
-/**
- * The base `Span` type is basically a `WriteableSpan`.
- * There are places where we basically want to allow passing _any_ span,
- * so in these cases we type this as `AbstractSpan` which could be either a regular `Span` or a `ReadableSpan`.
- * You'll have to make sur to check revelant fields before accessing them.
- *
- * Note that technically, the `Span` exported from `@opentelemwetry/sdk-trace-base` matches this,
- * but we cannot be 100% sure that we are actually getting such a span, so this type is more defensive.
- */
-export type AbstractSpan = WriteableSpan | ReadableSpan | Span;
diff --git a/packages/node/test/async/domain.test.ts b/packages/node-experimental/test/async/domain.test.ts
similarity index 100%
rename from packages/node/test/async/domain.test.ts
rename to packages/node-experimental/test/async/domain.test.ts
diff --git a/packages/node/test/async/hooks.test.ts b/packages/node-experimental/test/async/hooks.test.ts
similarity index 100%
rename from packages/node/test/async/hooks.test.ts
rename to packages/node-experimental/test/async/hooks.test.ts
diff --git a/packages/node/test/client.test.ts b/packages/node-experimental/test/client.test.ts
similarity index 100%
rename from packages/node/test/client.test.ts
rename to packages/node-experimental/test/client.test.ts
diff --git a/packages/node/test/eventbuilders.test.ts b/packages/node-experimental/test/eventbuilders.test.ts
similarity index 100%
rename from packages/node/test/eventbuilders.test.ts
rename to packages/node-experimental/test/eventbuilders.test.ts
diff --git a/packages/node/test/fixtures/testDeepReadDirSync/cats/eddy.txt b/packages/node-experimental/test/fixtures/testDeepReadDirSync/cats/eddy.txt
similarity index 100%
rename from packages/node/test/fixtures/testDeepReadDirSync/cats/eddy.txt
rename to packages/node-experimental/test/fixtures/testDeepReadDirSync/cats/eddy.txt
diff --git a/packages/node/test/fixtures/testDeepReadDirSync/cats/persephone.txt b/packages/node-experimental/test/fixtures/testDeepReadDirSync/cats/persephone.txt
similarity index 100%
rename from packages/node/test/fixtures/testDeepReadDirSync/cats/persephone.txt
rename to packages/node-experimental/test/fixtures/testDeepReadDirSync/cats/persephone.txt
diff --git a/packages/node/test/fixtures/testDeepReadDirSync/cats/piper.txt b/packages/node-experimental/test/fixtures/testDeepReadDirSync/cats/piper.txt
similarity index 100%
rename from packages/node/test/fixtures/testDeepReadDirSync/cats/piper.txt
rename to packages/node-experimental/test/fixtures/testDeepReadDirSync/cats/piper.txt
diff --git a/packages/node/test/fixtures/testDeepReadDirSync/cats/sassafras.txt b/packages/node-experimental/test/fixtures/testDeepReadDirSync/cats/sassafras.txt
similarity index 100%
rename from packages/node/test/fixtures/testDeepReadDirSync/cats/sassafras.txt
rename to packages/node-experimental/test/fixtures/testDeepReadDirSync/cats/sassafras.txt
diff --git a/packages/node/test/fixtures/testDeepReadDirSync/cats/teaberry.txt b/packages/node-experimental/test/fixtures/testDeepReadDirSync/cats/teaberry.txt
similarity index 100%
rename from packages/node/test/fixtures/testDeepReadDirSync/cats/teaberry.txt
rename to packages/node-experimental/test/fixtures/testDeepReadDirSync/cats/teaberry.txt
diff --git a/packages/node/test/fixtures/testDeepReadDirSync/debra.txt b/packages/node-experimental/test/fixtures/testDeepReadDirSync/debra.txt
similarity index 100%
rename from packages/node/test/fixtures/testDeepReadDirSync/debra.txt
rename to packages/node-experimental/test/fixtures/testDeepReadDirSync/debra.txt
diff --git a/packages/node/test/fixtures/testDeepReadDirSync/dogs/theBigs/charlie.txt b/packages/node-experimental/test/fixtures/testDeepReadDirSync/dogs/theBigs/charlie.txt
similarity index 100%
rename from packages/node/test/fixtures/testDeepReadDirSync/dogs/theBigs/charlie.txt
rename to packages/node-experimental/test/fixtures/testDeepReadDirSync/dogs/theBigs/charlie.txt
diff --git a/packages/node/test/fixtures/testDeepReadDirSync/dogs/theBigs/maisey.txt b/packages/node-experimental/test/fixtures/testDeepReadDirSync/dogs/theBigs/maisey.txt
similarity index 100%
rename from packages/node/test/fixtures/testDeepReadDirSync/dogs/theBigs/maisey.txt
rename to packages/node-experimental/test/fixtures/testDeepReadDirSync/dogs/theBigs/maisey.txt
diff --git a/packages/node/test/fixtures/testDeepReadDirSync/dogs/theSmalls/bodhi.txt b/packages/node-experimental/test/fixtures/testDeepReadDirSync/dogs/theSmalls/bodhi.txt
similarity index 100%
rename from packages/node/test/fixtures/testDeepReadDirSync/dogs/theSmalls/bodhi.txt
rename to packages/node-experimental/test/fixtures/testDeepReadDirSync/dogs/theSmalls/bodhi.txt
diff --git a/packages/node/test/fixtures/testDeepReadDirSync/dogs/theSmalls/cory.txt b/packages/node-experimental/test/fixtures/testDeepReadDirSync/dogs/theSmalls/cory.txt
similarity index 100%
rename from packages/node/test/fixtures/testDeepReadDirSync/dogs/theSmalls/cory.txt
rename to packages/node-experimental/test/fixtures/testDeepReadDirSync/dogs/theSmalls/cory.txt
diff --git a/packages/node/test/handlers.test.ts b/packages/node-experimental/test/handlers.test.ts
similarity index 100%
rename from packages/node/test/handlers.test.ts
rename to packages/node-experimental/test/handlers.test.ts
diff --git a/packages/node/test/helper/error.ts b/packages/node-experimental/test/helper/error.ts
similarity index 100%
rename from packages/node/test/helper/error.ts
rename to packages/node-experimental/test/helper/error.ts
diff --git a/packages/node/test/helper/node-client-options.ts b/packages/node-experimental/test/helper/node-client-options.ts
similarity index 100%
rename from packages/node/test/helper/node-client-options.ts
rename to packages/node-experimental/test/helper/node-client-options.ts
diff --git a/packages/node/test/index.test.ts b/packages/node-experimental/test/index.test.ts
similarity index 100%
rename from packages/node/test/index.test.ts
rename to packages/node-experimental/test/index.test.ts
diff --git a/packages/node-experimental/test/integrations/contextlines.test.ts b/packages/node-experimental/test/integrations/contextlines.test.ts
index c4ef1efaa292..67f3ad793fbc 100644
--- a/packages/node-experimental/test/integrations/contextlines.test.ts
+++ b/packages/node-experimental/test/integrations/contextlines.test.ts
@@ -1,37 +1,30 @@
-import { promises } from 'fs';
-import type { StackFrame } from '@sentry/types';
+import * as fs from 'fs';
+import type { Event, Integration, StackFrame } from '@sentry/types';
import { parseStackFrames } from '@sentry/utils';
-import { _contextLinesIntegration, resetFileContentCache } from '../../src/integrations/contextlines';
-import { defaultStackParser } from '../../src/sdk/api';
-import { getError } from '../helpers/error';
-
-jest.mock('fs', () => {
- const actual = jest.requireActual('fs');
- return {
- ...actual,
- promises: {
- ...actual.promises,
- readFile: jest.fn(actual.promises),
- },
- };
-});
+import { contextLinesIntegration } from '../../src';
+import { resetFileContentCache } from '../../src/integrations/contextlines';
+import { defaultStackParser } from '../../src/sdk';
+import { getError } from '../helper/error';
describe('ContextLines', () => {
- const readFileSpy = promises.readFile as unknown as jest.SpyInstance;
- let contextLines: ReturnType;
+ let readFileSpy: jest.SpyInstance;
+ let contextLines: Integration;
async function addContext(frames: StackFrame[]): Promise {
- await contextLines.processEvent({ exception: { values: [{ stacktrace: { frames } }] } });
+ await (contextLines as Integration & { processEvent: (event: Event) => Promise }).processEvent({
+ exception: { values: [{ stacktrace: { frames } }] },
+ });
}
beforeEach(() => {
- contextLines = _contextLinesIntegration();
+ readFileSpy = jest.spyOn(fs, 'readFile');
+ contextLines = contextLinesIntegration();
resetFileContentCache();
});
afterEach(() => {
- jest.clearAllMocks();
+ jest.restoreAllMocks();
});
describe('lru file cache', () => {
@@ -108,7 +101,7 @@ describe('ContextLines', () => {
});
test('parseStack with no context', async () => {
- contextLines = _contextLinesIntegration({ frameContextLines: 0 });
+ contextLines = contextLinesIntegration({ frameContextLines: 0 });
expect.assertions(1);
const frames = parseStackFrames(defaultStackParser, new Error('test'));
@@ -120,6 +113,7 @@ describe('ContextLines', () => {
test('does not attempt to readfile multiple times if it fails', async () => {
expect.assertions(1);
+ contextLines = contextLinesIntegration();
readFileSpy.mockImplementation(() => {
throw new Error("ENOENT: no such file or directory, open '/does/not/exist.js'");
diff --git a/packages/node/test/integrations/http.test.ts b/packages/node-experimental/test/integrations/http.test.ts
similarity index 100%
rename from packages/node/test/integrations/http.test.ts
rename to packages/node-experimental/test/integrations/http.test.ts
diff --git a/packages/node-experimental/test/integrations/localvariables.test.ts b/packages/node-experimental/test/integrations/localvariables.test.ts
index db9385214d42..abc1d241f842 100644
--- a/packages/node-experimental/test/integrations/localvariables.test.ts
+++ b/packages/node-experimental/test/integrations/localvariables.test.ts
@@ -1,12 +1,12 @@
import { createRateLimiter } from '../../src/integrations/local-variables/common';
import { createCallbackList } from '../../src/integrations/local-variables/local-variables-sync';
-import { NODE_MAJOR } from '../../src/nodeVersion';
+import { NODE_VERSION } from '../../src/nodeVersion';
jest.setTimeout(20_000);
const describeIf = (condition: boolean) => (condition ? describe : describe.skip);
-describeIf(NODE_MAJOR >= 18)('LocalVariables', () => {
+describeIf(NODE_VERSION.major >= 18)('LocalVariables', () => {
describe('createCallbackList', () => {
it('Should call callbacks in reverse order', done => {
const log: number[] = [];
diff --git a/packages/node-experimental/test/integrations/spotlight.test.ts b/packages/node-experimental/test/integrations/spotlight.test.ts
index 6b888c22edcd..a892677d0dd0 100644
--- a/packages/node-experimental/test/integrations/spotlight.test.ts
+++ b/packages/node-experimental/test/integrations/spotlight.test.ts
@@ -2,9 +2,9 @@ import * as http from 'http';
import type { Envelope, EventEnvelope } from '@sentry/types';
import { createEnvelope, logger } from '@sentry/utils';
-import { spotlightIntegration } from '../../src/integrations/spotlight';
-import { NodeClient } from '../../src/sdk/client';
-import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOptions';
+import { NodeClient, spotlightIntegration } from '../../src';
+import { Spotlight } from '../../src/integrations';
+import { getDefaultNodeClientOptions } from '../helper/node-client-options';
describe('Spotlight', () => {
const loggerSpy = jest.spyOn(logger, 'warn');
@@ -17,9 +17,12 @@ describe('Spotlight', () => {
const options = getDefaultNodeClientOptions();
const client = new NodeClient(options);
- it('has a name', () => {
- const integration = spotlightIntegration();
+ it('has a name and id', () => {
+ // eslint-disable-next-line deprecation/deprecation
+ const integration = new Spotlight();
expect(integration.name).toEqual('Spotlight');
+ // eslint-disable-next-line deprecation/deprecation
+ expect(Spotlight.id).toEqual('Spotlight');
});
it('registers a callback on the `beforeEnvelope` hook', () => {
diff --git a/packages/node/test/integrations/undici.test.ts b/packages/node-experimental/test/integrations/undici.test.ts
similarity index 100%
rename from packages/node/test/integrations/undici.test.ts
rename to packages/node-experimental/test/integrations/undici.test.ts
diff --git a/packages/node/test/manual/colorize.js b/packages/node-experimental/test/manual/colorize.js
similarity index 100%
rename from packages/node/test/manual/colorize.js
rename to packages/node-experimental/test/manual/colorize.js
diff --git a/packages/node/test/manual/express-scope-separation/start.js b/packages/node-experimental/test/manual/express-scope-separation/start.js
similarity index 100%
rename from packages/node/test/manual/express-scope-separation/start.js
rename to packages/node-experimental/test/manual/express-scope-separation/start.js
diff --git a/packages/node/test/manual/memory-leak/.gitignore b/packages/node-experimental/test/manual/memory-leak/.gitignore
similarity index 100%
rename from packages/node/test/manual/memory-leak/.gitignore
rename to packages/node-experimental/test/manual/memory-leak/.gitignore
diff --git a/packages/node/test/manual/memory-leak/README.md b/packages/node-experimental/test/manual/memory-leak/README.md
similarity index 100%
rename from packages/node/test/manual/memory-leak/README.md
rename to packages/node-experimental/test/manual/memory-leak/README.md
diff --git a/packages/node/test/manual/memory-leak/context-memory.js b/packages/node-experimental/test/manual/memory-leak/context-memory.js
similarity index 100%
rename from packages/node/test/manual/memory-leak/context-memory.js
rename to packages/node-experimental/test/manual/memory-leak/context-memory.js
diff --git a/packages/node/test/manual/memory-leak/express-patient.js b/packages/node-experimental/test/manual/memory-leak/express-patient.js
similarity index 100%
rename from packages/node/test/manual/memory-leak/express-patient.js
rename to packages/node-experimental/test/manual/memory-leak/express-patient.js
diff --git a/packages/node/test/manual/memory-leak/large-module-src.js b/packages/node-experimental/test/manual/memory-leak/large-module-src.js
similarity index 100%
rename from packages/node/test/manual/memory-leak/large-module-src.js
rename to packages/node-experimental/test/manual/memory-leak/large-module-src.js
diff --git a/packages/node/test/manual/memory-leak/manager.js b/packages/node-experimental/test/manual/memory-leak/manager.js
similarity index 100%
rename from packages/node/test/manual/memory-leak/manager.js
rename to packages/node-experimental/test/manual/memory-leak/manager.js
diff --git a/packages/node/test/manual/memory-leak/poke-patient.sh b/packages/node-experimental/test/manual/memory-leak/poke-patient.sh
similarity index 100%
rename from packages/node/test/manual/memory-leak/poke-patient.sh
rename to packages/node-experimental/test/manual/memory-leak/poke-patient.sh
diff --git a/packages/node/test/manual/release-health/runner.js b/packages/node-experimental/test/manual/release-health/runner.js
similarity index 100%
rename from packages/node/test/manual/release-health/runner.js
rename to packages/node-experimental/test/manual/release-health/runner.js
diff --git a/packages/node/test/manual/release-health/session-aggregates/aggregates-disable-single-session.js b/packages/node-experimental/test/manual/release-health/session-aggregates/aggregates-disable-single-session.js
similarity index 100%
rename from packages/node/test/manual/release-health/session-aggregates/aggregates-disable-single-session.js
rename to packages/node-experimental/test/manual/release-health/session-aggregates/aggregates-disable-single-session.js
diff --git a/packages/node/test/manual/release-health/single-session/caught-exception-errored-session.js b/packages/node-experimental/test/manual/release-health/single-session/caught-exception-errored-session.js
similarity index 100%
rename from packages/node/test/manual/release-health/single-session/caught-exception-errored-session.js
rename to packages/node-experimental/test/manual/release-health/single-session/caught-exception-errored-session.js
diff --git a/packages/node/test/manual/release-health/single-session/errors-in-session-capped-to-one.js b/packages/node-experimental/test/manual/release-health/single-session/errors-in-session-capped-to-one.js
similarity index 100%
rename from packages/node/test/manual/release-health/single-session/errors-in-session-capped-to-one.js
rename to packages/node-experimental/test/manual/release-health/single-session/errors-in-session-capped-to-one.js
diff --git a/packages/node/test/manual/release-health/single-session/healthy-session.js b/packages/node-experimental/test/manual/release-health/single-session/healthy-session.js
similarity index 100%
rename from packages/node/test/manual/release-health/single-session/healthy-session.js
rename to packages/node-experimental/test/manual/release-health/single-session/healthy-session.js
diff --git a/packages/node/test/manual/release-health/single-session/terminal-state-sessions-sent-once.js b/packages/node-experimental/test/manual/release-health/single-session/terminal-state-sessions-sent-once.js
similarity index 100%
rename from packages/node/test/manual/release-health/single-session/terminal-state-sessions-sent-once.js
rename to packages/node-experimental/test/manual/release-health/single-session/terminal-state-sessions-sent-once.js
diff --git a/packages/node/test/manual/release-health/single-session/uncaught-exception-crashed-session.js b/packages/node-experimental/test/manual/release-health/single-session/uncaught-exception-crashed-session.js
similarity index 100%
rename from packages/node/test/manual/release-health/single-session/uncaught-exception-crashed-session.js
rename to packages/node-experimental/test/manual/release-health/single-session/uncaught-exception-crashed-session.js
diff --git a/packages/node/test/manual/release-health/single-session/unhandled-rejection-crashed-session.js b/packages/node-experimental/test/manual/release-health/single-session/unhandled-rejection-crashed-session.js
similarity index 100%
rename from packages/node/test/manual/release-health/single-session/unhandled-rejection-crashed-session.js
rename to packages/node-experimental/test/manual/release-health/single-session/unhandled-rejection-crashed-session.js
diff --git a/packages/node/test/manual/release-health/test-utils.js b/packages/node-experimental/test/manual/release-health/test-utils.js
similarity index 100%
rename from packages/node/test/manual/release-health/test-utils.js
rename to packages/node-experimental/test/manual/release-health/test-utils.js
diff --git a/packages/node/test/manual/webpack-async-context/index.js b/packages/node-experimental/test/manual/webpack-async-context/index.js
similarity index 100%
rename from packages/node/test/manual/webpack-async-context/index.js
rename to packages/node-experimental/test/manual/webpack-async-context/index.js
diff --git a/packages/node/test/manual/webpack-async-context/npm-build.js b/packages/node-experimental/test/manual/webpack-async-context/npm-build.js
similarity index 100%
rename from packages/node/test/manual/webpack-async-context/npm-build.js
rename to packages/node-experimental/test/manual/webpack-async-context/npm-build.js
diff --git a/packages/node/test/manual/webpack-async-context/package.json b/packages/node-experimental/test/manual/webpack-async-context/package.json
similarity index 100%
rename from packages/node/test/manual/webpack-async-context/package.json
rename to packages/node-experimental/test/manual/webpack-async-context/package.json
diff --git a/packages/node/test/manual/webpack-async-context/yarn.lock b/packages/node-experimental/test/manual/webpack-async-context/yarn.lock
similarity index 100%
rename from packages/node/test/manual/webpack-async-context/yarn.lock
rename to packages/node-experimental/test/manual/webpack-async-context/yarn.lock
diff --git a/packages/node/test/module.test.ts b/packages/node-experimental/test/module.test.ts
similarity index 100%
rename from packages/node/test/module.test.ts
rename to packages/node-experimental/test/module.test.ts
diff --git a/packages/node/test/onuncaughtexception.test.ts b/packages/node-experimental/test/onuncaughtexception.test.ts
similarity index 100%
rename from packages/node/test/onuncaughtexception.test.ts
rename to packages/node-experimental/test/onuncaughtexception.test.ts
diff --git a/packages/node/test/onunhandledrejection.test.ts b/packages/node-experimental/test/onunhandledrejection.test.ts
similarity index 100%
rename from packages/node/test/onunhandledrejection.test.ts
rename to packages/node-experimental/test/onunhandledrejection.test.ts
diff --git a/packages/node/test/performance.test.ts b/packages/node-experimental/test/performance.test.ts
similarity index 100%
rename from packages/node/test/performance.test.ts
rename to packages/node-experimental/test/performance.test.ts
diff --git a/packages/node/test/sdk.test.ts b/packages/node-experimental/test/sdk.test.ts
similarity index 100%
rename from packages/node/test/sdk.test.ts
rename to packages/node-experimental/test/sdk.test.ts
diff --git a/packages/node/test/stacktrace.test.ts b/packages/node-experimental/test/stacktrace.test.ts
similarity index 100%
rename from packages/node/test/stacktrace.test.ts
rename to packages/node-experimental/test/stacktrace.test.ts
diff --git a/packages/node-experimental/test/transports/http.test.ts b/packages/node-experimental/test/transports/http.test.ts
index e945c086959a..ddf73039a009 100644
--- a/packages/node-experimental/test/transports/http.test.ts
+++ b/packages/node-experimental/test/transports/http.test.ts
@@ -1,4 +1,6 @@
+/* eslint-disable deprecation/deprecation */
import * as http from 'http';
+
import { createGunzip } from 'zlib';
import { createTransport } from '@sentry/core';
import type { EventEnvelope, EventItem } from '@sentry/types';
@@ -49,18 +51,19 @@ function setupTestServer(
res.end();
// also terminate socket because keepalive hangs connection a bit
- // eslint-disable-next-line deprecation/deprecation
- res.connection?.end();
+ if (res.connection) {
+ res.connection.end();
+ }
});
- testServer.listen(18101);
+ testServer.listen(18099);
return new Promise(resolve => {
testServer?.on('listening', resolve);
});
}
-const TEST_SERVER_URL = 'http://localhost:18101';
+const TEST_SERVER_URL = 'http://localhost:18099';
const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
[{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem,
diff --git a/packages/node-experimental/test/transports/https.test.ts b/packages/node-experimental/test/transports/https.test.ts
index 8b0d3312ba54..a45319c40e42 100644
--- a/packages/node-experimental/test/transports/https.test.ts
+++ b/packages/node-experimental/test/transports/https.test.ts
@@ -1,3 +1,4 @@
+/* eslint-disable deprecation/deprecation */
import * as http from 'http';
import * as https from 'https';
import { createTransport } from '@sentry/core';
@@ -49,18 +50,19 @@ function setupTestServer(
res.end();
// also terminate socket because keepalive hangs connection a bit
- // eslint-disable-next-line deprecation/deprecation
- res.connection?.end();
+ if (res.connection) {
+ res.connection.end();
+ }
});
- testServer.listen(8100);
+ testServer.listen(8099);
return new Promise(resolve => {
testServer?.on('listening', resolve);
});
}
-const TEST_SERVER_URL = 'https://localhost:8100';
+const TEST_SERVER_URL = 'https://localhost:8099';
const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
[{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem,
diff --git a/packages/node/test/utils.ts b/packages/node-experimental/test/utils.ts
similarity index 100%
rename from packages/node/test/utils.ts
rename to packages/node-experimental/test/utils.ts
diff --git a/packages/node-experimental/tsconfig.json b/packages/node-experimental/tsconfig.json
index 8f38d240197e..89a9b9e0e2fe 100644
--- a/packages/node-experimental/tsconfig.json
+++ b/packages/node-experimental/tsconfig.json
@@ -4,7 +4,6 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["es2018"],
- "module": "Node16"
+ "lib": ["es2018"]
}
}
diff --git a/packages/node-experimental/tsconfig.test.json b/packages/node-experimental/tsconfig.test.json
index 87f6afa06b86..52333183eb70 100644
--- a/packages/node-experimental/tsconfig.test.json
+++ b/packages/node-experimental/tsconfig.test.json
@@ -1,7 +1,7 @@
{
"extends": "./tsconfig.json",
- "include": ["test/**/*"],
+ "include": ["test/**/*", "src/**/*.d.ts"],
"compilerOptions": {
// should include all types from `./tsconfig.json` plus types for all test frameworks used
diff --git a/packages/node/LICENSE b/packages/node/LICENSE
index 535ef0561e1b..d11896ba1181 100644
--- a/packages/node/LICENSE
+++ b/packages/node/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2019 Sentry (https://sentry.io) and individual contributors. All rights reserved.
+Copyright (c) 2023 Sentry (https://sentry.io) and individual contributors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
diff --git a/packages/node/README.md b/packages/node/README.md
index af6819c9a4ce..324700199b53 100644
--- a/packages/node/README.md
+++ b/packages/node/README.md
@@ -4,36 +4,28 @@
-# Legacy Sentry SDK for NodeJS
+# Official Sentry SDK for Node
-[](https://www.npmjs.com/package/@sentry/node-experimental)
-[](https://www.npmjs.com/package/@sentry/node-experimental)
-[](https://www.npmjs.com/package/@sentry/node-experimental)
+[](https://www.npmjs.com/package/@sentry/node)
+[](https://www.npmjs.com/package/@sentry/node)
+[](https://www.npmjs.com/package/@sentry/node)
-## Links
-
-- [Official SDK Docs](https://docs.sentry.io/quickstart/)
-- [TypeDoc](http://getsentry.github.io/sentry-javascript/)
+## Installation
-## Status
+```bash
+npm install @sentry/node
-Since v8, this is the _legacy_ SDK, and it will most likely be completely removed before v8 is fully stable. It only
-exists so that Meta-SDKs like `@sentry/nextjs` or `@sentry/sveltekit` can be migrated to the new `@sentry/node`
-step-by-step.
-
-You should instead use [@sentry/node](./../node-experimental/).
+# Or yarn
+yarn add @sentry/node
+```
## Usage
-To use this SDK, call `init(options)` as early as possible in the main entry module. This will initialize the SDK and
-hook into the environment. Note that you can turn off almost all side effects using the respective options. Minimum
-supported Node version is Node 14.
-
-```javascript
-// CJS syntax
-const Sentry = require('@sentry/node-experimental');
-// ESM syntax
-import * as Sentry from '@sentry/node-experimental';
+```js
+// CJS Syntax
+const Sentry = require('@sentry/node');
+// ESM Syntax
+import * as Sentry from '@sentry/node';
Sentry.init({
dsn: '__DSN__',
@@ -41,28 +33,27 @@ Sentry.init({
});
```
-To set context information or send manual events, use the exported functions of `@sentry/node-experimental`. Note that
-these functions will not perform any action before you have called `init()`:
+Note that it is necessary to initialize Sentry **before you import any package that may be instrumented by us**.
-```javascript
-// Set user information, as well as tags and further extras
-Sentry.setExtra('battery', 0.7);
-Sentry.setTag('user_mode', 'admin');
-Sentry.setUser({ id: '4711' });
+[More information on how to set up Sentry for Node in v8.](./../../docs/v8-node.md)
-// Add a breadcrumb for future events
-Sentry.addBreadcrumb({
- message: 'My Breadcrumb',
- // ...
-});
+### ESM Support
-// Capture exceptions, messages or manual events
-Sentry.captureMessage('Hello, world!');
-Sentry.captureException(new Error('Good bye'));
-Sentry.captureEvent({
- message: 'Manual',
- stacktrace: [
- // ...
- ],
-});
+Due to the way OpenTelemetry handles instrumentation, this only works out of the box for CommonJS (`require`)
+applications.
+
+There is experimental support for running OpenTelemetry with ESM (`"type": "module"`):
+
+```bash
+node --experimental-loader=@opentelemetry/instrumentation/hook.mjs ./app.js
```
+
+You'll need to install `@opentelemetry/instrumentation` in your app to ensure this works.
+
+See
+[OpenTelemetry Instrumentation Docs](https://github.com/open-telemetry/opentelemetry-js/tree/main/experimental/packages/opentelemetry-instrumentation#instrumentation-for-es-modules-in-nodejs-experimental)
+for details on this - but note that this is a) experimental, and b) does not work with all integrations.
+
+## Links
+
+- [Official SDK Docs](https://docs.sentry.io/quickstart/)
diff --git a/packages/node/package.json b/packages/node/package.json
index f71fdbbdc24d..885ebe84f0b7 100644
--- a/packages/node/package.json
+++ b/packages/node/package.json
@@ -1,7 +1,7 @@
{
- "name": "@sentry/node-experimental",
+ "name": "@sentry/node",
"version": "8.0.0-alpha.7",
- "description": "The old version of Sentry SDK for Node.js, without OpenTelemetry support.",
+ "description": "Sentry Node SDK using OpenTelemetry for performance instrumentation",
"repository": "git://github.com/getsentry/sentry-javascript.git",
"homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/node",
"author": "Sentry",
@@ -42,22 +42,39 @@
"access": "public"
},
"dependencies": {
- "@sentry-internal/tracing": "8.0.0-alpha.7",
+ "@opentelemetry/api": "1.7.0",
+ "@opentelemetry/context-async-hooks": "1.21.0",
+ "@opentelemetry/core": "1.21.0",
+ "@opentelemetry/instrumentation": "0.48.0",
+ "@opentelemetry/instrumentation-express": "0.35.0",
+ "@opentelemetry/instrumentation-fastify": "0.33.0",
+ "@opentelemetry/instrumentation-graphql": "0.37.0",
+ "@opentelemetry/instrumentation-hapi": "0.34.0",
+ "@opentelemetry/instrumentation-http": "0.48.0",
+ "@opentelemetry/instrumentation-koa": "0.37.0",
+ "@opentelemetry/instrumentation-mongodb": "0.39.0",
+ "@opentelemetry/instrumentation-mongoose": "0.35.0",
+ "@opentelemetry/instrumentation-mysql": "0.35.0",
+ "@opentelemetry/instrumentation-mysql2": "0.35.0",
+ "@opentelemetry/instrumentation-nestjs-core": "0.34.0",
+ "@opentelemetry/instrumentation-pg": "0.38.0",
+ "@opentelemetry/resources": "1.21.0",
+ "@opentelemetry/sdk-trace-base": "1.21.0",
+ "@opentelemetry/semantic-conventions": "1.21.0",
+ "@prisma/instrumentation": "5.9.0",
"@sentry/core": "8.0.0-alpha.7",
+ "@sentry/opentelemetry": "8.0.0-alpha.7",
"@sentry/types": "8.0.0-alpha.7",
"@sentry/utils": "8.0.0-alpha.7"
},
"devDependencies": {
- "@types/cookie": "0.5.2",
- "@types/express": "^4.17.14",
- "@types/lru-cache": "^5.1.0",
- "@types/node": "14.18.63",
- "express": "^4.17.1",
- "nock": "^13.0.5",
- "undici": "^5.21.0"
+ "@types/node": "^14.18.0"
+ },
+ "optionalDependencies": {
+ "opentelemetry-instrumentation-fetch-node": "1.1.2"
},
"scripts": {
- "build": "run-s build:transpile build:types",
+ "build": "run-p build:transpile build:types",
"build:dev": "yarn build",
"build:transpile": "rollup -c rollup.npm.config.mjs",
"build:types": "run-s build:types:core build:types:downlevel",
@@ -72,23 +89,13 @@
"clean": "rimraf build coverage sentry-node-*.tgz",
"fix": "eslint . --format stylish --fix",
"lint": "eslint . --format stylish",
- "test": "run-s test:jest test:express test:webpack test:release-health",
- "test:express": "node test/manual/express-scope-separation/start.js",
+ "test": "yarn test:jest",
"test:jest": "jest",
- "test:release-health": "node test/manual/release-health/runner.js",
- "test:webpack": "cd test/manual/webpack-async-context/ && yarn --silent --ignore-engines && node npm-build.js",
"test:watch": "jest --watch",
"yalc:publish": "ts-node ../../scripts/prepack.ts && yalc publish build --push --sig"
},
"volta": {
"extends": "../../package.json"
},
- "madge": {
- "detectiveOptions": {
- "ts": {
- "skipTypeImports": true
- }
- }
- },
"sideEffects": false
}
diff --git a/packages/node/rollup.anr-worker.config.mjs b/packages/node/rollup.anr-worker.config.mjs
index 48463d5763ee..bd3c1d4b825c 100644
--- a/packages/node/rollup.anr-worker.config.mjs
+++ b/packages/node/rollup.anr-worker.config.mjs
@@ -1,34 +1,31 @@
import { makeBaseBundleConfig } from '@sentry-internal/rollup-utils';
-function createAnrWorkerConfig(destDir, esm) {
- return makeBaseBundleConfig({
- bundleType: 'node-worker',
- entrypoints: ['src/integrations/anr/worker.ts'],
- licenseTitle: '@sentry/node',
- outputFileBase: () => 'worker-script.js',
- packageSpecificConfig: {
- output: {
- dir: destDir,
- sourcemap: false,
- },
- plugins: [
- {
- name: 'output-base64-worker-script',
- renderChunk(code) {
- const base64Code = Buffer.from(code).toString('base64');
- if (esm) {
- return `export const base64WorkerScript = '${base64Code}';`;
- } else {
- return `exports.base64WorkerScript = '${base64Code}';`;
- }
- },
+export function createAnrWorkerCode() {
+ let base64Code;
+
+ return {
+ workerRollupConfig: makeBaseBundleConfig({
+ bundleType: 'node-worker',
+ entrypoints: ['src/integrations/anr/worker.ts'],
+ licenseTitle: '@sentry/node',
+ outputFileBase: () => 'worker-script.js',
+ packageSpecificConfig: {
+ output: {
+ dir: 'build/esm/integrations/anr',
+ sourcemap: false,
},
- ],
+ plugins: [
+ {
+ name: 'output-base64-worker-script',
+ renderChunk(code) {
+ base64Code = Buffer.from(code).toString('base64');
+ },
+ },
+ ],
+ },
+ }),
+ getBase64Code() {
+ return base64Code;
},
- });
+ };
}
-
-export const anrWorkerConfigs = [
- createAnrWorkerConfig('build/esm/integrations/anr', true),
- createAnrWorkerConfig('build/cjs/integrations/anr', false),
-];
diff --git a/packages/node/rollup.npm.config.mjs b/packages/node/rollup.npm.config.mjs
index 88c90de4825f..17c0727d7eff 100644
--- a/packages/node/rollup.npm.config.mjs
+++ b/packages/node/rollup.npm.config.mjs
@@ -1,8 +1,32 @@
+import replace from '@rollup/plugin-replace';
import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils';
-import { anrWorkerConfigs } from './rollup.anr-worker.config.mjs';
+import { createAnrWorkerCode } from './rollup.anr-worker.config.mjs';
+
+const { workerRollupConfig, getBase64Code } = createAnrWorkerCode();
export default [
- ...makeNPMConfigVariants(makeBaseNPMConfig()),
- // The ANR worker builds must come after the main build because they overwrite the worker-script.js file
- ...anrWorkerConfigs,
+ // The worker needs to be built first since it's output is used in the main bundle.
+ workerRollupConfig,
+ ...makeNPMConfigVariants(
+ makeBaseNPMConfig({
+ packageSpecificConfig: {
+ output: {
+ // set exports to 'named' or 'auto' so that rollup doesn't warn
+ exports: 'named',
+ // set preserveModules to false because we want to bundle everything into one file.
+ preserveModules: false,
+ },
+ plugins: [
+ replace({
+ delimiters: ['###', '###'],
+ // removes some webpack warnings
+ preventAssignment: true,
+ values: {
+ base64WorkerScript: () => getBase64Code(),
+ },
+ }),
+ ],
+ },
+ }),
+ ),
];
diff --git a/packages/node-experimental/src/cron/index.ts b/packages/node/src/cron/index.ts
similarity index 100%
rename from packages/node-experimental/src/cron/index.ts
rename to packages/node/src/cron/index.ts
diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts
index 36b27086ef08..33bbcca54213 100644
--- a/packages/node/src/index.ts
+++ b/packages/node/src/index.ts
@@ -1,150 +1,127 @@
-export type {
- Breadcrumb,
- BreadcrumbHint,
- PolymorphicRequest,
- Request,
- SdkInfo,
- Event,
- EventHint,
- Exception,
- Session,
- SeverityLevel,
- Span,
- StackFrame,
- Stacktrace,
- Thread,
- Transaction,
- User,
-} from '@sentry/types';
-export type { AddRequestDataToEventOptions, TransactionNamingScheme } from '@sentry/utils';
+export { httpIntegration } from './integrations/http';
+export { nativeNodeFetchIntegration } from './integrations/node-fetch';
+
+export { consoleIntegration } from './integrations/console';
+export { nodeContextIntegration } from './integrations/context';
+export { contextLinesIntegration } from './integrations/contextlines';
+export { localVariablesIntegration } from './integrations/local-variables';
+export { modulesIntegration } from './integrations/modules';
+export { onUncaughtExceptionIntegration } from './integrations/onuncaughtexception';
+export { onUnhandledRejectionIntegration } from './integrations/onunhandledrejection';
+export { anrIntegration } from './integrations/anr';
+
+export { expressIntegration, expressErrorHandler, setupExpressErrorHandler } from './integrations/tracing/express';
+export { fastifyIntegration, setupFastifyErrorHandler } from './integrations/tracing/fastify';
+export { graphqlIntegration } from './integrations/tracing/graphql';
+export { mongoIntegration } from './integrations/tracing/mongo';
+export { mongooseIntegration } from './integrations/tracing/mongoose';
+export { mysqlIntegration } from './integrations/tracing/mysql';
+export { mysql2Integration } from './integrations/tracing/mysql2';
+export { nestIntegration } from './integrations/tracing/nest';
+export { postgresIntegration } from './integrations/tracing/postgres';
+export { prismaIntegration } from './integrations/tracing/prisma';
+export { hapiIntegration, setupHapiErrorHandler } from './integrations/tracing/hapi';
+export { spotlightIntegration } from './integrations/spotlight';
+
+export { init, getDefaultIntegrations } from './sdk/init';
+export { initOpenTelemetry } from './sdk/initOtel';
+export { getAutoPerformanceIntegrations } from './integrations/tracing';
+export { getSentryRelease, defaultStackParser } from './sdk/api';
+export { createGetModuleFromFilename } from './utils/module';
+export { makeNodeTransport } from './transports';
+export { NodeClient } from './sdk/client';
+export { cron } from './cron';
export type { NodeOptions } from './types';
export {
- addEventProcessor,
+ addRequestDataToEvent,
+ DEFAULT_USER_INCLUDES,
+ extractRequestData,
+} from '@sentry/utils';
+
+// These are custom variants that need to be used instead of the core one
+// As they have slightly different implementations
+export { continueTrace } from '@sentry/opentelemetry';
+
+export {
addBreadcrumb,
- addIntegration,
- captureException,
- captureEvent,
- captureMessage,
+ isInitialized,
+ getGlobalScope,
close,
createTransport,
flush,
- // eslint-disable-next-line deprecation/deprecation
- getCurrentHub,
- getClient,
- isInitialized,
- getCurrentScope,
- getGlobalScope,
- getIsolationScope,
Hub,
- setCurrentClient,
- Scope,
SDK_VERSION,
- setContext,
- setExtra,
- setExtras,
- setTag,
- setTags,
- setUser,
getSpanStatusFromHttpCode,
setHttpStatus,
- withScope,
- withIsolationScope,
captureCheckIn,
withMonitor,
- setMeasurement,
- getActiveSpan,
- getRootSpan,
- startSpan,
- startInactiveSpan,
- startSpanManual,
- withActiveSpan,
- getSpanDescendants,
- continueTrace,
- parameterize,
+ requestDataIntegration,
functionToStringIntegration,
inboundFiltersIntegration,
linkedErrorsIntegration,
- requestDataIntegration,
- metricsDefault as metrics,
- startSession,
- captureSession,
- endSession,
-} from '@sentry/core';
-
-export {
+ addEventProcessor,
+ setContext,
+ setExtra,
+ setExtras,
+ setTag,
+ setTags,
+ setUser,
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
-} from '@sentry/core';
-
-export { autoDiscoverNodePerformanceMonitoringIntegrations } from './tracing';
-
-export { NodeClient } from './client';
-export { makeNodeTransport } from './transports';
-export {
- getDefaultIntegrations,
- init,
- defaultStackParser,
- getSentryRelease,
-} from './sdk';
-export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from '@sentry/utils';
-
-export { createGetModuleFromFilename } from './module';
-
-import * as Handlers from './handlers';
-import * as NodeIntegrations from './integrations';
-import * as TracingIntegrations from './tracing/integrations';
-
-// TODO: Deprecate this once we migrated tracing integrations
-export const Integrations = {
- ...NodeIntegrations,
- ...TracingIntegrations,
-};
-
-export {
+ setCurrentClient,
+ Scope,
+ setMeasurement,
+ getSpanDescendants,
+ parameterize,
+ getClient,
+ // eslint-disable-next-line deprecation/deprecation
+ getCurrentHub,
+ getCurrentScope,
+ getIsolationScope,
+ withScope,
+ withIsolationScope,
+ captureException,
+ captureEvent,
+ captureMessage,
captureConsoleIntegration,
debugIntegration,
dedupeIntegration,
extraErrorDataIntegration,
rewriteFramesIntegration,
sessionTimingIntegration,
+ metricsDefault as metrics,
+ startSession,
+ captureSession,
+ endSession,
+ addIntegration,
+ startSpan,
+ startSpanManual,
+ startInactiveSpan,
+ getActiveSpan,
+ withActiveSpan,
+ getRootSpan,
+ spanToJSON,
} from '@sentry/core';
-export { consoleIntegration } from './integrations/console';
-export { onUncaughtExceptionIntegration } from './integrations/onuncaughtexception';
-export { onUnhandledRejectionIntegration } from './integrations/onunhandledrejection';
-export { modulesIntegration } from './integrations/modules';
-export { contextLinesIntegration } from './integrations/contextlines';
-export { nodeContextIntegration } from './integrations/context';
-export { localVariablesIntegration } from './integrations/local-variables';
-export { spotlightIntegration } from './integrations/spotlight';
-export { anrIntegration } from './integrations/anr';
-export { hapiIntegration } from './integrations/hapi';
-// eslint-disable-next-line deprecation/deprecation
-export { Undici, nativeNodeFetchintegration } from './integrations/undici';
-// eslint-disable-next-line deprecation/deprecation
-export { Http, httpIntegration } from './integrations/http';
-
-// TODO(v8): Remove all of these exports. They were part of a hotfix #10339 where we produced wrong .d.ts files because we were packing packages inside the /build folder.
-export type { LocalVariablesIntegrationOptions } from './integrations/local-variables/common';
-export type { DebugSession } from './integrations/local-variables/local-variables-sync';
-export type { AnrIntegrationOptions } from './integrations/anr/common';
-// ---
-
-export { Handlers };
-
-export { hapiErrorPlugin } from './integrations/hapi';
-
-import { instrumentCron } from './cron/cron';
-import { instrumentNodeCron } from './cron/node-cron';
-import { instrumentNodeSchedule } from './cron/node-schedule';
-
-/** Methods to instrument cron libraries for Sentry check-ins */
-export const cron = {
- instrumentCron,
- instrumentNodeCron,
- instrumentNodeSchedule,
-};
+export type {
+ Breadcrumb,
+ BreadcrumbHint,
+ PolymorphicRequest,
+ Request,
+ SdkInfo,
+ Event,
+ EventHint,
+ Exception,
+ Session,
+ SeverityLevel,
+ StackFrame,
+ Stacktrace,
+ Thread,
+ Transaction,
+ User,
+ Span,
+} from '@sentry/types';
diff --git a/packages/node/src/integrations/anr/common.ts b/packages/node/src/integrations/anr/common.ts
index 5617871ccb24..e2e50fae4179 100644
--- a/packages/node/src/integrations/anr/common.ts
+++ b/packages/node/src/integrations/anr/common.ts
@@ -37,6 +37,7 @@ export interface WorkerStartData extends AnrIntegrationOptions {
debug: boolean;
sdkMetadata: SdkMetadata;
dsn: DsnComponents;
+ tunnel: string | undefined;
release: string | undefined;
environment: string;
dist: string | undefined;
diff --git a/packages/node/src/integrations/anr/index.ts b/packages/node/src/integrations/anr/index.ts
index 7e0de1d0badc..7dbe9e905cb4 100644
--- a/packages/node/src/integrations/anr/index.ts
+++ b/packages/node/src/integrations/anr/index.ts
@@ -1,37 +1,41 @@
-// TODO (v8): This import can be removed once we only support Node with global URL
-import { URL } from 'url';
-import { defineIntegration, getCurrentScope } from '@sentry/core';
-import type { Contexts, Event, EventHint, IntegrationFn } from '@sentry/types';
-import { dynamicRequire, logger } from '@sentry/utils';
-import type { Worker, WorkerOptions } from 'worker_threads';
-import type { NodeClient } from '../../client';
+import { defineIntegration, mergeScopeData } from '@sentry/core';
+import type { Contexts, Event, EventHint, Integration, IntegrationFn, ScopeData } from '@sentry/types';
+import { GLOBAL_OBJ, logger } from '@sentry/utils';
+import * as inspector from 'inspector';
+import { Worker } from 'worker_threads';
+import { getCurrentScope, getGlobalScope, getIsolationScope } from '../..';
import { NODE_VERSION } from '../../nodeVersion';
+import type { NodeClient } from '../../sdk/client';
import type { AnrIntegrationOptions, WorkerStartData } from './common';
import { base64WorkerScript } from './worker-script';
const DEFAULT_INTERVAL = 50;
const DEFAULT_HANG_THRESHOLD = 5000;
-type WorkerNodeV14 = Worker & { new (filename: string | URL, options?: WorkerOptions): Worker };
-
-type WorkerThreads = {
- Worker: WorkerNodeV14;
-};
-
function log(message: string, ...args: unknown[]): void {
logger.log(`[ANR] ${message}`, ...args);
}
-/**
- * We need to use dynamicRequire because worker_threads is not available in node < v12 and webpack error will when
- * targeting those versions
- */
-function getWorkerThreads(): WorkerThreads {
- return dynamicRequire(module, 'worker_threads');
+function globalWithScopeFetchFn(): typeof GLOBAL_OBJ & { __SENTRY_GET_SCOPES__?: () => ScopeData } {
+ return GLOBAL_OBJ;
+}
+
+/** Fetches merged scope data */
+function getScopeData(): ScopeData {
+ const scope = getGlobalScope().getScopeData();
+ mergeScopeData(scope, getIsolationScope().getScopeData());
+ mergeScopeData(scope, getCurrentScope().getScopeData());
+
+ // We remove attachments because they likely won't serialize well as json
+ scope.attachments = [];
+ // We can't serialize event processor functions
+ scope.eventProcessors = [];
+
+ return scope;
}
/**
- * Gets contexts by calling all event processors. This relies on being called after all integrations are setup
+ * Gets contexts by calling all event processors. This shouldn't be called until all integrations are setup
*/
async function getContexts(client: NodeClient): Promise {
let event: Event | null = { message: 'ANR' };
@@ -45,40 +49,76 @@ async function getContexts(client: NodeClient): Promise {
return event?.contexts || {};
}
-interface InspectorApi {
- open: (port: number) => void;
- url: () => string | undefined;
-}
-
const INTEGRATION_NAME = 'Anr';
+type AnrInternal = { startWorker: () => void; stopWorker: () => void };
+
const _anrIntegration = ((options: Partial = {}) => {
+ if (NODE_VERSION.major < 16 || (NODE_VERSION.major === 16 && NODE_VERSION.minor < 17)) {
+ throw new Error('ANR detection requires Node 16.17.0 or later');
+ }
+
+ let worker: Promise<() => void> | undefined;
+ let client: NodeClient | undefined;
+
+ // Hookup the scope fetch function to the global object so that it can be called from the worker thread via the
+ // debugger when it pauses
+ const gbl = globalWithScopeFetchFn();
+ gbl.__SENTRY_GET_SCOPES__ = getScopeData;
+
return {
name: INTEGRATION_NAME,
- setup(client: NodeClient) {
- if (NODE_VERSION.major < 16 || (NODE_VERSION.major === 16 && NODE_VERSION.minor < 17)) {
- throw new Error('ANR detection requires Node 16.17.0 or later');
+ startWorker: () => {
+ if (worker) {
+ return;
+ }
+
+ if (client) {
+ worker = _startWorker(client, options);
+ }
+ },
+ stopWorker: () => {
+ if (worker) {
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ worker.then(stop => {
+ stop();
+ worker = undefined;
+ });
}
+ },
+ setup(initClient: NodeClient) {
+ client = initClient;
- // setImmediate is used to ensure that all other integrations have been setup
- setImmediate(() => _startWorker(client, options));
+ // setImmediate is used to ensure that all other integrations have had their setup called first.
+ // This allows us to call into all integrations to fetch the full context
+ setImmediate(() => this.startWorker());
},
- };
+ } as Integration & AnrInternal;
}) satisfies IntegrationFn;
-export const anrIntegration = defineIntegration(_anrIntegration);
+type AnrReturn = (options?: Partial) => Integration & AnrInternal;
+
+export const anrIntegration = defineIntegration(_anrIntegration) as AnrReturn;
/**
* Starts the ANR worker thread
+ *
+ * @returns A function to stop the worker
*/
-async function _startWorker(client: NodeClient, _options: Partial): Promise {
- const contexts = await getContexts(client);
+async function _startWorker(
+ client: NodeClient,
+ integrationOptions: Partial,
+): Promise<() => void> {
const dsn = client.getDsn();
if (!dsn) {
- return;
+ return () => {
+ //
+ };
}
+ const contexts = await getContexts(client);
+
// These will not be accurate if sent later from the worker thread
delete contexts.app?.app_memory;
delete contexts.device?.free_memory;
@@ -93,28 +133,25 @@ async function _startWorker(client: NodeClient, _options: Partial {
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ worker.terminate();
+ clearInterval(timer);
+ };
}
diff --git a/packages/node/src/integrations/anr/worker-script.ts b/packages/node/src/integrations/anr/worker-script.ts
index 16394eaacfe1..c70323e0fc50 100644
--- a/packages/node/src/integrations/anr/worker-script.ts
+++ b/packages/node/src/integrations/anr/worker-script.ts
@@ -1,2 +1,2 @@
-// This file is a placeholder that gets overwritten in the build directory.
-export const base64WorkerScript = '';
+// This string is a placeholder that gets overwritten with the worker code.
+export const base64WorkerScript = '###base64WorkerScript###';
diff --git a/packages/node/src/integrations/anr/worker.ts b/packages/node/src/integrations/anr/worker.ts
index a8b984b48379..21bdcbbb0631 100644
--- a/packages/node/src/integrations/anr/worker.ts
+++ b/packages/node/src/integrations/anr/worker.ts
@@ -1,11 +1,12 @@
import {
+ applyScopeDataToEvent,
createEventEnvelope,
createSessionEnvelope,
getEnvelopeEndpointWithUrlEncodedAuth,
makeSession,
updateSession,
} from '@sentry/core';
-import type { Event, Session, StackFrame, TraceContext } from '@sentry/types';
+import type { Event, ScopeData, Session, StackFrame } from '@sentry/types';
import {
callFrameToStackFrame,
normalizeUrlToBase,
@@ -16,12 +17,11 @@ import {
import { Session as InspectorSession } from 'inspector';
import { parentPort, workerData } from 'worker_threads';
-import { createGetModuleFromFilename } from '../../module';
import { makeNodeTransport } from '../../transports';
+import { createGetModuleFromFilename } from '../../utils/module';
import type { WorkerStartData } from './common';
type VoidFunction = () => void;
-type InspectorSessionNodeV12 = InspectorSession & { connectToMainThread: VoidFunction };
const options: WorkerStartData = workerData;
let session: Session | undefined;
@@ -34,7 +34,7 @@ function log(msg: string): void {
}
}
-const url = getEnvelopeEndpointWithUrlEncodedAuth(options.dsn);
+const url = getEnvelopeEndpointWithUrlEncodedAuth(options.dsn, options.tunnel, options.sdkMetadata.sdk);
const transport = makeNodeTransport({
url,
recordDroppedEvent: () => {
@@ -48,7 +48,7 @@ async function sendAbnormalSession(): Promise {
log('Sending abnormal session');
updateSession(session, { status: 'abnormal', abnormal_mechanism: 'anr_foreground' });
- const envelope = createSessionEnvelope(session, options.dsn, options.sdkMetadata);
+ const envelope = createSessionEnvelope(session, options.dsn, options.sdkMetadata, options.tunnel);
// Log the envelope so to aid in testing
log(JSON.stringify(envelope));
@@ -87,7 +87,23 @@ function prepareStackFrames(stackFrames: StackFrame[] | undefined): StackFrame[]
return strippedFrames;
}
-async function sendAnrEvent(frames?: StackFrame[], traceContext?: TraceContext): Promise {
+function applyScopeToEvent(event: Event, scope: ScopeData): void {
+ applyScopeDataToEvent(event, scope);
+
+ if (!event.contexts?.trace) {
+ const { traceId, spanId, parentSpanId } = scope.propagationContext;
+ event.contexts = {
+ trace: {
+ trace_id: traceId,
+ span_id: spanId,
+ parent_span_id: parentSpanId,
+ },
+ ...event.contexts,
+ };
+ }
+}
+
+async function sendAnrEvent(frames?: StackFrame[], scope?: ScopeData): Promise {
if (hasSentAnrEvent) {
return;
}
@@ -100,7 +116,7 @@ async function sendAnrEvent(frames?: StackFrame[], traceContext?: TraceContext):
const event: Event = {
event_id: uuid4(),
- contexts: { ...options.contexts, trace: traceContext },
+ contexts: options.contexts,
release: options.release,
environment: options.environment,
dist: options.dist,
@@ -120,15 +136,19 @@ async function sendAnrEvent(frames?: StackFrame[], traceContext?: TraceContext):
tags: options.staticTags,
};
- const envelope = createEventEnvelope(event, options.dsn, options.sdkMetadata);
- // Log the envelope so to aid in testing
+ if (scope) {
+ applyScopeToEvent(event, scope);
+ }
+
+ const envelope = createEventEnvelope(event, options.dsn, options.sdkMetadata, options.tunnel);
+ // Log the envelope to aid in testing
log(JSON.stringify(envelope));
await transport.send(envelope);
await transport.flush(2000);
- // Delay for 5 seconds so that stdio can flush in the main event loop ever restarts.
- // This is mainly for the benefit of logging/debugging issues.
+ // Delay for 5 seconds so that stdio can flush if the main event loop ever restarts.
+ // This is mainly for the benefit of logging or debugging.
setTimeout(() => {
process.exit(0);
}, 5_000);
@@ -139,7 +159,7 @@ let debuggerPause: VoidFunction | undefined;
if (options.captureStackTrace) {
log('Connecting to debugger');
- const session = new InspectorSession() as InspectorSessionNodeV12;
+ const session = new InspectorSession();
session.connectToMainThread();
log('Connected to debugger');
@@ -172,20 +192,23 @@ if (options.captureStackTrace) {
'Runtime.evaluate',
{
// Grab the trace context from the current scope
- expression:
- 'const ctx = __SENTRY__.acs?.getCurrentScope().getPropagationContext() || {}; ctx.traceId + "-" + ctx.spanId + "-" + ctx.parentSpanId',
+ expression: 'global.__SENTRY_GET_SCOPES__();',
// Don't re-trigger the debugger if this causes an error
silent: true,
+ // Serialize the result to json otherwise only primitives are supported
+ returnByValue: true,
},
- (_, param) => {
- const traceId = param && param.result ? (param.result.value as string) : '--';
- const [trace_id, span_id, parent_span_id] = traceId.split('-') as (string | undefined)[];
+ (err, param) => {
+ if (err) {
+ log(`Error executing script: '${err.message}'`);
+ }
+
+ const scopes = param && param.result ? (param.result.value as ScopeData) : undefined;
session.post('Debugger.resume');
session.post('Debugger.disable');
- const context = trace_id?.length && span_id?.length ? { trace_id, span_id, parent_span_id } : undefined;
- sendAnrEvent(stackFrames, context).then(null, () => {
+ sendAnrEvent(stackFrames, scopes).then(null, () => {
log('Sending ANR event failed.');
});
},
diff --git a/packages/node/src/integrations/console.ts b/packages/node/src/integrations/console.ts
index 5a185b8fcee1..0b3d27fe8510 100644
--- a/packages/node/src/integrations/console.ts
+++ b/packages/node/src/integrations/console.ts
@@ -1,6 +1,6 @@
import * as util from 'util';
-import { addBreadcrumb, convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core';
-import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
+import { addBreadcrumb, defineIntegration, getClient } from '@sentry/core';
+import type { IntegrationFn } from '@sentry/types';
import { addConsoleInstrumentationHandler, severityLevelFromString } from '@sentry/utils';
const INTEGRATION_NAME = 'Console';
@@ -30,16 +30,7 @@ const _consoleIntegration = (() => {
};
}) satisfies IntegrationFn;
-export const consoleIntegration = defineIntegration(_consoleIntegration);
-
/**
- * Console module integration.
- * @deprecated Use `consoleIntegration()` instead.
+ * Capture console logs as breadcrumbs.
*/
-// eslint-disable-next-line deprecation/deprecation
-export const Console = convertIntegrationFnToClass(INTEGRATION_NAME, consoleIntegration) as IntegrationClass<
- Integration & { setup: (client: Client) => void }
->;
-
-// eslint-disable-next-line deprecation/deprecation
-export type Console = typeof Console;
+export const consoleIntegration = defineIntegration(_consoleIntegration);
diff --git a/packages/node/src/integrations/context.ts b/packages/node/src/integrations/context.ts
index fa5184204bf2..c33d97e79044 100644
--- a/packages/node/src/integrations/context.ts
+++ b/packages/node/src/integrations/context.ts
@@ -1,10 +1,9 @@
-/* eslint-disable max-lines */
import { execFile } from 'child_process';
import { readFile, readdir } from 'fs';
import * as os from 'os';
import { join } from 'path';
import { promisify } from 'util';
-import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
+import { defineIntegration } from '@sentry/core';
import type {
AppContext,
CloudResourceContext,
@@ -12,13 +11,10 @@ import type {
CultureContext,
DeviceContext,
Event,
- Integration,
- IntegrationClass,
IntegrationFn,
OsContext,
} from '@sentry/types';
-// TODO: Required until we drop support for Node v8
export const readFileAsync = promisify(readFile);
export const readDirAsync = promisify(readdir);
@@ -108,27 +104,10 @@ const _nodeContextIntegration = ((options: ContextOptions = {}) => {
};
}) satisfies IntegrationFn;
-export const nodeContextIntegration = defineIntegration(_nodeContextIntegration);
-
/**
- * Add node modules / packages to the event.
- * @deprecated Use `nodeContextIntegration()` instead.
+ * Capture context about the environment and the device that the client is running on, to events.
*/
-// eslint-disable-next-line deprecation/deprecation
-export const Context = convertIntegrationFnToClass(INTEGRATION_NAME, nodeContextIntegration) as IntegrationClass<
- Integration & { processEvent: (event: Event) => Promise }
-> & {
- new (options?: {
- app?: boolean;
- os?: boolean;
- device?: { cpu?: boolean; memory?: boolean } | boolean;
- culture?: boolean;
- cloudResource?: boolean;
- }): Integration;
-};
-
-// eslint-disable-next-line deprecation/deprecation
-export type Context = typeof Context;
+export const nodeContextIntegration = defineIntegration(_nodeContextIntegration);
/**
* Updates the context with dynamic values that can change
diff --git a/packages/node/src/integrations/contextlines.ts b/packages/node/src/integrations/contextlines.ts
index 7c367f51a970..3755e164e5ea 100644
--- a/packages/node/src/integrations/contextlines.ts
+++ b/packages/node/src/integrations/contextlines.ts
@@ -1,4 +1,4 @@
-import { readFile } from 'fs';
+import { promises } from 'fs';
import { defineIntegration } from '@sentry/core';
import type { Event, IntegrationFn, StackFrame } from '@sentry/types';
import { LRUMap, addContextToFrame } from '@sentry/utils';
@@ -7,15 +7,7 @@ const FILE_CONTENT_CACHE = new LRUMap(100);
const DEFAULT_LINES_OF_CONTEXT = 7;
const INTEGRATION_NAME = 'ContextLines';
-// TODO: Replace with promisify when minimum supported node >= v8
-function readTextFileAsync(path: string): Promise {
- return new Promise((resolve, reject) => {
- readFile(path, 'utf8', (err, data) => {
- if (err) reject(err);
- else resolve(data);
- });
- });
-}
+const readFileAsync = promises.readFile;
/**
* Resets the file cache. Exists for testing purposes.
@@ -35,7 +27,8 @@ interface ContextLinesOptions {
frameContextLines?: number;
}
-const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => {
+/** Exported only for tests, as a type-safe variant. */
+export const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => {
const contextLines = options.frameContextLines !== undefined ? options.frameContextLines : DEFAULT_LINES_OF_CONTEXT;
return {
@@ -46,6 +39,9 @@ const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => {
};
}) satisfies IntegrationFn;
+/**
+ * Capture the lines before and after the frame's context.
+ */
export const contextLinesIntegration = defineIntegration(_contextLinesIntegration);
async function addSourceContext(event: Event, contextLines: number): Promise {
@@ -139,7 +135,7 @@ async function _readSourceFile(filename: string): Promise {
// If we made it to here, it means that our file is not cache nor marked as failed, so attempt to read it
let content: string[] | null = null;
try {
- const rawFileContents = await readTextFileAsync(filename);
+ const rawFileContents = await readFileAsync(filename, 'utf-8');
content = rawFileContents.split('\n');
} catch (_) {
// if we fail, we will mark the file as null in the cache and short circuit next time we try to read it
diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts
index 9eb7deab5318..ed93ebacdaa5 100644
--- a/packages/node/src/integrations/http.ts
+++ b/packages/node/src/integrations/http.ts
@@ -1,468 +1,155 @@
-/* eslint-disable max-lines */
-import type * as http from 'http';
-import type * as https from 'https';
-import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, startInactiveSpan } from '@sentry/core';
-import { defineIntegration, getIsolationScope, hasTracingEnabled } from '@sentry/core';
-import {
- addBreadcrumb,
- getClient,
- getCurrentScope,
- getDynamicSamplingContextFromClient,
- getDynamicSamplingContextFromSpan,
- isSentryRequestUrl,
- setHttpStatus,
- spanToJSON,
- spanToTraceHeader,
-} from '@sentry/core';
-import type {
- ClientOptions,
- Integration,
- IntegrationFn,
- SanitizedRequestData,
- TracePropagationTargets,
-} from '@sentry/types';
-import {
- LRUMap,
- dropUndefinedKeys,
- dynamicSamplingContextToSentryBaggageHeader,
- fill,
- generateSentryTraceHeader,
- logger,
- stringMatchesSomePattern,
-} from '@sentry/utils';
-
-import type { NodeClient } from '../client';
-import { DEBUG_BUILD } from '../debug-build';
-import { NODE_VERSION } from '../nodeVersion';
-import type { NodeClientOptions } from '../types';
-import type { RequestMethod, RequestMethodArgs, RequestOptions } from './utils/http';
-import { cleanSpanName, extractRawUrl, extractUrl, normalizeRequestArgs } from './utils/http';
-
-interface TracingOptions {
- /**
- * List of strings/regex controlling to which outgoing requests
- * the SDK will attach tracing headers.
- *
- * By default the SDK will attach those headers to all outgoing
- * requests. If this option is provided, the SDK will match the
- * request URL of outgoing requests against the items in this
- * array, and only attach tracing headers if a match was found.
- *
- * @deprecated Use top level `tracePropagationTargets` option instead.
- * This option will be removed in v8.
- *
- * ```
- * Sentry.init({
- * tracePropagationTargets: ['api.site.com'],
- * })
- */
- tracePropagationTargets?: TracePropagationTargets;
-
- /**
- * Function determining whether or not to create spans to track outgoing requests to the given URL.
- * By default, spans will be created for all outgoing requests.
- */
- shouldCreateSpanForRequest?: (url: string) => boolean;
-
- /**
- * This option is just for compatibility with v7.
- * In v8, this will be the default behavior.
- */
- enableIfHasTracingEnabled?: boolean;
-}
+import type { ServerResponse } from 'http';
+import type { Span } from '@opentelemetry/api';
+import { SpanKind } from '@opentelemetry/api';
+import { registerInstrumentations } from '@opentelemetry/instrumentation';
+import { HttpInstrumentation } from '@opentelemetry/instrumentation-http';
+
+import { addBreadcrumb, defineIntegration, getIsolationScope, isSentryRequestUrl } from '@sentry/core';
+import { _INTERNAL, getClient, getSpanKind } from '@sentry/opentelemetry';
+import type { IntegrationFn } from '@sentry/types';
+
+import type { NodeClient } from '../sdk/client';
+import { setIsolationScope } from '../sdk/scope';
+import type { HTTPModuleRequestIncomingMessage } from '../transports/http-module';
+import { addOriginToSpan } from '../utils/addOriginToSpan';
+import { getRequestUrl } from '../utils/getRequestUrl';
interface HttpOptions {
/**
- * Whether breadcrumbs should be recorded for requests
+ * Whether breadcrumbs should be recorded for requests.
* Defaults to true
*/
breadcrumbs?: boolean;
/**
- * Whether tracing spans should be created for requests
- * Defaults to false
+ * Do not capture spans or breadcrumbs for outgoing HTTP requests to URLs where the given callback returns `true`.
+ * This controls both span & breadcrumb creation - spans will be non recording if tracing is disabled.
*/
- tracing?: TracingOptions | boolean;
-}
+ ignoreOutgoingRequests?: (url: string) => boolean;
-/* These are the newer options for `httpIntegration`. */
-interface HttpIntegrationOptions {
/**
- * Whether breadcrumbs should be recorded for requests
- * Defaults to true.
+ * Do not capture spans or breadcrumbs for incoming HTTP requests to URLs where the given callback returns `true`.
+ * This controls both span & breadcrumb creation - spans will be non recording if tracing is disabled.
*/
- breadcrumbs?: boolean;
-
- /**
- * Whether tracing spans should be created for requests
- * If not set, this will be enabled/disabled based on if tracing is enabled.
- */
- tracing?: boolean;
-
- /**
- * Function determining whether or not to create spans to track outgoing requests to the given URL.
- * By default, spans will be created for all outgoing requests.
- */
- shouldCreateSpanForRequest?: (url: string) => boolean;
+ ignoreIncomingRequests?: (url: string) => boolean;
}
-const _httpIntegration = ((options: HttpIntegrationOptions = {}) => {
- const { breadcrumbs, tracing, shouldCreateSpanForRequest } = options;
-
- const convertedOptions: HttpOptions = {
- breadcrumbs,
- tracing:
- tracing === false
- ? false
- : dropUndefinedKeys({
- // If tracing is forced to `true`, we don't want to set `enableIfHasTracingEnabled`
- enableIfHasTracingEnabled: tracing === true ? undefined : true,
- shouldCreateSpanForRequest,
- }),
- };
- // eslint-disable-next-line deprecation/deprecation
- return new Http(convertedOptions) as unknown as Integration;
-}) satisfies IntegrationFn;
-
-/**
- * The http module integration instruments Node's internal http module. It creates breadcrumbs, spans for outgoing
- * http requests, and attaches trace data when tracing is enabled via its `tracing` option.
- *
- * By default, this will always create breadcrumbs, and will create spans if tracing is enabled.
- */
-export const httpIntegration = defineIntegration(_httpIntegration);
-
-/**
- * The http integration instruments Node's internal http and https modules.
- * It creates breadcrumbs and spans for outgoing HTTP requests which will be attached to the currently active span.
- *
- * @deprecated Use `httpIntegration()` instead.
- */
-export class Http implements Integration {
- /**
- * @inheritDoc
- */
- public static id: string = 'Http';
+const _httpIntegration = ((options: HttpOptions = {}) => {
+ const _breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
+ const _ignoreOutgoingRequests = options.ignoreOutgoingRequests;
+ const _ignoreIncomingRequests = options.ignoreIncomingRequests;
+
+ return {
+ name: 'Http',
+ setupOnce() {
+ const instrumentations = [
+ new HttpInstrumentation({
+ ignoreOutgoingRequestHook: request => {
+ const url = getRequestUrl(request);
+
+ if (!url) {
+ return false;
+ }
- /**
- * @inheritDoc
- */
- // eslint-disable-next-line deprecation/deprecation
- public name: string = Http.id;
+ if (isSentryRequestUrl(url, getClient())) {
+ return true;
+ }
- private readonly _breadcrumbs: boolean;
- private readonly _tracing: TracingOptions | undefined;
+ if (_ignoreOutgoingRequests && _ignoreOutgoingRequests(url)) {
+ return true;
+ }
- /**
- * @inheritDoc
- */
- public constructor(options: HttpOptions = {}) {
- this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs;
- this._tracing = !options.tracing ? undefined : options.tracing === true ? {} : options.tracing;
- }
+ return false;
+ },
- /**
- * @inheritDoc
- */
- public setupOnce(): void {
- const clientOptions = getClient()?.getOptions();
+ ignoreIncomingRequestHook: request => {
+ const url = getRequestUrl(request);
- // If `tracing` is not explicitly set, we default this based on whether or not tracing is enabled.
- // But for compatibility, we only do that if `enableIfHasTracingEnabled` is set.
- const shouldCreateSpans = _shouldCreateSpans(this._tracing, clientOptions);
+ const method = request.method?.toUpperCase();
+ // We do not capture OPTIONS/HEAD requests as transactions
+ if (method === 'OPTIONS' || method === 'HEAD') {
+ return true;
+ }
- // No need to instrument if we don't want to track anything
- if (!this._breadcrumbs && !shouldCreateSpans) {
- return;
- }
+ if (_ignoreIncomingRequests && _ignoreIncomingRequests(url)) {
+ return true;
+ }
- const shouldCreateSpanForRequest = _getShouldCreateSpanForRequest(shouldCreateSpans, this._tracing, clientOptions);
+ return false;
+ },
- // eslint-disable-next-line deprecation/deprecation
- const tracePropagationTargets = clientOptions?.tracePropagationTargets || this._tracing?.tracePropagationTargets;
+ requireParentforOutgoingSpans: true,
+ requireParentforIncomingSpans: false,
+ requestHook: (span, req) => {
+ _updateSpan(span);
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const httpModule = require('http');
- const wrappedHttpHandlerMaker = _createWrappedRequestMethodFactory(
- httpModule,
- this._breadcrumbs,
- shouldCreateSpanForRequest,
- tracePropagationTargets,
- );
- fill(httpModule, 'get', wrappedHttpHandlerMaker);
- fill(httpModule, 'request', wrappedHttpHandlerMaker);
+ // Update the isolation scope, isolate this request
+ if (getSpanKind(span) === SpanKind.SERVER) {
+ const isolationScope = getIsolationScope().clone();
+ isolationScope.setSDKProcessingMetadata({ request: req });
- // NOTE: Prior to Node 9, `https` used internals of `http` module, thus we don't patch it.
- // If we do, we'd get double breadcrumbs and double spans for `https` calls.
- // It has been changed in Node 9, so for all versions equal and above, we patch `https` separately.
- if (NODE_VERSION.major > 8) {
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const httpsModule = require('node:https');
- const wrappedHttpsHandlerMaker = _createWrappedRequestMethodFactory(
- httpsModule,
- this._breadcrumbs,
- shouldCreateSpanForRequest,
- tracePropagationTargets,
- );
- fill(httpsModule, 'get', wrappedHttpsHandlerMaker);
- fill(httpsModule, 'request', wrappedHttpsHandlerMaker);
- }
- }
-}
+ const client = getClient();
+ if (client && client.getOptions().autoSessionTracking) {
+ isolationScope.setRequestSession({ status: 'ok' });
+ }
+ setIsolationScope(isolationScope);
+ }
+ },
+ responseHook: (span, res) => {
+ if (_breadcrumbs) {
+ _addRequestBreadcrumb(span, res);
+ }
-// for ease of reading below
-type OriginalRequestMethod = RequestMethod;
-type WrappedRequestMethod = RequestMethod;
-type WrappedRequestMethodFactory = (original: OriginalRequestMethod) => WrappedRequestMethod;
+ const client = getClient();
+ if (client && client.getOptions().autoSessionTracking) {
+ setImmediate(() => {
+ client['_captureRequestSession']();
+ });
+ }
+ },
+ }),
+ ];
+
+ registerInstrumentations({
+ instrumentations,
+ });
+ },
+ };
+}) satisfies IntegrationFn;
/**
- * Function which creates a function which creates wrapped versions of internal `request` and `get` calls within `http`
- * and `https` modules. (NB: Not a typo - this is a creator^2!)
- *
- * @param breadcrumbsEnabled Whether or not to record outgoing requests as breadcrumbs
- * @param tracingEnabled Whether or not to record outgoing requests as tracing spans
- *
- * @returns A function which accepts the exiting handler and returns a wrapped handler
+ * The http integration instruments Node's internal http and https modules.
+ * It creates breadcrumbs and spans for outgoing HTTP requests which will be attached to the currently active span.
*/
-function _createWrappedRequestMethodFactory(
- httpModule: typeof http | typeof https,
- breadcrumbsEnabled: boolean,
- shouldCreateSpanForRequest: ((url: string) => boolean) | undefined,
- tracePropagationTargets: TracePropagationTargets | undefined,
-): WrappedRequestMethodFactory {
- // We're caching results so we don't have to recompute regexp every time we create a request.
- const createSpanUrlMap = new LRUMap(100);
- const headersUrlMap = new LRUMap(100);
-
- const shouldCreateSpan = (url: string): boolean => {
- if (shouldCreateSpanForRequest === undefined) {
- return true;
- }
-
- const cachedDecision = createSpanUrlMap.get(url);
- if (cachedDecision !== undefined) {
- return cachedDecision;
- }
-
- const decision = shouldCreateSpanForRequest(url);
- createSpanUrlMap.set(url, decision);
- return decision;
- };
-
- const shouldAttachTraceData = (url: string): boolean => {
- if (tracePropagationTargets === undefined) {
- return true;
- }
-
- const cachedDecision = headersUrlMap.get(url);
- if (cachedDecision !== undefined) {
- return cachedDecision;
- }
-
- const decision = stringMatchesSomePattern(url, tracePropagationTargets);
- headersUrlMap.set(url, decision);
- return decision;
- };
-
- /**
- * Captures Breadcrumb based on provided request/response pair
- */
- function addRequestBreadcrumb(
- event: string,
- requestSpanData: SanitizedRequestData,
- req: http.ClientRequest,
- res?: http.IncomingMessage,
- ): void {
- if (!getClient()?.getIntegrationByName('Http')) {
- return;
- }
-
- addBreadcrumb(
- {
- category: 'http',
- data: {
- status_code: res && res.statusCode,
- ...requestSpanData,
- },
- type: 'http',
- },
- {
- event,
- request: req,
- response: res,
- },
- );
- }
-
- return function wrappedRequestMethodFactory(originalRequestMethod: OriginalRequestMethod): WrappedRequestMethod {
- return function wrappedMethod(this: unknown, ...args: RequestMethodArgs): http.ClientRequest {
- const requestArgs = normalizeRequestArgs(httpModule, args);
- const requestOptions = requestArgs[0];
- const rawRequestUrl = extractRawUrl(requestOptions);
- const requestUrl = extractUrl(requestOptions);
- const client = getClient();
-
- // we don't want to record requests to Sentry as either breadcrumbs or spans, so just use the original method
- if (isSentryRequestUrl(requestUrl, client)) {
- return originalRequestMethod.apply(httpModule, requestArgs);
- }
-
- const scope = getCurrentScope();
- const isolationScope = getIsolationScope();
-
- const attributes = getRequestSpanData(requestUrl, requestOptions);
-
- const requestSpan = shouldCreateSpan(rawRequestUrl)
- ? startInactiveSpan({
- onlyIfParent: true,
- op: 'http.client',
- name: `${attributes['http.method']} ${attributes.url}`,
- attributes: {
- ...attributes,
- [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.node.http',
- },
- })
- : undefined;
-
- if (client && shouldAttachTraceData(rawRequestUrl)) {
- const { traceId, spanId, sampled, dsc } = {
- ...isolationScope.getPropagationContext(),
- ...scope.getPropagationContext(),
- };
-
- const sentryTraceHeader = requestSpan
- ? spanToTraceHeader(requestSpan)
- : generateSentryTraceHeader(traceId, spanId, sampled);
-
- const sentryBaggageHeader = dynamicSamplingContextToSentryBaggageHeader(
- dsc ||
- (requestSpan
- ? getDynamicSamplingContextFromSpan(requestSpan)
- : getDynamicSamplingContextFromClient(traceId, client)),
- );
-
- addHeadersToRequestOptions(requestOptions, requestUrl, sentryTraceHeader, sentryBaggageHeader);
- } else {
- DEBUG_BUILD &&
- logger.log(
- `[Tracing] Not adding sentry-trace header to outgoing request (${requestUrl}) due to mismatching tracePropagationTargets option.`,
- );
- }
-
- // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
- return originalRequestMethod
- .apply(httpModule, requestArgs)
- .once('response', function (this: http.ClientRequest, res: http.IncomingMessage): void {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const req = this;
- if (breadcrumbsEnabled) {
- addRequestBreadcrumb('response', attributes, req, res);
- }
- if (requestSpan) {
- if (res.statusCode) {
- setHttpStatus(requestSpan, res.statusCode);
- }
- requestSpan.updateName(cleanSpanName(spanToJSON(requestSpan).description || '', requestOptions, req) || '');
- requestSpan.end();
- }
- })
- .once('error', function (this: http.ClientRequest): void {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const req = this;
+export const httpIntegration = defineIntegration(_httpIntegration);
- if (breadcrumbsEnabled) {
- addRequestBreadcrumb('error', attributes, req);
- }
- if (requestSpan) {
- setHttpStatus(requestSpan, 500);
- requestSpan.updateName(cleanSpanName(spanToJSON(requestSpan).description || '', requestOptions, req) || '');
- requestSpan.end();
- }
- });
- };
- };
+/** Update the span with data we need. */
+function _updateSpan(span: Span): void {
+ addOriginToSpan(span, 'auto.http.otel.http');
}
-function addHeadersToRequestOptions(
- requestOptions: RequestOptions,
- requestUrl: string,
- sentryTraceHeader: string,
- sentryBaggageHeader: string | undefined,
-): void {
- // Don't overwrite sentry-trace and baggage header if it's already set.
- const headers = requestOptions.headers || {};
- if (headers['sentry-trace']) {
+/** Add a breadcrumb for outgoing requests. */
+function _addRequestBreadcrumb(span: Span, response: HTTPModuleRequestIncomingMessage | ServerResponse): void {
+ if (getSpanKind(span) !== SpanKind.CLIENT) {
return;
}
- DEBUG_BUILD &&
- logger.log(`[Tracing] Adding sentry-trace header ${sentryTraceHeader} to outgoing request to "${requestUrl}": `);
-
- requestOptions.headers = {
- ...requestOptions.headers,
- 'sentry-trace': sentryTraceHeader,
- // Setting a header to `undefined` will crash in node so we only set the baggage header when it's defined
- ...(sentryBaggageHeader &&
- sentryBaggageHeader.length > 0 && { baggage: normalizeBaggageHeader(requestOptions, sentryBaggageHeader) }),
- };
-}
-
-function getRequestSpanData(requestUrl: string, requestOptions: RequestOptions): SanitizedRequestData {
- const method = requestOptions.method || 'GET';
- const data: SanitizedRequestData = {
- url: requestUrl,
- 'http.method': method,
- };
- if (requestOptions.hash) {
- // strip leading "#"
- data['http.fragment'] = requestOptions.hash.substring(1);
- }
- if (requestOptions.search) {
- // strip leading "?"
- data['http.query'] = requestOptions.search.substring(1);
- }
- return data;
-}
-
-function normalizeBaggageHeader(
- requestOptions: RequestOptions,
- sentryBaggageHeader: string | undefined,
-): string | string[] | undefined {
- if (!requestOptions.headers || !requestOptions.headers.baggage) {
- return sentryBaggageHeader;
- } else if (!sentryBaggageHeader) {
- return requestOptions.headers.baggage as string | string[];
- } else if (Array.isArray(requestOptions.headers.baggage)) {
- return [...requestOptions.headers.baggage, sentryBaggageHeader];
- }
- // Type-cast explanation:
- // Technically this the following could be of type `(number | string)[]` but for the sake of simplicity
- // we say this is undefined behaviour, since it would not be baggage spec conform if the user did this.
- return [requestOptions.headers.baggage, sentryBaggageHeader] as string[];
-}
-
-/** Exported for tests only. */
-export function _shouldCreateSpans(
- tracingOptions: TracingOptions | undefined,
- clientOptions: Partial | undefined,
-): boolean {
- return tracingOptions === undefined
- ? false
- : tracingOptions.enableIfHasTracingEnabled
- ? hasTracingEnabled(clientOptions)
- : true;
-}
-
-/** Exported for tests only. */
-export function _getShouldCreateSpanForRequest(
- shouldCreateSpans: boolean,
- tracingOptions: TracingOptions | undefined,
- clientOptions: Partial | undefined,
-): undefined | ((url: string) => boolean) {
- const handler = shouldCreateSpans
- ? // eslint-disable-next-line deprecation/deprecation
- tracingOptions?.shouldCreateSpanForRequest || clientOptions?.shouldCreateSpanForRequest
- : () => false;
-
- return handler;
+ const data = _INTERNAL.getRequestSpanData(span);
+ addBreadcrumb(
+ {
+ category: 'http',
+ data: {
+ status_code: response.statusCode,
+ ...data,
+ },
+ type: 'http',
+ },
+ {
+ event: 'response',
+ // TODO FN: Do we need access to `request` here?
+ // If we do, we'll have to use the `applyCustomAttributesOnSpan` hook instead,
+ // but this has worse context semantics than request/responseHook.
+ response,
+ },
+ );
}
diff --git a/packages/node/src/integrations/local-variables/local-variables-sync.ts b/packages/node/src/integrations/local-variables/local-variables-sync.ts
index e1ec4b57023c..91fb9005b4c3 100644
--- a/packages/node/src/integrations/local-variables/local-variables-sync.ts
+++ b/packages/node/src/integrations/local-variables/local-variables-sync.ts
@@ -1,11 +1,11 @@
-/* eslint-disable max-lines */
-import { convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core';
-import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackParser } from '@sentry/types';
+import { defineIntegration, getClient } from '@sentry/core';
+import type { Event, Exception, IntegrationFn, StackParser } from '@sentry/types';
import { LRUMap, logger } from '@sentry/utils';
-import type { Debugger, InspectorNotification, Runtime, Session } from 'inspector';
-import type { NodeClient } from '../../client';
+import type { Debugger, InspectorNotification, Runtime } from 'inspector';
+import { Session } from 'inspector';
-import { NODE_VERSION } from '../../nodeVersion';
+import { NODE_MAJOR } from '../../nodeVersion';
+import type { NodeClient } from '../../sdk/client';
import type {
FrameVariables,
LocalVariablesIntegrationOptions,
@@ -79,22 +79,6 @@ class AsyncSession implements DebugSession {
/** Throws if inspector API is not available */
public constructor() {
- /*
- TODO: We really should get rid of this require statement below for a couple of reasons:
- 1. It makes the integration unusable in the SvelteKit SDK, as it's not possible to use `require`
- in SvelteKit server code (at least not by default).
- 2. Throwing in a constructor is bad practice
-
- More context for a future attempt to fix this:
- We already tried replacing it with import but didn't get it to work because of async problems.
- We still called import in the constructor but assigned to a promise which we "awaited" in
- `configureAndConnect`. However, this broke the Node integration tests as no local variables
- were reported any more. We probably missed a place where we need to await the promise, too.
- */
-
- // Node can be built without inspector support so this can throw
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- const { Session } = require('inspector');
this._session = new Session();
}
@@ -304,14 +288,16 @@ const _localVariablesSyncIntegration = ((
return;
}
- const frameCount = exception.stacktrace?.frames?.length || 0;
+ // Filter out frames where the function name is `new Promise` since these are in the error.stack frames
+ // but do not appear in the debugger call frames
+ const frames = (exception.stacktrace?.frames || []).filter(frame => frame.function !== 'new Promise');
- for (let i = 0; i < frameCount; i++) {
+ for (let i = 0; i < frames.length; i++) {
// Sentry frames are in reverse order
- const frameIndex = frameCount - i - 1;
+ const frameIndex = frames.length - i - 1;
// Drop out if we run out of frames to match up
- if (!exception?.stacktrace?.frames?.[frameIndex] || !cachedFrame[i]) {
+ if (!frames[frameIndex] || !cachedFrame[i]) {
break;
}
@@ -319,14 +305,14 @@ const _localVariablesSyncIntegration = ((
// We need to have vars to add
cachedFrame[i].vars === undefined ||
// We're not interested in frames that are not in_app because the vars are not relevant
- exception.stacktrace.frames[frameIndex].in_app === false ||
+ frames[frameIndex].in_app === false ||
// The function names need to match
- !functionNamesMatch(exception.stacktrace.frames[frameIndex].function, cachedFrame[i].function)
+ !functionNamesMatch(frames[frameIndex].function, cachedFrame[i].function)
) {
continue;
}
- exception.stacktrace.frames[frameIndex].vars = cachedFrame[i].vars;
+ frames[frameIndex].vars = cachedFrame[i].vars;
}
}
@@ -347,7 +333,7 @@ const _localVariablesSyncIntegration = ((
if (session && clientOptions?.includeLocalVariables) {
// Only setup this integration if the Node version is >= v18
// https://github.com/getsentry/sentry-javascript/issues/7697
- const unsupportedNodeVersion = NODE_VERSION.major < 18;
+ const unsupportedNodeVersion = NODE_MAJOR < 18;
if (unsupportedNodeVersion) {
logger.log('The `LocalVariables` integration is only supported on Node >= v18.');
@@ -400,19 +386,7 @@ const _localVariablesSyncIntegration = ((
};
}) satisfies IntegrationFn;
-export const localVariablesSyncIntegration = defineIntegration(_localVariablesSyncIntegration);
-
/**
* Adds local variables to exception frames.
- * @deprecated Use `localVariablesSyncIntegration()` instead.
*/
-// eslint-disable-next-line deprecation/deprecation
-export const LocalVariablesSync = convertIntegrationFnToClass(
- INTEGRATION_NAME,
- localVariablesSyncIntegration,
-) as IntegrationClass Event; setup: (client: NodeClient) => void }> & {
- new (options?: LocalVariablesIntegrationOptions, session?: DebugSession): Integration;
-};
-
-// eslint-disable-next-line deprecation/deprecation
-export type LocalVariablesSync = typeof LocalVariablesSync;
+export const localVariablesSyncIntegration = defineIntegration(_localVariablesSyncIntegration);
diff --git a/packages/node/src/integrations/modules.ts b/packages/node/src/integrations/modules.ts
index 1f9aff7303e3..ad30bb4d7a3b 100644
--- a/packages/node/src/integrations/modules.ts
+++ b/packages/node/src/integrations/modules.ts
@@ -1,12 +1,31 @@
import { existsSync, readFileSync } from 'fs';
import { dirname, join } from 'path';
-import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
-import type { Event, Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
+import { defineIntegration } from '@sentry/core';
+import type { IntegrationFn } from '@sentry/types';
let moduleCache: { [key: string]: string };
const INTEGRATION_NAME = 'Modules';
+const _modulesIntegration = (() => {
+ return {
+ name: INTEGRATION_NAME,
+ processEvent(event) {
+ event.modules = {
+ ...event.modules,
+ ..._getModules(),
+ };
+
+ return event;
+ },
+ };
+}) satisfies IntegrationFn;
+
+/**
+ * Add node modules / packages to the event.
+ */
+export const modulesIntegration = defineIntegration(_modulesIntegration);
+
/** Extract information about paths */
function getPaths(): string[] {
try {
@@ -75,31 +94,3 @@ function _getModules(): { [key: string]: string } {
}
return moduleCache;
}
-
-const _modulesIntegration = (() => {
- return {
- name: INTEGRATION_NAME,
- processEvent(event) {
- event.modules = {
- ...event.modules,
- ..._getModules(),
- };
-
- return event;
- },
- };
-}) satisfies IntegrationFn;
-
-export const modulesIntegration = defineIntegration(_modulesIntegration);
-
-/**
- * Add node modules / packages to the event.
- * @deprecated Use `modulesIntegration()` instead.
- */
-// eslint-disable-next-line deprecation/deprecation
-export const Modules = convertIntegrationFnToClass(INTEGRATION_NAME, modulesIntegration) as IntegrationClass<
- Integration & { processEvent: (event: Event) => Event }
->;
-
-// eslint-disable-next-line deprecation/deprecation
-export type Modules = typeof Modules;
diff --git a/packages/node-experimental/src/integrations/node-fetch.ts b/packages/node/src/integrations/node-fetch.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/node-fetch.ts
rename to packages/node/src/integrations/node-fetch.ts
diff --git a/packages/node/src/integrations/onuncaughtexception.ts b/packages/node/src/integrations/onuncaughtexception.ts
index 68be68a6d6cc..e56c3c0801d7 100644
--- a/packages/node/src/integrations/onuncaughtexception.ts
+++ b/packages/node/src/integrations/onuncaughtexception.ts
@@ -1,11 +1,11 @@
-import { captureException, convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
+import { captureException, defineIntegration } from '@sentry/core';
import { getClient } from '@sentry/core';
-import type { Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
+import type { IntegrationFn } from '@sentry/types';
import { logger } from '@sentry/utils';
-import type { NodeClient } from '../client';
import { DEBUG_BUILD } from '../debug-build';
-import { logAndExitProcess } from './utils/errorhandling';
+import type { NodeClient } from '../sdk/client';
+import { logAndExitProcess } from '../utils/errorhandling';
type OnFatalErrorHandler = (firstError: Error, secondError?: Error) => void;
@@ -54,27 +54,10 @@ const _onUncaughtExceptionIntegration = ((options: Partial void }> & {
- new (
- options?: Partial<{
- exitEvenIfOtherHandlersAreRegistered: boolean;
- onFatalError?(this: void, firstError: Error, secondError?: Error): void;
- }>,
- ): Integration;
-};
-
-// eslint-disable-next-line deprecation/deprecation
-export type OnUncaughtException = typeof OnUncaughtException;
+export const onUncaughtExceptionIntegration = defineIntegration(_onUncaughtExceptionIntegration);
type ErrorHandler = { _errorHandler: boolean } & ((error: Error) => void);
@@ -103,20 +86,19 @@ export function makeErrorHandler(client: NodeClient, options: OnUncaughtExceptio
// exit behaviour of the SDK accordingly:
// - If other listeners are attached, do not exit.
// - If the only listener attached is ours, exit.
- const userProvidedListenersCount = (
- global.process.listeners('uncaughtException') as TaggedListener[]
- ).reduce((acc, listener) => {
- if (
+ const userProvidedListenersCount = (global.process.listeners('uncaughtException') as TaggedListener[]).filter(
+ listener => {
// There are 3 listeners we ignore:
- listener.name === 'domainUncaughtExceptionClear' || // as soon as we're using domains this listener is attached by node itself
- (listener.tag && listener.tag === 'sentry_tracingErrorCallback') || // the handler we register for tracing
- (listener as ErrorHandler)._errorHandler // the handler we register in this integration
- ) {
- return acc;
- } else {
- return acc + 1;
- }
- }, 0);
+ return (
+ // as soon as we're using domains this listener is attached by node itself
+ listener.name !== 'domainUncaughtExceptionClear' &&
+ // the handler we register for tracing
+ listener.tag !== 'sentry_tracingErrorCallback' &&
+ // the handler we register in this integration
+ (listener as ErrorHandler)._errorHandler !== true
+ );
+ },
+ ).length;
const processWouldExit = userProvidedListenersCount === 0;
const shouldApplyFatalHandlingLogic = options.exitEvenIfOtherHandlersAreRegistered || processWouldExit;
diff --git a/packages/node/src/integrations/onunhandledrejection.ts b/packages/node/src/integrations/onunhandledrejection.ts
index 9f3801b7a0cf..e1bc0b4145cf 100644
--- a/packages/node/src/integrations/onunhandledrejection.ts
+++ b/packages/node/src/integrations/onunhandledrejection.ts
@@ -1,8 +1,7 @@
-import { captureException, convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core';
-import type { Client, Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
+import { captureException, defineIntegration, getClient } from '@sentry/core';
+import type { Client, IntegrationFn } from '@sentry/types';
import { consoleSandbox } from '@sentry/utils';
-
-import { logAndExitProcess } from './utils/errorhandling';
+import { logAndExitProcess } from '../utils/errorhandling';
type UnhandledRejectionMode = 'none' | 'warn' | 'strict';
@@ -27,22 +26,10 @@ const _onUnhandledRejectionIntegration = ((options: Partial void }> & {
- new (options?: Partial<{ mode: UnhandledRejectionMode }>): Integration;
-};
-
-// eslint-disable-next-line deprecation/deprecation
-export type OnUnhandledRejection = typeof OnUnhandledRejection;
+export const onUnhandledRejectionIntegration = defineIntegration(_onUnhandledRejectionIntegration);
/**
* Send an exception with reason
diff --git a/packages/node/src/integrations/spotlight.ts b/packages/node/src/integrations/spotlight.ts
index eb9c34260b61..21629ad340ac 100644
--- a/packages/node/src/integrations/spotlight.ts
+++ b/packages/node/src/integrations/spotlight.ts
@@ -1,7 +1,6 @@
import * as http from 'http';
-import { URL } from 'url';
-import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core';
-import type { Client, Envelope, Integration, IntegrationClass, IntegrationFn } from '@sentry/types';
+import { defineIntegration } from '@sentry/core';
+import type { Client, Envelope, IntegrationFn } from '@sentry/types';
import { logger, serializeEnvelope } from '@sentry/utils';
type SpotlightConnectionOptions = {
@@ -30,30 +29,14 @@ const _spotlightIntegration = ((options: Partial = {
};
}) satisfies IntegrationFn;
-export const spotlightIntegration = defineIntegration(_spotlightIntegration);
-
/**
* Use this integration to send errors and transactions to Spotlight.
*
* Learn more about spotlight at https://spotlightjs.com
*
* Important: This integration only works with Node 18 or newer.
- *
- * @deprecated Use `spotlightIntegration()` instead.
*/
-// eslint-disable-next-line deprecation/deprecation
-export const Spotlight = convertIntegrationFnToClass(INTEGRATION_NAME, spotlightIntegration) as IntegrationClass<
- Integration & { setup: (client: Client) => void }
-> & {
- new (
- options?: Partial<{
- sidecarUrl?: string;
- }>,
- ): Integration;
-};
-
-// eslint-disable-next-line deprecation/deprecation
-export type Spotlight = typeof Spotlight;
+export const spotlightIntegration = defineIntegration(_spotlightIntegration);
function connectToSpotlight(client: Client, options: Required): void {
const spotlightUrl = parseSidecarUrl(options.sidecarUrl);
diff --git a/packages/node-experimental/src/integrations/tracing/express.ts b/packages/node/src/integrations/tracing/express.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/express.ts
rename to packages/node/src/integrations/tracing/express.ts
diff --git a/packages/node-experimental/src/integrations/tracing/fastify.ts b/packages/node/src/integrations/tracing/fastify.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/fastify.ts
rename to packages/node/src/integrations/tracing/fastify.ts
diff --git a/packages/node-experimental/src/integrations/tracing/graphql.ts b/packages/node/src/integrations/tracing/graphql.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/graphql.ts
rename to packages/node/src/integrations/tracing/graphql.ts
diff --git a/packages/node-experimental/src/integrations/tracing/hapi/index.ts b/packages/node/src/integrations/tracing/hapi/index.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/hapi/index.ts
rename to packages/node/src/integrations/tracing/hapi/index.ts
diff --git a/packages/node-experimental/src/integrations/tracing/hapi/types.ts b/packages/node/src/integrations/tracing/hapi/types.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/hapi/types.ts
rename to packages/node/src/integrations/tracing/hapi/types.ts
diff --git a/packages/node-experimental/src/integrations/tracing/index.ts b/packages/node/src/integrations/tracing/index.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/index.ts
rename to packages/node/src/integrations/tracing/index.ts
diff --git a/packages/node-experimental/src/integrations/tracing/koa.ts b/packages/node/src/integrations/tracing/koa.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/koa.ts
rename to packages/node/src/integrations/tracing/koa.ts
diff --git a/packages/node-experimental/src/integrations/tracing/mongo.ts b/packages/node/src/integrations/tracing/mongo.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/mongo.ts
rename to packages/node/src/integrations/tracing/mongo.ts
diff --git a/packages/node-experimental/src/integrations/tracing/mongoose.ts b/packages/node/src/integrations/tracing/mongoose.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/mongoose.ts
rename to packages/node/src/integrations/tracing/mongoose.ts
diff --git a/packages/node-experimental/src/integrations/tracing/mysql.ts b/packages/node/src/integrations/tracing/mysql.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/mysql.ts
rename to packages/node/src/integrations/tracing/mysql.ts
diff --git a/packages/node-experimental/src/integrations/tracing/mysql2.ts b/packages/node/src/integrations/tracing/mysql2.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/mysql2.ts
rename to packages/node/src/integrations/tracing/mysql2.ts
diff --git a/packages/node-experimental/src/integrations/tracing/nest.ts b/packages/node/src/integrations/tracing/nest.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/nest.ts
rename to packages/node/src/integrations/tracing/nest.ts
diff --git a/packages/node-experimental/src/integrations/tracing/postgres.ts b/packages/node/src/integrations/tracing/postgres.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/postgres.ts
rename to packages/node/src/integrations/tracing/postgres.ts
diff --git a/packages/node-experimental/src/integrations/tracing/prisma.ts b/packages/node/src/integrations/tracing/prisma.ts
similarity index 100%
rename from packages/node-experimental/src/integrations/tracing/prisma.ts
rename to packages/node/src/integrations/tracing/prisma.ts
diff --git a/packages/node/src/nodeVersion.ts b/packages/node/src/nodeVersion.ts
index 1574237f3fb4..1f07883b771b 100644
--- a/packages/node/src/nodeVersion.ts
+++ b/packages/node/src/nodeVersion.ts
@@ -1,3 +1,4 @@
import { parseSemver } from '@sentry/utils';
export const NODE_VERSION = parseSemver(process.versions.node) as { major: number; minor: number; patch: number };
+export const NODE_MAJOR = NODE_VERSION.major;
diff --git a/packages/node-experimental/src/otel/contextManager.ts b/packages/node/src/otel/contextManager.ts
similarity index 100%
rename from packages/node-experimental/src/otel/contextManager.ts
rename to packages/node/src/otel/contextManager.ts
diff --git a/packages/node/src/proxy/helpers.ts b/packages/node/src/proxy/helpers.ts
index a5064408855d..031878511f6c 100644
--- a/packages/node/src/proxy/helpers.ts
+++ b/packages/node/src/proxy/helpers.ts
@@ -30,8 +30,6 @@
import * as http from 'node:http';
import * as https from 'node:https';
import type { Readable } from 'stream';
-// TODO (v8): Remove this when Node < 12 is no longer supported
-import type { URL } from 'url';
export type ThenableRequest = http.ClientRequest & {
then: Promise['then'];
diff --git a/packages/node/src/proxy/index.ts b/packages/node/src/proxy/index.ts
index 15c700ed3e62..83f72d56fb4e 100644
--- a/packages/node/src/proxy/index.ts
+++ b/packages/node/src/proxy/index.ts
@@ -32,8 +32,6 @@ import type * as http from 'http';
import type { OutgoingHttpHeaders } from 'http';
import * as net from 'net';
import * as tls from 'tls';
-// TODO (v8): Remove this when Node < 12 is no longer supported
-import { URL } from 'url';
import { logger } from '@sentry/utils';
import { Agent } from './base';
import type { AgentConnectOpts } from './base';
diff --git a/packages/node-experimental/src/sdk/api.ts b/packages/node/src/sdk/api.ts
similarity index 100%
rename from packages/node-experimental/src/sdk/api.ts
rename to packages/node/src/sdk/api.ts
diff --git a/packages/node-experimental/src/sdk/client.ts b/packages/node/src/sdk/client.ts
similarity index 100%
rename from packages/node-experimental/src/sdk/client.ts
rename to packages/node/src/sdk/client.ts
diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node/src/sdk/init.ts
similarity index 100%
rename from packages/node-experimental/src/sdk/init.ts
rename to packages/node/src/sdk/init.ts
diff --git a/packages/node-experimental/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts
similarity index 100%
rename from packages/node-experimental/src/sdk/initOtel.ts
rename to packages/node/src/sdk/initOtel.ts
diff --git a/packages/node-experimental/src/sdk/scope.ts b/packages/node/src/sdk/scope.ts
similarity index 100%
rename from packages/node-experimental/src/sdk/scope.ts
rename to packages/node/src/sdk/scope.ts
diff --git a/packages/node/src/transports/http-module.ts b/packages/node/src/transports/http-module.ts
index 64b255cc869c..f5cbe6fd35f9 100644
--- a/packages/node/src/transports/http-module.ts
+++ b/packages/node/src/transports/http-module.ts
@@ -1,7 +1,5 @@
-import type { IncomingHttpHeaders, RequestOptions as HTTPRequestOptions } from 'http';
-import type { RequestOptions as HTTPSRequestOptions } from 'https';
-import type { Writable } from 'stream';
-import type { URL } from 'url';
+import type { ClientRequest, IncomingHttpHeaders, RequestOptions as HTTPRequestOptions } from 'node:http';
+import type { RequestOptions as HTTPSRequestOptions } from 'node:https';
export type HTTPModuleRequestOptions = HTTPRequestOptions | HTTPSRequestOptions | string | URL;
@@ -26,15 +24,5 @@ export interface HTTPModule {
* @param options These are {@see TransportOptions}
* @param callback Callback when request is finished
*/
- request(options: HTTPModuleRequestOptions, callback?: (res: HTTPModuleRequestIncomingMessage) => void): Writable;
-
- // This is the type for nodejs versions that handle the URL argument
- // (v10.9.0+), but we do not use it just yet because we support older node
- // versions:
-
- // request(
- // url: string | URL,
- // options: http.RequestOptions | https.RequestOptions,
- // callback?: (res: http.IncomingMessage) => void,
- // ): http.ClientRequest;
+ request(options: HTTPModuleRequestOptions, callback?: (res: HTTPModuleRequestIncomingMessage) => void): ClientRequest;
}
diff --git a/packages/node/src/transports/http.ts b/packages/node/src/transports/http.ts
index a6a05fc07c95..4cbe7ece1f60 100644
--- a/packages/node/src/transports/http.ts
+++ b/packages/node/src/transports/http.ts
@@ -1,8 +1,9 @@
import * as http from 'node:http';
import * as https from 'node:https';
import { Readable } from 'stream';
-import { URL } from 'url';
import { createGzip } from 'zlib';
+import { context } from '@opentelemetry/api';
+import { suppressTracing } from '@opentelemetry/core';
import { createTransport } from '@sentry/core';
import type {
BaseTransportOptions,
@@ -13,7 +14,6 @@ import type {
} from '@sentry/types';
import { consoleSandbox } from '@sentry/utils';
import { HttpsProxyAgent } from '../proxy';
-
import type { HTTPModule } from './http-module';
export interface NodeTransportOptions extends BaseTransportOptions {
@@ -81,8 +81,11 @@ export function makeNodeTransport(options: NodeTransportOptions): Transport {
? (new HttpsProxyAgent(proxy) as http.Agent)
: new nativeHttpModule.Agent({ keepAlive, maxSockets: 30, timeout: 2000 });
- const requestExecutor = createRequestExecutor(options, options.httpModule ?? nativeHttpModule, agent);
- return createTransport(options, requestExecutor);
+ // This ensures we do not generate any spans in OpenTelemetry for the transport
+ return context.with(suppressTracing(context.active()), () => {
+ const requestExecutor = createRequestExecutor(options, options.httpModule ?? nativeHttpModule, agent);
+ return createTransport(options, requestExecutor);
+ });
}
/**
diff --git a/packages/node/src/types.ts b/packages/node/src/types.ts
index 01f91fb46cbe..d78e1761fd79 100644
--- a/packages/node/src/types.ts
+++ b/packages/node/src/types.ts
@@ -1,6 +1,7 @@
-import type { ClientOptions, Options, SamplingContext, TracePropagationTargets } from '@sentry/types';
+import type { Span as WriteableSpan } from '@opentelemetry/api';
+import type { ReadableSpan } from '@opentelemetry/sdk-trace-base';
+import type { ClientOptions, Options, SamplingContext, Scope, Span, TracePropagationTargets } from '@sentry/types';
-import type { NodeClient } from './client';
import type { NodeTransportOptions } from './transports';
export interface BaseNodeOptions {
@@ -51,14 +52,6 @@ export interface BaseNodeOptions {
*/
includeLocalVariables?: boolean;
- /**
- * Specify a custom NodeClient to be used. Must extend NodeClient!
- * This is not a public, supported API, but used internally only.
- *
- * @hidden
- * */
- clientClass?: typeof NodeClient;
-
/**
* If you use Spotlight by Sentry during development, use
* this option to forward captured Sentry events to Spotlight.
@@ -71,23 +64,15 @@ export interface BaseNodeOptions {
*/
spotlight?: boolean | string;
- // TODO (v8): Remove this in v8
/**
- * @deprecated Moved to constructor options of the `Http` and `Undici` integration.
- * @example
- * ```js
- * Sentry.init({
- * integrations: [
- * new Sentry.Integrations.Http({
- * tracing: {
- * shouldCreateSpanForRequest: (url: string) => false,
- * }
- * });
- * ],
- * });
- * ```
+ * If this is set to true, the SDK will not set up OpenTelemetry automatically.
+ * In this case, you _have_ to ensure to set it up correctly yourself, including:
+ * * The `SentrySpanProcessor`
+ * * The `SentryPropagator`
+ * * The `SentryContextManager`
+ * * The `SentrySampler`
*/
- shouldCreateSpanForRequest?(this: void, url: string): boolean;
+ skipOpenTelemetrySetup?: boolean;
/** Callback that is executed when a fatal global error occurs. */
onFatalError?(this: void, error: Error): void;
@@ -104,3 +89,19 @@ export interface NodeOptions extends Options, BaseNodeOpti
* @see NodeClient for more information.
*/
export interface NodeClientOptions extends ClientOptions, BaseNodeOptions {}
+
+export interface CurrentScopes {
+ scope: Scope;
+ isolationScope: Scope;
+}
+
+/**
+ * The base `Span` type is basically a `WriteableSpan`.
+ * There are places where we basically want to allow passing _any_ span,
+ * so in these cases we type this as `AbstractSpan` which could be either a regular `Span` or a `ReadableSpan`.
+ * You'll have to make sur to check revelant fields before accessing them.
+ *
+ * Note that technically, the `Span` exported from `@opentelemwetry/sdk-trace-base` matches this,
+ * but we cannot be 100% sure that we are actually getting such a span, so this type is more defensive.
+ */
+export type AbstractSpan = WriteableSpan | ReadableSpan | Span;
diff --git a/packages/node-experimental/src/utils/addOriginToSpan.ts b/packages/node/src/utils/addOriginToSpan.ts
similarity index 100%
rename from packages/node-experimental/src/utils/addOriginToSpan.ts
rename to packages/node/src/utils/addOriginToSpan.ts
diff --git a/packages/node-experimental/src/utils/errorhandling.ts b/packages/node/src/utils/errorhandling.ts
similarity index 100%
rename from packages/node-experimental/src/utils/errorhandling.ts
rename to packages/node/src/utils/errorhandling.ts
diff --git a/packages/node-experimental/src/utils/getRequestUrl.ts b/packages/node/src/utils/getRequestUrl.ts
similarity index 100%
rename from packages/node-experimental/src/utils/getRequestUrl.ts
rename to packages/node/src/utils/getRequestUrl.ts
diff --git a/packages/node/src/module.ts b/packages/node/src/utils/module.ts
similarity index 100%
rename from packages/node/src/module.ts
rename to packages/node/src/utils/module.ts
diff --git a/packages/node-experimental/src/utils/prepareEvent.ts b/packages/node/src/utils/prepareEvent.ts
similarity index 100%
rename from packages/node-experimental/src/utils/prepareEvent.ts
rename to packages/node/src/utils/prepareEvent.ts
diff --git a/packages/node-experimental/test/helpers/error.ts b/packages/node/test/helpers/error.ts
similarity index 100%
rename from packages/node-experimental/test/helpers/error.ts
rename to packages/node/test/helpers/error.ts
diff --git a/packages/node-experimental/test/helpers/getDefaultNodeClientOptions.ts b/packages/node/test/helpers/getDefaultNodeClientOptions.ts
similarity index 100%
rename from packages/node-experimental/test/helpers/getDefaultNodeClientOptions.ts
rename to packages/node/test/helpers/getDefaultNodeClientOptions.ts
diff --git a/packages/node-experimental/test/helpers/mockSdkInit.ts b/packages/node/test/helpers/mockSdkInit.ts
similarity index 100%
rename from packages/node-experimental/test/helpers/mockSdkInit.ts
rename to packages/node/test/helpers/mockSdkInit.ts
diff --git a/packages/node-experimental/test/integration/breadcrumbs.test.ts b/packages/node/test/integration/breadcrumbs.test.ts
similarity index 100%
rename from packages/node-experimental/test/integration/breadcrumbs.test.ts
rename to packages/node/test/integration/breadcrumbs.test.ts
diff --git a/packages/node-experimental/test/integration/scope.test.ts b/packages/node/test/integration/scope.test.ts
similarity index 100%
rename from packages/node-experimental/test/integration/scope.test.ts
rename to packages/node/test/integration/scope.test.ts
diff --git a/packages/node-experimental/test/integration/transactions.test.ts b/packages/node/test/integration/transactions.test.ts
similarity index 100%
rename from packages/node-experimental/test/integration/transactions.test.ts
rename to packages/node/test/integration/transactions.test.ts
diff --git a/packages/node/test/integrations/contextlines.test.ts b/packages/node/test/integrations/contextlines.test.ts
index 67f3ad793fbc..c4ef1efaa292 100644
--- a/packages/node/test/integrations/contextlines.test.ts
+++ b/packages/node/test/integrations/contextlines.test.ts
@@ -1,30 +1,37 @@
-import * as fs from 'fs';
-import type { Event, Integration, StackFrame } from '@sentry/types';
+import { promises } from 'fs';
+import type { StackFrame } from '@sentry/types';
import { parseStackFrames } from '@sentry/utils';
-import { contextLinesIntegration } from '../../src';
-import { resetFileContentCache } from '../../src/integrations/contextlines';
-import { defaultStackParser } from '../../src/sdk';
-import { getError } from '../helper/error';
+import { _contextLinesIntegration, resetFileContentCache } from '../../src/integrations/contextlines';
+import { defaultStackParser } from '../../src/sdk/api';
+import { getError } from '../helpers/error';
+
+jest.mock('fs', () => {
+ const actual = jest.requireActual('fs');
+ return {
+ ...actual,
+ promises: {
+ ...actual.promises,
+ readFile: jest.fn(actual.promises),
+ },
+ };
+});
describe('ContextLines', () => {
- let readFileSpy: jest.SpyInstance;
- let contextLines: Integration;
+ const readFileSpy = promises.readFile as unknown as jest.SpyInstance;
+ let contextLines: ReturnType;
async function addContext(frames: StackFrame[]): Promise {
- await (contextLines as Integration & { processEvent: (event: Event) => Promise }).processEvent({
- exception: { values: [{ stacktrace: { frames } }] },
- });
+ await contextLines.processEvent({ exception: { values: [{ stacktrace: { frames } }] } });
}
beforeEach(() => {
- readFileSpy = jest.spyOn(fs, 'readFile');
- contextLines = contextLinesIntegration();
+ contextLines = _contextLinesIntegration();
resetFileContentCache();
});
afterEach(() => {
- jest.restoreAllMocks();
+ jest.clearAllMocks();
});
describe('lru file cache', () => {
@@ -101,7 +108,7 @@ describe('ContextLines', () => {
});
test('parseStack with no context', async () => {
- contextLines = contextLinesIntegration({ frameContextLines: 0 });
+ contextLines = _contextLinesIntegration({ frameContextLines: 0 });
expect.assertions(1);
const frames = parseStackFrames(defaultStackParser, new Error('test'));
@@ -113,7 +120,6 @@ describe('ContextLines', () => {
test('does not attempt to readfile multiple times if it fails', async () => {
expect.assertions(1);
- contextLines = contextLinesIntegration();
readFileSpy.mockImplementation(() => {
throw new Error("ENOENT: no such file or directory, open '/does/not/exist.js'");
diff --git a/packages/node-experimental/test/integrations/express.test.ts b/packages/node/test/integrations/express.test.ts
similarity index 100%
rename from packages/node-experimental/test/integrations/express.test.ts
rename to packages/node/test/integrations/express.test.ts
diff --git a/packages/node/test/integrations/localvariables.test.ts b/packages/node/test/integrations/localvariables.test.ts
index abc1d241f842..db9385214d42 100644
--- a/packages/node/test/integrations/localvariables.test.ts
+++ b/packages/node/test/integrations/localvariables.test.ts
@@ -1,12 +1,12 @@
import { createRateLimiter } from '../../src/integrations/local-variables/common';
import { createCallbackList } from '../../src/integrations/local-variables/local-variables-sync';
-import { NODE_VERSION } from '../../src/nodeVersion';
+import { NODE_MAJOR } from '../../src/nodeVersion';
jest.setTimeout(20_000);
const describeIf = (condition: boolean) => (condition ? describe : describe.skip);
-describeIf(NODE_VERSION.major >= 18)('LocalVariables', () => {
+describeIf(NODE_MAJOR >= 18)('LocalVariables', () => {
describe('createCallbackList', () => {
it('Should call callbacks in reverse order', done => {
const log: number[] = [];
diff --git a/packages/node/test/integrations/spotlight.test.ts b/packages/node/test/integrations/spotlight.test.ts
index a892677d0dd0..6b888c22edcd 100644
--- a/packages/node/test/integrations/spotlight.test.ts
+++ b/packages/node/test/integrations/spotlight.test.ts
@@ -2,9 +2,9 @@ import * as http from 'http';
import type { Envelope, EventEnvelope } from '@sentry/types';
import { createEnvelope, logger } from '@sentry/utils';
-import { NodeClient, spotlightIntegration } from '../../src';
-import { Spotlight } from '../../src/integrations';
-import { getDefaultNodeClientOptions } from '../helper/node-client-options';
+import { spotlightIntegration } from '../../src/integrations/spotlight';
+import { NodeClient } from '../../src/sdk/client';
+import { getDefaultNodeClientOptions } from '../helpers/getDefaultNodeClientOptions';
describe('Spotlight', () => {
const loggerSpy = jest.spyOn(logger, 'warn');
@@ -17,12 +17,9 @@ describe('Spotlight', () => {
const options = getDefaultNodeClientOptions();
const client = new NodeClient(options);
- it('has a name and id', () => {
- // eslint-disable-next-line deprecation/deprecation
- const integration = new Spotlight();
+ it('has a name', () => {
+ const integration = spotlightIntegration();
expect(integration.name).toEqual('Spotlight');
- // eslint-disable-next-line deprecation/deprecation
- expect(Spotlight.id).toEqual('Spotlight');
});
it('registers a callback on the `beforeEnvelope` hook', () => {
diff --git a/packages/node-experimental/test/sdk/api.test.ts b/packages/node/test/sdk/api.test.ts
similarity index 100%
rename from packages/node-experimental/test/sdk/api.test.ts
rename to packages/node/test/sdk/api.test.ts
diff --git a/packages/node-experimental/test/sdk/client.test.ts b/packages/node/test/sdk/client.test.ts
similarity index 100%
rename from packages/node-experimental/test/sdk/client.test.ts
rename to packages/node/test/sdk/client.test.ts
diff --git a/packages/node-experimental/test/sdk/init.test.ts b/packages/node/test/sdk/init.test.ts
similarity index 100%
rename from packages/node-experimental/test/sdk/init.test.ts
rename to packages/node/test/sdk/init.test.ts
diff --git a/packages/node-experimental/test/sdk/scope.test.ts b/packages/node/test/sdk/scope.test.ts
similarity index 100%
rename from packages/node-experimental/test/sdk/scope.test.ts
rename to packages/node/test/sdk/scope.test.ts
diff --git a/packages/node/test/transports/http.test.ts b/packages/node/test/transports/http.test.ts
index ddf73039a009..e945c086959a 100644
--- a/packages/node/test/transports/http.test.ts
+++ b/packages/node/test/transports/http.test.ts
@@ -1,6 +1,4 @@
-/* eslint-disable deprecation/deprecation */
import * as http from 'http';
-
import { createGunzip } from 'zlib';
import { createTransport } from '@sentry/core';
import type { EventEnvelope, EventItem } from '@sentry/types';
@@ -51,19 +49,18 @@ function setupTestServer(
res.end();
// also terminate socket because keepalive hangs connection a bit
- if (res.connection) {
- res.connection.end();
- }
+ // eslint-disable-next-line deprecation/deprecation
+ res.connection?.end();
});
- testServer.listen(18099);
+ testServer.listen(18101);
return new Promise(resolve => {
testServer?.on('listening', resolve);
});
}
-const TEST_SERVER_URL = 'http://localhost:18099';
+const TEST_SERVER_URL = 'http://localhost:18101';
const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
[{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem,
diff --git a/packages/node/test/transports/https.test.ts b/packages/node/test/transports/https.test.ts
index a45319c40e42..8b0d3312ba54 100644
--- a/packages/node/test/transports/https.test.ts
+++ b/packages/node/test/transports/https.test.ts
@@ -1,4 +1,3 @@
-/* eslint-disable deprecation/deprecation */
import * as http from 'http';
import * as https from 'https';
import { createTransport } from '@sentry/core';
@@ -50,19 +49,18 @@ function setupTestServer(
res.end();
// also terminate socket because keepalive hangs connection a bit
- if (res.connection) {
- res.connection.end();
- }
+ // eslint-disable-next-line deprecation/deprecation
+ res.connection?.end();
});
- testServer.listen(8099);
+ testServer.listen(8100);
return new Promise(resolve => {
testServer?.on('listening', resolve);
});
}
-const TEST_SERVER_URL = 'https://localhost:8099';
+const TEST_SERVER_URL = 'https://localhost:8100';
const EVENT_ENVELOPE = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, [
[{ type: 'event' }, { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }] as EventItem,
diff --git a/packages/node-experimental/test/utils/getRequestUrl.test.ts b/packages/node/test/utils/getRequestUrl.test.ts
similarity index 100%
rename from packages/node-experimental/test/utils/getRequestUrl.test.ts
rename to packages/node/test/utils/getRequestUrl.test.ts
diff --git a/packages/node/tsconfig.json b/packages/node/tsconfig.json
index 89a9b9e0e2fe..8f38d240197e 100644
--- a/packages/node/tsconfig.json
+++ b/packages/node/tsconfig.json
@@ -4,6 +4,7 @@
"include": ["src/**/*"],
"compilerOptions": {
- "lib": ["es2018"]
+ "lib": ["es2018"],
+ "module": "Node16"
}
}
diff --git a/packages/node/tsconfig.test.json b/packages/node/tsconfig.test.json
index 52333183eb70..87f6afa06b86 100644
--- a/packages/node/tsconfig.test.json
+++ b/packages/node/tsconfig.test.json
@@ -1,7 +1,7 @@
{
"extends": "./tsconfig.json",
- "include": ["test/**/*", "src/**/*.d.ts"],
+ "include": ["test/**/*"],
"compilerOptions": {
// should include all types from `./tsconfig.json` plus types for all test frameworks used
diff --git a/packages/remix/test/integration/package.json b/packages/remix/test/integration/package.json
index e5a763551fe9..a78fe7479c9b 100644
--- a/packages/remix/test/integration/package.json
+++ b/packages/remix/test/integration/package.json
@@ -25,7 +25,7 @@
"resolutions": {
"@sentry/browser": "file:../../../browser",
"@sentry/core": "file:../../../core",
- "@sentry/node": "file:../../../node-experimental",
+ "@sentry/node": "file:../../../node",
"@sentry/opentelemetry": "file:../../../opentelemetry",
"@sentry/react": "file:../../../react",
"@sentry-internal/replay": "file:../../../replay-internal",