Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@instana/core

Package Overview
Dependencies
Maintainers
0
Versions
256
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@instana/core - npm Package Compare versions

Comparing version 3.21.0 to 5.0.0

70

CHANGELOG.md

@@ -6,2 +6,72 @@ # Change Log

# [5.0.0](https://github.com/instana/nodejs/compare/v3.21.0...v5.0.0) (2024-10-23)
### Bug Fixes
- deprecated kafka-avro ([#1337](https://github.com/instana/nodejs/issues/1337)) ([5647c3f](https://github.com/instana/nodejs/commit/5647c3fc8383329b187b6edd54dcbbfd5a90f021))
- dropped support for disabling AWS SDK instrumentation in old syntax ([#1383](https://github.com/instana/nodejs/issues/1383)) ([48bebf3](https://github.com/instana/nodejs/commit/48bebf3d2342a2dbe1f9c06ab0a5a3ad10a26c29))
- dropped support for node v14 and v16 ([#1348](https://github.com/instana/nodejs/issues/1348)) ([aaa9ad4](https://github.com/instana/nodejs/commit/aaa9ad41ebf82b11eedcf913afc31d3addd53868))
- dropped support for q library ([#1377](https://github.com/instana/nodejs/issues/1377)) ([c7f1fa5](https://github.com/instana/nodejs/commit/c7f1fa57f76a0cb8faefafaa0a30eb45a898b53a))
- dropped support for x-instana-service header ([#1355](https://github.com/instana/nodejs/issues/1355)) ([7aa5f4b](https://github.com/instana/nodejs/commit/7aa5f4b87e07fc5d1d804aeae1eaea173fdb33c6))
- **kafka:** enforced string format for Kafka trace headers and dropped binary support ([#1296](https://github.com/instana/nodejs/issues/1296)) ([2c822d3](https://github.com/instana/nodejs/commit/2c822d3c68966737a1e83d4141bd5a5ac3958cc8))
### Features
- added support for root exit spans ([#1297](https://github.com/instana/nodejs/issues/1297)) ([f1e1f30](https://github.com/instana/nodejs/commit/f1e1f30b87983bf9109a0ac097ec10458edd3643))
### BREAKING CHANGES
- - Removed the ability to disable AWS SDK instrumentation using the old syntax disabledTracers: ['aws-sdk/v2/index'].
* Migrate to the new syntax for disabling instrumentation: disabledTracers: ['aws-sdk/v2'].
- - Migration: Please configure the Instana agent to capture the X-Instana-Service header in the agent's configuration file.
* For details, see: https://www.ibm.com/docs/en/instana-observability/current?topic=applications-services#specify-the-x-instana-service-http-header.
- - Dropped support for Node.js versions 14 and 16.
* Reason: These versions have reached their end of life.
* More info: https://github.com/nodejs/Release?tab=readme-ov-file#end-of-life-releases
- **kafka:** - Removed the ability to configure the header format; headers will always be sent in 'string' format.
* Removed support for 'binary' format and code related to sending headers in 'binary' or 'both' formats.
refs INSTA-809
# [4.0.0](https://github.com/instana/nodejs/compare/v3.21.0...v4.0.0) (2024-10-23)
### Bug Fixes
- deprecated kafka-avro ([#1337](https://github.com/instana/nodejs/issues/1337)) ([5647c3f](https://github.com/instana/nodejs/commit/5647c3fc8383329b187b6edd54dcbbfd5a90f021))
- dropped support for disabling AWS SDK instrumentation in old syntax ([#1383](https://github.com/instana/nodejs/issues/1383)) ([48bebf3](https://github.com/instana/nodejs/commit/48bebf3d2342a2dbe1f9c06ab0a5a3ad10a26c29))
- dropped support for node v14 and v16 ([#1348](https://github.com/instana/nodejs/issues/1348)) ([aaa9ad4](https://github.com/instana/nodejs/commit/aaa9ad41ebf82b11eedcf913afc31d3addd53868))
- dropped support for q library ([#1377](https://github.com/instana/nodejs/issues/1377)) ([c7f1fa5](https://github.com/instana/nodejs/commit/c7f1fa57f76a0cb8faefafaa0a30eb45a898b53a))
- dropped support for x-instana-service header ([#1355](https://github.com/instana/nodejs/issues/1355)) ([7aa5f4b](https://github.com/instana/nodejs/commit/7aa5f4b87e07fc5d1d804aeae1eaea173fdb33c6))
- **kafka:** enforced string format for Kafka trace headers and dropped binary support ([#1296](https://github.com/instana/nodejs/issues/1296)) ([2c822d3](https://github.com/instana/nodejs/commit/2c822d3c68966737a1e83d4141bd5a5ac3958cc8))
### Features
- added support for root exit spans ([#1297](https://github.com/instana/nodejs/issues/1297)) ([f1e1f30](https://github.com/instana/nodejs/commit/f1e1f30b87983bf9109a0ac097ec10458edd3643))
### BREAKING CHANGES
- - Removed the ability to disable AWS SDK instrumentation using the old syntax disabledTracers: ['aws-sdk/v2/index'].
* Migrate to the new syntax for disabling instrumentation: disabledTracers: ['aws-sdk/v2'].
- - Migration: Please configure the Instana agent to capture the X-Instana-Service header in the agent's configuration file.
* For details, see: https://www.ibm.com/docs/en/instana-observability/current?topic=applications-services#specify-the-x-instana-service-http-header.
- - Dropped support for Node.js versions 14 and 16.
* Reason: These versions have reached their end of life.
* More info: https://github.com/nodejs/Release?tab=readme-ov-file#end-of-life-releases
- **kafka:** - Removed the ability to configure the header format; headers will always be sent in 'string' format.
* Removed support for 'binary' format and code related to sending headers in 'binary' or 'both' formats.
refs INSTA-809
# [3.21.0](https://github.com/instana/nodejs/compare/v3.20.2...v3.21.0) (2024-10-17)

@@ -8,0 +78,0 @@

4

package.json
{
"name": "@instana/core",
"version": "3.21.0",
"version": "5.0.0",
"description": "Core library for Instana's Node.js packages",

@@ -75,3 +75,3 @@ "main": "src/index.js",

},
"gitHead": "88f34c35252f7f71028456f408e668d7ab624e96"
"gitHead": "209e1ac4c64c5224c99bffcc82a172c3d22e4438"
}

@@ -31,2 +31,4 @@ /*

let processIdentityProvider = null;
/** @type {Boolean} */
let allowRootExitSpan;

@@ -52,2 +54,3 @@ /*

processIdentityProvider = _processIdentityProvider;
allowRootExitSpan = config?.tracing?.allowRootExitSpan;
}

@@ -248,4 +251,3 @@

if (traceId) {
// The incoming trace ID/span ID from an upstream tracer could be shorter than the standard length. Some of our code
// (in particular, the binary Kafka trace correlation header X_INSTANA_C) assumes the standard length. We normalize
// The incoming trace ID/span ID from an upstream tracer could be shorter than the standard length. We normalize
// both IDs here by left-padding with 0 characters.

@@ -506,2 +508,6 @@

* | skipIsTracing | Instrumentation wants to handle `cls.isTracing` on it's own (e.g db2)
* | checkReducedSpan | If no active entry span is present, there is an option for
* | | falling back to the most recent parent span stored as reduced span
* | | by setting checkReducedSpan attribute to true.
* | skipAllowRootExitSpanPresence | An instrumentation can ignore this feature to reduce noise.
*

@@ -516,3 +522,5 @@ * @param {Object.<string, *>} options

skipParentSpanCheck: false,
skipIsTracing: false
skipIsTracing: false,
checkReducedSpan: false,
skipAllowRootExitSpanPresence: false
},

@@ -522,5 +530,19 @@ options

const parentSpan = getCurrentSpan();
let isReducedSpan = false;
let parentSpan = getCurrentSpan();
// If there is no active entry span, we fall back to the reduced span of the most recent entry span.
// See comment in packages/core/src/tracing/clsHooked/unset.js#storeReducedSpan.
if (opts.checkReducedSpan && !parentSpan) {
parentSpan = getReducedSpan();
// We need to remember if a reduced span was used, because for reduced spans
// we do NOT trace anymore. The async context got already closed.
if (parentSpan) {
isReducedSpan = true;
}
}
const suppressed = tracingSuppressed();
const isExitSpanResult = isExitSpan(parentSpan);
const isParentSpanAnExitSpan = isExitSpan(parentSpan);

@@ -530,16 +552,47 @@ // CASE: first ask for suppressed, because if we skip the entry span, we won't have a parentSpan

if (suppressed) {
if (opts.extendedResponse) return { skip: true, suppressed, isExitSpan: isExitSpanResult };
if (opts.extendedResponse) {
return { skip: true, suppressed, isExitSpan: isParentSpanAnExitSpan, parentSpan, allowRootExitSpan };
}
return true;
}
if (!opts.skipParentSpanCheck && (!parentSpan || isExitSpanResult)) {
if (opts.extendedResponse) return { skip: true, suppressed, isExitSpan: isExitSpanResult };
else return true;
// DESC: If `allowRootExitSpan` is true, then we have to ignore if there is a parent or not.
// NOTE: The feature completely ignores the state of `isTracing`, because
// every exit span would be a separate trace. `isTracing` is always false,
// because we don't have a parent span. The http server span also does not check of `isTracing`,
// because it's the root span.
// CASE: Instrumentations can disable the `allowRootExitSpan` feature e.g. loggers.
if (!opts.skipAllowRootExitSpanPresence && allowRootExitSpan) {
if (opts.extendedResponse) {
return { skip: false, suppressed, isExitSpan: isParentSpanAnExitSpan, parentSpan, allowRootExitSpan };
}
return false;
}
// Parent span check is required skipParentSpanCheck and no parent is present but an exit span only
if (!opts.skipParentSpanCheck && (!parentSpan || isParentSpanAnExitSpan)) {
if (opts.extendedResponse) {
return { skip: true, suppressed, isExitSpan: isParentSpanAnExitSpan, parentSpan, allowRootExitSpan };
}
return true;
}
const skipIsActive = opts.isActive === false;
const skipIsTracing = !opts.skipIsTracing ? !isTracing() : false;
let skipIsTracing = !opts.skipIsTracing ? !isTracing() : false;
// See comment on top.
if (isReducedSpan) {
skipIsTracing = false;
}
const skip = skipIsActive || skipIsTracing;
if (opts.extendedResponse) return { skip, suppressed, isExitSpan: isExitSpanResult };
else return skip;
if (opts.extendedResponse) {
return { skip, suppressed, isExitSpan: isParentSpanAnExitSpan, parentSpan, allowRootExitSpan };
}
return skip;
}

@@ -546,0 +599,0 @@

@@ -10,21 +10,16 @@ /*

const semver = require('semver');
/**
* In order to increase Node.js version support, this loads an implementation of a CLS (continuation local storage) API
* which is appropriate for the version of on Node.js that is running.
* - Node.js 14.0 - 16.6: AsyncLocalStorage
* - Node.js 16.7 - 16.6: our vendored-in fork of cls-hooked (based on async_hooks) (see below for reasons)
* - Node.js >= 16.14: AsyncLocalStorage
* which is appropriate for the version of Node.js that is running.
* If 'INSTANA_FORCE_LEGACY_CLS' is set to 'true', the legacy implementation will be used.
*
* There is a bug introduced in Node 16.7 which breaks AsyncLocalStorage: https://github.com/nodejs/node/issues/40693
* - AsyncLocalStorage fix introduced in v17.2: https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V17.md#commits-5
* - AsyncLocalStorage fix introduced in v16.14: https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V16.md#commits
* Previous known Issues:
* - Node.js 16.7 introduced a bug that breaks AsyncLocalStorage:
* https://github.com/nodejs/node/issues/40693
* This bug affected the functionality of AsyncLocalStorage until it was fixed in:
* - Node.js 16.14: https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V16.md#commits
* - Node.js 17.2: https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V17.md#commits-5
*
*/
const includePrerelease = process.env.NODE_ENV === 'test';
if (
process.env.INSTANA_FORCE_LEGACY_CLS !== 'true' &&
semver.satisfies(process.versions.node, '14.0 - 16.6 || ^16.14 || >=17.2', { includePrerelease })
) {
if (process.env.INSTANA_FORCE_LEGACY_CLS !== 'true') {
module.exports = require('./async_local_storage_context');

@@ -31,0 +26,0 @@ } else {

@@ -20,11 +20,6 @@ /*

// legacy kafka trace correlation (binary values)
exports.kafkaLegacyTraceContextHeaderName = 'X_INSTANA_C';
exports.kafkaLegacyTraceLevelHeaderName = 'X_INSTANA_L';
exports.kafkaLegacyTraceLevelValueSuppressed = Buffer.from([0]);
exports.kafkaLegacyTraceLevelValueInherit = Buffer.from([1]);
// New Kafka trace correlation (string values) was introduced as an opt-in feature in 2021-10. Initially, it was sent
// out along with the legacy binary headers by default starting in 2022-10. However, as of 2024-10, only string headers
// are supported, and the legacy binary headers are no longer supported.
// New kafka trace correlation (string values). Available as opt-in since 2021-10, and send out together with the legacy
// binary headers by default starting in 2022-10. We will switch over to these headers completely (omitting the legacy
// headers approximately in 2023-10.
exports.kafkaTraceIdHeaderName = 'X_INSTANA_T';

@@ -34,10 +29,2 @@ exports.kafkaSpanIdHeaderName = 'X_INSTANA_S';

/**
* @typedef {'binary' | 'string' | 'both'} KafkaTraceCorrelationFormat
*/
// With the current phase 1 of the Kafka header format migration, 'both', is the default.
// With phase 2 (starting approximately October 2023) it will no longer be configurable and will always use 'string'.
/** @type {KafkaTraceCorrelationFormat} */
exports.kafkaHeaderFormatDefault = 'both';
exports.kafkaTraceCorrelationDefault = true;

@@ -48,5 +35,3 @@

exports.kafkaSpanIdHeaderName,
exports.kafkaTraceLevelHeaderName,
exports.kafkaLegacyTraceContextHeaderName,
exports.kafkaLegacyTraceLevelHeaderName
exports.kafkaTraceLevelHeaderName
];

@@ -59,5 +44,2 @@

exports.serviceNameHeaderName = 'X-Instana-Service';
exports.serviceNameHeaderNameLowerCase = exports.serviceNameHeaderName.toLowerCase();
exports.ENTRY = 1;

@@ -64,0 +46,0 @@ exports.EXIT = 2;

@@ -18,8 +18,3 @@ /*

const { otelInstrumentations } = require('./opentelemetry-instrumentations');
const {
esmSupportedVersion,
isLatestEsmSupportedVersion,
hasExperimentalLoaderFlag,
isESMApp
} = require('../util/esm');
const { isLatestEsmSupportedVersion, hasExperimentalLoaderFlag, isESMApp } = require('../util/esm');
const iitmHook = require('../util/iitmHook');

@@ -50,4 +45,4 @@ const { getPreloadFlags } = require('../util/getPreloadFlags');

let instrumentations = [
'./instrumentation/cloud/aws-sdk/v2/index',
'./instrumentation/cloud/aws-sdk/v3/index',
'./instrumentation/cloud/aws-sdk/v2',
'./instrumentation/cloud/aws-sdk/v3',
'./instrumentation/cloud/aws-sdk/v2/sdk',

@@ -61,3 +56,2 @@ './instrumentation/cloud/aws-sdk/v2/sqs',

'./instrumentation/control_flow/graphqlSubscriptions',
'./instrumentation/control_flow/q',
'./instrumentation/database/elasticsearch',

@@ -129,3 +123,2 @@ './instrumentation/database/ioredis',

* @property {boolean} [traceCorrelation]
* @property {string} [headerFormat]
*/

@@ -145,3 +138,2 @@

exports.util = tracingUtil;
exports.esmSupportedVersion = esmSupportedVersion;
exports.isLatestEsmSupportedVersion = isLatestEsmSupportedVersion;

@@ -196,3 +188,3 @@

// Consider removing this in the next major release(v4.x) of the @instana package.
// Consider removing this in the next major release of the @instana package.
if (hasExperimentalLoaderFlag()) {

@@ -199,0 +191,0 @@ // eslint-disable-next-line no-console

@@ -30,4 +30,2 @@ /*

exports.instrumentationName = 'aws-sdk/v2';
exports.isActive = function () {

@@ -34,0 +32,0 @@ return isActive;

@@ -34,4 +34,2 @@ /*

exports.instrumentationName = 'aws-sdk/v3';
exports.init = function init() {

@@ -88,8 +86,7 @@ sqsConsumer.init();

} else {
// This code can be removed once all AWS SDK v3 instrumentations have been refactored to use the new approach
// introduced in https://github.com/instana/nodejs/pull/838 for kinesis. That is: Do not use an explicit
// operationsInfo/operationsMap map that restricts the traced operations to a subset of possible operations, but
// instead allow _all_ operations to be traced, using the operation name from `command.constructor.name` for
// span.data.$spanName.op. We plan to finish this refactoring before or with the next major release (4.x) of the
// @instana packages.
// This logic should not be used in AWS SDK v4. All AWS SDK v4 instrumentations must use the new approach
// introduced in https://github.com/instana/nodejs/pull/838 for Kinesis. That is: Do not use an explicit
// operationsInfo/operationsMap that restricts the traced operations to a subset of possible operations.
// Instead, allow all operations to be traced using the operation name from `command.constructor.name`
// for span.data.$spanName.op.
awsProduct = operationMap[smithySendArgs[0].constructor.name];

@@ -96,0 +93,0 @@ if (awsProduct) {

@@ -51,8 +51,27 @@ /*

/**
* We need to set `skipIsTracing` check for db2, because of e.g. prepare
* statements or transactions. We don't always know if we are tracing or not.
*/
function skipTracing(ignoreClsTracing = false) {
// CASES: instrumentation is disabled, db call is disabled via suppress header
return (
cls.skipExitTracing({ isActive, skipIsTracing: true, skipParentSpanCheck: true }) ||
(ignoreClsTracing ? false : !cls.isTracing())
);
const skipExitResult = cls.skipExitTracing({
isActive,
skipIsTracing: true,
skipParentSpanCheck: true,
extendedResponse: true
});
const isTracing = cls.isTracing();
// CASE: `cls.skipExitTracing` decided to skip, respect that
if (skipExitResult.skip) return true;
// CASE: the target parent function wants us to ignore cls.isTracing
// because e.g. we don't know if we trace (parent got lost)
if (ignoreClsTracing) return false;
// CASE: We ignore if tracing or not for `allowRootExitSpan`. See cls file.
if (skipExitResult.allowRootExitSpan) return false;
return !isTracing;
}

@@ -105,3 +124,7 @@

// NOTE: See function description
cls.setCurrentSpan(parentSpan);
// NOTE: We need to ensure that there is a parentSpan, see 'allowRootExitSpan: true'.
if (parentSpan) {
cls.setCurrentSpan(parentSpan);
}
return originalCallback.apply(this, arguments);

@@ -335,4 +358,8 @@ });

// cls.isTracing is false when `execute` is minimum called twice
// then parentSpan is undefined, because there is no current span and no remembered span yet
// CASE 1: `cls.isTracing` is false when `execute` is minimum called twice, because
// because there is NO current span and no remembered span.
// We skip checking `cls.isTracing` because we want to trace the second call.
//
// CASE 2: `allowRootExitSpan: true` is set, we might not have an entry span at all on
// the first execute call.
if (skipTracing(!parentSpan || !!prepareCallParentSpan)) {

@@ -348,4 +375,7 @@ return false;

* - prepare call happens in http context, we need to remember it
*
* CASE: When setting `allowRootExitSpan: true`, the parentSpan might be null.
*/
cls.setCurrentSpan(rememberedParentSpan || parentSpan);
const parentToSetAsCurrent = rememberedParentSpan || parentSpan;
if (parentToSetAsCurrent) cls.setCurrentSpan(parentToSetAsCurrent);
return true;

@@ -352,0 +382,0 @@ };

@@ -47,10 +47,9 @@ /*

const client = this;
const parentSpan = cls.getCurrentSpan();
// We need to skip parentSpan condition because the parentSpan check is too specific in this fn
const skipExitTracingResult = cls.skipExitTracing({ isActive, skipParentSpanCheck: true, extendedResponse: true });
const parentSpan = skipExitTracingResult.parentSpan;
let callback;
if (
command.promise == null ||
typeof command.name !== 'string' ||
cls.skipExitTracing({ isActive, skipParentSpanCheck: true })
) {
if (command.promise == null || typeof command.name !== 'string' || skipExitTracingResult.skip) {
return original.apply(this, arguments);

@@ -64,2 +63,3 @@ }

if (
parentSpan &&
parentSpan.n === exports.spanName &&

@@ -71,4 +71,8 @@ (parentSpan.data.redis.command === 'multi' || parentSpan.data.redis.command === 'pipeline') &&

parentSpanSubCommands.push(command.name);
} else if (constants.isExitSpan(parentSpan)) {
// Apart from the special case of multi/pipeline calls, redis exits can't be child spans of other exits.
} else if (
// If allowRootExitSpan is not enabled then an EXIT SPAN can't exist alone
(!skipExitTracingResult.allowRootExitSpan && parentSpan && constants.isExitSpan(parentSpan)) ||
!parentSpan
) {
// Apart from the special case of multi/pipeline calls, redis exits can't be child spans of other exits
return original.apply(this, arguments);

@@ -132,6 +136,13 @@ }

const client = this;
const parentSpan = cls.getCurrentSpan();
const skipExitTracingResult = cls.skipExitTracing({ isActive, skipParentSpanCheck: true, extendedResponse: true });
const parentSpan = skipExitTracingResult.parentSpan;
// NOTE: multiple redis transaction can have a parent ioredis call
if (cls.skipExitTracing({ isActive, skipParentSpanCheck: true }) || constants.isExitSpan(parentSpan)) {
// If cls.skipExitTracing wants to skip the tracing then skip it
// Also if allowRootExitSpan is not enabled then an EXIT SPAN can't exist alone
if (
skipExitTracingResult.skip ||
(!skipExitTracingResult.allowRootExitSpan && parentSpan && constants.isExitSpan(parentSpan)) ||
(!skipExitTracingResult.allowRootExitSpan && !parentSpan)
) {
return original.apply(this, arguments);

@@ -138,0 +149,0 @@ }

@@ -70,3 +70,3 @@ /*

const wrapExec = execOriginalFn => {
return function instrumentedExecAsPipelineInstana() {
return function instrumentedExecAsMultiInstana() {
return instrumentMultiExec(this, arguments, execOriginalFn, addressUrl, true, false, selfMadeQueue);

@@ -92,2 +92,3 @@ };

shimmer.wrap(result, 'addCommand', wrapAddCommand);
shimmer.wrap(result, 'exec', wrapExec);

@@ -306,10 +307,19 @@

function instrumentMultiExec(origCtx, origArgs, original, address, isAtomic, cbStyle, queue) {
if (cls.skipExitTracing({ isActive })) {
const skipExitResult = cls.skipExitTracing({ isActive, extendedResponse: true });
if (skipExitResult.skip) {
return original.apply(origCtx, origArgs);
}
const parentSpan = cls.getCurrentSpan();
const parentSpan = skipExitResult.parentSpan;
return cls.ns.runAndReturn(() => {
const span = cls.startSpan(exports.spanName, constants.EXIT, parentSpan.t, parentSpan.s);
let span;
if (skipExitResult.allowRootExitSpan) {
span = cls.startSpan(exports.spanName, constants.EXIT);
} else {
span = cls.startSpan(exports.spanName, constants.EXIT, parentSpan.t, parentSpan.s);
}
span.stack = tracingUtil.getStackTrace(instrumentMultiExec);

@@ -316,0 +326,0 @@ span.data.redis = {

@@ -39,3 +39,3 @@ /*

if (cls.skipExitTracing({ isActive })) {
if (cls.skipExitTracing({ isActive, skipAllowRootExitSpanPresence: true })) {
return originalLog.apply(this, arguments);

@@ -42,0 +42,0 @@ }

@@ -32,3 +32,3 @@ /*

if (cls.skipExitTracing({ isActive })) {
if (cls.skipExitTracing({ isActive, skipAllowRootExitSpanPresence: true })) {
return originalLog.apply(this, arguments);

@@ -35,0 +35,0 @@ }

@@ -37,3 +37,3 @@ /*

return function (level) {
if (cls.skipExitTracing({ isActive })) {
if (cls.skipExitTracing({ isActive, skipAllowRootExitSpanPresence: true })) {
return originalLog.apply(this, arguments);

@@ -40,0 +40,0 @@ }

@@ -38,3 +38,3 @@ /*

return function log(mergingObject, message) {
if (cls.skipExitTracing({ isActive })) {
if (cls.skipExitTracing({ isActive, skipAllowRootExitSpanPresence: true })) {
return originalLoggingFunction.apply(this, arguments);

@@ -41,0 +41,0 @@ }

@@ -71,3 +71,3 @@ /*

return function (message) {
if (cls.skipExitTracing({ isActive })) {
if (cls.skipExitTracing({ isActive, skipAllowRootExitSpanPresence: true })) {
return originalMethod.apply(this, arguments);

@@ -74,0 +74,0 @@ }

@@ -57,3 +57,4 @@ /*

const skipTracingResult = cls.skipExitTracing({ isActive, extendedResponse: true, skipParentSpanCheck: true });
const parentSpan = cls.getCurrentSpan();
const parentSpan = skipTracingResult.parentSpan;
const isExitSpan = skipTracingResult.isExitSpan;

@@ -70,3 +71,3 @@

// allow rabbitmq parent exit spans, this is actually the span started in instrumentedChannelModelPublish
if (!parentSpan || (isExitSpan && parentSpan.n !== 'rabbitmq')) {
if (!skipTracingResult.allowRootExitSpan && (!parentSpan || (isExitSpan && parentSpan.n !== 'rabbitmq'))) {
return originalSendMessage.apply(ctx, originalArgs);

@@ -73,0 +74,0 @@ }

@@ -40,3 +40,4 @@ /*

const skipIsTracing = !!repeatableJob;
const parentSpan = cls.getCurrentSpan();
// We need to skip parentSpan condition because the parentSpan check is too specific in this fn
const skipTracingResult = cls.skipExitTracing({

@@ -46,5 +47,7 @@ isActive,

skipParentSpanCheck: true,
skipIsTracing
skipIsTracing,
checkReducedSpan: false
});
// NOTE: it makes sense to skip EXIT span tracing only if skipTracingResult.allowRootExitSpan is not enabled
/**

@@ -57,4 +60,4 @@ * Repeatable jobs cannot be persisted to a parent span, since we don't know for how long they will run.

skipTracingResult.skip ||
skipTracingResult.isExitSpan ||
(!parentSpan && !repeatableJob) ||
(!skipTracingResult.allowRootExitSpan && skipTracingResult.isExitSpan) ||
(!skipTracingResult.allowRootExitSpan && !skipTracingResult.parentSpan && !repeatableJob) ||
repeatableJobIsSuppressed

@@ -61,0 +64,0 @@ ) {

@@ -21,3 +21,2 @@ /*

let traceCorrelationEnabled = constants.kafkaTraceCorrelationDefault;
let headerFormat = constants.kafkaHeaderFormatDefault;

@@ -29,5 +28,3 @@ let isActive = false;

hook.onFileLoad(/\/kafkajs\/src\/consumer\/runner\.js/, instrumentConsumer);
hook.onModuleLoad('kafkajs', logWarningForKafkaHeaderFormat);
traceCorrelationEnabled = config.tracing.kafka.traceCorrelation;
headerFormat = config.tracing.kafka.headerFormat;
};

@@ -37,3 +34,2 @@

traceCorrelationEnabled = config.tracing.kafka.traceCorrelation;
headerFormat = config.tracing.kafka.headerFormat;
};

@@ -46,5 +42,2 @@

}
if (typeof extraConfig.tracing.kafka.headerFormat === 'string') {
headerFormat = extraConfig.tracing.kafka.headerFormat;
}
}

@@ -261,15 +254,2 @@ isActive = true;

}
// Only fall back to legacy binary trace correlation headers if no new header is present.
if (traceId == null && parentSpanId == null && level == null) {
// The newer string header format has not been found, fall back to legacy binary headers.
if (message.headers[constants.kafkaLegacyTraceContextHeaderName]) {
const traceContextBuffer = message.headers[constants.kafkaLegacyTraceContextHeaderName];
if (Buffer.isBuffer(traceContextBuffer) && traceContextBuffer.length === 24) {
const traceContext = tracingUtil.readTraceContextFromBuffer(traceContextBuffer);
traceId = traceContext.t;
parentSpanId = traceContext.s;
}
}
level = readTraceLevelBinary(message);
}
}

@@ -357,23 +337,2 @@

if (traceId == null && parentSpanId == null && level == null) {
// The newer string header format has not been found, fall back to legacy binary headers.
for (let msgIdx = 0; msgIdx < batch.messages.length; msgIdx++) {
if (
batch.messages[msgIdx].headers &&
batch.messages[msgIdx].headers[constants.kafkaLegacyTraceContextHeaderName]
) {
const traceContextBuffer = batch.messages[msgIdx].headers[constants.kafkaLegacyTraceContextHeaderName];
if (Buffer.isBuffer(traceContextBuffer) && traceContextBuffer.length === 24) {
const traceContext = tracingUtil.readTraceContextFromBuffer(traceContextBuffer);
traceId = traceContext.t;
parentSpanId = traceContext.s;
}
}
level = readTraceLevelBinary(batch.messages[msgIdx]);
if (traceId != null || parentSpanId != null || level != null) {
break;
}
}
}
for (let msgIdx = 0; msgIdx < batch.messages.length; msgIdx++) {

@@ -420,12 +379,2 @@ removeInstanaHeadersFromMessage(batch.messages[msgIdx]);

function readTraceLevelBinary(message) {
if (message.headers[constants.kafkaLegacyTraceLevelHeaderName]) {
const traceLevelBuffer = message.headers[constants.kafkaLegacyTraceLevelHeaderName];
if (Buffer.isBuffer(traceLevelBuffer) && traceLevelBuffer.length >= 1) {
return String(traceLevelBuffer.readInt8());
}
}
return '1';
}
function addTraceContextHeaderToAllMessages(messages, span) {

@@ -435,35 +384,7 @@ if (!traceCorrelationEnabled) {

}
switch (headerFormat) {
case 'binary':
addLegacyTraceContextHeaderToAllMessages(messages, span);
break;
case 'string':
addTraceIdSpanIdToAllMessages(messages, span);
break;
case 'both':
// fall through (both is the default)
default:
addLegacyTraceContextHeaderToAllMessages(messages, span);
addTraceIdSpanIdToAllMessages(messages, span);
}
// Add trace ID and span ID headers to all Kafka messages for trace correlation.
// 'string' headers are used by default starting from v4.
addTraceIdSpanIdToAllMessages(messages, span);
}
function addLegacyTraceContextHeaderToAllMessages(messages, span) {
if (Array.isArray(messages)) {
for (let msgIdx = 0; msgIdx < messages.length; msgIdx++) {
if (messages[msgIdx].headers == null) {
messages[msgIdx].headers = {
[constants.kafkaLegacyTraceContextHeaderName]: tracingUtil.renderTraceContextToBuffer(span),
[constants.kafkaLegacyTraceLevelHeaderName]: constants.kafkaLegacyTraceLevelValueInherit
};
} else if (messages[msgIdx].headers && typeof messages[msgIdx].headers === 'object') {
messages[msgIdx].headers[constants.kafkaLegacyTraceContextHeaderName] =
tracingUtil.renderTraceContextToBuffer(span);
messages[msgIdx].headers[constants.kafkaLegacyTraceLevelHeaderName] =
constants.kafkaLegacyTraceLevelValueInherit;
}
}
}
}
function addTraceIdSpanIdToAllMessages(messages, span) {

@@ -492,32 +413,6 @@ if (Array.isArray(messages)) {

}
switch (headerFormat) {
case 'binary':
addTraceLevelSuppressionToAllMessagesBinary(messages);
break;
case 'string':
addTraceLevelSuppressionToAllMessagesString(messages);
break;
case 'both':
// fall through (both is the default)
default:
addTraceLevelSuppressionToAllMessagesBinary(messages);
addTraceLevelSuppressionToAllMessagesString(messages);
}
// Since v4, only 'string' format is supported by default.
addTraceLevelSuppressionToAllMessagesString(messages);
}
function addTraceLevelSuppressionToAllMessagesBinary(messages) {
if (Array.isArray(messages)) {
for (let msgIdx = 0; msgIdx < messages.length; msgIdx++) {
if (messages[msgIdx].headers == null) {
messages[msgIdx].headers = {
[constants.kafkaLegacyTraceLevelHeaderName]: constants.kafkaLegacyTraceLevelValueSuppressed
};
} else if (messages[msgIdx].headers && typeof messages[msgIdx].headers === 'object') {
messages[msgIdx].headers[constants.kafkaLegacyTraceLevelHeaderName] =
constants.kafkaLegacyTraceLevelValueSuppressed;
}
}
}
}
function addTraceLevelSuppressionToAllMessagesString(messages) {

@@ -545,11 +440,1 @@ if (Array.isArray(messages)) {

}
// Note: This function can be removed as soon as we finish the Kafka header migration phase2.
// Might happen in major release v4.
function logWarningForKafkaHeaderFormat() {
logger.warn(
'[Deprecation Warning] The configuration option for specifying the Kafka header format will be removed in the ' +
'next major release as the format will no longer be configurable and Instana tracers will only send string ' +
'headers. More details see: https://ibm.biz/kafka-trace-correlation-header.'
);
}

@@ -15,4 +15,2 @@ /*

const { getFunctionArguments } = require('../../../util/function_arguments');
let traceCorrelationEnabled = constants.kafkaTraceCorrelationDefault;
let logger;

@@ -23,2 +21,3 @@ logger = require('../../../logger').getLogger('tracing/rdkafka', newLogger => {

let traceCorrelationEnabled = constants.kafkaTraceCorrelationDefault;
let isActive = false;

@@ -30,4 +29,4 @@

hook.onModuleLoad('node-rdkafka', instrumentConsumer);
hook.onModuleLoad('node-rdkafka', logWarningForKafkaHeaderFormat);
hook.onModuleLoad('kafka-avro', logDeprecationKafkaAvroMessage);
traceCorrelationEnabled = config.tracing.kafka.traceCorrelation;

@@ -46,3 +45,2 @@ };

}
isActive = true;

@@ -55,13 +53,2 @@ };

// Note: This function can be removed as soon as we finish the Kafka header migration phase 2 and remove the ability to
// configure the header format. Might happen in major release v4.
function logWarningForKafkaHeaderFormat() {
logger.warn(
'[Deprecation Warning] The Kafka header format configuration will be removed in the next major release. ' +
'Instana tracers will only support string headers, as binary headers are not compatible with node-rdkafka. ' +
'For more information, see the GitHub issue: https://github.com/Blizzard/node-rdkafka/pull/968, and review our ' +
'official documentation on Kafka header configuration: https://ibm.biz/kafka-trace-correlation-header.'
);
}
function instrumentProducer(ProducerClass) {

@@ -320,12 +307,2 @@ shimmer.wrap(ProducerClass.prototype, 'produce', shimProduce);

function readTraceLevelBinary(instanaHeadersAsObject) {
if (instanaHeadersAsObject[constants.kafkaLegacyTraceLevelHeaderName]) {
const traceLevelBuffer = instanaHeadersAsObject[constants.kafkaLegacyTraceLevelHeaderName];
if (Buffer.isBuffer(traceLevelBuffer) && traceLevelBuffer.length >= 1) {
return String(traceLevelBuffer.readInt8());
}
}
return '1';
}
function addTraceContextHeader(headers, span) {

@@ -378,3 +355,3 @@ if (!traceCorrelationEnabled) {

// CASE: Look for the the newer string header format first.
// Since v4, only 'string' format is supported.
if (instanaHeadersAsObject[constants.kafkaTraceIdHeaderName]) {

@@ -395,19 +372,10 @@ traceId = String(instanaHeadersAsObject[constants.kafkaTraceIdHeaderName]);

// CASE: Only fall back to legacy binary trace correlation headers if no new header is present.
if (traceId == null && parentSpanId == null && level == null) {
// The newer string header format has not been found, fall back to legacy binary headers.
if (instanaHeadersAsObject[constants.kafkaLegacyTraceContextHeaderName]) {
const traceContextBuffer = instanaHeadersAsObject[constants.kafkaLegacyTraceContextHeaderName];
if (Buffer.isBuffer(traceContextBuffer) && traceContextBuffer.length === 24) {
const traceContext = tracingUtil.readTraceContextFromBuffer(traceContextBuffer);
traceId = traceContext.t;
parentSpanId = traceContext.s;
}
}
level = readTraceLevelBinary(instanaHeadersAsObject);
}
return { level, traceId, longTraceId, parentSpanId };
}
function logDeprecationKafkaAvroMessage() {
logger.warn(
// eslint-disable-next-line max-len
'[Deprecation Warning] The support for kafka-avro library is deprecated and might be removed in the next major release. See https://github.com/waldophotos/kafka-avro/issues/120'
);
}

@@ -170,6 +170,11 @@ /*

// We pass `fallbackToSharedContext: true` to getCurrentSpan to access the GraphQL query context, which then
// WE DO NOT MAKE USE OF `cls.skipExitTracing` in the graphql instrumentation,
// because we have a special usage of `cls.getReducedSpan(true)`. Feel free to refactor this.
// CASE 1: We pass `fallbackToSharedContext: true` to getCurrentSpan to access the GraphQL query context, which then
// triggered this subscription query. We need to connect them.
// Additionally, if there is no active entry span, we fall back to the reduced span of the most recent entry span. See
// CASE 2: If there is no active entry span, we fall back to the reduced span of the most recent entry span. See
// comment in packages/core/src/tracing/clsHooked/unset.js#storeReducedSpan.
// CASE 3: We can ignore `allowRootExitSpan` is enabled, because subscriptions only work with the
// apollo-server-express and it doesn't make sense to enable `allowRootExitSpan` for this case.
const parentSpan = cls.getCurrentSpan(true) || cls.getReducedSpan(true);

@@ -312,3 +317,5 @@

}
const activeEntrySpan = cls.getCurrentSpan();
if (activeEntrySpan && activeEntrySpan.k === constants.ENTRY) {

@@ -315,0 +322,0 @@ // Most of the heavy lifting to trace Apollo Federation gateways (implemented by @apollo/gateway) is done by our

@@ -67,9 +67,10 @@ /*

let w3cTraceContext = cls.getW3cTraceContext();
const skipTracingResult = cls.skipExitTracing({ isActive, extendedResponse: true, skipParentSpanCheck: true });
// If there is no active entry span, we fall back to the reduced span of the most recent entry span. See comment in
// packages/core/src/tracing/clsHooked/unset.js#storeReducedSpan.
const parentSpan = cls.getCurrentSpan() || cls.getReducedSpan();
const skipTracingResult = cls.skipExitTracing({
isActive,
extendedResponse: true,
checkReducedSpan: true
});
if (skipTracingResult.skip || !parentSpan || constants.isExitSpan(parentSpan)) {
if (skipTracingResult.skip) {
if (skipTracingResult.suppressed) {

@@ -76,0 +77,0 @@ addTraceLevelHeader(headers, '0', w3cTraceContext);

@@ -134,8 +134,2 @@ /*

const incomingServiceName =
span.data.http.header && span.data.http.header[constants.serviceNameHeaderNameLowerCase];
if (incomingServiceName != null) {
span.data.service = incomingServiceName;
}
if (!headers['x-instana-t']) {

@@ -142,0 +136,0 @@ // In cases where we have started a fresh trace (that is, there is no X-INSTANA-T in the incoming request

@@ -181,16 +181,8 @@ /*

extendedResponse: true,
skipParentSpanCheck: true,
skipIsTracing: true
checkReducedSpan: true
});
// If there is no active entry span, we fall back to the reduced span of the most recent entry span. See comment in
// packages/core/src/tracing/clsHooked/unset.js#storeReducedSpan.
const parentSpan = cls.getCurrentSpan() || cls.getReducedSpan();
const parentSpan = skipTracingResult.parentSpan;
if (
skipTracingResult.skip ||
!parentSpan ||
constants.isExitSpan(parentSpan) ||
shouldBeBypassed(parentSpan, options)
) {
if (skipTracingResult.skip || shouldBeBypassed(skipTracingResult.parentSpan, options)) {
let traceLevelHeaderHasBeenAdded = false;

@@ -211,3 +203,4 @@ if (skipTracingResult.suppressed) {

cls.ns.run(() => {
const span = cls.startSpan('node.http.client', constants.EXIT, parentSpan.t, parentSpan.s);
// NOTE: Check for parentSpan existence, because of allowRootExitSpan is being enabled
const span = cls.startSpan('node.http.client', constants.EXIT, parentSpan?.t, parentSpan?.s);

@@ -214,0 +207,0 @@ // startSpan updates the W3C trace context and writes it back to CLS, so we have to refetch the updated context

@@ -107,8 +107,2 @@ /*

const incomingServiceName =
span.data.http.header && span.data.http.header[constants.serviceNameHeaderNameLowerCase];
if (incomingServiceName != null) {
span.data.service = incomingServiceName;
}
if (!req.headers['x-instana-t']) {

@@ -115,0 +109,0 @@ // In cases where we have started a fresh trace (that is, there is no X-INSTANA-T in the incoming request

@@ -86,9 +86,6 @@ /*

extendedResponse: true,
skipParentSpanCheck: true,
skipIsTracing: true
checkReducedSpan: true
});
// If there is no active entry span, we fall back to the reduced span of the most recent entry span. See comment in
// packages/core/src/tracing/clsHooked/unset.js#storeReducedSpan.
const parentSpan = cls.getCurrentSpan() || cls.getReducedSpan();
const parentSpan = skipTracingResult.parentSpan;

@@ -101,6 +98,8 @@ const originalThis = this;

if (skipTracingResult.skip || !parentSpan || constants.isExitSpan(parentSpan)) {
// If allowRootExitSpan is not enabled, then an exit span can't be traced alone
if (skipTracingResult.skip) {
if (skipTracingResult.suppressed) {
injectSuppressionHeader(originalArgs, w3cTraceContext);
}
return originalFetch.apply(originalThis, originalArgs);

@@ -110,3 +109,4 @@ }

return cls.ns.runAndReturn(() => {
const span = cls.startSpan('node.http.client', constants.EXIT, parentSpan.t, parentSpan.s);
// NOTE: Check for parentSpan existence, because of allowRootExitSpan is being enabled
const span = cls.startSpan('node.http.client', constants.EXIT, parentSpan?.t, parentSpan?.s);

@@ -113,0 +113,0 @@ // startSpan updates the W3C trace context and writes it back to CLS, so we have to refetch the updated context

@@ -8,3 +8,2 @@ /*

const semver = require('semver');
const { AsyncHooksContextManager } = require('@opentelemetry/context-async-hooks');

@@ -14,2 +13,3 @@ const api = require('@opentelemetry/api');

const constants = require('../constants');
const supportedVersion = require('../supportedVersion');

@@ -28,4 +28,2 @@ // NOTE: Please refrain from utilizing third-party instrumentations.

module.exports.minimumNodeJsVersion = '14.0.0';
// NOTE: using a logger might create a recursive execution

@@ -35,5 +33,3 @@ // logger.debug -> creates fs call -> calls transformToInstanaSpan -> calls logger.debug

module.exports.init = (_config, cls) => {
// CASE: otel offically does not support Node < 14
// https://github.com/open-telemetry/opentelemetry-js/tree/main#supported-runtimes
if (semver.lt(process.versions.node, module.exports.minimumNodeJsVersion)) {
if (!supportedVersion(process.versions.node)) {
return;

@@ -40,0 +36,0 @@ }

@@ -9,2 +9,3 @@ /*

const semver = require('semver');
const { minimumNodeJsVersion } = require('../util/nodeJsVersionCheck');

@@ -14,3 +15,3 @@ /** @type {(version: string) => boolean} */

const includePrerelease = process.env.NODE_ENV === 'test';
return semver.satisfies(version, '>=14.0.0', { includePrerelease });
return semver.satisfies(version, `>=${minimumNodeJsVersion}`, { includePrerelease });
};

@@ -10,11 +10,2 @@ /*

/**
* Check if the given Node.js version supports ESM.
* @param {string} version - The Node.js version to check.
* @returns {boolean} - True if ESM is supported, false otherwise.
*/
exports.esmSupportedVersion = function esmSupportedVersion(version) {
return semver.gte(version, '14.0.0');
};
/**
* Check if the given Node.js version is the latest version that supports ESM.

@@ -21,0 +12,0 @@ * @param {string} version - The Node.js version to check.

@@ -22,3 +22,3 @@ /*

*
* Note: In the next major release (4.x), we plan to transition all CJS modules in ESM applications to be
* Note: In the next major release, we might transition all CJS modules in ESM applications to be
* supported with iitmHook. For now, this approach is chosen to minimize risk.

@@ -25,0 +25,0 @@ */

@@ -11,3 +11,2 @@ /*

atMostOnce: require('./atMostOnce'),
buffer: require('./buffer'),
clone: require('./clone'),

@@ -14,0 +13,0 @@ compression: require('./compression'),

@@ -72,4 +72,3 @@ /*

/\/request\/index.js/,
/\/@apollo\/federation\/dist\//,
/\/q\/q.js/
/\/@apollo\/federation\/dist\//
];

@@ -76,0 +75,0 @@

@@ -10,3 +10,3 @@ /*

*/
exports.minimumNodeJsVersion = 14;
exports.minimumNodeJsVersion = 18;

@@ -13,0 +13,0 @@ /**

@@ -29,2 +29,3 @@ /*

* @property {KafkaTracingOptions} [kafka]
* @property {boolean} [allowRootExitSpan]
*/

@@ -40,3 +41,2 @@

* @property {boolean} [traceCorrelation]
* @property {import('../tracing/constants').KafkaTraceCorrelationFormat} [headerFormat]
*/

@@ -86,3 +86,2 @@

* @property {boolean} [traceCorrelation]
* @property {string} [headerFormat]
*/

@@ -109,2 +108,3 @@

useOpentelemetry: true,
allowRootExitSpan: false,
automaticTracingEnabled: true,

@@ -123,4 +123,3 @@ activateImmediately: false,

kafka: {
traceCorrelation: true,
headerFormat: constants.kafkaHeaderFormatDefault
traceCorrelation: true
}

@@ -134,4 +133,2 @@ },

const validKafkaHeaderFormats = ['binary', 'string', 'both'];
const validSecretsMatcherModes = ['equals-ignore-case', 'equals', 'contains-ignore-case', 'contains', 'regex', 'none'];

@@ -229,2 +226,3 @@

normalizeTracingKafka(config);
normalizeAllowRootExitSpan(config);
}

@@ -257,2 +255,24 @@

*/
function normalizeAllowRootExitSpan(config) {
if (config.tracing.allowRootExitSpan === false) {
return;
}
if (config.tracing.allowRootExitSpan === true) {
return;
}
const INSTANA_ALLOW_ROOT_EXIT_SPAN = process.env['INSTANA_ALLOW_ROOT_EXIT_SPAN']?.toLowerCase();
config.tracing.allowRootExitSpan =
INSTANA_ALLOW_ROOT_EXIT_SPAN === '1' ||
INSTANA_ALLOW_ROOT_EXIT_SPAN === 'true' ||
defaults.tracing.allowRootExitSpan;
return;
}
/**
*
* @param {InstanaConfig} config
*/
function normalizeUseOpentelemetry(config) {

@@ -559,29 +579,2 @@ if (config.tracing.useOpentelemetry === false) {

}
// @ts-ignore
config.tracing.kafka.headerFormat =
config.tracing.kafka.headerFormat || process.env.INSTANA_KAFKA_HEADER_FORMAT || defaults.tracing.kafka.headerFormat;
if (typeof config.tracing.kafka.headerFormat !== 'string') {
logger.warn(
`The value of config.tracing.kafka.headerFormat ("${config.tracing.kafka.headerFormat}") is not a string. ` +
`Assuming the default value "${defaults.tracing.kafka.headerFormat}".`
);
config.tracing.kafka.headerFormat = defaults.tracing.kafka.headerFormat;
return;
}
// @ts-ignore
config.tracing.kafka.headerFormat = config.tracing.kafka.headerFormat.toLowerCase();
if (validKafkaHeaderFormats.indexOf(config.tracing.kafka.headerFormat) < 0) {
logger.warn(
'The value of config.tracing.kafka.headerFormat (or the value of INSTANA_KAFKA_HEADER_FORMAT) ' +
`("${config.tracing.kafka.headerFormat}") is not a supported header format. Assuming the default ` +
`value "${defaults.tracing.kafka.headerFormat}".`
);
config.tracing.kafka.headerFormat = defaults.tracing.kafka.headerFormat;
return;
}
if (config.tracing.kafka.headerFormat !== defaults.tracing.kafka.headerFormat) {
logger.info(`Kafka trace correlation header format has been set to "${config.tracing.kafka.headerFormat}".`);
}
}

@@ -588,0 +581,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc