You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@instana/serverless

Package Overview
Dependencies
Maintainers
4
Versions
291
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@instana/serverless - npm Package Compare versions

Comparing version
4.20.0
to
4.21.0
+11
-0
CHANGELOG.md

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

# [4.21.0](https://github.com/instana/nodejs/compare/v4.20.0...v4.21.0) (2025-07-31)
### Features
* **aws-lambda:** improved overhaul performance ([#1315](https://github.com/instana/nodejs/issues/1315)) ([4620113](https://github.com/instana/nodejs/commit/46201132dac6a73e7719d085c4edaa5c5a5ae526))
# [4.20.0](https://github.com/instana/nodejs/compare/v4.19.1...v4.20.0) (2025-07-30)

@@ -8,0 +19,0 @@

+3
-3
{
"name": "@instana/serverless",
"version": "4.20.0",
"version": "4.21.0",
"description": "Internal utility package for serverless Node.js tracing and monitoring with Instana",

@@ -68,7 +68,7 @@ "author": {

"dependencies": {
"@instana/core": "4.20.0",
"@instana/core": "4.21.0",
"agent-base": "^6.0.2",
"https-proxy-agent": "^7.0.2"
},
"gitHead": "6b4956a569bc7fb5437028e20896bab3da2143a4"
"gitHead": "356f582d62210fb69f7174a417ab70ad7c21c3e7"
}

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

const constants = require('./constants');
const layerExtensionHostname = 'localhost';

@@ -20,3 +19,15 @@ const layerExtensionPort = process.env.INSTANA_LAYER_EXTENSION_PORT

const timeoutEnvVar = 'INSTANA_TIMEOUT';
const layerExtensionTimeout = process.env.INSTANA_LAMBDA_EXTENSION_TIMEOUT_IN_MS
// NOTE: The heartbeat is usually really, really fast (<30ms).
const layerExtensionHeartbeatTimeout = 100;
// NOTE: The initial heartbeat can be very slow when the Lambda is in cold start.
const initialLayerExtensionHeartbeatTimeout = 2000;
// NOTE: When lambda is in cold start, the communication between the handler
// and the extension can take a while. We need to have a bigger timeout
// for the initially.
const initialLayerExtensionRequestTimeout = 2000;
const layerExtensionRequestTimeout = process.env.INSTANA_LAMBDA_EXTENSION_TIMEOUT_IN_MS
? Number(process.env.INSTANA_LAMBDA_EXTENSION_TIMEOUT_IN_MS)

@@ -26,9 +37,7 @@ : 500;

const proxyEnvVar = 'INSTANA_ENDPOINT_PROXY';
let proxyAgent;
const disableCaCheckEnvVar = 'INSTANA_DISABLE_CA_CHECK';
const disableCaCheck = process.env[disableCaCheckEnvVar] === 'true';
let requestHasFailed = false;
let proxyAgent;
let warningsHaveBeenLogged = false;
let firstRequestToExtension = true;

@@ -38,5 +47,6 @@ const defaults = {

identityProvider: null,
stopSendingOnFailure: true,
isLambdaRequest: false,
backendTimeout: 500,
useLambdaExtension: false
useLambdaExtension: false,
retries: false
};

@@ -69,2 +79,4 @@

const requests = {};
exports.init = function init(opts) {

@@ -126,14 +138,7 @@ options = Object.assign(defaults, opts);

exports.setLogger = function setLogger(_logger) {
logger = _logger;
};
/**
*
* "finalLambdaRequest":
* When using AWS Lambda, we send metrics and spans together
* using the function "sendBundle". The variable was invented to indicate
* that this is the last request to be sent before the AWS Lambda runtime might freeze the process.
* Currently, there is exactly one request to send all the data and
* the variable is always true.
* using the function "sendBundle" at the end of the invocation - before the AWS Lambda
* runtime might freeze the process. The span buffer sends data reguarly using `sendSpans`.
*/

@@ -159,8 +164,14 @@ exports.sendBundle = function sendBundle(bundle, finalLambdaRequest, callback) {

let heartbeatInterval;
let heartbeatIsActive = false;
function scheduleLambdaExtensionHeartbeatRequest() {
const executeHeartbeat = () => {
const executeHeartbeat = (heartbeatOpts = {}) => {
if (heartbeatIsActive) {
return;
}
const startTime = Date.now();
const requestId = getRequestId();
const startTime = Date.now();
logger.debug(`[${requestId}] Executing Heartbeat request to Lambda extension.`);
heartbeatIsActive = true;

@@ -173,17 +184,17 @@ const req = uninstrumented.http.request(

method: 'POST',
Connection: 'close',
// This sets a timeout for establishing the socket connection, see setTimeout below for a timeout for an
// idle connection after the socket has been opened.
timeout: layerExtensionTimeout
headers: {
Connection: 'keep-alive'
}
},
res => {
logger.debug(`[${requestId}] Took ${Date.now() - startTime} ms to send heartbeat to the extension.`);
if (res.statusCode === 200) {
logger.debug(`[${requestId}] The Instana Lambda extension Heartbeat request has succeeded.`);
logger.debug(`[${requestId}] The Instana Lambda extension heartbeat request has succeeded.`);
} else {
handleHeartbeatError(
new Error(
`[${requestId}] The Instana Lambda extension Heartbeat request has ` +
`returned an unexpected status code: ${res.statusCode}.`
)
logger.debug(
`[${requestId}] The Instana Lambda extension heartbeat request has failed. Status Code: ${res.statusCode}`
);
handleHeartbeatError();
}

@@ -193,2 +204,3 @@

// we need to register the handlers to avoid running into a timeout
// because the request expects to receive body data
});

@@ -200,2 +212,4 @@

logger.debug(`[${requestId}] Took ${duration}ms to receive response from extension`);
heartbeatIsActive = false;
});

@@ -205,21 +219,17 @@ }

req.once('finish', () => {
const endTime = Date.now();
const duration = endTime - startTime;
logger.debug(`[${requestId}] Took ${duration}ms to send data to extension`);
req.once('error', e => {
logger.debug(`[${requestId}] The Heartbeat request did not succeed.`, e);
// req.destroyed indicates that we have run into a timeout and have
// already handled the timeout error.
if (req.destroyed) {
return;
}
handleHeartbeatError();
});
function handleHeartbeatError(e) {
// Make sure we do not try to talk to the Lambda extension again.
options.useLambdaExtension = false;
clearInterval(heartbeatInterval);
req.setTimeout(heartbeatOpts.heartbeatTimeout, () => {
logger.debug(`[${requestId}] Heartbeat request timed out.`);
logger.debug(
`[${requestId}] The Instana Lambda extension Heartbeat request did not succeed. ` +
'Falling back to talking to the Instana back ' +
`end directly. ${e?.message} ${e?.stack}`
);
}
req.once('error', e => {
// req.destroyed indicates that we have run into a timeout and have already handled the timeout error.

@@ -230,11 +240,13 @@ if (req.destroyed) {

handleHeartbeatError(e);
handleHeartbeatError();
});
// Handle timeouts that occur after connecting to the socket (no response from the extension).
req.setTimeout(layerExtensionTimeout, () => {
handleHeartbeatError(new Error(`[${requestId}] The Lambda extension Heartbeat request timed out.`));
function handleHeartbeatError() {
logger.warn(`[${requestId}] Heartbeat request failed. Falling back to the serverless acceptor instead.`);
destroyRequest(req);
});
options.useLambdaExtension = false;
clearInterval(heartbeatInterval);
cleanupRequest(req);
heartbeatIsActive = false;
}

@@ -245,11 +257,15 @@ req.end();

// call immediately
executeHeartbeat();
// timeout is bigger because of possible coldstart
executeHeartbeat({ heartbeatTimeout: initialLayerExtensionHeartbeatTimeout });
// NOTE: it is fine to use interval, because the req timeout is 300ms and the interval is 500
heartbeatInterval = setInterval(executeHeartbeat, 500);
heartbeatInterval = setInterval(() => {
logger.debug('Heartbeat interval is alive.');
executeHeartbeat({ heartbeatTimeout: layerExtensionHeartbeatTimeout });
}, 300);
heartbeatInterval.unref();
}
function getTransport(localUseLambdaExtension) {
if (localUseLambdaExtension) {
function getTransport() {
if (options.useLambdaExtension) {
// The Lambda extension is always HTTP without TLS on localhost.

@@ -262,7 +278,16 @@ return uninstrumented.http;

function getBackendTimeout(localUseLambdaExtension) {
return localUseLambdaExtension ? layerExtensionTimeout : options.backendTimeout;
function getBackendTimeout() {
if (options.useLambdaExtension) {
if (firstRequestToExtension) {
firstRequestToExtension = false;
return initialLayerExtensionRequestTimeout;
} else {
return layerExtensionRequestTimeout;
}
}
return options.backendTimeout;
}
function send({ resourcePath, payload, finalLambdaRequest, callback, requestId }) {
function send({ resourcePath, payload, finalLambdaRequest, callback, tries, requestId }) {
let callbackWasCalled = false;

@@ -275,15 +300,4 @@ const handleCallback = args => {

// We need a local copy of the global useLambdaExtension variable, otherwise it might be changed concurrently by
// scheduleLambdaExtensionHeartbeatRequest. But we need to remember the value at the time we _started_ the request to
// decide whether to fall back to sending to the back end directly or give up sending data completely.
let localUseLambdaExtension = options.useLambdaExtension;
if (requestHasFailed && options.stopSendingOnFailure) {
logger.info(
`[${requestId}] Not attempting to send data to ${resourcePath} as a previous request ` +
'has already timed out or failed.'
);
handleCallback();
return;
if (tries === undefined) {
tries = 0;
}

@@ -315,3 +329,3 @@

const requestPath =
localUseLambdaExtension || environmentUtil.getBackendPath() === '/'
options.useLambdaExtension || environmentUtil.getBackendPath() === '/'
? resourcePath

@@ -324,4 +338,4 @@ : environmentUtil.getBackendPath() + resourcePath;

const reqOptions = {
hostname: localUseLambdaExtension ? layerExtensionHostname : environmentUtil.getBackendHost(),
port: localUseLambdaExtension ? layerExtensionPort : environmentUtil.getBackendPort(),
hostname: options.useLambdaExtension ? layerExtensionHostname : environmentUtil.getBackendHost(),
port: options.useLambdaExtension ? layerExtensionPort : environmentUtil.getBackendPort(),
path: requestPath,

@@ -332,2 +346,3 @@ method: 'POST',

'Content-Length': Buffer.byteLength(serializedPayload),
Connection: 'keep-alive',
[constants.xInstanaHost]: hostHeader,

@@ -344,5 +359,7 @@ [constants.xInstanaKey]: environmentUtil.getInstanaAgentKey()

reqOptions.timeout = getBackendTimeout(localUseLambdaExtension);
// This timeout is for **inactivity** - Backend sends no data at all
// So if the timeout is set to 500ms, it does not mean that the request will be aborted after 500ms
reqOptions.timeout = getBackendTimeout(options.useLambdaExtension);
if (proxyAgent && !localUseLambdaExtension) {
if (proxyAgent && !options.useLambdaExtension) {
reqOptions.agent = proxyAgent;

@@ -352,4 +369,5 @@ }

let req;
const skipWaitingForHttpResponse = !proxyAgent && !localUseLambdaExtension;
const transport = getTransport(localUseLambdaExtension);
const skipWaitingForHttpResponse = !proxyAgent && !options.useLambdaExtension;
const transport = getTransport(options.useLambdaExtension);
const start = Date.now();

@@ -383,3 +401,3 @@ if (skipWaitingForHttpResponse) {

if (finalLambdaRequest) {
cleanupRequest(req);
cleanupRequests();
}

@@ -391,2 +409,22 @@

if (options.isLambdaRequest) {
requests[requestId] = req;
}
req.on('response', res => {
const { statusCode } = res;
if (statusCode >= 200 && statusCode < 300) {
logger.debug(`${requestId} Received response from Instana (${requestPath}).`);
} else {
logger.debug(`${requestId} Received response from Instana has been failed (${requestPath}).`);
}
logger.debug(`[${requestId}] Received HTTP status code ${statusCode} from Instana (${requestPath}).`);
logger.debug(`[${requestId}] Sending and receiving data to Instana took: ${Date.now() - start} ms.`);
cleanupRequest(req);
delete requests[requestId];
});
// See above for the difference between the timeout attribute in the request options and handling the 'timeout'

@@ -398,7 +436,19 @@ // event. This only adds a read timeout after the connection has been established and we need the timout attribute

// > socket.setTimeout() will be called.
req.on('timeout', () =>
onTimeout(localUseLambdaExtension, req, resourcePath, payload, finalLambdaRequest, handleCallback, requestId)
);
req.on('timeout', () => {
logger.debug(`[${requestId}] Timeout while sending data to Instana (${requestPath}).`);
if (options.isLambdaRequest) {
delete requests[requestId];
}
onTimeout(req, resourcePath, payload, finalLambdaRequest, handleCallback, tries, requestId);
});
req.on('error', e => {
logger.debug(`[${requestId}] Error while sending data to Instana (${requestPath}): ${e?.message} ${e?.stack}`);
if (options.isLambdaRequest) {
delete requests[requestId];
}
// CASE: we manually destroy streams, skip these errors

@@ -414,3 +464,3 @@ // Otherwise we will produce `Error: socket hang up` errors in the logs

if (localUseLambdaExtension) {
if (options.useLambdaExtension) {
// This is a failure from talking to the Lambda extension on localhost. Most probably it is simply not available

@@ -421,22 +471,23 @@ // because @instana/aws-lambda has been installed as a normal npm dependency instead of using Instana's

// target in place.
logger.debug(
`[${requestId}] Could not connect to the Instana Lambda extension. ` +
`Falling back to talking to the Instana back end directly: ${e?.message} ${e?.stack}`
);
logger.debug(`[${requestId}] Could not connect to the Instana Lambda extension (tries: ${tries}).`);
// Make sure we do not try to talk to the Lambda extension again.
options.useLambdaExtension = localUseLambdaExtension = false;
clearInterval(heartbeatInterval);
if (options.retries === false || tries >= 1) {
clearInterval(heartbeatInterval);
// Retry the request immediately, this time sending it to serverless-acceptor directly.
send({ resourcePath, payload, finalLambdaRequest, callback, requestId });
// Retry the request immediately, this time sending it to serverless-acceptor directly.
logger.warn(
// eslint-disable-next-line max-len
`[${requestId}] Trying to send data to Instana serverless acceptor instead because the Lambda extension cannot be reached in time.`
);
options.useLambdaExtension = false;
return send({ resourcePath, payload, finalLambdaRequest, callback, tries: 0, requestId });
}
logger.debug(`[${requestId}] Retrying...`);
send({ resourcePath, payload, finalLambdaRequest, callback, tries: tries + 1, requestId });
} else {
// We are not using the Lambda extension, because we are either not in an AWS Lambda, or a previous request to the
// extension has already failed. Thus, this is a failure from talking directly to serverless-acceptor
// (or a user-provided proxy).
requestHasFailed = true;
if (proxyAgent) {
logger.warn(
`[${requestId}] Could not send traces and metrics to Instana. Could not connect to the configured proxy ` +
`[${requestId}] Could not send trace data to ${resourcePath}. Could not connect to the configured proxy ` +
`${process.env[proxyEnvVar]}.` +

@@ -447,3 +498,3 @@ `${e?.message} ${e?.stack}`

logger.warn(
`[${requestId}] Could not send traces and metrics to Instana. ` +
`[${requestId}] Could not send trace data to ${resourcePath}. ` +
`The Instana back end seems to be unavailable. ${e?.message} , ${e?.stack}`

@@ -453,6 +504,13 @@ );

handleCallback(e);
if (options.retries === false || tries >= 1) {
logger.debug(`[${requestId}] Giving up...`);
return handleCallback(e);
}
logger.debug(`[${requestId}] Retrying...`);
send({ resourcePath, payload, finalLambdaRequest, callback, tries: tries + 1, requestId });
}
});
// This only indicates that the request has been successfully send! Independent of the response!
req.on('finish', () => {

@@ -470,5 +528,7 @@ logger.debug(

if (skipWaitingForHttpResponse) {
// NOTE: When the callback of `.end` is called, the data was successfully send to the server.
// That does not mean the server has responded in any way!
req.end(serializedPayload, () => {
if (finalLambdaRequest) {
cleanupRequest(req);
if (options.isLambdaRequest && finalLambdaRequest) {
cleanupRequests();
}

@@ -486,27 +546,30 @@

function onTimeout(localUseLambdaExtension, req, resourcePath, payload, finalLambdaRequest, handleCallback, requestId) {
if (localUseLambdaExtension) {
function onTimeout(req, resourcePath, payload, finalLambdaRequest, handleCallback, tries, requestId) {
if (options.useLambdaExtension) {
// This is a timeout from talking to the Lambda extension on localhost. Most probably it is simply not available
// because @instana/aws-lambda has been installed as a normal npm dependency instead of using Instana's
// Lambda layer. We use this failure as a signal to not try to the extension again and instead fall back to
// talking to serverless-acceptor directly. We also immediately retry the current request with that new downstream
// talking to serverless acceptor directly. We also immediately retry the current request with that new downstream
// target in place.
logger.debug(
`[${requestId}] Request timed out while trying to talk to Instana Lambda extension. ` +
'Falling back to talking to the Instana back end directly.'
);
logger.debug(`[${requestId}] Request timed out while trying to talk to Instana Lambda extension.`);
// Make sure we do not try to talk to the Lambda extension again.
options.useLambdaExtension = localUseLambdaExtension = false;
clearInterval(heartbeatInterval);
destroyRequest(req);
cleanupRequest(req);
// Retry the request immediately, this time sending it to serverless-acceptor directly.
send({ resourcePath, payload, finalLambdaRequest, callback: handleCallback, requestId });
// CASE: It could be that a parallel request or the heartbeat already set useLambdaExtension to false.
if (options.retries === false || tries >= 1) {
clearInterval(heartbeatInterval);
// Retry the request immediately, this time sending it to serverless acceptor directly.
logger.warn(
`[${requestId}] Trying to send data to Instana serverless acceptor instead because the Lambda extension ` +
'cannot be reached in time.'
);
options.useLambdaExtension = false;
return send({ resourcePath, payload, finalLambdaRequest, callback: handleCallback, tries: 0, requestId });
}
logger.debug(`[${requestId}] Retrying...`);
send({ resourcePath, payload, finalLambdaRequest, callback: handleCallback, tries: tries + 1, requestId });
} else {
// We are not using the Lambda extension, because we are either not in an AWS Lambda, or a previous request to the
// extension has already failed. Thus, this is a timeout from talking directly to serverless-acceptor
// (or a user-provided proxy).
requestHasFailed = true;
// We need to destroy the request manually, otherwise it keeps the runtime running

@@ -518,3 +581,3 @@ // (and timing out) when:

// https://nodejs.org/api/http.html#http_event_timeout.
destroyRequest(req);
cleanupRequest(req);

@@ -528,6 +591,20 @@ const message =

logger.warn(`[${requestId}] ${message}`);
handleCallback(new Error(message));
if (options.retries === false || tries >= 1) {
logger.debug(`[${requestId}] Giving up...`);
return handleCallback();
}
logger.debug(`[${requestId}] Retrying...`);
send({ resourcePath, payload, finalLambdaRequest, callback: handleCallback, tries: tries + 1, requestId });
}
}
function cleanupRequests() {
Object.keys(requests).forEach(key => {
const requestToCleanup = requests[key];
cleanupRequest(requestToCleanup);
});
}
function cleanupRequest(req) {

@@ -534,0 +611,0 @@ // When the Node.js process is frozen while the request is pending, and then thawed later,