Skip to content

Commit 866639a

Browse files
committed
add manual datadog(handle) case where tracer isn't initialized.
1 parent 6dd3d8e commit 866639a

7 files changed

Lines changed: 735 additions & 28 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Manual-wrap companion to http-requests.js, run as the manual-http-requests
2+
// case — the exact shape of the AWS suite's http-requests function
3+
// (manual datadog() wrap, no userland dd-trace init).
4+
//
5+
// With no tracer initialized, TraceListener falls back to the library's own
6+
// patchHttp (src/trace/patch-http.ts), which wraps http/https and logs
7+
// "HTTP GET <url> TraceHeaders: [...]" per request. On real Lambda the
8+
// injected headers come from the platform's pass-through _X_AMZN_TRACE_ID;
9+
// under RIE that variable cannot be emulated (the RIC owns it), so the
10+
// golden pins the empty-context form (TraceHeaders: []) plus the mock echo
11+
// of the exact header set sent. The path under test — patchHttp wiring —
12+
// fails this golden the moment it stops wrapping or logging; the
13+
// context-dependent header values are pinned by the patch-http unit tests
14+
// and the AWS suite.
15+
const { datadog } = require("datadog-lambda-js");
16+
const { handle } = require("./http-requests");
17+
18+
module.exports.handle = datadog(handle);

‎integration_tests/container/cjs/http-requests.js‎

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
// Ported from integration_tests/http-requests.js with two seam adaptations:
2-
// 1. The AWS suite hits the real ip-ranges.datadoghq.com/.eu endpoints; the
3-
// local harness is hermetic, so URLs come from MOCK_HTTP_URLS (set by
4-
// run.sh to the mock-http container on the case's docker network). The
5-
// default keeps this file runnable outside the harness.
6-
// 2. The handler is left UNWRAPPED and runs through the npm redirect entry
7-
// (dist/handler.handler), because the manual-wrap path never produces
8-
// traces without a userland dd-trace init — true in the AWS suite too
9-
// (its http-requests snapshots contain no trace JSON). Redirect mode
10-
// initializes the tracer first, so the http plugin patches axios and the
11-
// mock echo below shows the injected x-datadog-* headers in the golden.
1+
// Ported from integration_tests/http-requests.js with one seam adaptation:
2+
// the AWS suite hits the real ip-ranges.datadoghq.com/.eu endpoints; the
3+
// local harness is hermetic, so URLs come from MOCK_HTTP_URLS (set by run.sh
4+
// to the mock-http container on the case's docker network). The default keeps
5+
// this file runnable outside the harness.
6+
//
7+
// This entry point is UNWRAPPED and runs through the npm redirect entry
8+
// (dist/handler.handler) in the cjs-http-requests case. Redirect mode
9+
// initializes dd-trace first, so the tracer's http plugin patches axios and
10+
// the mock echo below shows the injected x-datadog-* headers in the golden.
11+
// The manual-wrap variant of the AWS suite's function — which exercises the
12+
// library's own patchHttp fallback instead — lives in http-requests-manual.js
13+
// and runs as the manual-http-requests case.
1214
//
1315
// The mock echoes the request headers it received, and this handler logs them,
14-
// so the golden shows the injected downstream trace context
16+
// so both goldens show the injected downstream trace context
1517
// (x-datadog-trace-id / x-datadog-parent-id, normalized to XXXX).
1618
const { sendDistributionMetric } = require("datadog-lambda-js");
1719
const axios = require("axios");
@@ -36,4 +38,3 @@ async function handle(event, context) {
3638
}
3739

3840
module.exports.handle = handle;
39-

