@instana/core
Version:
Core library for Instana's Node.js packages
386 lines (340 loc) • 13.1 kB
JavaScript
/*
* (c) Copyright IBM Corp. 2021
* (c) Copyright Instana Inc. and contributors 2016
*/
;
const sdk = require('./sdk');
const constants = require('./constants');
const tracingMetrics = require('./metrics');
const opentracing = require('./opentracing');
const spanHandle = require('./spanHandle');
const tracingHeaders = require('./tracingHeaders');
const tracingUtil = require('./tracingUtil');
const spanBuffer = require('./spanBuffer');
const shimmer = require('./shimmer');
const supportedVersion = require('./supportedVersion');
const { otelInstrumentations } = require('./opentelemetry-instrumentations');
const cls = require('./cls');
const coreUtil = require('../util');
let tracingEnabled = false;
let tracingActivated = false;
let instrumenationsInitialized = false;
let automaticTracingEnabled = false;
/** @type {import('../util/normalizeConfig').InstanaConfig} */
let config = null;
/** @typedef {import('../../../collector/src/pidStore')} CollectorPIDStore */
/**
* @typedef {Object} TracingMetrics
* @property {number} pid
* @property {{opened: number, closed: number, dropped: number}} metrics
*/
/** @type {CollectorPIDStore} */
let processIdentityProvider = null;
// Note: Also update initializedTooLateHeuristic.js and the accompanying test when adding instrumentations.
let instrumentations = [
'./instrumentation/cloud/aws-sdk/v2',
'./instrumentation/cloud/aws-sdk/v3',
'./instrumentation/cloud/aws-sdk/v2/sdk',
'./instrumentation/cloud/aws-sdk/v2/sqs',
'./instrumentation/cloud/azure/blob',
'./instrumentation/cloud/gcp/pubsub',
'./instrumentation/cloud/gcp/storage',
'./instrumentation/control_flow/bluebird',
'./instrumentation/control_flow/clsHooked',
'./instrumentation/control_flow/graphqlSubscriptions',
'./instrumentation/databases/elasticsearch',
'./instrumentation/databases/ioredis',
'./instrumentation/databases/memcached',
'./instrumentation/databases/mongodb',
'./instrumentation/databases/mongoose',
'./instrumentation/databases/mssql',
'./instrumentation/databases/mysql',
'./instrumentation/databases/pg',
'./instrumentation/databases/pgNative',
'./instrumentation/databases/prisma',
'./instrumentation/databases/redis',
'./instrumentation/databases/db2',
'./instrumentation/databases/couchbase',
'./instrumentation/frameworks/express',
'./instrumentation/frameworks/fastify',
'./instrumentation/frameworks/hapi',
'./instrumentation/frameworks/koa',
'./instrumentation/logging/bunyan',
'./instrumentation/logging/log4js',
'./instrumentation/logging/pino',
'./instrumentation/logging/winston',
'./instrumentation/logging/console',
'./instrumentation/messaging/amqp',
'./instrumentation/messaging/kafkaJs',
'./instrumentation/messaging/kafkaNode',
'./instrumentation/messaging/rdkafka',
'./instrumentation/messaging/nats',
'./instrumentation/messaging/natsStreaming',
'./instrumentation/messaging/bull',
'./instrumentation/process/memored',
'./instrumentation/process/process',
'./instrumentation/protocols/graphql',
'./instrumentation/protocols/grpcJs',
'./instrumentation/protocols/http2Client',
'./instrumentation/protocols/http2Server',
'./instrumentation/protocols/httpClient',
'./instrumentation/protocols/httpServer',
'./instrumentation/protocols/nativeFetch',
'./instrumentation/protocols/superagent'
];
/**
* @type {string[]}
*/
const customInstrumentations = process.env.INSTANA_CUSTOM_INSTRUMENTATIONS
? process.env.INSTANA_CUSTOM_INSTRUMENTATIONS.split(',')
: [];
if (customInstrumentations.length > 0) {
instrumentations = instrumentations.concat(customInstrumentations);
}
/**
* This is a temporary type definition for instrumented modules until we get to add types to these modules.
* For now it is safe to say that these modules are objects with the following methods:
* @typedef {Object} InstanaInstrumentedModule
* @property {Function} init
* @property {Function} activate
* @property {Function} deactivate
* @property {Function} [updateConfig]
* @property {boolean} [batchable]
* @property {string} [spanName]
* @property {string} [instrumentationName]
*/
/**
* @typedef {Object} KafkaTracingConfig
* @property {boolean} [traceCorrelation]
*/
/**
* @typedef {Object.<string,IgnoreEndpointsFields[]>} IgnoreEndpoints
*/
/**
* @typedef {Object} IgnoreEndpointsFields
* @property {string[]} [methods]
* @property {string[]} [endpoints]
* @property {string[]} [connections]
*/
/**
* @typedef {TracingDisableOptions|boolean} Disable
*/
/**
* @typedef {Object} TracingDisableOptions
* @property {string[]} [instrumentations]
* @property {string[]} [groups]
*/
/** @type {Array.<InstanaInstrumentedModule>} */
let additionalInstrumentationModules = [];
/** @type {Object.<string, InstanaInstrumentedModule>} */
const instrumentationModules = {};
exports.constants = constants;
exports.tracingHeaders = tracingHeaders;
exports.opentracing = opentracing;
exports.sdk = sdk;
exports.spanBuffer = spanBuffer;
exports.supportedVersion = supportedVersion;
exports.util = tracingUtil;
/**
* @param {Array.<InstanaInstrumentedModule>} _additionalInstrumentationModules
*/
exports.registerAdditionalInstrumentations = function registerAdditionalInstrumentations(
_additionalInstrumentationModules
) {
additionalInstrumentationModules = additionalInstrumentationModules.concat(_additionalInstrumentationModules);
};
/**
* @param {import('../util/normalizeConfig').InstanaConfig} preliminaryConfig
*/
exports.preInit = function preInit(preliminaryConfig) {
/**
* On e.g. Fargate the `preInit` function is called as early as possible
* and all our modules (shimmer, cls, sdk etc.) need to be initialized.
* Imagine on of these modules logs a warning. The logger module needs to be
* injected to be able to log anything.
*
* `preInit` has a limited functionality e.g. we do not activate the instrumentations.
* That means, any span creation would get skipped.
*
* `preInit` only monkey patches the libraries as early as possible to be able to start
* tracing as soon as the data to `init` the core package is fully available.
*
* The time between `preInit` and `init` is usually very short, but e.g. on Fargate
* it can take a bit of time because we are doing an async request to the meta API.
*
* Another possible use case is, that its theoretically possible that the customer
* can already start using the SDK although we are not fully initialized.
*/
spanHandle.init(preliminaryConfig);
shimmer.init(preliminaryConfig);
cls.init(preliminaryConfig);
sdk.init(preliminaryConfig, cls);
initInstrumenations(preliminaryConfig);
};
/**
* @param {import('../util/normalizeConfig').InstanaConfig} _config
* @param {import('..').DownstreamConnection} downstreamConnection
* @param {CollectorPIDStore} _processIdentityProvider
*/
exports.init = function init(_config, downstreamConnection, _processIdentityProvider) {
if (process.env.INSTANA_DEBUG || process.env.INSTANA_LOG_LEVEL === 'debug') {
const preloadFlags = coreUtil.preloadFlags.get();
// eslint-disable-next-line no-console
console.debug(`The App is using the following preload flags: ${preloadFlags}`);
}
// Consider removing this in the next major release of the @instana package.
if (coreUtil.esm.hasExperimentalLoaderFlag()) {
// eslint-disable-next-line no-console
console.warn(
'Node.js introduced breaking changes in versions 18.19.0 and above, leading to the discontinuation of support ' +
`for the --experimental-loader flag by Instana. The current Node.js version is ${process.version}. ` +
"To ensure tracing by Instana, please use the '--import' flag instead. For more information, " +
'refer to the Instana documentation: ' +
'https://www.ibm.com/docs/en/instana-observability/current?topic=nodejs-collector-installation.'
);
}
config = _config;
processIdentityProvider = _processIdentityProvider;
tracingEnabled = config.tracing.enabled;
automaticTracingEnabled = config.tracing.automaticTracingEnabled;
spanHandle.init(config);
shimmer.init(config);
// NOTE: We have to init cls & SDK if tracing disabled, because
// you can use the SDK to create spans and SDK needs CLS.
// The initialization only sets the logger and gets ready for traffic.
cls.init(config, processIdentityProvider);
sdk.init(config, cls);
if (tracingEnabled) {
tracingUtil.init(config);
tracingHeaders.init(config);
spanBuffer.init(config, downstreamConnection);
opentracing.init(config, automaticTracingEnabled, processIdentityProvider);
if (automaticTracingEnabled) {
initInstrumenations(config);
if (_config.tracing.useOpentelemetry) {
otelInstrumentations.init(config, cls);
}
if (coreUtil.esm.isESMApp()) {
coreUtil.iitmHook.activate();
}
}
}
if (config.tracing.activateImmediately) {
exports.activate();
}
};
/**
* @param {import('../util/normalizeConfig').InstanaConfig} _config
*/
function initInstrumenations(_config) {
// initialize all instrumentations
if (!instrumenationsInitialized) {
instrumentations.forEach(instrumentationKey => {
instrumentationModules[instrumentationKey] = require(instrumentationKey);
if (
!coreUtil.disableInstrumentation.isInstrumentationDisabled({
instrumentationModules,
instrumentationKey
})
) {
instrumentationModules[instrumentationKey].init(_config);
}
if (instrumentationModules[instrumentationKey].batchable && instrumentationModules[instrumentationKey].spanName) {
spanBuffer.addBatchableSpanName(instrumentationModules[instrumentationKey].spanName);
}
});
additionalInstrumentationModules.forEach(instrumentationModule => {
instrumentationModule.init(_config);
});
instrumenationsInitialized = true;
} else {
instrumentations.forEach(instrumentationKey => {
if (instrumentationModules[instrumentationKey].updateConfig) {
instrumentationModules[instrumentationKey].updateConfig(_config);
}
});
}
}
exports.activate = function activate(extraConfig = {}) {
if (tracingEnabled && !tracingActivated) {
tracingActivated = true;
coreUtil.activate(extraConfig);
spanBuffer.activate(extraConfig);
opentracing.activate();
sdk.activate();
if (automaticTracingEnabled) {
instrumentations.forEach(instrumentationKey => {
// If instrumentation is disabled via agent config, we skip tracing using the `isActive` flag
// within the instrumentation logic.
// However, modules already shimmered (e.g., logging) remain wrapped — we don't currently unwrap them.
// This may lead to minor performance overhead or partial interception. Proper unwrapping via
// `shimmer.unwrap(...)` would need broader changes. Given the very low performance and functional
// impact at present, we are accepting this behavior for now.
//
// We will revisit this in the future, especially if we encounter issues with disabled instrumentations.
if (
!coreUtil.disableInstrumentation.isInstrumentationDisabled({
instrumentationModules,
instrumentationKey
})
) {
instrumentationModules[instrumentationKey].activate(extraConfig);
}
});
}
}
};
exports.deactivate = function deactivate() {
if (tracingEnabled && tracingActivated) {
tracingActivated = false;
if (automaticTracingEnabled) {
instrumentations.forEach(instrumentationKey => {
instrumentationModules[instrumentationKey].deactivate();
});
}
opentracing.deactivate();
spanBuffer.deactivate();
sdk.deactivate();
}
};
exports.getHandleForCurrentSpan = function getHandleForCurrentSpan() {
return spanHandle.getHandleForCurrentSpan(cls);
};
exports.getCls = function getCls() {
// This only provides a value if tracing is enabled, otherwise cls will not be required and is null.
return cls;
};
/**
* @returns {TracingMetrics}
*/
exports._getAndResetTracingMetrics = function _getAndResetTracingMetrics() {
return {
pid:
processIdentityProvider && typeof processIdentityProvider.getEntityId === 'function'
? processIdentityProvider.getEntityId()
: undefined,
metrics: tracingMetrics.getAndReset()
};
};
/**
* @param {string} name
* @param {*} module_
*/
exports._instrument = function _instrument(name, module_) {
if (name === 'superagent') {
require('./instrumentation/protocols/superagent').instrument(module_);
} else {
throw new Error(`An unknown or unsupported instrumentation has been requested: ${name}`);
}
};
exports._debugCurrentSpanName = function _debugCurrentSpanName() {
if (!cls) {
return 'current: no cls';
}
const s = cls.ns.get('com.instana.span');
if (!s) {
return 'current: no span';
}
return `current: ${s.n}`;
};
exports.shimmer = require('./shimmer');