diff --git a/packages/auto-instrumentations-node/src/utils.ts b/packages/auto-instrumentations-node/src/utils.ts index eea8d06869..8dbe604926 100644 --- a/packages/auto-instrumentations-node/src/utils.ts +++ b/packages/auto-instrumentations-node/src/utils.ts @@ -166,15 +166,35 @@ export function getNodeAutoInstrumentations( // Defaults are defined by the instrumentation itself const userConfig: any = inputConfigs[name] ?? {}; - if ( - userConfig.enabled === false || - !enabledInstrumentationsFromEnv.includes(name) || - disabledInstrumentationsFromEnv.includes(name) - ) { - diag.debug(`Disabling instrumentation for ${name}`); + // Configuration priority: Environment variables > programmatic config + // If both OTEL_NODE_ENABLED_INSTRUMENTATIONS and OTEL_NODE_DISABLED_INSTRUMENTATIONS are set, ENABLED is applied first, then DISABLED is applied to that list. + // If the same instrumentation is in both lists, it will be disabled. + // When env vars are unset, allow programmatic config to override default exclusions + + if (disabledInstrumentationsFromEnv.includes(name)) { + diag.debug(`Disabling instrumentation for ${name} - disabled by env var`); continue; } + const isEnabledEnvSet = !!process.env.OTEL_NODE_ENABLED_INSTRUMENTATIONS; + if (isEnabledEnvSet) { + if (!enabledInstrumentationsFromEnv.includes(name)) { + diag.debug(`Disabling instrumentation for ${name} - not in enabled env var list`); + continue; + } + } else { + if (userConfig.enabled === false) { + diag.debug(`Disabling instrumentation for ${name} - disabled by user config`); + continue; + } + + const isDefaultExcluded = defaultExcludedInstrumentations.includes(name); + if (isDefaultExcluded && userConfig.enabled !== true) { + diag.debug(`Disabling instrumentation for ${name} - excluded by default and not explicitly enabled`); + continue; + } + } + try { diag.debug(`Loading instrumentation for ${name}`); instrumentations.push(new Instance(userConfig)); diff --git a/packages/auto-instrumentations-node/test/utils.test.ts b/packages/auto-instrumentations-node/test/utils.test.ts index 1241ca6cce..d65678273c 100644 --- a/packages/auto-instrumentations-node/test/utils.test.ts +++ b/packages/auto-instrumentations-node/test/utils.test.ts @@ -172,6 +172,76 @@ describe('utils', () => { spy.restore(); }); + + it('should enable fs instrumentation when explicitly enabled and OTEL_NODE_ENABLED_INSTRUMENTATIONS is not set', () => { + const instrumentations = getNodeAutoInstrumentations({ + '@opentelemetry/instrumentation-fs': { + enabled: true, + }, + }); + const fsInstrumentation = instrumentations.find( + instr => + instr.instrumentationName === '@opentelemetry/instrumentation-fs' + ); + assert.notStrictEqual( + fsInstrumentation, + undefined, + 'fs instrumentation should be included when explicitly enabled in config and env var is not set' + ); + }); + + it('should respect OTEL_NODE_ENABLED_INSTRUMENTATIONS - env var overrides user config', () => { + process.env.OTEL_NODE_ENABLED_INSTRUMENTATIONS = 'fs,http'; + try { + const instrumentations = getNodeAutoInstrumentations({ + '@opentelemetry/instrumentation-fs': { + enabled: false, // User tries to disable but env var overrides + }, + '@opentelemetry/instrumentation-express': { + enabled: true, // User tries to enable but not in env var list + }, + }); + + const fsInstrumentation = instrumentations.find( + instr => instr.instrumentationName === '@opentelemetry/instrumentation-fs' + ); + const httpInstrumentation = instrumentations.find( + instr => instr.instrumentationName === '@opentelemetry/instrumentation-http' + ); + const expressInstrumentation = instrumentations.find( + instr => instr.instrumentationName === '@opentelemetry/instrumentation-express' + ); + + assert.notStrictEqual(fsInstrumentation, undefined, 'fs should be enabled by env var despite user config'); + assert.notStrictEqual(httpInstrumentation, undefined, 'http should be enabled by env var'); + assert.strictEqual(expressInstrumentation, undefined, 'express should be disabled - not in env var list'); + } finally { + delete process.env.OTEL_NODE_ENABLED_INSTRUMENTATIONS; + } + }); + + it('should respect OTEL_NODE_DISABLED_INSTRUMENTATIONS with absolute priority', () => { + process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS = 'http'; + try { + const instrumentations = getNodeAutoInstrumentations({ + '@opentelemetry/instrumentation-http': { + enabled: true, // User tries to enable but env var disables + }, + }); + const httpInstrumentation = instrumentations.find( + instr => + instr.instrumentationName === '@opentelemetry/instrumentation-http' + ); + + assert.strictEqual( + httpInstrumentation, + undefined, + 'http instrumentation should be disabled by OTEL_NODE_DISABLED_INSTRUMENTATIONS even when user enables it' + ); + } finally { + delete process.env.OTEL_NODE_DISABLED_INSTRUMENTATIONS; + } + }); }); describe('getResourceDetectorsFromEnv', () => {