‎integration_tests_local/README.md‎

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ The case names are:
5454
| `manual-status-500` | manual wrap with userland `dd-trace` init returning a 500 API Gateway response (`DD_TRACE_ENABLED=true`); error span tag + enhanced error metrics |
5555
| `manual-send-metrics` | manual wrap calling `sendDistributionMetric` inside and outside the handler; per-event return values |
5656
| `manual-process-input` | manual wrap with userland `dd-trace` init reading the active span; per-event return values |
57-
| `cjs-http-requests` | downstream HTTP calls against a hermetic mock server; asserts injected `x-datadog-*`/`traceparent` headers and log injection |
57+
| `cjs-http-requests` | downstream HTTP calls against a hermetic mock server in redirect mode; asserts injected `x-datadog-*`/`traceparent` headers and log injection via dd-trace's http plugin |
58+
| `manual-http-requests` | same handler, manual wrap without userland dd-trace init; exercises the library's own `patchHttp` fallback (request wrapping + per-request logging + exact header set via mock echo) |
5859
| `cjs-custom-extractor` | `DD_TRACE_EXTRACTOR=extractor.extract`; asserts `_dd.parent_source: event` on the inferred span |
5960
| `cjs-proactive-init` | eager-init managed-instances RIE path with a 15 s init→invoke gap; asserts proactive-initialization markers on the raw logs |
6061

@@ -68,8 +69,12 @@ Unless `SKIP_PACK=true` is set, each run repacks the library under test
6869
like `scripts/run_integration_tests.sh` does, so the containers always test
6970
the working tree. The layer fixture instead assembles
7071
`integration_tests/container/layer/layer_pkg/` from the repo build via
71-
`prepare-layer.js`, mirroring the release Dockerfile's layer layout, with
72-
dependency versions pinned from the lockfile-resolved `node_modules` set.
72+
`prepare-layer.js`, mirroring the release Dockerfile's layer layout. The
73+
dependency manifest mirrors the release build's full dependency closure —
74+
production dependencies plus every package `scripts/move_ddtrace_dependency.js`
75+
moves (dd-trace, @datadog/native-appsec, @datadog/pprof, @opentelemetry/api,
76+
@opentelemetry/api-logs) — with versions pinned from the lockfile-resolved
77+
`node_modules` set.
7378

7479
The harness takes no flags or positional arguments — configuration is
7580
environment-only, and an unexpected argument is rejected rather than ignored.
@@ -126,6 +131,9 @@ filter to absorb it would be invisible. Current overrides:
126131
`index.mjs` frames) to error stack traces.
127132
- `cjs-http-requests_node18.log`: dd-trace's `dns`/`net` plugins fire on
128133
Node 18 only, adding `_dd.integration` span meta lines.
134+
- `manual-http-requests_node18.log`: Node 18's HTTP agent sends
135+
`connection: close` where 20+ send `keep-alive`; the mock echo pins the
136+
difference.
129137
- `cjs-proactive-init_node{18,20,24,26}.log`: Node's
130138
`TimeoutOverflowWarning` emission (count and JSON-record wrapping) varies
131139
by runtime major under the managed-instances path. The case's actual
@@ -137,16 +145,22 @@ instead of overwriting it — otherwise the last runtime to run would silently
137145
define the expectation for all of them. To capture a genuine per-runtime
138146
divergence, `touch` the override file first so the write targets it.
139147

140-
## The mock HTTP server (cjs-http-requests)
148+
## The mock HTTP server (cjs-http-requests, manual-http-requests)
141149

142-
`cjs-http-requests` exercises downstream header injection without touching
150+
Both HTTP cases exercise downstream header injection without touching
143151
the network: `run.sh` creates a per-run docker network, starts a mock server
144152
on it (reusing the Lambda base image with `--entrypoint node`), and passes
145153
`MOCK_HTTP_URLS` to the fixture. The mock echoes the request headers it
146154
received, so the golden pins exactly which trace-propagation headers the
147155
library injected. Container-to-container traffic stays on the throwaway
148156
network; only the readiness probe goes through the host.
149157

