Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion nodejs/build-layer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ npm exec node-prune

echo "Preparing Splunk layer"
cp nodejs-otel-handler ./build/
cp loader.mjs ./build/
cp ../scripts/* ./build/
mkdir -p build/nodejs
mv node_modules ./build/nodejs/
Expand All @@ -27,7 +28,11 @@ find . \( -name "*.map" -o -name "*.ts" \) -type f -delete
# remove PACKAGES (CAREFUL!)
rm -rf ./nodejs/node_modules/graphql ./nodejs/node_modules/bson
# remove various folders (modules, protobuf definitions)
find . \( -name "protos" -o -name "esm" \) -type d -prune -exec rm -rf {} \;
find . -name "protos" -type d -prune -exec rm -rf {} \;
find . -name "esm" -type d \
-not -path "./nodejs/node_modules/import-in-the-middle/*" \
-not -path "./nodejs/node_modules/require-in-the-middle/*" \
-prune -exec rm -rf {} \;
# optimize PROTOBUF
find . -path "./nodejs/node_modules/protobufjs/*" \( -name "bin" -o -name "cli" -o -name "dist" -o -name "scripts" \) -type d -prune -exec rm -rf {} \;
# optimize SPLUNK
Expand Down
101 changes: 101 additions & 0 deletions nodejs/loader.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as module from 'module';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any changes in this from upstream? If not, then we should copy this into our build by simply pulling it from the git submodule. If there are, then there should be a comment stating where it came from and what the tweaks are so that future maintainers can have a chance to reconcile diffs.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, they seem identical, so the submodule would work great. At the very least we should provide attribution where it's coming from.

import * as path from 'path';
import * as fs from 'fs';

function _hasFolderPackageJsonTypeModule(folder) {
if (folder.endsWith('/node_modules')) {
return false;
}
const pj = path.join(folder, '/package.json');
if (fs.existsSync(pj)) {
try {
const pkg = JSON.parse(fs.readFileSync(pj).toString());
if (pkg) {
if (pkg.type === 'module') {
return true;
} else {
return false;
}
}
} catch (e) {
console.warn(`${pj} cannot be read, it will be ignored for ES module detection purposes.`, e);
return false;
}
}
if (folder === '/') {
return false;
}
return _hasFolderPackageJsonTypeModule(path.resolve(folder, '..'));
}

function _hasPackageJsonTypeModule(file) {
const jsPath = file + '.js';
if (fs.existsSync(jsPath)) {
return _hasFolderPackageJsonTypeModule(path.resolve(path.dirname(jsPath)));
}
return false;
}

function _resolveHandlerFileName() {
const taskRoot = process.env.LAMBDA_TASK_ROOT;
const handlerDef = process.env._HANDLER;
if (!taskRoot || !handlerDef) {
return null;
}
const handler = path.basename(handlerDef);
const moduleRoot = handlerDef.substr(0, handlerDef.length - handler.length);
const [module] = handler.split('.', 2);
return path.resolve(taskRoot, moduleRoot, module);
}

function _isHandlerAnESModule() {
try {
const handlerFileName = _resolveHandlerFileName();
if (!handlerFileName) {
return false;
}
if (fs.existsSync(handlerFileName + '.mjs')) {
return true;
} else if (fs.existsSync(handlerFileName + '.cjs')) {
return false;
} else {
return _hasPackageJsonTypeModule(handlerFileName);
}
} catch (e) {
console.error('Unknown error occurred while checking whether handler is an ES module', e);
return false;
}
}

function _isSupportedNodeForEsmLoaderHook() {
try {
const nodeVersion = process.versions && process.versions.node;
if (!nodeVersion) {
return false;
}
const [majorStr, minorStr] = nodeVersion.split('.', 3);
const major = Number.parseInt(majorStr, 10);
const minor = Number.parseInt(minorStr, 10);
if (!Number.isFinite(major) || !Number.isFinite(minor)) {
return false;
}
return major > 20 || (major === 20 && minor >= 6);
} catch {
return false;
}
}

let registered = false;

export function registerLoader() {
if (!registered) {
if (_isSupportedNodeForEsmLoaderHook() && typeof module.register === 'function') {
module.register('import-in-the-middle/hook.mjs', import.meta.url);
}
registered = true;
}
}

if (_isHandlerAnESModule()) {
registerLoader();
}
12 changes: 11 additions & 1 deletion nodejs/nodejs-otel-handler
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
#!/bin/bash
source /opt/splunk-default-config

export NODE_OPTIONS="${NODE_OPTIONS} --require /opt/wrapper.js"
node_version="$(node -p "process.versions.node" 2>/dev/null || echo "")"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't really correct, as the actual node executable to run is contained somewhere in $@ - bearing in mind that the chain of shell handlers could be deeper than 1. The complexity of supporting that is not worth it and likely we will wind up simply dropping support for nodes < 20 anyway and so this code could be dropped.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will mean we will need to drop support for versions < 20.
We could update everything and do a release that supports the older runtimes.
Then, make another release that drops older runtime support and adds the clean mjs fix.

PS: AWS will still allow Lambda functions to be updated for nodejs version 18 till Sep 30, 2026.
https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html#runtimes-deprecated

Copy link
Contributor

@seemk seemk Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Node.js 18 reached end of life last year. According to OTel's policy only Active or Maintenance LTS versions are supported. It would be completely fine to drop support for Node < 20 now (we just have to document it).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think that's the conclusion we're reaching. This will still need to wait for a regular update release before we merge it though, and that's gated on open-telemetry/opentelemetry-lambda#2122 (comment) at the moment.

node_major="${node_version%%.*}"
node_minor="${node_version#*.}"
node_minor="${node_minor%%.*}"

if [[ "$node_major" =~ ^[0-9]+$ ]] && [[ "$node_minor" =~ ^[0-9]+$ ]] && { [ "$node_major" -gt 20 ] || { [ "$node_major" -eq 20 ] && [ "$node_minor" -ge 6 ]; }; }
then
export NODE_OPTIONS="${NODE_OPTIONS} --import /opt/loader.mjs --require /opt/wrapper.js"
else
export NODE_OPTIONS="${NODE_OPTIONS} --require /opt/wrapper.js"
fi

if [[ $OTEL_RESOURCE_ATTRIBUTES != *"service.name="* ]]; then
export OTEL_RESOURCE_ATTRIBUTES="service.name=${AWS_LAMBDA_FUNCTION_NAME},${OTEL_RESOURCE_ATTRIBUTES}"
Expand Down