From bf70c17b77152368b4d02de8d305f8ba430a91a7 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 8 Sep 2025 23:09:58 +0200 Subject: [PATCH 01/24] feat: Tidy existing loader hook --- packages/node-core/src/index.ts | 3 +- .../node-core/src/integrations/modules.ts | 2 +- packages/node-core/src/sdk/esmLoader.ts | 50 +++++++++---------- packages/node-core/src/sdk/index.ts | 10 ++-- packages/node-core/src/utils/commonjs.ts | 8 --- .../createMissingInstrumentationContext.ts | 2 +- packages/node-core/src/utils/detection.ts | 49 ++++++++++++++++++ .../node-core/src/utils/ensureIsWrapped.ts | 2 +- packages/node/src/sdk/initOtel.ts | 38 ++------------ 9 files changed, 87 insertions(+), 77 deletions(-) delete mode 100644 packages/node-core/src/utils/commonjs.ts create mode 100644 packages/node-core/src/utils/detection.ts diff --git a/packages/node-core/src/index.ts b/packages/node-core/src/index.ts index cf581bd63b66..57d8e2ece0ae 100644 --- a/packages/node-core/src/index.ts +++ b/packages/node-core/src/index.ts @@ -35,7 +35,8 @@ export { getSentryRelease, defaultStackParser } from './sdk/api'; export { createGetModuleFromFilename } from './utils/module'; export { addOriginToSpan } from './utils/addOriginToSpan'; export { getRequestUrl } from './utils/getRequestUrl'; -export { isCjs } from './utils/commonjs'; +export { initializeEsmLoader } from './sdk/esmLoader'; +export { isCjs } from './utils/detection'; export { ensureIsWrapped } from './utils/ensureIsWrapped'; export { createMissingInstrumentationContext } from './utils/createMissingInstrumentationContext'; export { envToBool } from './utils/envToBool'; diff --git a/packages/node-core/src/integrations/modules.ts b/packages/node-core/src/integrations/modules.ts index 6724a473b5bb..7079f4a2fab8 100644 --- a/packages/node-core/src/integrations/modules.ts +++ b/packages/node-core/src/integrations/modules.ts @@ -1,7 +1,7 @@ import { existsSync, readFileSync } from 'node:fs'; import { dirname, join } from 'node:path'; import type { IntegrationFn } from '@sentry/core'; -import { isCjs } from '../utils/commonjs'; +import { isCjs } from '../utils/detection'; type ModuleInfo = Record; diff --git a/packages/node-core/src/sdk/esmLoader.ts b/packages/node-core/src/sdk/esmLoader.ts index 2f0d8b405333..14f24f0df311 100644 --- a/packages/node-core/src/sdk/esmLoader.ts +++ b/packages/node-core/src/sdk/esmLoader.ts @@ -1,31 +1,31 @@ -import { consoleSandbox, debug, GLOBAL_OBJ } from '@sentry/core'; +import { debug, GLOBAL_OBJ } from '@sentry/core'; import { createAddHookMessageChannel } from 'import-in-the-middle'; -import moduleModule from 'module'; +import * as moduleModule from 'module'; +import { supportsEsmLoaderHooks } from '../utils/detection'; -/** Initialize the ESM loader. */ -export function maybeInitializeEsmLoader(): void { - const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number); +/** + * Initialize the ESM loader - This method is private and not part of the public + * API. + * + * @ignore + */ +export function initializeEsmLoader(): void { + if (!supportsEsmLoaderHooks()) { + return; + } + + if (!GLOBAL_OBJ._sentryEsmLoaderHookRegistered) { + GLOBAL_OBJ._sentryEsmLoaderHookRegistered = true; - // Register hook was added in v20.6.0 and v18.19.0 - if (nodeMajor >= 21 || (nodeMajor === 20 && nodeMinor >= 6) || (nodeMajor === 18 && nodeMinor >= 19)) { - if (!GLOBAL_OBJ._sentryEsmLoaderHookRegistered) { - try { - const { addHookMessagePort } = createAddHookMessageChannel(); - // @ts-expect-error register is available in these versions - moduleModule.register('import-in-the-middle/hook.mjs', import.meta.url, { - data: { addHookMessagePort, include: [] }, - transferList: [addHookMessagePort], - }); - } catch (error) { - debug.warn('Failed to register ESM hook', error); - } + try { + const { addHookMessagePort } = createAddHookMessageChannel(); + // @ts-expect-error register is available in these versions + moduleModule.register('import-in-the-middle/hook.mjs', import.meta.url, { + data: { addHookMessagePort, include: [] }, + transferList: [addHookMessagePort], + }); + } catch (error) { + debug.warn("Failed to register 'import-in-the-middle' hook", error); } - } else { - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn( - `[Sentry] You are using Node.js v${process.versions.node} in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or upgrade your Node.js version.`, - ); - }); } } diff --git a/packages/node-core/src/sdk/index.ts b/packages/node-core/src/sdk/index.ts index e5b12166d962..c4a16d76a1d0 100644 --- a/packages/node-core/src/sdk/index.ts +++ b/packages/node-core/src/sdk/index.ts @@ -35,11 +35,11 @@ import { INTEGRATION_NAME as SPOTLIGHT_INTEGRATION_NAME, spotlightIntegration } import { systemErrorIntegration } from '../integrations/systemError'; import { makeNodeTransport } from '../transports'; import type { NodeClientOptions, NodeOptions } from '../types'; -import { isCjs } from '../utils/commonjs'; +import { isCjs } from '../utils/detection'; import { envToBool } from '../utils/envToBool'; import { defaultStackParser, getSentryRelease } from './api'; import { NodeClient } from './client'; -import { maybeInitializeEsmLoader } from './esmLoader'; +import { initializeEsmLoader } from './esmLoader'; /** * Get default integrations for the Node-Core SDK. @@ -106,8 +106,8 @@ function _init( } } - if (!isCjs() && options.registerEsmLoaderHooks !== false) { - maybeInitializeEsmLoader(); + if (options.registerEsmLoaderHooks !== false) { + initializeEsmLoader(); } setOpenTelemetryContextAsyncContextStrategy(); @@ -131,7 +131,7 @@ function _init( client.init(); - debug.log(`Running in ${isCjs() ? 'CommonJS' : 'ESM'} mode.`); + debug.log(`SDK initialized from ${isCjs() ? 'CommonJS' : 'ESM'}`); client.startClientReportTracking(); diff --git a/packages/node-core/src/utils/commonjs.ts b/packages/node-core/src/utils/commonjs.ts deleted file mode 100644 index 23a9b97f9fc1..000000000000 --- a/packages/node-core/src/utils/commonjs.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** Detect CommonJS. */ -export function isCjs(): boolean { - try { - return typeof module !== 'undefined' && typeof module.exports !== 'undefined'; - } catch { - return false; - } -} diff --git a/packages/node-core/src/utils/createMissingInstrumentationContext.ts b/packages/node-core/src/utils/createMissingInstrumentationContext.ts index 1930bcf782eb..3af8dc9e176a 100644 --- a/packages/node-core/src/utils/createMissingInstrumentationContext.ts +++ b/packages/node-core/src/utils/createMissingInstrumentationContext.ts @@ -1,5 +1,5 @@ import type { MissingInstrumentationContext } from '@sentry/core'; -import { isCjs } from './commonjs'; +import { isCjs } from './detection'; export const createMissingInstrumentationContext = (pkg: string): MissingInstrumentationContext => ({ package: pkg, diff --git a/packages/node-core/src/utils/detection.ts b/packages/node-core/src/utils/detection.ts new file mode 100644 index 000000000000..fe649097d737 --- /dev/null +++ b/packages/node-core/src/utils/detection.ts @@ -0,0 +1,49 @@ +import * as moduleModule from 'node:module'; +import { consoleSandbox } from '@sentry/core'; + +/** Detect CommonJS. */ +export function isCjs(): boolean { + try { + return typeof module !== 'undefined' && typeof module.exports !== 'undefined'; + } catch { + return false; + } +} + +/** + * Check if the current Node.js version supports module.register + */ +export function supportsEsmLoaderHooks(): boolean { + logIfUnsupportedNodeVersion(); + + return 'register' in moduleModule; +} + +let hasWarnedAboutNodeVersion: boolean | undefined; + +/** + * Log a warning if the current Node.js version is not supported in ESM mode + */ +function logIfUnsupportedNodeVersion(): void { + if (hasWarnedAboutNodeVersion) { + return; + } + + if (isCjs()) { + return; + } + + const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number); + if (nodeMajor >= 21 || (nodeMajor === 20 && nodeMinor >= 6) || (nodeMajor === 18 && nodeMinor >= 19)) { + return; + } + + hasWarnedAboutNodeVersion = true; + + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] You are using Node.js v${process.versions.node} in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or upgrade your Node.js version.`, + ); + }); +} diff --git a/packages/node-core/src/utils/ensureIsWrapped.ts b/packages/node-core/src/utils/ensureIsWrapped.ts index 70253d9debb7..a73c087ebaf2 100644 --- a/packages/node-core/src/utils/ensureIsWrapped.ts +++ b/packages/node-core/src/utils/ensureIsWrapped.ts @@ -1,8 +1,8 @@ import { isWrapped } from '@opentelemetry/instrumentation'; import { consoleSandbox, getClient, getGlobalScope, hasSpansEnabled, isEnabled } from '@sentry/core'; import type { NodeClient } from '../sdk/client'; -import { isCjs } from './commonjs'; import { createMissingInstrumentationContext } from './createMissingInstrumentationContext'; +import { isCjs } from './detection'; /** * Checks and warns if a framework isn't wrapped by opentelemetry. diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index ef27be0514c3..ab528b312a72 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -7,11 +7,9 @@ import { ATTR_SERVICE_VERSION, SEMRESATTRS_SERVICE_NAMESPACE, } from '@opentelemetry/semantic-conventions'; -import { consoleSandbox, debug as coreDebug, GLOBAL_OBJ, SDK_VERSION } from '@sentry/core'; -import { type NodeClient, isCjs, SentryContextManager, setupOpenTelemetryLogger } from '@sentry/node-core'; +import { debug as coreDebug, SDK_VERSION } from '@sentry/core'; +import { type NodeClient, initializeEsmLoader, SentryContextManager, setupOpenTelemetryLogger } from '@sentry/node-core'; import { SentryPropagator, SentrySampler, SentrySpanProcessor } from '@sentry/opentelemetry'; -import { createAddHookMessageChannel } from 'import-in-the-middle'; -import moduleModule from 'module'; import { DEBUG_BUILD } from '../debug-build'; import { getOpenTelemetryInstrumentationToPreload } from '../integrations/tracing'; @@ -35,34 +33,6 @@ export function initOpenTelemetry(client: NodeClient, options: AdditionalOpenTel client.traceProvider = provider; } -/** Initialize the ESM loader. */ -export function maybeInitializeEsmLoader(): void { - const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number); - - // Register hook was added in v20.6.0 and v18.19.0 - if (nodeMajor >= 21 || (nodeMajor === 20 && nodeMinor >= 6) || (nodeMajor === 18 && nodeMinor >= 19)) { - if (!GLOBAL_OBJ._sentryEsmLoaderHookRegistered) { - try { - const { addHookMessagePort } = createAddHookMessageChannel(); - // @ts-expect-error register is available in these versions - moduleModule.register('import-in-the-middle/hook.mjs', import.meta.url, { - data: { addHookMessagePort, include: [] }, - transferList: [addHookMessagePort], - }); - } catch (error) { - coreDebug.warn('Failed to register ESM hook', error); - } - } - } else { - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn( - `[Sentry] You are using Node.js v${process.versions.node} in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or upgrade your Node.js version.`, - ); - }); - } -} - interface NodePreloadOptions { debug?: boolean; integrations?: string[]; @@ -80,9 +50,7 @@ export function preloadOpenTelemetry(options: NodePreloadOptions = {}): void { coreDebug.enable(); } - if (!isCjs()) { - maybeInitializeEsmLoader(); - } + initializeEsmLoader(); // These are all integrations that we need to pre-load to ensure they are set up before any other code runs getPreloadMethods(options.integrations).forEach(fn => { From 9ee96ab3c69ff94c940c6caaf3e503689ec08945 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 8 Sep 2025 23:21:39 +0200 Subject: [PATCH 02/24] Oh linty --- packages/node/src/sdk/initOtel.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/node/src/sdk/initOtel.ts b/packages/node/src/sdk/initOtel.ts index ab528b312a72..67de29821537 100644 --- a/packages/node/src/sdk/initOtel.ts +++ b/packages/node/src/sdk/initOtel.ts @@ -8,7 +8,12 @@ import { SEMRESATTRS_SERVICE_NAMESPACE, } from '@opentelemetry/semantic-conventions'; import { debug as coreDebug, SDK_VERSION } from '@sentry/core'; -import { type NodeClient, initializeEsmLoader, SentryContextManager, setupOpenTelemetryLogger } from '@sentry/node-core'; +import { + type NodeClient, + initializeEsmLoader, + SentryContextManager, + setupOpenTelemetryLogger, +} from '@sentry/node-core'; import { SentryPropagator, SentrySampler, SentrySpanProcessor } from '@sentry/opentelemetry'; import { DEBUG_BUILD } from '../debug-build'; import { getOpenTelemetryInstrumentationToPreload } from '../integrations/tracing'; From 9a576438c932acfc4df1078763cf91ed79091073 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 8 Sep 2025 23:39:21 +0200 Subject: [PATCH 03/24] fix detection --- packages/node-core/src/utils/detection.ts | 40 +++++++++-------------- 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/packages/node-core/src/utils/detection.ts b/packages/node-core/src/utils/detection.ts index fe649097d737..a4f2cdc19740 100644 --- a/packages/node-core/src/utils/detection.ts +++ b/packages/node-core/src/utils/detection.ts @@ -1,4 +1,3 @@ -import * as moduleModule from 'node:module'; import { consoleSandbox } from '@sentry/core'; /** Detect CommonJS. */ @@ -10,40 +9,31 @@ export function isCjs(): boolean { } } -/** - * Check if the current Node.js version supports module.register - */ -export function supportsEsmLoaderHooks(): boolean { - logIfUnsupportedNodeVersion(); - - return 'register' in moduleModule; -} - let hasWarnedAboutNodeVersion: boolean | undefined; /** - * Log a warning if the current Node.js version is not supported in ESM mode + * Check if the current Node.js version supports module.register */ -function logIfUnsupportedNodeVersion(): void { - if (hasWarnedAboutNodeVersion) { - return; - } - +export function supportsEsmLoaderHooks(): boolean { if (isCjs()) { - return; + return false; } const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number); if (nodeMajor >= 21 || (nodeMajor === 20 && nodeMinor >= 6) || (nodeMajor === 18 && nodeMinor >= 19)) { - return; + return true; } - hasWarnedAboutNodeVersion = true; + if (!hasWarnedAboutNodeVersion) { + hasWarnedAboutNodeVersion = true; + + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.warn( + `[Sentry] You are using Node.js v${process.versions.node} in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or upgrade your Node.js version.`, + ); + }); + } - consoleSandbox(() => { - // eslint-disable-next-line no-console - console.warn( - `[Sentry] You are using Node.js v${process.versions.node} in ESM mode ("import syntax"). The Sentry Node.js SDK is not compatible with ESM in Node.js versions before 18.19.0 or before 20.6.0. Please either build your application with CommonJS ("require() syntax"), or upgrade your Node.js version.`, - ); - }); + return false; } From 9fb250e1af5ab5044a7e8533c8bb17bd1596a149 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 9 Sep 2025 10:25:42 +0200 Subject: [PATCH 04/24] Use `NODE_MAJOR` and `NODE_MINOR` --- packages/node-core/src/utils/detection.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node-core/src/utils/detection.ts b/packages/node-core/src/utils/detection.ts index a4f2cdc19740..f7ae9a792c27 100644 --- a/packages/node-core/src/utils/detection.ts +++ b/packages/node-core/src/utils/detection.ts @@ -1,4 +1,5 @@ import { consoleSandbox } from '@sentry/core'; +import { NODE_MAJOR, NODE_MINOR } from '../nodeVersion'; /** Detect CommonJS. */ export function isCjs(): boolean { @@ -19,8 +20,7 @@ export function supportsEsmLoaderHooks(): boolean { return false; } - const [nodeMajor = 0, nodeMinor = 0] = process.versions.node.split('.').map(Number); - if (nodeMajor >= 21 || (nodeMajor === 20 && nodeMinor >= 6) || (nodeMajor === 18 && nodeMinor >= 19)) { + if (NODE_MAJOR >= 21 || (NODE_MAJOR === 20 && NODE_MINOR >= 6) || (NODE_MAJOR === 18 && NODE_MINOR >= 19)) { return true; } From 594286ce3b763298dc2b95927352cb9db55f3df0 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Wed, 10 Sep 2025 14:22:33 +0200 Subject: [PATCH 05/24] feat: `pino` integration --- .../node-integration-tests/package.json | 1 + .../suites/pino/instrument.mjs | 8 ++ .../suites/pino/scenario.mjs | 18 +++ .../suites/pino/test.ts | 97 +++++++++++++++ packages/core/src/utils/worldwide.ts | 1 + packages/node-core/package.json | 2 + packages/node-core/src/index.ts | 1 + packages/node-core/src/integrations/pino.ts | 117 ++++++++++++++++++ .../src/sdk/apm-js-collab-tracing-hooks.d.ts | 11 ++ packages/node-core/src/sdk/index.ts | 3 + packages/node-core/src/sdk/injectLoader.ts | 57 +++++++++ packages/node/src/index.ts | 1 + yarn.lock | 36 ++++++ 13 files changed, 353 insertions(+) create mode 100644 dev-packages/node-integration-tests/suites/pino/instrument.mjs create mode 100644 dev-packages/node-integration-tests/suites/pino/scenario.mjs create mode 100644 dev-packages/node-integration-tests/suites/pino/test.ts create mode 100644 packages/node-core/src/integrations/pino.ts create mode 100644 packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts create mode 100644 packages/node-core/src/sdk/injectLoader.ts diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 5f5c80094fc5..cdaff95f02d9 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -60,6 +60,7 @@ "node-schedule": "^2.1.1", "openai": "5.18.1", "pg": "8.16.0", + "pino": "^9.9.4", "postgres": "^3.4.7", "proxy": "^2.1.1", "redis-4": "npm:redis@^4.6.14", diff --git a/dev-packages/node-integration-tests/suites/pino/instrument.mjs b/dev-packages/node-integration-tests/suites/pino/instrument.mjs new file mode 100644 index 000000000000..f57a519a2331 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/pino/instrument.mjs @@ -0,0 +1,8 @@ +import * as Sentry from '@sentry/node'; + +Sentry.init({ + dsn: process.env.SENTRY_DSN, + release: '1.0', + enableLogs: true, + integrations: [Sentry.pinoIntegration()], +}); diff --git a/dev-packages/node-integration-tests/suites/pino/scenario.mjs b/dev-packages/node-integration-tests/suites/pino/scenario.mjs new file mode 100644 index 000000000000..3ff6c0b5e08d --- /dev/null +++ b/dev-packages/node-integration-tests/suites/pino/scenario.mjs @@ -0,0 +1,18 @@ +import * as Sentry from '@sentry/node'; +import pino from 'pino'; + +const logger = pino({}); + +Sentry.withIsolationScope(() => { + Sentry.startSpan({ name: 'startup' }, () => { + logger.info({ user: 'user-id', something: { more: 3, complex: 'nope' } }, 'hello world'); + }); +}); + +setTimeout(() => { + Sentry.withIsolationScope(() => { + Sentry.startSpan({ name: 'later' }, () => { + logger.error(new Error('oh no')); + }); + }); +}, 1000); diff --git a/dev-packages/node-integration-tests/suites/pino/test.ts b/dev-packages/node-integration-tests/suites/pino/test.ts new file mode 100644 index 000000000000..79ac28cfe47b --- /dev/null +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -0,0 +1,97 @@ +import { join } from 'path'; +import { describe, expect, test } from 'vitest'; +import { createRunner } from '../../utils/runner'; + +describe('Pino integration', () => { + test('has different trace ids for logs from different spans', async () => { + // expect.assertions(1); + const instrumentPath = join(__dirname, 'instrument.mjs'); + + await createRunner(__dirname, 'scenario.mjs') + .withMockSentryServer() + .withInstrument(instrumentPath) + .ignore('event') + .expect({ + log: log => { + const traceId1 = log.items?.[0]?.trace_id; + const traceId2 = log.items?.[1]?.trace_id; + expect(traceId1).not.toBe(traceId2); + }, + }) + .start() + .completed(); + }); + + test('captures event and logs', async () => { + // expect.assertions(1); + const instrumentPath = join(__dirname, 'instrument.mjs'); + + await createRunner(__dirname, 'scenario.mjs') + .withMockSentryServer() + .withInstrument(instrumentPath) + .expect({ + event: { + exception: { + values: [ + { + type: 'Error', + value: 'oh no', + mechanism: { + type: 'pino', + handled: true, + }, + stacktrace: { + frames: expect.arrayContaining([ + expect.objectContaining({ + function: '?', + in_app: true, + module: 'scenario', + context_line: " logger.error(new Error('oh no'));", + }), + ]), + }, + }, + ], + }, + }, + }) + .expect({ + log: { + items: [ + { + timestamp: expect.any(Number), + level: 'info', + body: 'hello world', + trace_id: expect.any(String), + severity_number: 9, + attributes: expect.objectContaining({ + 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, + 'sentry.pino.level': { value: 30, type: 'integer' }, + user: { value: 'user-id', type: 'string' }, + 'something.more': { value: 3, type: 'integer' }, + 'something.complex': { value: 'nope', type: 'string' }, + 'sentry.release': { value: '1.0', type: 'string' }, + 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, + }), + }, + { + timestamp: expect.any(Number), + level: 'error', + body: 'oh no', + trace_id: expect.any(String), + severity_number: 17, + attributes: expect.objectContaining({ + 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, + 'sentry.pino.level': { value: 50, type: 'integer' }, + err: { value: '{}', type: 'string' }, + 'sentry.release': { value: '1.0', type: 'string' }, + 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, + }), + }, + ], + }, + }) + .start() + .completed(); + }); +}); diff --git a/packages/core/src/utils/worldwide.ts b/packages/core/src/utils/worldwide.ts index e2f1ad5fc2b2..d040ad5f8fb4 100644 --- a/packages/core/src/utils/worldwide.ts +++ b/packages/core/src/utils/worldwide.ts @@ -48,6 +48,7 @@ export type InternalGlobal = { */ _sentryModuleMetadata?: Record; _sentryEsmLoaderHookRegistered?: boolean; + _sentryInjectLoaderHookRegistered?: boolean; } & Carrier; /** Get's the global object for the current JavaScript runtime */ diff --git a/packages/node-core/package.json b/packages/node-core/package.json index 695eea2c6626..bd180412c5ff 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -66,11 +66,13 @@ "@opentelemetry/semantic-conventions": "^1.34.0" }, "dependencies": { + "@apm-js-collab/tracing-hooks": "^0.1.1", "@sentry/core": "10.11.0", "@sentry/opentelemetry": "10.11.0", "import-in-the-middle": "^1.14.2" }, "devDependencies": { + "@apm-js-collab/code-transformer": "^0.7.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.0.0", "@opentelemetry/core": "^2.0.0", diff --git a/packages/node-core/src/index.ts b/packages/node-core/src/index.ts index 57d8e2ece0ae..c40fef0da7fa 100644 --- a/packages/node-core/src/index.ts +++ b/packages/node-core/src/index.ts @@ -24,6 +24,7 @@ export { spotlightIntegration } from './integrations/spotlight'; export { systemErrorIntegration } from './integrations/systemError'; export { childProcessIntegration } from './integrations/childProcess'; export { createSentryWinstonTransport } from './integrations/winston'; +export { pinoIntegration } from './integrations/pino'; export { SentryContextManager } from './otel/contextManager'; export { setupOpenTelemetryLogger } from './otel/logger'; diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts new file mode 100644 index 000000000000..854730977832 --- /dev/null +++ b/packages/node-core/src/integrations/pino.ts @@ -0,0 +1,117 @@ +import { tracingChannel } from 'node:diagnostics_channel'; +import type { IntegrationFn, LogSeverityLevel } from '@sentry/core'; +import { + _INTERNAL_captureLog, + addExceptionMechanism, + captureException, + captureMessage, + severityLevelFromString, + withScope, +} from '@sentry/core'; +import { addInstrumentationConfig } from '../sdk/injectLoader'; + +type LevelMapping = { + // Fortunately pino uses the same levels as Sentry + labels: { [level: number]: LogSeverityLevel }; +}; + +type Pino = { + levels: LevelMapping; +}; + +type MergeObject = { + [key: string]: unknown; + err?: Error; +}; + +type PinoHookArgs = { + self: Pino; + arguments: [MergeObject, string, number]; +}; + +type Options = { + /** + * Levels that trigger capturing of events. + * + * @default ["error", "fatal"] + */ + eventLevels?: LogSeverityLevel[]; + /** + * By default, Sentry will mark captured console messages as handled. + * Set this to `false` if you want to mark them as unhandled instead. + * + * @default true + */ + handled?: boolean; +}; + +function attributesFromObject(obj: object, attr: Record, key?: string): Record { + for (const [k, v] of Object.entries(obj)) { + const newKey = key ? `${key}.${k}` : k; + if (v && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Error)) { + attributesFromObject(v as object, attr, newKey); + } else { + attr[newKey] = v; + } + } + return attr; +} + +export const pinoIntegration = ((options: Options = { eventLevels: ['error', 'fatal'], handled: true }) => { + return { + name: 'Pino', + setup: () => { + addInstrumentationConfig({ + channelName: 'pino-log', + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore https://github.com/apm-js-collab/orchestrion-js/pull/35 + module: { name: 'pino', versionRange: '>=8.0.0', filePath: 'lib/tools.js' }, + functionQuery: { + functionName: 'asJson', + kind: 'Sync', + }, + }); + + const channel = tracingChannel('orchestrion:pino:pino-log'); + + channel.start.subscribe(data => { + const { self, arguments: args } = data as PinoHookArgs; + const [obj, message, levelNumber] = args; + const level = self?.levels?.labels?.[levelNumber] || 'info'; + + const attributes = attributesFromObject(obj, { + 'sentry.origin': 'auto.logging.pino', + 'sentry.pino.level': levelNumber, + }); + + _INTERNAL_captureLog({ level, message, attributes }); + + if (options.eventLevels?.includes(level)) { + const captureContext = { + level: severityLevelFromString(level), + }; + + withScope(scope => { + scope.addEventProcessor(event => { + event.logger = 'pino'; + + addExceptionMechanism(event, { + handled: !!options.handled, + type: 'pino', + }); + + return event; + }); + + if (obj.err) { + captureException(obj.err, captureContext); + return; + } + + captureMessage(message, captureContext); + }); + } + }); + }, + }; +}) satisfies IntegrationFn; diff --git a/packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts b/packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts new file mode 100644 index 000000000000..532bb0183133 --- /dev/null +++ b/packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts @@ -0,0 +1,11 @@ +declare module '@apm-js-collab/tracing-hooks' { + import type { InstrumentationConfig } from '@apm-js-collab/code-transformer'; + + type PatchConfig = { instrumentations: InstrumentationConfig[]; packages: Set }; + + /** Hooks require */ + export default class ModulePatch { + public constructor(config: PatchConfig): ModulePatch; + public patch(): void; + } +} diff --git a/packages/node-core/src/sdk/index.ts b/packages/node-core/src/sdk/index.ts index c4a16d76a1d0..cbc56b7ce218 100644 --- a/packages/node-core/src/sdk/index.ts +++ b/packages/node-core/src/sdk/index.ts @@ -40,6 +40,7 @@ import { envToBool } from '../utils/envToBool'; import { defaultStackParser, getSentryRelease } from './api'; import { NodeClient } from './client'; import { initializeEsmLoader } from './esmLoader'; +import { initializeInjectionLoader } from './injectLoader'; /** * Get default integrations for the Node-Core SDK. @@ -131,6 +132,8 @@ function _init( client.init(); + initializeInjectionLoader(); + debug.log(`SDK initialized from ${isCjs() ? 'CommonJS' : 'ESM'}`); client.startClientReportTracking(); diff --git a/packages/node-core/src/sdk/injectLoader.ts b/packages/node-core/src/sdk/injectLoader.ts new file mode 100644 index 000000000000..6c175a9dd4bf --- /dev/null +++ b/packages/node-core/src/sdk/injectLoader.ts @@ -0,0 +1,57 @@ +import type { InstrumentationConfig } from '@apm-js-collab/code-transformer'; +import ModulePatch from '@apm-js-collab/tracing-hooks'; +import { debug, GLOBAL_OBJ } from '@sentry/core'; +import * as moduleModule from 'module'; +import { supportsEsmLoaderHooks } from '../utils/detection'; + +let instrumentationConfigs: InstrumentationConfig[] | undefined; + +/** + * Add an instrumentation config to be used by the injection loader. + * + * This should be called before `initializeInjectionLoader` is called. + */ +export function addInstrumentationConfig(config: InstrumentationConfig): void { + if (!instrumentationConfigs) { + instrumentationConfigs = []; + } + + instrumentationConfigs.push(config); +} + +/** + * Initialize the injection loader - This method is private and not part of the public + * API. + * + * @ignore + */ +export function initializeInjectionLoader(): void { + if (!supportsEsmLoaderHooks()) { + return; + } + + if (!GLOBAL_OBJ._sentryInjectLoaderHookRegistered) { + GLOBAL_OBJ._sentryInjectLoaderHookRegistered = true; + + const instrumentations = instrumentationConfigs || []; + if (instrumentations.length === 0) { + return; + } + + const packages = new Set(instrumentations.map(i => i.module.name)); + + // Patch require to support CJS modules + const requirePatch = new ModulePatch({ instrumentations, packages }); + requirePatch.patch(); + + // Add ESM loader to support ESM modules + try { + // @ts-expect-error register is available in these versions + moduleModule.register('@apm-js-collab/tracing-hooks/hook.mjs', import.meta.url, { + data: { instrumentations, packages }, + }); + } catch (error) { + debug.warn("Failed to register '@apm-js-collab/tracing-hooks' hook", error); + } + } +} diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index f510ca733d19..8d98d8a57e17 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -166,6 +166,7 @@ export { disableAnrDetectionForCallback, spotlightIntegration, childProcessIntegration, + pinoIntegration, createSentryWinstonTransport, SentryContextManager, systemErrorIntegration, diff --git a/yarn.lock b/yarn.lock index db7641e5a892..ac9785a33b46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -328,6 +328,20 @@ resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-0.7.10.tgz#ae829f170158e297a9b6a28f161a8e487d00814d" integrity sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww== +"@apm-js-collab/code-transformer@^0.7.0": + version "0.7.0" + resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.7.0.tgz#ddf2a4b5f71679af38d3bd4693ed63d13965060b" + integrity sha512-ZusgjGSB1adwnhxNzyVPDpDqr3VcoGa+G6F/iRXihwmChA9/+hqMY4vOTSciAUJ+FS3poAH0VSC6K7D9fnyw0w== + +"@apm-js-collab/tracing-hooks@^0.1.1": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.1.1.tgz#73f86cd18827e6d6253eda5322a6feb5753cfc58" + integrity sha512-4s8fUde6La3n09m10Ol3zQXUaiDZyuzFKwvdEgbZVKT/XXqycFbqaKfut+UWOAs/yyiocK87MAC9C2JV51CpKg== + dependencies: + "@apm-js-collab/code-transformer" "^0.7.0" + debug "^4.4.1" + module-details-from-path "^1.0.4" + "@apollo/protobufjs@1.2.6": version "1.2.6" resolved "https://registry.yarnpkg.com/@apollo/protobufjs/-/protobufjs-1.2.6.tgz#d601e65211e06ae1432bf5993a1a0105f2862f27" @@ -22421,6 +22435,11 @@ module-details-from-path@^1.0.3: resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== +module-details-from-path@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.4.tgz#b662fdcd93f6c83d3f25289da0ce81c8d9685b94" + integrity sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w== + module-lookup-amd@^8.0.5: version "8.0.5" resolved "https://registry.yarnpkg.com/module-lookup-amd/-/module-lookup-amd-8.0.5.tgz#aaeea41979105b49339380ca3f7d573db78c32a5" @@ -24739,6 +24758,23 @@ pino@^9.0.0: sonic-boom "^4.0.1" thread-stream "^3.0.0" +pino@^9.9.4: + version "9.9.4" + resolved "https://registry.yarnpkg.com/pino/-/pino-9.9.4.tgz#21ed2c27cc177f797e3249c99d340f0bcd6b248e" + integrity sha512-d1XorUQ7sSKqVcYdXuEYs2h1LKxejSorMEJ76XoZ0pPDf8VzJMe7GlPXpMBZeQ9gE4ZPIp5uGD+5Nw7scxiigg== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.1.1" + on-exit-leak-free "^2.1.0" + pino-abstract-transport "^2.0.0" + pino-std-serializers "^7.0.0" + process-warning "^5.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.2.0" + safe-stable-stringify "^2.3.1" + sonic-boom "^4.0.1" + thread-stream "^3.0.0" + pirates@^4.0.1: version "4.0.6" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" From 630a8c7709210c9e8d00c2d9d9a61efe6774113f Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Fri, 12 Sep 2025 15:44:46 +0200 Subject: [PATCH 06/24] Update deps --- packages/node-core/package.json | 4 ++-- packages/node-core/src/integrations/pino.ts | 2 -- .../src/sdk/apm-js-collab-tracing-hooks.d.ts | 2 +- packages/node-core/src/sdk/injectLoader.ts | 6 ++---- yarn.lock | 14 +++++++++----- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/node-core/package.json b/packages/node-core/package.json index bd180412c5ff..8a0c370c516f 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -66,13 +66,13 @@ "@opentelemetry/semantic-conventions": "^1.34.0" }, "dependencies": { - "@apm-js-collab/tracing-hooks": "^0.1.1", + "@apm-js-collab/tracing-hooks": "^0.2.0", "@sentry/core": "10.11.0", "@sentry/opentelemetry": "10.11.0", "import-in-the-middle": "^1.14.2" }, "devDependencies": { - "@apm-js-collab/code-transformer": "^0.7.0", + "@apm-js-collab/code-transformer": "^0.7.2", "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.0.0", "@opentelemetry/core": "^2.0.0", diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index 854730977832..e3d2350f36e9 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -63,8 +63,6 @@ export const pinoIntegration = ((options: Options = { eventLevels: ['error', 'fa setup: () => { addInstrumentationConfig({ channelName: 'pino-log', - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore https://github.com/apm-js-collab/orchestrion-js/pull/35 module: { name: 'pino', versionRange: '>=8.0.0', filePath: 'lib/tools.js' }, functionQuery: { functionName: 'asJson', diff --git a/packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts b/packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts index 532bb0183133..a7e0f3b2177b 100644 --- a/packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts +++ b/packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts @@ -1,7 +1,7 @@ declare module '@apm-js-collab/tracing-hooks' { import type { InstrumentationConfig } from '@apm-js-collab/code-transformer'; - type PatchConfig = { instrumentations: InstrumentationConfig[]; packages: Set }; + type PatchConfig = { instrumentations: InstrumentationConfig[]; }; /** Hooks require */ export default class ModulePatch { diff --git a/packages/node-core/src/sdk/injectLoader.ts b/packages/node-core/src/sdk/injectLoader.ts index 6c175a9dd4bf..e88d9b6e90a7 100644 --- a/packages/node-core/src/sdk/injectLoader.ts +++ b/packages/node-core/src/sdk/injectLoader.ts @@ -38,17 +38,15 @@ export function initializeInjectionLoader(): void { return; } - const packages = new Set(instrumentations.map(i => i.module.name)); - // Patch require to support CJS modules - const requirePatch = new ModulePatch({ instrumentations, packages }); + const requirePatch = new ModulePatch({ instrumentations }); requirePatch.patch(); // Add ESM loader to support ESM modules try { // @ts-expect-error register is available in these versions moduleModule.register('@apm-js-collab/tracing-hooks/hook.mjs', import.meta.url, { - data: { instrumentations, packages }, + data: { instrumentations }, }); } catch (error) { debug.warn("Failed to register '@apm-js-collab/tracing-hooks' hook", error); diff --git a/yarn.lock b/yarn.lock index ac9785a33b46..a700c6e6e748 100644 --- a/yarn.lock +++ b/yarn.lock @@ -333,10 +333,15 @@ resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.7.0.tgz#ddf2a4b5f71679af38d3bd4693ed63d13965060b" integrity sha512-ZusgjGSB1adwnhxNzyVPDpDqr3VcoGa+G6F/iRXihwmChA9/+hqMY4vOTSciAUJ+FS3poAH0VSC6K7D9fnyw0w== -"@apm-js-collab/tracing-hooks@^0.1.1": - version "0.1.1" - resolved "https://registry.yarnpkg.com/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.1.1.tgz#73f86cd18827e6d6253eda5322a6feb5753cfc58" - integrity sha512-4s8fUde6La3n09m10Ol3zQXUaiDZyuzFKwvdEgbZVKT/XXqycFbqaKfut+UWOAs/yyiocK87MAC9C2JV51CpKg== +"@apm-js-collab/code-transformer@^0.7.2": + version "0.7.2" + resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.7.2.tgz#8c848f9d28f8389c92cdd08bcdae938bc681acb6" + integrity sha512-waZeIZFmPCf5/nD3O1gMHlHEPMRwgnaTfVTaDbkclg5OByy8IowR/dUDij5tC77VYH/ieXIujSx9vPFvhx35Fg== + +"@apm-js-collab/tracing-hooks@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.2.0.tgz#b34f0857055895400e1ff0c15be5fd98cc378530" + integrity sha512-tBTkPVXDRQa5wrH18UHqgGowHF33+v6B4LeixmaeBCpwOwnCjNs21lHZDlP8JAkvWLRBJ4O2zFLQnJk6B7IM+w== dependencies: "@apm-js-collab/code-transformer" "^0.7.0" debug "^4.4.1" @@ -28787,7 +28792,6 @@ stylus@0.59.0, stylus@^0.59.0: sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills: version "3.36.0" - uid fd682f6129e507c00bb4e6319cc5d6b767e36061 resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061" dependencies: "@jridgewell/gen-mapping" "^0.3.2" From c2f81e16f436d959f5f069896912d9dd67ffd58c Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Fri, 12 Sep 2025 15:54:00 +0200 Subject: [PATCH 07/24] Oh lint! --- packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts b/packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts index a7e0f3b2177b..c4ae4897678d 100644 --- a/packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts +++ b/packages/node-core/src/sdk/apm-js-collab-tracing-hooks.d.ts @@ -1,7 +1,7 @@ declare module '@apm-js-collab/tracing-hooks' { import type { InstrumentationConfig } from '@apm-js-collab/code-transformer'; - type PatchConfig = { instrumentations: InstrumentationConfig[]; }; + type PatchConfig = { instrumentations: InstrumentationConfig[] }; /** Hooks require */ export default class ModulePatch { From 850c19da4da6bac7c51b5fb56929bde403bb1318 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Fri, 12 Sep 2025 16:25:44 +0200 Subject: [PATCH 08/24] Correct yarn.lock --- yarn.lock | 33 +++------------------------------ 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/yarn.lock b/yarn.lock index 1836350141bb..ba5ead31da9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -328,12 +328,7 @@ resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-0.7.10.tgz#ae829f170158e297a9b6a28f161a8e487d00814d" integrity sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww== -"@apm-js-collab/code-transformer@^0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.7.0.tgz#ddf2a4b5f71679af38d3bd4693ed63d13965060b" - integrity sha512-ZusgjGSB1adwnhxNzyVPDpDqr3VcoGa+G6F/iRXihwmChA9/+hqMY4vOTSciAUJ+FS3poAH0VSC6K7D9fnyw0w== - -"@apm-js-collab/code-transformer@^0.7.2": +"@apm-js-collab/code-transformer@^0.7.0", "@apm-js-collab/code-transformer@^0.7.2": version "0.7.2" resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.7.2.tgz#8c848f9d28f8389c92cdd08bcdae938bc681acb6" integrity sha512-waZeIZFmPCf5/nD3O1gMHlHEPMRwgnaTfVTaDbkclg5OByy8IowR/dUDij5tC77VYH/ieXIujSx9vPFvhx35Fg== @@ -22479,12 +22474,7 @@ module-definition@^6.0.1: ast-module-types "^6.0.1" node-source-walk "^7.0.1" -module-details-from-path@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" - integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== - -module-details-from-path@^1.0.4: +module-details-from-path@^1.0.3, module-details-from-path@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.4.tgz#b662fdcd93f6c83d3f25289da0ce81c8d9685b94" integrity sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w== @@ -24790,24 +24780,7 @@ pino-std-serializers@^7.0.0: resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b" integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== -pino@^9.0.0: - version "9.7.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-9.7.0.tgz#ff7cd86eb3103ee620204dbd5ca6ffda8b53f645" - integrity sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.1.1" - on-exit-leak-free "^2.1.0" - pino-abstract-transport "^2.0.0" - pino-std-serializers "^7.0.0" - process-warning "^5.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.2.0" - safe-stable-stringify "^2.3.1" - sonic-boom "^4.0.1" - thread-stream "^3.0.0" - -pino@^9.9.4: +pino@^9.0.0, pino@^9.9.4: version "9.9.4" resolved "https://registry.yarnpkg.com/pino/-/pino-9.9.4.tgz#21ed2c27cc177f797e3249c99d340f0bcd6b248e" integrity sha512-d1XorUQ7sSKqVcYdXuEYs2h1LKxejSorMEJ76XoZ0pPDf8VzJMe7GlPXpMBZeQ9gE4ZPIp5uGD+5Nw7scxiigg== From 7610cdab1e9f22c94c5aae7dfd612c5a5f37b685 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 15 Sep 2025 12:43:19 +0200 Subject: [PATCH 09/24] Tests --- .../node-exports-test-app/scripts/consistentExports.ts | 1 + dev-packages/node-integration-tests/suites/pino/test.ts | 7 ++++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts index 596109c0a596..17c6f714c499 100644 --- a/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts +++ b/dev-packages/e2e-tests/test-applications/node-exports-test-app/scripts/consistentExports.ts @@ -53,6 +53,7 @@ const DEPENDENTS: Dependent[] = [ 'NODE_VERSION', 'childProcessIntegration', 'systemErrorIntegration', + 'pinoIntegration', ], }, { diff --git a/dev-packages/node-integration-tests/suites/pino/test.ts b/dev-packages/node-integration-tests/suites/pino/test.ts index 79ac28cfe47b..e8a2b17974ad 100644 --- a/dev-packages/node-integration-tests/suites/pino/test.ts +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -1,9 +1,10 @@ import { join } from 'path'; -import { describe, expect, test } from 'vitest'; +import { describe, expect } from 'vitest'; +import { conditionalTest } from '../../utils'; import { createRunner } from '../../utils/runner'; describe('Pino integration', () => { - test('has different trace ids for logs from different spans', async () => { + conditionalTest({ min: 20 })('has different trace ids for logs from different spans', async () => { // expect.assertions(1); const instrumentPath = join(__dirname, 'instrument.mjs'); @@ -22,7 +23,7 @@ describe('Pino integration', () => { .completed(); }); - test('captures event and logs', async () => { + conditionalTest({ min: 20 })('captures event and logs', async () => { // expect.assertions(1); const instrumentPath = join(__dirname, 'instrument.mjs'); From 2647b20929ba61dee215a776afcb9199fa988385 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 15 Sep 2025 14:34:19 +0200 Subject: [PATCH 10/24] More test fixes --- dev-packages/node-integration-tests/suites/pino/test.ts | 8 ++++---- packages/astro/src/index.server.ts | 1 + packages/aws-serverless/src/index.ts | 1 + packages/google-cloud-serverless/src/index.ts | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/pino/test.ts b/dev-packages/node-integration-tests/suites/pino/test.ts index e8a2b17974ad..1efc16094fc3 100644 --- a/dev-packages/node-integration-tests/suites/pino/test.ts +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -1,10 +1,10 @@ import { join } from 'path'; -import { describe, expect } from 'vitest'; +import { expect, test } from 'vitest'; import { conditionalTest } from '../../utils'; import { createRunner } from '../../utils/runner'; -describe('Pino integration', () => { - conditionalTest({ min: 20 })('has different trace ids for logs from different spans', async () => { +conditionalTest({ min: 20 })('Pino integration', () => { + test('has different trace ids for logs from different spans', async () => { // expect.assertions(1); const instrumentPath = join(__dirname, 'instrument.mjs'); @@ -23,7 +23,7 @@ describe('Pino integration', () => { .completed(); }); - conditionalTest({ min: 20 })('captures event and logs', async () => { + test('captures event and logs', async () => { // expect.assertions(1); const instrumentPath = join(__dirname, 'instrument.mjs'); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 5abf8d51633d..fa30eebfb553 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -90,6 +90,7 @@ export { onUnhandledRejectionIntegration, openAIIntegration, parameterize, + pinoIntegration, postgresIntegration, postgresJsIntegration, prismaIntegration, diff --git a/packages/aws-serverless/src/index.ts b/packages/aws-serverless/src/index.ts index 541f8a97a410..21d495255bd3 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -104,6 +104,7 @@ export { mysql2Integration, redisIntegration, tediousIntegration, + pinoIntegration, postgresIntegration, postgresJsIntegration, prismaIntegration, diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index e8042e4260a8..1f54dfa1df9a 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -104,6 +104,7 @@ export { mysql2Integration, redisIntegration, tediousIntegration, + pinoIntegration, postgresIntegration, postgresJsIntegration, prismaIntegration, From 60893899b0ce85c910e7845dec9a46c720e5e2bc Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Mon, 15 Sep 2025 18:14:07 +0200 Subject: [PATCH 11/24] Fix size limit --- .size-limit.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 32d5d19e1495..7ebfd8cdeb74 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -224,7 +224,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '51 KB', + limit: '55 KB', }, // Node SDK (ESM) { @@ -233,14 +233,14 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '156 KB', + limit: '160 KB', }, { name: '@sentry/node - without tracing', path: 'packages/node/build/esm/index.js', import: createImport('initWithoutDefaultIntegrations', 'getDefaultIntegrationsWithoutPerformance'), gzip: true, - limit: '95 KB', + limit: '100 KB', ignore: [...builtinModules, ...nodePrefixedBuiltinModules], modifyWebpackConfig: function (config) { const webpack = require('webpack'); From 421ad5030760736ead1d4f6b569bb7cdf2973d03 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 18 Sep 2025 15:19:14 +0200 Subject: [PATCH 12/24] Make hook tree-shakable --- packages/node-core/src/sdk/injectLoader.ts | 52 +++++++++++++--------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/packages/node-core/src/sdk/injectLoader.ts b/packages/node-core/src/sdk/injectLoader.ts index e88d9b6e90a7..355a6462d37b 100644 --- a/packages/node-core/src/sdk/injectLoader.ts +++ b/packages/node-core/src/sdk/injectLoader.ts @@ -5,6 +5,32 @@ import * as moduleModule from 'module'; import { supportsEsmLoaderHooks } from '../utils/detection'; let instrumentationConfigs: InstrumentationConfig[] | undefined; +let setupHookFn: (() => void) | undefined; + +function setupHook(): void { + if (!GLOBAL_OBJ._sentryInjectLoaderHookRegistered) { + GLOBAL_OBJ._sentryInjectLoaderHookRegistered = true; + + const instrumentations = instrumentationConfigs || []; + if (instrumentations.length === 0) { + return; + } + + // Patch require to support CJS modules + const requirePatch = new ModulePatch({ instrumentations }); + requirePatch.patch(); + + // Add ESM loader to support ESM modules + try { + // @ts-expect-error register is available in these versions + moduleModule.register('@apm-js-collab/tracing-hooks/hook.mjs', import.meta.url, { + data: { instrumentations }, + }); + } catch (error) { + debug.warn("Failed to register '@apm-js-collab/tracing-hooks' hook", error); + } + } +} /** * Add an instrumentation config to be used by the injection loader. @@ -17,6 +43,8 @@ export function addInstrumentationConfig(config: InstrumentationConfig): void { } instrumentationConfigs.push(config); + + setupHookFn = setupHook; } /** @@ -30,26 +58,8 @@ export function initializeInjectionLoader(): void { return; } - if (!GLOBAL_OBJ._sentryInjectLoaderHookRegistered) { - GLOBAL_OBJ._sentryInjectLoaderHookRegistered = true; - - const instrumentations = instrumentationConfigs || []; - if (instrumentations.length === 0) { - return; - } - - // Patch require to support CJS modules - const requirePatch = new ModulePatch({ instrumentations }); - requirePatch.patch(); - - // Add ESM loader to support ESM modules - try { - // @ts-expect-error register is available in these versions - moduleModule.register('@apm-js-collab/tracing-hooks/hook.mjs', import.meta.url, { - data: { instrumentations }, - }); - } catch (error) { - debug.warn("Failed to register '@apm-js-collab/tracing-hooks' hook", error); - } + if (setupHookFn) { + setupHookFn(); + setupHookFn = undefined; } } From b12150c9cf725542f023b80398a1435556672d75 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 18 Sep 2025 15:29:18 +0200 Subject: [PATCH 13/24] Pino added tracing channel --- packages/node-core/src/integrations/pino.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index e3d2350f36e9..8262e2babfde 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -63,16 +63,19 @@ export const pinoIntegration = ((options: Options = { eventLevels: ['error', 'fa setup: () => { addInstrumentationConfig({ channelName: 'pino-log', - module: { name: 'pino', versionRange: '>=8.0.0', filePath: 'lib/tools.js' }, + module: { name: 'pino', versionRange: '>=8.0.0 < 9.19.0', filePath: 'lib/tools.js' }, functionQuery: { functionName: 'asJson', kind: 'Sync', }, }); - const channel = tracingChannel('orchestrion:pino:pino-log'); + const injectedChannel = tracingChannel('orchestrion:pino:pino-log'); + // From Pino v9.19.0 a tracing channel is available directly from Pino: + // https://github.com/pinojs/pino/pull/2281 + const integratedChannel = tracingChannel('pino_asJson'); - channel.start.subscribe(data => { + const onPinoStart = (data: unknown): void => { const { self, arguments: args } = data as PinoHookArgs; const [obj, message, levelNumber] = args; const level = self?.levels?.labels?.[levelNumber] || 'info'; @@ -109,7 +112,10 @@ export const pinoIntegration = ((options: Options = { eventLevels: ['error', 'fa captureMessage(message, captureContext); }); } - }); + }; + + injectedChannel.start.subscribe(onPinoStart); + integratedChannel.start.subscribe(onPinoStart); }, }; }) satisfies IntegrationFn; From 30a668853acc614653a9bbcee9f2b2566fb3bfa1 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 18 Sep 2025 16:04:40 +0200 Subject: [PATCH 14/24] Better bundling? --- .../node-integration-tests/package.json | 2 +- packages/core/src/utils/worldwide.ts | 1 + packages/node-core/src/integrations/pino.ts | 6 +- packages/node-core/src/sdk/index.ts | 4 +- packages/node-core/src/sdk/injectLoader.ts | 56 +++++++------------ 5 files changed, 27 insertions(+), 42 deletions(-) diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index d767ab001b8d..787c7850a736 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -62,7 +62,7 @@ "node-schedule": "^2.1.1", "openai": "5.18.1", "pg": "8.16.0", - "pino": "^9.9.4", + "pino": "9.9.4", "postgres": "^3.4.7", "prisma": "6.15.0", "proxy": "^2.1.1", diff --git a/packages/core/src/utils/worldwide.ts b/packages/core/src/utils/worldwide.ts index d040ad5f8fb4..2eb7f39f3a24 100644 --- a/packages/core/src/utils/worldwide.ts +++ b/packages/core/src/utils/worldwide.ts @@ -48,6 +48,7 @@ export type InternalGlobal = { */ _sentryModuleMetadata?: Record; _sentryEsmLoaderHookRegistered?: boolean; + _sentryInjectLoaderHookRegister?: () => void; _sentryInjectLoaderHookRegistered?: boolean; } & Carrier; diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index 8262e2babfde..ff32ac80f3d2 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -63,7 +63,9 @@ export const pinoIntegration = ((options: Options = { eventLevels: ['error', 'fa setup: () => { addInstrumentationConfig({ channelName: 'pino-log', - module: { name: 'pino', versionRange: '>=8.0.0 < 9.19.0', filePath: 'lib/tools.js' }, + // From Pino v9.10.0 a tracing channel is available directly from Pino: + // https://github.com/pinojs/pino/pull/2281 + module: { name: 'pino', versionRange: '>=8.0.0 < 9.10.0', filePath: 'lib/tools.js' }, functionQuery: { functionName: 'asJson', kind: 'Sync', @@ -71,8 +73,6 @@ export const pinoIntegration = ((options: Options = { eventLevels: ['error', 'fa }); const injectedChannel = tracingChannel('orchestrion:pino:pino-log'); - // From Pino v9.19.0 a tracing channel is available directly from Pino: - // https://github.com/pinojs/pino/pull/2281 const integratedChannel = tracingChannel('pino_asJson'); const onPinoStart = (data: unknown): void => { diff --git a/packages/node-core/src/sdk/index.ts b/packages/node-core/src/sdk/index.ts index cbc56b7ce218..d53f5d4faefb 100644 --- a/packages/node-core/src/sdk/index.ts +++ b/packages/node-core/src/sdk/index.ts @@ -7,6 +7,7 @@ import { functionToStringIntegration, getCurrentScope, getIntegrationsToSetup, + GLOBAL_OBJ, hasSpansEnabled, inboundFiltersIntegration, linkedErrorsIntegration, @@ -40,7 +41,6 @@ import { envToBool } from '../utils/envToBool'; import { defaultStackParser, getSentryRelease } from './api'; import { NodeClient } from './client'; import { initializeEsmLoader } from './esmLoader'; -import { initializeInjectionLoader } from './injectLoader'; /** * Get default integrations for the Node-Core SDK. @@ -132,7 +132,7 @@ function _init( client.init(); - initializeInjectionLoader(); + GLOBAL_OBJ._sentryInjectLoaderHookRegister?.(); debug.log(`SDK initialized from ${isCjs() ? 'CommonJS' : 'ESM'}`); diff --git a/packages/node-core/src/sdk/injectLoader.ts b/packages/node-core/src/sdk/injectLoader.ts index 355a6462d37b..e82adcc3b395 100644 --- a/packages/node-core/src/sdk/injectLoader.ts +++ b/packages/node-core/src/sdk/injectLoader.ts @@ -5,10 +5,26 @@ import * as moduleModule from 'module'; import { supportsEsmLoaderHooks } from '../utils/detection'; let instrumentationConfigs: InstrumentationConfig[] | undefined; -let setupHookFn: (() => void) | undefined; -function setupHook(): void { - if (!GLOBAL_OBJ._sentryInjectLoaderHookRegistered) { +/** + * Add an instrumentation config to be used by the injection loader. + */ +export function addInstrumentationConfig(config: InstrumentationConfig): void { + if (!supportsEsmLoaderHooks()) { + return; + } + + if (!instrumentationConfigs) { + instrumentationConfigs = []; + } + + instrumentationConfigs.push(config); + + GLOBAL_OBJ._sentryInjectLoaderHookRegister = () => { + if (GLOBAL_OBJ._sentryInjectLoaderHookRegistered) { + return; + } + GLOBAL_OBJ._sentryInjectLoaderHookRegistered = true; const instrumentations = instrumentationConfigs || []; @@ -29,37 +45,5 @@ function setupHook(): void { } catch (error) { debug.warn("Failed to register '@apm-js-collab/tracing-hooks' hook", error); } - } -} - -/** - * Add an instrumentation config to be used by the injection loader. - * - * This should be called before `initializeInjectionLoader` is called. - */ -export function addInstrumentationConfig(config: InstrumentationConfig): void { - if (!instrumentationConfigs) { - instrumentationConfigs = []; - } - - instrumentationConfigs.push(config); - - setupHookFn = setupHook; -} - -/** - * Initialize the injection loader - This method is private and not part of the public - * API. - * - * @ignore - */ -export function initializeInjectionLoader(): void { - if (!supportsEsmLoaderHooks()) { - return; - } - - if (setupHookFn) { - setupHookFn(); - setupHookFn = undefined; - } + }; } From 412cc67d38b6f4c60a43c1236cb39b31b58fe88a Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 18 Sep 2025 16:54:39 +0200 Subject: [PATCH 15/24] Fixes --- .size-limit.js | 6 +++--- packages/node-core/src/sdk/injectLoader.ts | 3 --- yarn.lock | 3 +-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/.size-limit.js b/.size-limit.js index 7ebfd8cdeb74..32d5d19e1495 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -224,7 +224,7 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '55 KB', + limit: '51 KB', }, // Node SDK (ESM) { @@ -233,14 +233,14 @@ module.exports = [ import: createImport('init'), ignore: [...builtinModules, ...nodePrefixedBuiltinModules], gzip: true, - limit: '160 KB', + limit: '156 KB', }, { name: '@sentry/node - without tracing', path: 'packages/node/build/esm/index.js', import: createImport('initWithoutDefaultIntegrations', 'getDefaultIntegrationsWithoutPerformance'), gzip: true, - limit: '100 KB', + limit: '95 KB', ignore: [...builtinModules, ...nodePrefixedBuiltinModules], modifyWebpackConfig: function (config) { const webpack = require('webpack'); diff --git a/packages/node-core/src/sdk/injectLoader.ts b/packages/node-core/src/sdk/injectLoader.ts index e82adcc3b395..667996ebbe53 100644 --- a/packages/node-core/src/sdk/injectLoader.ts +++ b/packages/node-core/src/sdk/injectLoader.ts @@ -28,9 +28,6 @@ export function addInstrumentationConfig(config: InstrumentationConfig): void { GLOBAL_OBJ._sentryInjectLoaderHookRegistered = true; const instrumentations = instrumentationConfigs || []; - if (instrumentations.length === 0) { - return; - } // Patch require to support CJS modules const requirePatch = new ModulePatch({ instrumentations }); diff --git a/yarn.lock b/yarn.lock index 4f21a2a62647..847de1ef3387 100644 --- a/yarn.lock +++ b/yarn.lock @@ -24742,7 +24742,7 @@ pino-std-serializers@^7.0.0: resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b" integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== -pino@^9.0.0, pino@^9.9.4: +pino@9.9.4, pino@^9.0.0: version "9.9.4" resolved "https://registry.yarnpkg.com/pino/-/pino-9.9.4.tgz#21ed2c27cc177f797e3249c99d340f0bcd6b248e" integrity sha512-d1XorUQ7sSKqVcYdXuEYs2h1LKxejSorMEJ76XoZ0pPDf8VzJMe7GlPXpMBZeQ9gE4ZPIp5uGD+5Nw7scxiigg== @@ -28784,7 +28784,6 @@ stylus@0.59.0, stylus@^0.59.0: sucrase@^3.27.0, sucrase@^3.35.0, sucrase@getsentry/sucrase#es2020-polyfills: version "3.36.0" - uid fd682f6129e507c00bb4e6319cc5d6b767e36061 resolved "https://codeload.github.com/getsentry/sucrase/tar.gz/fd682f6129e507c00bb4e6319cc5d6b767e36061" dependencies: "@jridgewell/gen-mapping" "^0.3.2" From 2e14fbe1b7ae05c525149fca2d68416f940779fe Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 18 Sep 2025 17:39:40 +0200 Subject: [PATCH 16/24] Fix Pino >= 9.10.0 --- packages/node-core/src/integrations/pino.ts | 36 +++++++++++++-------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index ff32ac80f3d2..d68cadae4f0a 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -5,6 +5,7 @@ import { addExceptionMechanism, captureException, captureMessage, + defineIntegration, severityLevelFromString, withScope, } from '@sentry/core'; @@ -24,10 +25,7 @@ type MergeObject = { err?: Error; }; -type PinoHookArgs = { - self: Pino; - arguments: [MergeObject, string, number]; -}; +type PinoHookArgs = [MergeObject, string, number]; type Options = { /** @@ -57,10 +55,14 @@ function attributesFromObject(obj: object, attr: Record, key?: return attr; } -export const pinoIntegration = ((options: Options = { eventLevels: ['error', 'fatal'], handled: true }) => { +const DEFAULT_OPTIONS: Options = { eventLevels: ['error', 'fatal'], handled: true }; + +export const pinoIntegration = defineIntegration((options: Options = DEFAULT_OPTIONS) => { return { name: 'Pino', - setup: () => { + setup: client => { + const enableLogs = !!client.getOptions().enableLogs; + addInstrumentationConfig({ channelName: 'pino-log', // From Pino v9.10.0 a tracing channel is available directly from Pino: @@ -73,10 +75,9 @@ export const pinoIntegration = ((options: Options = { eventLevels: ['error', 'fa }); const injectedChannel = tracingChannel('orchestrion:pino:pino-log'); - const integratedChannel = tracingChannel('pino_asJson'); + const integratedChannel = tracingChannel('tracing:pino_asJson'); - const onPinoStart = (data: unknown): void => { - const { self, arguments: args } = data as PinoHookArgs; + function onPinoStart(self: Pino, args: PinoHookArgs): void { const [obj, message, levelNumber] = args; const level = self?.levels?.labels?.[levelNumber] || 'info'; @@ -85,7 +86,9 @@ export const pinoIntegration = ((options: Options = { eventLevels: ['error', 'fa 'sentry.pino.level': levelNumber, }); - _INTERNAL_captureLog({ level, message, attributes }); + if (enableLogs) { + _INTERNAL_captureLog({ level, message, attributes }); + } if (options.eventLevels?.includes(level)) { const captureContext = { @@ -112,10 +115,17 @@ export const pinoIntegration = ((options: Options = { eventLevels: ['error', 'fa captureMessage(message, captureContext); }); } - }; + } - injectedChannel.start.subscribe(onPinoStart); - integratedChannel.start.subscribe(onPinoStart); + injectedChannel.start.subscribe(data => { + const { self, arguments: args } = data as { self: Pino; arguments: PinoHookArgs }; + onPinoStart(self, args); + }); + + integratedChannel.start.subscribe(data => { + const { instance, arguments: args } = data as { instance: Pino; arguments: PinoHookArgs }; + onPinoStart(instance, args); + }); }, }; }) satisfies IntegrationFn; From 1115467c4e2e454699e2e8f96a5a061a84a555ff Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 18 Sep 2025 18:19:21 +0200 Subject: [PATCH 17/24] Lint --- packages/node-core/src/integrations/pino.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index d68cadae4f0a..507953cc6978 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -57,6 +57,10 @@ function attributesFromObject(obj: object, attr: Record, key?: const DEFAULT_OPTIONS: Options = { eventLevels: ['error', 'fatal'], handled: true }; +/** + * Integration for Pino logging library. + * Captures Pino logs as Sentry logs and optionally captures some log levels as events. + */ export const pinoIntegration = defineIntegration((options: Options = DEFAULT_OPTIONS) => { return { name: 'Pino', From 8b5a8218429c88312b7f48d4c29844317b4f0ee8 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Fri, 19 Sep 2025 13:18:44 +0200 Subject: [PATCH 18/24] Fix yarn.lock --- yarn.lock | 26 ++------------------------ 1 file changed, 2 insertions(+), 24 deletions(-) diff --git a/yarn.lock b/yarn.lock index 448252f4d1fd..d90fc4c19915 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22504,12 +22504,7 @@ module-definition@^6.0.1: ast-module-types "^6.0.1" node-source-walk "^7.0.1" -module-details-from-path@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" - integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== - -module-details-from-path@^1.0.4: +module-details-from-path@^1.0.3, module-details-from-path@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.4.tgz#b662fdcd93f6c83d3f25289da0ce81c8d9685b94" integrity sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w== @@ -24815,7 +24810,7 @@ pino-std-serializers@^7.0.0: resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-7.0.0.tgz#7c625038b13718dbbd84ab446bd673dc52259e3b" integrity sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA== -pino@9.9.4: +pino@9.9.4, pino@^9.0.0: version "9.9.4" resolved "https://registry.yarnpkg.com/pino/-/pino-9.9.4.tgz#21ed2c27cc177f797e3249c99d340f0bcd6b248e" integrity sha512-d1XorUQ7sSKqVcYdXuEYs2h1LKxejSorMEJ76XoZ0pPDf8VzJMe7GlPXpMBZeQ9gE4ZPIp5uGD+5Nw7scxiigg== @@ -24832,23 +24827,6 @@ pino@9.9.4: sonic-boom "^4.0.1" thread-stream "^3.0.0" -pino@^9.0.0: - version "9.7.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-9.7.0.tgz#ff7cd86eb3103ee620204dbd5ca6ffda8b53f645" - integrity sha512-vnMCM6xZTb1WDmLvtG2lE/2p+t9hDEIvTWJsu6FejkE62vB7gDhvzrpFR4Cw2to+9JNQxVnkAKVPA1KPB98vWg== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.1.1" - on-exit-leak-free "^2.1.0" - pino-abstract-transport "^2.0.0" - pino-std-serializers "^7.0.0" - process-warning "^5.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.2.0" - safe-stable-stringify "^2.3.1" - sonic-boom "^4.0.1" - thread-stream "^3.0.0" - pirates@^4.0.1: version "4.0.6" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" From 0715f388129fbf4666ac9e973898ee165f882f09 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Fri, 19 Sep 2025 20:50:17 +0200 Subject: [PATCH 19/24] Update deps --- packages/node-core/package.json | 4 ++-- yarn.lock | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/node-core/package.json b/packages/node-core/package.json index 0011766ef7f1..a05932da48b5 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -66,13 +66,13 @@ "@opentelemetry/semantic-conventions": "^1.37.0" }, "dependencies": { - "@apm-js-collab/tracing-hooks": "^0.2.0", + "@apm-js-collab/tracing-hooks": "^0.3.0", "@sentry/core": "10.12.0", "@sentry/opentelemetry": "10.12.0", "import-in-the-middle": "^1.14.2" }, "devDependencies": { - "@apm-js-collab/code-transformer": "^0.7.2", + "@apm-js-collab/code-transformer": "^0.8.0", "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.1.0", "@opentelemetry/core": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index d90fc4c19915..29eaecfc5321 100644 --- a/yarn.lock +++ b/yarn.lock @@ -335,17 +335,17 @@ dependencies: json-schema-to-ts "^3.1.1" -"@apm-js-collab/code-transformer@^0.7.0", "@apm-js-collab/code-transformer@^0.7.2": - version "0.7.2" - resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.7.2.tgz#8c848f9d28f8389c92cdd08bcdae938bc681acb6" - integrity sha512-waZeIZFmPCf5/nD3O1gMHlHEPMRwgnaTfVTaDbkclg5OByy8IowR/dUDij5tC77VYH/ieXIujSx9vPFvhx35Fg== +"@apm-js-collab/code-transformer@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.8.0.tgz#383bffab5131f0346e90665cebd36f64a51ed44e" + integrity sha512-4nueg5W0cZ6HdLDOD1xVCdPqhj17Rur6RQjE1Zv3Fzp4TJJU9v6rqlWtzpq9uudq2k05VWc22P000wX+c9lVaA== -"@apm-js-collab/tracing-hooks@^0.2.0": - version "0.2.0" - resolved "https://registry.yarnpkg.com/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.2.0.tgz#b34f0857055895400e1ff0c15be5fd98cc378530" - integrity sha512-tBTkPVXDRQa5wrH18UHqgGowHF33+v6B4LeixmaeBCpwOwnCjNs21lHZDlP8JAkvWLRBJ4O2zFLQnJk6B7IM+w== +"@apm-js-collab/tracing-hooks@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.3.0.tgz#78522bdef95385b0fd31bdf9b2575947e88884f9" + integrity sha512-YXyqZLr747VyFDd1g4UNBZlh1SSJfz8jMciVS46b+m5xb9Lnof75X5z8o7BeQfUudhhR6vtMCR9zKnBaPsmBDA== dependencies: - "@apm-js-collab/code-transformer" "^0.7.0" + "@apm-js-collab/code-transformer" "^0.8.0" debug "^4.4.1" module-details-from-path "^1.0.4" From 1a72eab28d40ca59945aa59499d045d39f3cebbd Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Sat, 20 Sep 2025 01:47:36 +0200 Subject: [PATCH 20/24] Just copy attributes --- .../suites/pino/test.ts | 6 ++++-- packages/node-core/src/integrations/pino.ts | 19 +++++-------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/pino/test.ts b/dev-packages/node-integration-tests/suites/pino/test.ts index 1efc16094fc3..418921a38cfb 100644 --- a/dev-packages/node-integration-tests/suites/pino/test.ts +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -69,8 +69,10 @@ conditionalTest({ min: 20 })('Pino integration', () => { 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, 'sentry.pino.level': { value: 30, type: 'integer' }, user: { value: 'user-id', type: 'string' }, - 'something.more': { value: 3, type: 'integer' }, - 'something.complex': { value: 'nope', type: 'string' }, + something: { + type: 'string', + value: '{"more":3,"complex":"nope"}', + }, 'sentry.release': { value: '1.0', type: 'string' }, 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, }), diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index 507953cc6978..d9e6dfb8f647 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -43,23 +43,13 @@ type Options = { handled?: boolean; }; -function attributesFromObject(obj: object, attr: Record, key?: string): Record { - for (const [k, v] of Object.entries(obj)) { - const newKey = key ? `${key}.${k}` : k; - if (v && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Error)) { - attributesFromObject(v as object, attr, newKey); - } else { - attr[newKey] = v; - } - } - return attr; -} - const DEFAULT_OPTIONS: Options = { eventLevels: ['error', 'fatal'], handled: true }; /** * Integration for Pino logging library. * Captures Pino logs as Sentry logs and optionally captures some log levels as events. + * + * Requires Pino >=v8.0.0 and Node >=20.6.0 or >=18.19.0 */ export const pinoIntegration = defineIntegration((options: Options = DEFAULT_OPTIONS) => { return { @@ -85,10 +75,11 @@ export const pinoIntegration = defineIntegration((options: Options = DEFAULT_OPT const [obj, message, levelNumber] = args; const level = self?.levels?.labels?.[levelNumber] || 'info'; - const attributes = attributesFromObject(obj, { + const attributes = { 'sentry.origin': 'auto.logging.pino', 'sentry.pino.level': levelNumber, - }); + ...obj, + }; if (enableLogs) { _INTERNAL_captureLog({ level, message, attributes }); From ac49a646412ce142576ff361274e4c7e90846563 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Sun, 21 Sep 2025 15:34:53 +0200 Subject: [PATCH 21/24] PR review --- .../suites/pino/instrument.mjs | 2 +- .../suites/pino/test.ts | 1 - packages/node-core/src/integrations/pino.ts | 63 +++++++++++++------ 3 files changed, 44 insertions(+), 22 deletions(-) diff --git a/dev-packages/node-integration-tests/suites/pino/instrument.mjs b/dev-packages/node-integration-tests/suites/pino/instrument.mjs index f57a519a2331..2c09097de1f4 100644 --- a/dev-packages/node-integration-tests/suites/pino/instrument.mjs +++ b/dev-packages/node-integration-tests/suites/pino/instrument.mjs @@ -4,5 +4,5 @@ Sentry.init({ dsn: process.env.SENTRY_DSN, release: '1.0', enableLogs: true, - integrations: [Sentry.pinoIntegration()], + integrations: [Sentry.pinoIntegration({ error: { levels: ['error', 'fatal'] } })], }); diff --git a/dev-packages/node-integration-tests/suites/pino/test.ts b/dev-packages/node-integration-tests/suites/pino/test.ts index 418921a38cfb..d6a4a7fc7167 100644 --- a/dev-packages/node-integration-tests/suites/pino/test.ts +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -5,7 +5,6 @@ import { createRunner } from '../../utils/runner'; conditionalTest({ min: 20 })('Pino integration', () => { test('has different trace ids for logs from different spans', async () => { - // expect.assertions(1); const instrumentPath = join(__dirname, 'instrument.mjs'); await createRunner(__dirname, 'scenario.mjs') diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index d9e6dfb8f647..80cd6e34dfcd 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -27,23 +27,41 @@ type MergeObject = { type PinoHookArgs = [MergeObject, string, number]; -type Options = { - /** - * Levels that trigger capturing of events. - * - * @default ["error", "fatal"] - */ - eventLevels?: LogSeverityLevel[]; - /** - * By default, Sentry will mark captured console messages as handled. - * Set this to `false` if you want to mark them as unhandled instead. - * - * @default true - */ - handled?: boolean; +type PinoOptions = { + error: { + /** + * Levels that trigger capturing of events. + * + * @default [] + */ + levels: LogSeverityLevel[]; + /** + * By default, Sentry will mark captured errors as handled. + * Set this to `false` if you want to mark them as unhandled instead. + * + * @default true + */ + handled: boolean; + }; + log: { + /** + * Levels that trigger capturing of logs. Logs are only captured if + * `enableLogs` is enabled. + * + * @default ["trace", "debug", "info", "warn", "error", "fatal"] + */ + levels: LogSeverityLevel[]; + }; }; -const DEFAULT_OPTIONS: Options = { eventLevels: ['error', 'fatal'], handled: true }; +const DEFAULT_OPTIONS: PinoOptions = { + error: { levels: [], handled: true }, + log: { levels: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] }, +}; + +type DeepPartial = { + [P in keyof T]?: T[P] extends object ? Partial : T[P]; +}; /** * Integration for Pino logging library. @@ -51,7 +69,12 @@ const DEFAULT_OPTIONS: Options = { eventLevels: ['error', 'fatal'], handled: tru * * Requires Pino >=v8.0.0 and Node >=20.6.0 or >=18.19.0 */ -export const pinoIntegration = defineIntegration((options: Options = DEFAULT_OPTIONS) => { +export const pinoIntegration = defineIntegration((userOptions: DeepPartial = {}) => { + const options: PinoOptions = { + error: { ...DEFAULT_OPTIONS.error, ...userOptions.error }, + log: { ...DEFAULT_OPTIONS.log, ...userOptions.log }, + }; + return { name: 'Pino', setup: client => { @@ -76,16 +99,16 @@ export const pinoIntegration = defineIntegration((options: Options = DEFAULT_OPT const level = self?.levels?.labels?.[levelNumber] || 'info'; const attributes = { + ...obj, 'sentry.origin': 'auto.logging.pino', 'sentry.pino.level': levelNumber, - ...obj, }; - if (enableLogs) { + if (enableLogs && options.log.levels.includes(level)) { _INTERNAL_captureLog({ level, message, attributes }); } - if (options.eventLevels?.includes(level)) { + if (options.error.levels.includes(level)) { const captureContext = { level: severityLevelFromString(level), }; @@ -95,7 +118,7 @@ export const pinoIntegration = defineIntegration((options: Options = DEFAULT_OPT event.logger = 'pino'; addExceptionMechanism(event, { - handled: !!options.handled, + handled: options.error.handled, type: 'pino', }); From c1f68707a1a3074e0b7c1bb75575e5e9a1f97bad Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 2 Oct 2025 01:01:10 +0100 Subject: [PATCH 22/24] update deps --- packages/node-core/package.json | 4 ++-- yarn.lock | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/node-core/package.json b/packages/node-core/package.json index 8715555c05e8..ed90625bac44 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -68,11 +68,11 @@ "dependencies": { "@sentry/core": "10.17.0", "@sentry/opentelemetry": "10.17.0", - "@apm-js-collab/tracing-hooks": "^0.3.0", + "@apm-js-collab/tracing-hooks": "^0.3.1", "import-in-the-middle": "^1.14.2" }, "devDependencies": { - "@apm-js-collab/code-transformer": "^0.8.0", + "@apm-js-collab/code-transformer": "^0.8.2", "@opentelemetry/api": "^1.9.0", "@opentelemetry/context-async-hooks": "^2.1.0", "@opentelemetry/core": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index 50cc3947e86a..847f6b97a773 100644 --- a/yarn.lock +++ b/yarn.lock @@ -340,10 +340,15 @@ resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.8.0.tgz#383bffab5131f0346e90665cebd36f64a51ed44e" integrity sha512-4nueg5W0cZ6HdLDOD1xVCdPqhj17Rur6RQjE1Zv3Fzp4TJJU9v6rqlWtzpq9uudq2k05VWc22P000wX+c9lVaA== -"@apm-js-collab/tracing-hooks@^0.3.0": - version "0.3.0" - resolved "https://registry.yarnpkg.com/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.3.0.tgz#78522bdef95385b0fd31bdf9b2575947e88884f9" - integrity sha512-YXyqZLr747VyFDd1g4UNBZlh1SSJfz8jMciVS46b+m5xb9Lnof75X5z8o7BeQfUudhhR6vtMCR9zKnBaPsmBDA== +"@apm-js-collab/code-transformer@^0.8.2": + version "0.8.2" + resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.8.2.tgz#a3160f16d1c4df9cb81303527287ad18d00994d1" + integrity sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA== + +"@apm-js-collab/tracing-hooks@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@apm-js-collab/tracing-hooks/-/tracing-hooks-0.3.1.tgz#414d3a93c3a15d8be543a3fac561f7c602b6a588" + integrity sha512-Vu1CbmPURlN5fTboVuKMoJjbO5qcq9fA5YXpskx3dXe/zTBvjODFoerw+69rVBlRLrJpwPqSDqEuJDEKIrTldw== dependencies: "@apm-js-collab/code-transformer" "^0.8.0" debug "^4.4.1" From 1a4b10ed118d7520978f7cb2d8fb9ffb3f3901df Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 2 Oct 2025 01:29:26 +0100 Subject: [PATCH 23/24] remove yarn.lock dupes --- yarn.lock | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 847f6b97a773..721f6fe3759e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -335,12 +335,7 @@ dependencies: json-schema-to-ts "^3.1.1" -"@apm-js-collab/code-transformer@^0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.8.0.tgz#383bffab5131f0346e90665cebd36f64a51ed44e" - integrity sha512-4nueg5W0cZ6HdLDOD1xVCdPqhj17Rur6RQjE1Zv3Fzp4TJJU9v6rqlWtzpq9uudq2k05VWc22P000wX+c9lVaA== - -"@apm-js-collab/code-transformer@^0.8.2": +"@apm-js-collab/code-transformer@^0.8.0", "@apm-js-collab/code-transformer@^0.8.2": version "0.8.2" resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.8.2.tgz#a3160f16d1c4df9cb81303527287ad18d00994d1" integrity sha512-YRjJjNq5KFSjDUoqu5pFUWrrsvGOxl6c3bu+uMFc9HNNptZ2rNU/TI2nLw4jnhQNtka972Ee2m3uqbvDQtPeCA== From 11f77b55717c669204c53d63c96969e3159a0e3b Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 2 Oct 2025 02:03:07 +0100 Subject: [PATCH 24/24] Test on latest Pino with integrated channel --- .../node-integration-tests/package.json | 1 + .../suites/pino/scenario-next.mjs | 18 +++++ .../suites/pino/test.ts | 75 ++++++++++++++++++- packages/node-core/src/integrations/pino.ts | 2 +- yarn.lock | 22 ++++++ 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 dev-packages/node-integration-tests/suites/pino/scenario-next.mjs diff --git a/dev-packages/node-integration-tests/package.json b/dev-packages/node-integration-tests/package.json index 3e1a862de777..e7e811b40d14 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -67,6 +67,7 @@ "openai": "5.18.1", "pg": "8.16.0", "pino": "9.9.4", + "pino-next": "npm:pino@^9.12.0", "postgres": "^3.4.7", "prisma": "6.15.0", "proxy": "^2.1.1", diff --git a/dev-packages/node-integration-tests/suites/pino/scenario-next.mjs b/dev-packages/node-integration-tests/suites/pino/scenario-next.mjs new file mode 100644 index 000000000000..11fc038fea3a --- /dev/null +++ b/dev-packages/node-integration-tests/suites/pino/scenario-next.mjs @@ -0,0 +1,18 @@ +import * as Sentry from '@sentry/node'; +import pino from 'pino-next'; + +const logger = pino({}); + +Sentry.withIsolationScope(() => { + Sentry.startSpan({ name: 'startup' }, () => { + logger.info({ user: 'user-id', something: { more: 3, complex: 'nope' } }, 'hello world'); + }); +}); + +setTimeout(() => { + Sentry.withIsolationScope(() => { + Sentry.startSpan({ name: 'later' }, () => { + logger.error(new Error('oh no')); + }); + }); +}, 1000); diff --git a/dev-packages/node-integration-tests/suites/pino/test.ts b/dev-packages/node-integration-tests/suites/pino/test.ts index d6a4a7fc7167..15a9397ebb27 100644 --- a/dev-packages/node-integration-tests/suites/pino/test.ts +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -23,7 +23,6 @@ conditionalTest({ min: 20 })('Pino integration', () => { }); test('captures event and logs', async () => { - // expect.assertions(1); const instrumentPath = join(__dirname, 'instrument.mjs'); await createRunner(__dirname, 'scenario.mjs') @@ -96,4 +95,78 @@ conditionalTest({ min: 20 })('Pino integration', () => { .start() .completed(); }); + + test('captures with Pino integrated channel', async () => { + const instrumentPath = join(__dirname, 'instrument.mjs'); + + await createRunner(__dirname, 'scenario-next.mjs') + .withMockSentryServer() + .withInstrument(instrumentPath) + .expect({ + event: { + exception: { + values: [ + { + type: 'Error', + value: 'oh no', + mechanism: { + type: 'pino', + handled: true, + }, + stacktrace: { + frames: expect.arrayContaining([ + expect.objectContaining({ + function: '?', + in_app: true, + module: 'scenario-next', + context_line: " logger.error(new Error('oh no'));", + }), + ]), + }, + }, + ], + }, + }, + }) + .expect({ + log: { + items: [ + { + timestamp: expect.any(Number), + level: 'info', + body: 'hello world', + trace_id: expect.any(String), + severity_number: 9, + attributes: expect.objectContaining({ + 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, + 'sentry.pino.level': { value: 30, type: 'integer' }, + user: { value: 'user-id', type: 'string' }, + something: { + type: 'string', + value: '{"more":3,"complex":"nope"}', + }, + 'sentry.release': { value: '1.0', type: 'string' }, + 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, + }), + }, + { + timestamp: expect.any(Number), + level: 'error', + body: 'oh no', + trace_id: expect.any(String), + severity_number: 17, + attributes: expect.objectContaining({ + 'sentry.origin': { value: 'auto.logging.pino', type: 'string' }, + 'sentry.pino.level': { value: 50, type: 'integer' }, + err: { value: '{}', type: 'string' }, + 'sentry.release': { value: '1.0', type: 'string' }, + 'sentry.sdk.name': { value: 'sentry.javascript.node', type: 'string' }, + }), + }, + ], + }, + }) + .start() + .completed(); + }); }); diff --git a/packages/node-core/src/integrations/pino.ts b/packages/node-core/src/integrations/pino.ts index 80cd6e34dfcd..af3f41735c4a 100644 --- a/packages/node-core/src/integrations/pino.ts +++ b/packages/node-core/src/integrations/pino.ts @@ -92,7 +92,7 @@ export const pinoIntegration = defineIntegration((userOptions: DeepPartial