158+
The two cases cover the two injection paths: `cjs-http-requests` runs through
159+
the redirect entry (dd-trace initialized → the tracer's http plugin injects),
160+
while `manual-http-requests` is manual-wrapped with no tracer (TraceListener
161+
falls back to the library's own `patchHttp`). See "Known emulation gaps" for
162+
what the manual case cannot pin locally.
163+
150164
## Proactive initialization (cjs-proactive-init)
151165

152166
The library stamps `initTime = Date.now()` at wrapper-module load
@@ -247,6 +261,16 @@ endpoints). Do not diff one suite's output against the other's snapshots.
247261
tag stable.
248262
- Enhanced metrics that depend on platform-provided values (e.g. real
249263
memory size / billed duration) may be absent or differ.
264+
- `_X_AMZN_TRACE_ID` cannot be emulated: the RIC owns that variable and
265+
clears it per invocation, so neither a container env var, an invoke-request
266+
header, nor a module-load assignment survives to extraction time. On real
267+
Lambda the platform's pass-through trace header is what gives the
268+
manual-wrap `patchHttp` path a context to inject; locally the
269+
`manual-http-requests` golden therefore pins `TraceHeaders: []` plus the
270+
mock echo of the exact header set. The wiring (patch, wrap, per-request
271+
log) is fully covered — a migration that breaks it fails the golden — but
272+
the injected header *values* on that path are pinned only by the
273+
`patch-http` unit tests and the AWS suite.
250274
- The layer fixture installs into a plain `/opt/nodejs/node_modules`
251275
directory rather than a real published layer zip, so layer-version
252276
metadata (e.g. an exact layer ARN in tags) cannot be reproduced locally.

‎integration_tests_local/prepare-layer.js‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,21 @@ fs.writeFileSync(
4646
JSON.stringify({ name: pkg.name, version: pkg.version, main: "index.js", types: "index.d.ts" }, null, 2) + "\n",
4747
);
4848

49-
// 3. deps.package.json: datadog-lambda-js's production dependencies plus
50-
// dd-trace (which the release build moves from devDependencies), each pinned
51-
// to the version yarn actually installed.
49+
// 3. deps.package.json: datadog-lambda-js's production dependencies plus the
50+
// packages the release build moves from devDependencies, each pinned to the
51+
// version yarn actually installed. LAYER_MOVED_DEPENDENCIES must stay in sync
52+
// with scripts/move_ddtrace_dependency.js — the release layer's dependency
53+
// closure includes all of them, so the local layer image must too, otherwise
54+
// layer-only behavior that probes these modules (pprof, appsec, otel) is
55+
// tested on an absent-branch that real layer users never run.
56+
const LAYER_MOVED_DEPENDENCIES = [
57+
"dd-trace",
58+
"@datadog/native-appsec",
59+
"@datadog/pprof",
60+
"@opentelemetry/api",
61+
"@opentelemetry/api-logs",
62+
];
63+
5264
function installedVersion(name) {
5365
// Read the hoisted install location directly: require.resolve("<name>/package.json")
5466
// breaks on packages whose exports map doesn't expose ./package.json.
@@ -73,7 +85,9 @@ const dependencies = {};
7385
for (const name of Object.keys(pkg.dependencies || {})) {
7486
dependencies[name] = installedVersion(name);
7587
}
76-
dependencies["dd-trace"] = installedVersion("dd-trace");
88+
for (const name of LAYER_MOVED_DEPENDENCIES) {
89+
dependencies[name] = installedVersion(name);
90+
}
7791

7892
fs.writeFileSync(
7993
path.join(layerDir, "deps.package.json"),

‎integration_tests_local/run.sh‎

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
# manual-status-500 | cjs | status-code-500s.handle | manual wrap; API GW 500 -> span error + enhanced error metric
2323
# manual-send-metrics | cjs | send-metrics.handle | manual wrap; sendDistributionMetric via DD_FLUSH_TO_LOG
2424
# manual-process-input | cjs | process-input.handle | manual wrap; dd-trace child spans via tracer.wrap
25-
# cjs-http-requests | cjs | node_modules/datadog-lambda-js/dist/handler.handler | npm redirect; downstream HTTP header injection (hermetic mock server)
25+
# cjs-http-requests | cjs | node_modules/datadog-lambda-js/dist/handler.handler | npm redirect; downstream HTTP header injection via dd-trace's http plugin (hermetic mock server)
26+
# manual-http-requests | cjs | http-requests-manual.handle | manual wrap; patchHttp fallback wrapping + per-request logging (hermetic mock)
2627
# cjs-custom-extractor | cjs | node_modules/datadog-lambda-js/dist/handler.handler | DD_TRACE_EXTRACTOR custom extractor + _dd.parent_source
2728
# cjs-proactive-init | cjs | node_modules/datadog-lambda-js/dist/handler.handler | proactive-initialization markers (raw-log assertions)
2829
#
@@ -111,6 +112,7 @@ ALL_CASES=(
111112
"manual-send-metrics"
112113
"manual-process-input"
113114
"cjs-http-requests"
115+
"manual-http-requests"
114116
"cjs-custom-extractor"
115117
"cjs-proactive-init"
116118
)
@@ -183,16 +185,40 @@ function configure_case() {
183185
case_return_mode=per-event
184186
;;
185187
cjs-http-requests)
186-
# Redirect mode, not manual wrap: the manual-wrap path never
187-
# produces traces without a userland dd-trace init (true in the
188-
# AWS suite as well — its http-requests snapshots have no trace
189-
# JSON), and traces are what make the injected downstream headers
190-
# observable in the golden.
188+
# Redirect mode, not manual wrap: redirect mode initializes
189+
# dd-trace before the user handler loads, so the tracer's http
190+
# plugin does the injection and the golden pins full trace JSON.
191+
# The manual-wrap fallback (patchHttp) is covered separately by
192+
# manual-http-requests below.
191193
case_image=cjs
192194
case_entry_handler="node_modules/datadog-lambda-js/dist/handler.handler"
193195
case_needs_mock=true
194196
case_extra_env=(-e DD_LAMBDA_HANDLER=http-requests.handle -e "MOCK_HTTP_URLS=http://mock-http:8080/ip-ranges-us,http://mock-http:8080/ip-ranges-eu")
195197
;;
198+
manual-http-requests)
199+
# Manual wrap without a userland dd-trace init — the exact shape
200+
# of the AWS suite's http-requests function. TraceListener falls
201+
# back to the library's own patchHttp (src/trace/patch-http.ts),
202+
# which injects the x-datadog-* headers and logs
203+
# "GET <url> TraceHeaders: [...]" per request; the mock echo pins
204+
# the same headers a second way. No trace JSON on this path (true
205+
# in the AWS suite as well).
206+
#
207+
# patchHttp needs an extracted trace context to inject headers.
208+
# On real Lambda that context is the platform's pass-through
209+
# _X_AMZN_TRACE_ID; under RIE it cannot be emulated (the RIC owns
210+
# the variable, and event-based extraction needs a tracer, which
211+
# this path deliberately lacks). The golden therefore pins what
212+
# is observable here: patchHttp wraps and logs every request
213+
# ("HTTP GET ... TraceHeaders: []"), the mock echo pins the exact
214+
# header set sent, and any change to either fails the case. The
215+
# context-dependent header values are pinned by the patch-http
216+
# unit tests and the AWS suite; see README "Known emulation gaps".
217+
case_image=cjs
218+
case_entry_handler="http-requests-manual.handle"
219+
case_needs_mock=true
220+
case_extra_env=(-e "MOCK_HTTP_URLS=http://mock-http:8080/ip-ranges-us,http://mock-http:8080/ip-ranges-eu")
221+
;;
196222
cjs-custom-extractor)
197223
case_image=cjs
198224
case_entry_handler="node_modules/datadog-lambda-js/dist/handler.handler"

0 commit comments

Comments
 (0)