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/package.json b/dev-packages/node-integration-tests/package.json index 48073f2ac817..e7e811b40d14 100644 --- a/dev-packages/node-integration-tests/package.json +++ b/dev-packages/node-integration-tests/package.json @@ -66,6 +66,8 @@ "node-schedule": "^2.1.1", "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/instrument.mjs b/dev-packages/node-integration-tests/suites/pino/instrument.mjs new file mode 100644 index 000000000000..2c09097de1f4 --- /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({ error: { levels: ['error', 'fatal'] } })], +}); 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/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..15a9397ebb27 --- /dev/null +++ b/dev-packages/node-integration-tests/suites/pino/test.ts @@ -0,0 +1,172 @@ +import { join } from 'path'; +import { expect, test } from 'vitest'; +import { conditionalTest } from '../../utils'; +import { createRunner } from '../../utils/runner'; + +conditionalTest({ min: 20 })('Pino integration', () => { + test('has different trace ids for logs from different spans', async () => { + 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 () => { + 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: { + 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(); + }); + + 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/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 790810e93797..f70d6e0a3573 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -94,6 +94,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 f7e72ec908ae..5a608a925edb 100644 --- a/packages/aws-serverless/src/index.ts +++ b/packages/aws-serverless/src/index.ts @@ -106,6 +106,7 @@ export { mysql2Integration, redisIntegration, tediousIntegration, + pinoIntegration, postgresIntegration, postgresJsIntegration, prismaIntegration, diff --git a/packages/core/src/utils/worldwide.ts b/packages/core/src/utils/worldwide.ts index e2f1ad5fc2b2..2eb7f39f3a24 100644 --- a/packages/core/src/utils/worldwide.ts +++ b/packages/core/src/utils/worldwide.ts @@ -48,6 +48,8 @@ export type InternalGlobal = { */ _sentryModuleMetadata?: Record; _sentryEsmLoaderHookRegistered?: boolean; + _sentryInjectLoaderHookRegister?: () => void; + _sentryInjectLoaderHookRegistered?: boolean; } & Carrier; /** Get's the global object for the current JavaScript runtime */ diff --git a/packages/google-cloud-serverless/src/index.ts b/packages/google-cloud-serverless/src/index.ts index bab9dc3a1cbb..8f1d236f7877 100644 --- a/packages/google-cloud-serverless/src/index.ts +++ b/packages/google-cloud-serverless/src/index.ts @@ -106,6 +106,7 @@ export { mysql2Integration, redisIntegration, tediousIntegration, + pinoIntegration, postgresIntegration, postgresJsIntegration, prismaIntegration, diff --git a/packages/node-core/package.json b/packages/node-core/package.json index 4e83faeb767d..ed90625bac44 100644 --- a/packages/node-core/package.json +++ b/packages/node-core/package.json @@ -68,9 +68,11 @@ "dependencies": { "@sentry/core": "10.17.0", "@sentry/opentelemetry": "10.17.0", + "@apm-js-collab/tracing-hooks": "^0.3.1", "import-in-the-middle": "^1.14.2" }, "devDependencies": { + "@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/packages/node-core/src/index.ts b/packages/node-core/src/index.ts index e6cf209d23f6..0f976bd23436 100644 --- a/packages/node-core/src/index.ts +++ b/packages/node-core/src/index.ts @@ -27,6 +27,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..af3f41735c4a --- /dev/null +++ b/packages/node-core/src/integrations/pino.ts @@ -0,0 +1,149 @@ +import { tracingChannel } from 'node:diagnostics_channel'; +import type { IntegrationFn, LogSeverityLevel } from '@sentry/core'; +import { + _INTERNAL_captureLog, + addExceptionMechanism, + captureException, + captureMessage, + defineIntegration, + 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 = [MergeObject, string, number]; + +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: 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. + * 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((userOptions: DeepPartial = {}) => { + const options: PinoOptions = { + error: { ...DEFAULT_OPTIONS.error, ...userOptions.error }, + log: { ...DEFAULT_OPTIONS.log, ...userOptions.log }, + }; + + return { + name: 'Pino', + setup: client => { + const enableLogs = !!client.getOptions().enableLogs; + + addInstrumentationConfig({ + channelName: 'pino-log', + // 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', + }, + }); + + const injectedChannel = tracingChannel('orchestrion:pino:pino-log'); + const integratedChannel = tracingChannel('pino_asJson'); + + function onPinoStart(self: Pino, args: PinoHookArgs): void { + const [obj, message, levelNumber] = args; + const level = self?.levels?.labels?.[levelNumber] || 'info'; + + const attributes = { + ...obj, + 'sentry.origin': 'auto.logging.pino', + 'sentry.pino.level': levelNumber, + }; + + if (enableLogs && options.log.levels.includes(level)) { + _INTERNAL_captureLog({ level, message, attributes }); + } + + if (options.error.levels.includes(level)) { + const captureContext = { + level: severityLevelFromString(level), + }; + + withScope(scope => { + scope.addEventProcessor(event => { + event.logger = 'pino'; + + addExceptionMechanism(event, { + handled: options.error.handled, + type: 'pino', + }); + + return event; + }); + + if (obj.err) { + captureException(obj.err, captureContext); + return; + } + + captureMessage(message, captureContext); + }); + } + } + + 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; 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..c4ae4897678d --- /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[] }; + + /** 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..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, @@ -131,6 +132,8 @@ function _init( client.init(); + GLOBAL_OBJ._sentryInjectLoaderHookRegister?.(); + 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..667996ebbe53 --- /dev/null +++ b/packages/node-core/src/sdk/injectLoader.ts @@ -0,0 +1,46 @@ +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. + */ +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 || []; + + // 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); + } + }; +} diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 4808f22b472b..db378e55f6ca 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -171,6 +171,7 @@ export { disableAnrDetectionForCallback, spotlightIntegration, childProcessIntegration, + pinoIntegration, createSentryWinstonTransport, SentryContextManager, systemErrorIntegration, diff --git a/yarn.lock b/yarn.lock index 3a6b6375c015..2b7fc676accc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -335,6 +335,20 @@ dependencies: json-schema-to-ts "^3.1.1" +"@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== + +"@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" + 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" @@ -22453,10 +22467,10 @@ 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.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== module-lookup-amd@^8.0.5: version "8.0.5" @@ -24741,15 +24755,32 @@ pino-abstract-transport@^2.0.0: dependencies: split2 "^4.0.0" +"pino-next@npm:pino@^9.12.0": + version "9.12.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-9.12.0.tgz#976e549cc29e21e5dbf56b47910dc52817dc5a97" + integrity sha512-0Gd0OezGvqtqMwgYxpL7P0pSHHzTJ0Lx992h+mNlMtRVfNnqweWmf0JmRWk5gJzHalyd2mxTzKjhiNbGS2Ztfw== + dependencies: + atomic-sleep "^1.0.0" + 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" + slow-redact "^0.3.0" + sonic-boom "^4.0.1" + thread-stream "^3.0.0" + pino-std-serializers@^7.0.0: version "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== +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== dependencies: atomic-sleep "^1.0.0" fast-redact "^3.1.1" @@ -27913,6 +27944,11 @@ sliced@1.0.1: resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" integrity sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA== +slow-redact@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/slow-redact/-/slow-redact-0.3.0.tgz#97b4d7bd04136404e529c1ab29f3cb50e903c746" + integrity sha512-cf723wn9JeRIYP9tdtd86GuqoR5937u64Io+CYjlm2i7jvu7g0H+Cp0l0ShAf/4ZL+ISUTVT+8Qzz7RZmp9FjA== + smart-buffer@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae"