diff --git a/packages/aws-lambda/src/wrapper.js b/packages/aws-lambda/src/wrapper.js index 8a7abe37d3..76a9d1e7b6 100644 --- a/packages/aws-lambda/src/wrapper.js +++ b/packages/aws-lambda/src/wrapper.js @@ -21,7 +21,14 @@ const { tracing, coreConfig } = instanaCore; const { tracingHeaders, constants, spanBuffer } = tracing; const lambdaConfigDefaults = { - tracing: { forceTransmissionStartingAt: 25, transmissionDelay: 100, initialTransmissionDelay: 100 } + // Preload OpenTelemetry deps to avoid lazy loading overhead + // See https://jsw.ibm.com/browse/INSTA-71262 + preloadOpentelemetry: true, + tracing: { + forceTransmissionStartingAt: 25, + transmissionDelay: 100, + initialTransmissionDelay: 100 + } }; // Node.js 24+ removed support for callback-based handlers (3 parameters). diff --git a/packages/core/src/config/index.js b/packages/core/src/config/index.js index bbc2a7fcd4..dde9e8e041 100644 --- a/packages/core/src/config/index.js +++ b/packages/core/src/config/index.js @@ -82,6 +82,7 @@ const allowedSecretMatchers = ['equals', 'equals-ignore-case', 'contains', 'cont * @property {InstanaTracingOption} [tracing] * @property {InstanaSecretsOption} [secrets] * @property {number} [timeBetweenHealthcheckCalls] + * @property {boolean} [preloadOpentelemetry] */ /** @type {import('../core').GenericLogger} */ @@ -122,6 +123,7 @@ let defaults = { ignoreEndpointsDisableSuppression: false, disableEOLEvents: false }, + preloadOpentelemetry: false, secrets: { matcherMode: 'contains-ignore-case', keywords: ['key', 'pass', 'secret'] @@ -171,6 +173,7 @@ module.exports.normalize = (userConfig, defaultsOverride = {}) => { normalizeMetricsConfig(targetConfig); normalizeTracingConfig(targetConfig); normalizeSecrets(targetConfig); + normalizePreloadOpentelemetry(targetConfig); return targetConfig; }; @@ -815,3 +818,14 @@ function normalizeDisableEOLEvents(config) { config.tracing.disableEOLEvents = defaults.tracing.disableEOLEvents; } + +/** + * @param {InstanaConfig} config + */ +function normalizePreloadOpentelemetry(config) { + if (config.preloadOpentelemetry === true) { + return; + } + + config.preloadOpentelemetry = defaults.preloadOpentelemetry; +} diff --git a/packages/core/src/tracing/index.js b/packages/core/src/tracing/index.js index 9b470f467c..65fd713186 100644 --- a/packages/core/src/tracing/index.js +++ b/packages/core/src/tracing/index.js @@ -15,7 +15,7 @@ const tracingUtil = require('./tracingUtil'); const spanBuffer = require('./spanBuffer'); const shimmer = require('./shimmer'); const supportedVersion = require('./supportedVersion'); -const { otelInstrumentations } = require('./opentelemetry-instrumentations'); +const otelInstrumentations = require('./opentelemetry-instrumentations'); const cls = require('./cls'); const coreUtil = require('../util'); @@ -162,6 +162,7 @@ exports.preInit = function preInit(preliminaryConfig) { * Another possible use case is, that its theoretically possible that the customer * can already start using the SDK although we are not fully initialized. */ + otelInstrumentations.preInit(preliminaryConfig); spanHandle.init(preliminaryConfig); shimmer.init(preliminaryConfig); cls.init(preliminaryConfig); diff --git a/packages/core/src/tracing/opentelemetry-instrumentations/confluent-kafka.js b/packages/core/src/tracing/opentelemetry-instrumentations/confluent-kafka.js index 3df80c30e5..f4d99ff076 100644 --- a/packages/core/src/tracing/opentelemetry-instrumentations/confluent-kafka.js +++ b/packages/core/src/tracing/opentelemetry-instrumentations/confluent-kafka.js @@ -7,8 +7,20 @@ const constants = require('../constants'); const W3cTraceContext = require('../w3c_trace_context/W3cTraceContext'); +let ConfluentKafkaInstrumentation; + +function initInstrumentation() { + ConfluentKafkaInstrumentation = + ConfluentKafkaInstrumentation || + require('@instana/instrumentation-confluent-kafka-javascript').ConfluentKafkaInstrumentation; +} + +module.exports.preInit = () => { + initInstrumentation(); +}; + module.exports.init = () => { - const { ConfluentKafkaInstrumentation } = require('@instana/instrumentation-confluent-kafka-javascript'); + initInstrumentation(); const instrumentation = new ConfluentKafkaInstrumentation({}); diff --git a/packages/core/src/tracing/opentelemetry-instrumentations/fs.js b/packages/core/src/tracing/opentelemetry-instrumentations/fs.js index e4e8d01567..8ea068cb33 100644 --- a/packages/core/src/tracing/opentelemetry-instrumentations/fs.js +++ b/packages/core/src/tracing/opentelemetry-instrumentations/fs.js @@ -4,11 +4,21 @@ 'use strict'; +let FsInstrumentation; + +function initInstrumentation() { + FsInstrumentation = FsInstrumentation || require('@opentelemetry/instrumentation-fs').FsInstrumentation; +} + +module.exports.preInit = () => { + initInstrumentation(); +}; + // NOTE: otel fs instrumentation does not capture the file name currently module.exports.init = ({ cls, api }) => { + initInstrumentation(); const constants = require('../constants'); const { NonRecordingSpan } = require('./files/NonRecordingSpan'); - const { FsInstrumentation } = require('@opentelemetry/instrumentation-fs'); // eslint-disable-next-line max-len // https://github.com/open-telemetry/opentelemetry-js-contrib/pull/1335/files#diff-9a2f445c78d964623d07987299501cbc3101cbe0f76f9e18d2d75787601539daR428 diff --git a/packages/core/src/tracing/opentelemetry-instrumentations/index.js b/packages/core/src/tracing/opentelemetry-instrumentations/index.js index fb758fe550..85133c5c48 100644 --- a/packages/core/src/tracing/opentelemetry-instrumentations/index.js +++ b/packages/core/src/tracing/opentelemetry-instrumentations/index.js @@ -5,7 +5,10 @@ 'use strict'; module.exports = { - get otelInstrumentations() { - return require('./wrap'); + get init() { + return require('./wrap').init; + }, + get preInit() { + return require('./wrap').preInit; } }; diff --git a/packages/core/src/tracing/opentelemetry-instrumentations/oracle.js b/packages/core/src/tracing/opentelemetry-instrumentations/oracle.js index 35a3c9b592..13bf58f281 100644 --- a/packages/core/src/tracing/opentelemetry-instrumentations/oracle.js +++ b/packages/core/src/tracing/opentelemetry-instrumentations/oracle.js @@ -6,8 +6,19 @@ const constants = require('../constants'); +let OracleInstrumentation; + +function initInstrumentation() { + OracleInstrumentation = + OracleInstrumentation || require('@opentelemetry/instrumentation-oracledb').OracleInstrumentation; +} + +module.exports.preInit = () => { + initInstrumentation(); +}; + module.exports.init = () => { - const { OracleInstrumentation } = require('@opentelemetry/instrumentation-oracledb'); + initInstrumentation(); const instrumentation = new OracleInstrumentation(); diff --git a/packages/core/src/tracing/opentelemetry-instrumentations/restify.js b/packages/core/src/tracing/opentelemetry-instrumentations/restify.js index bf4282f9d2..5c8182a87a 100644 --- a/packages/core/src/tracing/opentelemetry-instrumentations/restify.js +++ b/packages/core/src/tracing/opentelemetry-instrumentations/restify.js @@ -6,8 +6,20 @@ const constants = require('../constants'); +let RestifyInstrumentation; + +function initInstrumentation() { + if (!RestifyInstrumentation) { + RestifyInstrumentation = require('@opentelemetry/instrumentation-restify').RestifyInstrumentation; + } +} + +module.exports.preInit = () => { + initInstrumentation(); +}; + module.exports.init = () => { - const { RestifyInstrumentation } = require('@opentelemetry/instrumentation-restify'); + initInstrumentation(); const instrumentation = new RestifyInstrumentation(); diff --git a/packages/core/src/tracing/opentelemetry-instrumentations/socket.io.js b/packages/core/src/tracing/opentelemetry-instrumentations/socket.io.js index 240095f667..168e2bb9db 100644 --- a/packages/core/src/tracing/opentelemetry-instrumentations/socket.io.js +++ b/packages/core/src/tracing/opentelemetry-instrumentations/socket.io.js @@ -6,7 +6,19 @@ const constants = require('../constants'); +let SocketIoInstrumentation; + +function initInstrumentation() { + SocketIoInstrumentation = + SocketIoInstrumentation || require('@opentelemetry/instrumentation-socket.io').SocketIoInstrumentation; +} + const isOnEvent = otelSpan => otelSpan.name.indexOf('receive') !== -1; + +exports.preInit = () => { + initInstrumentation(); +}; + /** * socket.io-client is not instrumented. * We can easily instrument socket.io-client by instrumenting @socket.io/component-emitter @@ -18,7 +30,7 @@ const isOnEvent = otelSpan => otelSpan.name.indexOf('receive') !== -1; * headers or meta data, only payload! */ exports.init = () => { - const { SocketIoInstrumentation } = require('@opentelemetry/instrumentation-socket.io'); + initInstrumentation(); const instrumentation = new SocketIoInstrumentation(); if (!instrumentation.getConfig().enabled) { diff --git a/packages/core/src/tracing/opentelemetry-instrumentations/tedious.js b/packages/core/src/tracing/opentelemetry-instrumentations/tedious.js index 0b0753bdc7..a8ea5ef5bd 100644 --- a/packages/core/src/tracing/opentelemetry-instrumentations/tedious.js +++ b/packages/core/src/tracing/opentelemetry-instrumentations/tedious.js @@ -6,11 +6,22 @@ const constants = require('../constants'); +let TediousInstrumentation; + +function initInstrumentation() { + TediousInstrumentation = + TediousInstrumentation || require('@opentelemetry/instrumentation-tedious').TediousInstrumentation; +} + +module.exports.preInit = () => { + initInstrumentation(); +}; + module.exports.init = () => { // Opentelemetry only supports tedious version >=1.11.0 and <=15, please refer the following link // for more details: https://www.npmjs.com/package/@opentelemetry/instrumentation-tedious#supported-versions - const { TediousInstrumentation } = require('@opentelemetry/instrumentation-tedious'); + initInstrumentation(); const instrumentation = new TediousInstrumentation(); diff --git a/packages/core/src/tracing/opentelemetry-instrumentations/wrap.js b/packages/core/src/tracing/opentelemetry-instrumentations/wrap.js index a6c2b18715..8ebbcf66c5 100644 --- a/packages/core/src/tracing/opentelemetry-instrumentations/wrap.js +++ b/packages/core/src/tracing/opentelemetry-instrumentations/wrap.js @@ -5,10 +5,6 @@ 'use strict'; -const { AsyncHooksContextManager } = require('@opentelemetry/context-async-hooks'); -const { W3CTraceContextPropagator, hrTimeDuration, hrTimeToMilliseconds } = require('@opentelemetry/core'); -const api = require('@opentelemetry/api'); -const { BasicTracerProvider } = require('@opentelemetry/sdk-trace-base'); const utils = require('./utils'); const constants = require('../constants'); const supportedVersion = require('../supportedVersion'); @@ -27,6 +23,60 @@ const instrumentations = { '@instana/instrumentation-confluent-kafka-javascript': { name: 'confluent-kafka' } }; +let AsyncHooksContextManager; +let W3CTraceContextPropagator; +let hrTimeDuration; +let hrTimeToMilliseconds; +let api; +let BasicTracerProvider; +let coreModule; + +function initOtelCoreDependencies() { + api = api || require('@opentelemetry/api'); + coreModule = coreModule || require('@opentelemetry/core'); + AsyncHooksContextManager = + AsyncHooksContextManager || require('@opentelemetry/context-async-hooks').AsyncHooksContextManager; + BasicTracerProvider = BasicTracerProvider || require('@opentelemetry/sdk-trace-base').BasicTracerProvider; + + W3CTraceContextPropagator = coreModule.W3CTraceContextPropagator; + hrTimeDuration = coreModule.hrTimeDuration; + hrTimeToMilliseconds = coreModule.hrTimeToMilliseconds; +} + +function getInstrumentation(instr) { + if (!instr.module) { + instr.module = require(`./${instr.name}`); + } + return instr.module; +} + +function preInitInstrumentations() { + Object.values(instrumentations).forEach(instr => { + const instrumentation = getInstrumentation(instr); + instrumentation.preInit?.(); + }); +} + +function initInstrumentations(cls) { + Object.values(instrumentations).forEach(instr => { + const instrumentation = getInstrumentation(instr); + instrumentation.init?.({ cls, api: api }); + }); +} + +module.exports.preInit = config => { + if (!supportedVersion(process.versions.node)) { + return; + } + + if (!config?.preloadOpentelemetry) { + return; + } + + initOtelCoreDependencies(); + preInitInstrumentations(); +}; + // NOTE: using a logger might create a recursive execution // logger.debug -> creates fs call -> calls transformToInstanaSpan -> calls logger.debug // use uninstrumented logger, but useless for production @@ -35,12 +85,8 @@ module.exports.init = (_config, cls) => { return; } - Object.keys(instrumentations).forEach(k => { - const value = instrumentations[k]; - const instrumentation = require(`./${value.name}`); - instrumentation.init({ cls, api: api }); - value.module = instrumentation; - }); + initOtelCoreDependencies(); + initInstrumentations(cls); const prepareData = (otelSpan, instrumentation) => { const obj = { diff --git a/packages/core/test/config/normalizeConfig_test.js b/packages/core/test/config/normalizeConfig_test.js index 7c35400caf..b61295fe6b 100644 --- a/packages/core/test/config/normalizeConfig_test.js +++ b/packages/core/test/config/normalizeConfig_test.js @@ -943,6 +943,30 @@ describe('config.normalizeConfig', () => { expect(config.tracing.ignoreEndpoints).to.deep.equal({}); }); + it('preloadOpentelemetry should default to false', () => { + const config = coreConfig.normalize({}); + expect(config.preloadOpentelemetry).to.be.false; + }); + + it('preloadOpentelemetry should accept true value', () => { + const config = coreConfig.normalize({ + preloadOpentelemetry: true + }); + expect(config.preloadOpentelemetry).to.be.true; + }); + + it('preloadOpentelemetry should work with custom defaults', () => { + const customDefaults = { + preloadOpentelemetry: true, + tracing: { + forceTransmissionStartingAt: 25 + } + }; + const config = coreConfig.normalize({}, customDefaults); + expect(config.preloadOpentelemetry).to.be.true; + expect(config.tracing.forceTransmissionStartingAt).to.equal(25); + }); + describe('when testing ignore endpoints reading from INSTANA_IGNORE_ENDPOINTS_PATH env variable', () => { let filePaths; @@ -1048,6 +1072,8 @@ describe('config.normalizeConfig', () => { expect(config.tracing.useOpentelemetry).to.equal(true); expect(config.tracing.allowRootExitSpan).to.equal(false); + expect(config.preloadOpentelemetry).to.equal(false); + expect(config.secrets).to.be.an('object'); expect(config.secrets.matcherMode).to.equal('contains-ignore-case'); expect(config.secrets.keywords).to.deep.equal(['key', 'pass', 'secret']);