From cafba2d789cf954000f2ad0e1c6a7828894adf2c Mon Sep 17 00:00:00 2001 From: Joey Zhao <5253430+joeyzhao2018@users.noreply.github.com> Date: Fri, 24 Jul 2026 15:28:05 -0400 Subject: [PATCH 1/2] feat(handler): register dd-trace ESM loader hook programmatically ESM instrumentation (orchestrion/rewriter-based, e.g. the @aws/durable-execution-sdk-js integration) depends on dd-trace's loader-hook.mjs being registered via --import dd-trace/initialize.mjs. Runtimes that ignore NODE_OPTIONS (e.g. the AWS durable functions runtime) never register it, so ESM handlers get no rewritten instrumentation while CJS handlers work. Mirror what dd-trace/initialize.mjs does: resolve the dd-trace package root and Module.register('./loader-hook.mjs') from handler.mjs before the user's ESM handler is imported. Guards: - Skip when the loader hook is already active via NODE_OPTIONS/execArgv (module.register() does not dedupe; a second registration would run the rewriter twice on every module). - Skip when the tracer failed to initialize, matching initialize.mjs. - Use a namespace import for 'module' so runtimes without module.register (< 18.19 / < 20.6) don't fail at link time. --- src/handler.mjs | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/handler.mjs b/src/handler.mjs index 8e6ea3db0..b7e875a03 100644 --- a/src/handler.mjs +++ b/src/handler.mjs @@ -1,3 +1,6 @@ +import * as Module from "module"; +import { dirname } from "path"; +import { pathToFileURL } from "url"; import { datadog, datadogHandlerEnvVar, @@ -15,8 +18,38 @@ if (process.env.DD_TRACE_DISABLED_PLUGINS === undefined) { logDebug("disabled the dd-trace plugin 'fs'"); } +// True when dd-trace's ESM loader hook is already active via NODE_OPTIONS or +// execArgv (e.g. the standard ESM setup: NODE_OPTIONS=--import dd-trace/initialize.mjs). +// module.register() does not dedupe — a second registration chains another hooks +// worker and every module would be rewritten twice. +function esmLoaderAlreadyRegistered() { + const sources = [process.env.NODE_OPTIONS || "", ...process.execArgv]; + return sources.some((source) => /dd-trace[\\/][^\s]*\.mjs/.test(source)); +} + if (getEnvValue("DD_TRACE_ENABLED", "true").toLowerCase() === "true") { - initTracer(); + const tracer = initTracer(); + + // Register dd-trace's ESM loader hooks programmatically so that ESM imports + // (e.g. @aws/durable-execution-sdk-js) are rewritten for instrumentation. + // Normally this happens via --import dd-trace/initialize.mjs, but the AWS + // durable runtime ignores NODE_OPTIONS so the loader is never registered. + // This mirrors what dd-trace/initialize.mjs does at lines 77-84, including + // only registering when the tracer initialized — a bail-out leaves the hooks + // worker with nothing to instrument and can keep the process from exiting. + if (tracer && typeof Module.register === "function" && !esmLoaderAlreadyRegistered()) { + try { + const require = Module.createRequire(import.meta.url); + const ddTraceEntry = require.resolve("dd-trace", { + paths: ["/var/task/node_modules", ...(require.resolve.paths("dd-trace") || [])], + }); + const ddTraceRoot = pathToFileURL(dirname(ddTraceEntry) + "/").href; + Module.register("./loader-hook.mjs", ddTraceRoot); + logDebug("registered dd-trace ESM loader hook for ESM instrumentation"); + } catch (error) { + logDebug("failed to register dd-trace ESM loader hook", { error }); + } + } } const taskRootEnv = getEnvValue(lambdaTaskRootEnvVar, ""); From 8259c898ba27f98388111c88fe229b4cb60619a0 Mon Sep 17 00:00:00 2001 From: Joey Zhao <5253430+joeyzhao2018@users.noreply.github.com> Date: Tue, 28 Jul 2026 11:45:33 -0400 Subject: [PATCH 2/2] fix(handler): detect dd-trace/register.js in ESM loader dedupe guard --require dd-trace/register.js (and --import of it, which works on modern Node via CJS interop) also registers loader-hook.mjs, but the .mjs-only pattern missed it, causing a second registration and double rewriting. Match register.js alongside .mjs preload entry points. --- src/handler.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/handler.mjs b/src/handler.mjs index b7e875a03..ae5860104 100644 --- a/src/handler.mjs +++ b/src/handler.mjs @@ -19,12 +19,15 @@ if (process.env.DD_TRACE_DISABLED_PLUGINS === undefined) { } // True when dd-trace's ESM loader hook is already active via NODE_OPTIONS or -// execArgv (e.g. the standard ESM setup: NODE_OPTIONS=--import dd-trace/initialize.mjs). +// execArgv. Covers every preload entry point that registers the hook: +// --import dd-trace/initialize.mjs (documented ESM setup) +// --loader dd-trace/initialize.mjs (legacy) +// --require dd-trace/register.js (documented CJS setup; also registers the hook) // module.register() does not dedupe — a second registration chains another hooks // worker and every module would be rewritten twice. function esmLoaderAlreadyRegistered() { const sources = [process.env.NODE_OPTIONS || "", ...process.execArgv]; - return sources.some((source) => /dd-trace[\\/][^\s]*\.mjs/.test(source)); + return sources.some((source) => /dd-trace[\\/](?:[^\s]*\.mjs|register\.js)/.test(source)); } if (getEnvValue("DD_TRACE_ENABLED", "true").toLowerCase() === "true") {