This document explains why traces fail to appear in Datadog for each sample app and what changes were made to fix them.
Two distinct problems prevent traces from reaching Datadog:
-
Wrong exporter — the tracer detects
AWS_LAMBDA_FUNCTION_NAMEand switches to a stdout/log-based exporter instead of sending spans over HTTP to the embedded trace agent onlocalhost:8126. -
No spans created — the app uses the JDK/stdlib HTTP server, which is not in the tracer's auto-instrumentation catalog.
All language tracers that check for /tmp/datadog/mini_agent_ready (currently
Node.js, potentially others in future versions) need this file to exist before they
initialize, or they fall back to the log exporter.
File: cmd/serverless-init/main.go in the datadog-agent repo
Change: Create /tmp/datadog/mini_agent_ready inside setupTraceAgent(), after
the embedded trace agent has started and bound to port 8126:
// Sentinel file signals to language tracers (e.g. dd-trace-js) that an HTTP
// trace agent is available on localhost:8126. Without it, dd-trace detects
// AWS_LAMBDA_FUNCTION_NAME and switches to the log exporter.
if err := os.MkdirAll("/tmp/datadog", 0o755); err == nil {
if f, err := os.Create("/tmp/datadog/mini_agent_ready"); err == nil {
f.Close()
}
}This must be compiled into a new serverless-init binary and copied into each app
directory via make build.
Added to every Dockerfile:
ENV DD_TRACE_AGENT_URL=http://localhost:8126
ENV DD_TRACE_STARTUP_LOGS=true
ENV DD_TRACE_DEBUG=trueAlso fixed a linter-introduced typo (devmirovm → devmicrovm) and removed
duplicate DD_ENV entries.
Problem: dd-trace reads /tmp/datadog/mini_agent_ready at startup. If absent
(and AWS_LAMBDA_FUNCTION_NAME is set), it selects the log exporter and bypasses
localhost:8126 entirely. The relevant logic is in:
/dd_tracer/node/node_modules/dd-trace/packages/dd-trace/src/exporter.js
const usingAgent = inAWSLambda && (
fs.existsSync('/opt/extensions/datadog-agent') || // Datadog Lambda extension
fs.existsSync('/tmp/datadog/mini_agent_ready') // serverless-init sentinel
)
return inAWSLambda && !usingAgent ? require('./exporters/log') : require('./exporters/agent')Fix: The serverless-init sentinel file fix above (no app code changes needed).
Problem 1: ddtrace switches to the log writer in Lambda unless DD_AGENT_HOST,
DD_TRACE_AGENT_URL, or similar env vars are explicitly set. The check is in
ddtrace/internal/writer/writer.py:
def _use_log_writer() -> bool:
if env.get("DD_TRACE_AGENT_URL"): # set → use AgentWriter
return False
return in_aws_lambda() # unset → log writerFix 1 (Dockerfile): ENV DD_TRACE_AGENT_URL=http://localhost:8126
Problem 2: http.server.HTTPServer (Python stdlib) is not auto-instrumented by
ddtrace-run.
Fix 2 (app.py): Added a _span() context manager using ddtrace.tracer.trace()
and wrapped every request handler with it:
from contextlib import nullcontext
try:
from ddtrace import tracer as dd_tracer
except ImportError:
dd_tracer = None
def _span(method, path):
if dd_tracer:
return dd_tracer.trace("http.request", resource=f"{method} {path}", span_type="web")
return nullcontext()
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
with _span("GET", self.path):
...
def do_POST(self):
with _span("POST", self.path):
...Problem 1: dd-trace-go sets logToStdout = true when AWS_LAMBDA_FUNCTION_NAME
is detected (ddtrace/tracer/option.go:573). Once set, agentDisabled becomes true
and DD_TRACE_AGENT_URL has no effect.
Fix 1 (main.go):
tracer.Start(tracer.WithLambdaMode(false))Problem 2: Plain http.NewServeMux() creates no HTTP spans.
Fix 2 (main.go): Replace with the instrumented mux from dd-trace-go:
import ddhttp "gopkg.in/DataDog/dd-trace-go.v1/contrib/net/http"
mux := ddhttp.NewServeMux()Problem: com.sun.net.httpserver.HttpServer (JDK internal) is not in
dd-java-agent's instrumentation catalog — zero spans are created for any request.
Note: unlike the other tracers, dd-java-agent does not switch to a log exporter
on Lambda detection. DDAgentWriter (HTTP to localhost:8126) is always the default,
so DD_TRACE_AGENT_URL is not needed here.
Fix (pom.xml): Add dd-trace-api as a provided dependency (provided at
runtime by the javaagent, not bundled into the shaded jar):
<dependency>
<groupId>com.datadoghq</groupId>
<artifactId>dd-trace-api</artifactId>
<version>1.44.1</version>
<scope>provided</scope>
</dependency>Fix (App.java): Annotate all handler methods with @Trace. Because annotations
cannot be applied to lambdas, the emptyHook() lambda factory was refactored into
four named static methods:
import datadog.trace.api.Trace;
@Trace(operationName = "http.server.request", resourceName = "GET /health")
private static void handleHealth(HttpExchange exchange) throws IOException { ... }
@Trace(operationName = "http.server.request", resourceName = "POST /ready")
private static void handleReady(HttpExchange exchange) throws IOException { ... }
// ... handleLaunch, handleResume, handleSuspend, handleTerminate, handleExecuteNo code or Dockerfile changes needed.
The Datadog CLR profiler's IsRunningInLambda check requires both:
AWS_LAMBDA_FUNCTION_NAMEis set, and/opt/extensions/datadog-agentexists on disk
Since serverless-init does not install the Datadog Lambda extension binary at that
path, IsRunningInLambda is always false in a MicroVM container. The profiler
stays in its default HTTP mode and sends traces to DD_TRACE_AGENT_URL.
ASP.NET Core Minimal API (app.MapGet / app.MapPost) is fully auto-instrumented
by the CLR profiler. The dd-lib-dotnet-init image ships an ARM aarch64-native
Datadog.Trace.ClrProfiler.Native.so, so there is no architecture mismatch.
| Tracer | Detection mechanism | Switches to log exporter? | Override |
|---|---|---|---|
Node.js (dd-trace) |
Checks /tmp/datadog/mini_agent_ready |
Yes, if file absent | Create the sentinel file (serverless-init fix) |
Python (ddtrace) |
in_aws_lambda() in _use_log_writer() |
Yes, unless DD_TRACE_AGENT_URL set |
ENV DD_TRACE_AGENT_URL=http://localhost:8126 |
Go (dd-trace-go) |
os.LookupEnv("AWS_LAMBDA_FUNCTION_NAME") sets logToStdout=true |
Yes, DD_TRACE_AGENT_URL ineffective |
tracer.WithLambdaMode(false) |
Java (dd-java-agent) |
Reads env var but does not change writer | No — always DDAgentWriter |
N/A |
| .NET (CLR profiler) | Requires env var + /opt/extensions/datadog-agent on disk |
No | N/A |