OpenTelemetry OpenTelemetry

Instrument Apps Without Code Changes: Java, Python, and Node.js

Attach OpenTelemetry agents with environment variables and command-line flags only.

12 min Beginner

Zero-code instrumentation attaches an agent to your application at startup. The agent patches common libraries, such as HTTP servers and clients, database drivers, and message queues, and produces traces, metrics, and logs without changing the source. Configuration is the standard OTEL_* environment variables plus a few language-specific ones, so the whole setup is a Dockerfile line and a few variables. This guide covers Java, Python, and Node.js.

Prerequisite: somewhere to send data

Have a Collector listening, for example from the Docker Compose guide, on otel-collector:4318 (HTTP) or :4317 (gRPC). Every example below points at it with OTEL_EXPORTER_OTLP_ENDPOINT.

Java

Download the agent JAR once, then attach it with -javaagent:

curl -L -o opentelemetry-javaagent.jar \
  https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar

java -javaagent:./opentelemetry-javaagent.jar \
     -Dotel.service.name=checkout \
     -jar myapp.jar

Containers usually prefer environment variables, with JAVA_TOOL_OPTIONS carrying the flag so you don’t change the entrypoint:

FROM eclipse-temurin:21-jre
WORKDIR /app
COPY myapp.jar .
ADD https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/download/<version>/opentelemetry-javaagent.jar /otel/opentelemetry-javaagent.jar
ENV JAVA_TOOL_OPTIONS="-javaagent:/otel/opentelemetry-javaagent.jar"
CMD ["java", "-jar", "myapp.jar"]

Pin the agent version in the download URL rather than using latest, so builds are reproducible. Then configure it at runtime:

OTEL_SERVICE_NAME=checkout \
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 \
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
OTEL_RESOURCE_ATTRIBUTES=service.version=1.4.2,deployment.environment.name=staging \
java -jar myapp.jar

The agent can be configured three ways: system properties (-Dotel.service.name=...), environment variables, or a Java properties file referenced with OTEL_JAVAAGENT_CONFIGURATION_FILE=/path/to/file.properties (or -Dotel.javaagent.configuration-file). Keep the properties file in Git when you have many settings. For debugging the agent itself, set OTEL_JAVAAGENT_DEBUG=true, and expect very verbose output. The agent also supports a declarative YAML configuration file; see the Java agent documentation.

Python

Install the distribution, the OTLP exporter, and the instrumentation libraries that match your installed packages:

pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a install

opentelemetry-bootstrap -a install scans your environment and installs the instrumentation for what it finds, for example the Flask instrumentation when Flask is installed. Run opentelemetry-bootstrap with no arguments to see its recommendations without installing. Start the app through opentelemetry-instrument:

OTEL_SERVICE_NAME=checkout \
OTEL_TRACES_EXPORTER=otlp \
OTEL_METRICS_EXPORTER=otlp \
OTEL_LOGS_EXPORTER=otlp \
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 \
OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf \
opentelemetry-instrument python myapp.py

Note the trailing backslashes: each variable must be on the same logical command line as opentelemetry-instrument, or it won’t reach the process. For servers like Gunicorn or Uvicorn, wrap the server command: opentelemetry-instrument gunicorn app:app. Some setups with the uv package manager need a workaround for the bootstrap step, which the Python troubleshooting page describes. Log export is opt-in in Python, so check the logging section of the Python documentation if logs don’t appear.

A Dockerfile pattern:

FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt \
 && pip install opentelemetry-distro opentelemetry-exporter-otlp \
 && opentelemetry-bootstrap -a install
COPY . .
CMD ["opentelemetry-instrument", "python", "myapp.py"]

Node.js

Install the API and the auto-instrumentation bundle:

npm install --save @opentelemetry/api
npm install --save @opentelemetry/auto-instrumentations-node

Load it before your app with --require, or through NODE_OPTIONS:

export OTEL_SERVICE_NAME=checkout
export OTEL_TRACES_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318
export OTEL_NODE_RESOURCE_DETECTORS=env,host,os
export NODE_OPTIONS="--require @opentelemetry/auto-instrumentations-node/register"
node app.js

OTEL_NODE_RESOURCE_DETECTORS limits which resource detectors run (all are enabled by default). OTEL_NODE_ENABLED_INSTRUMENTATIONS limits which instrumentations load; see the module configuration page. OTEL_LOG_LEVEL accepts none, error, warn, info, debug, verbose, and all, with info as the default. Keep it at info in production, since debug output hurts performance. ES module applications may need an extra loader option; follow the Node.js documentation for your module system.

.NET, Go, and others

.NET has a zero-code agent configured the same way (environment variables plus a startup script). Go uses an eBPF-based agent that runs as a privileged sidecar and needs the target executable path, so it is mostly used through the Operator. Check the zero-code section of the documentation for each.

What you get, and what you don’t

You get spans for incoming and outgoing HTTP, database calls, and common messaging libraries, along with runtime metrics in some languages. You don’t get business-level spans, such as “calculate discount”, because the agent can’t know about them. Add a few manual spans or attributes with the OpenTelemetry API where the extra detail pays off, and keep the agent for everything else.

Verify it works

  1. Start the app with the Collector’s debug exporter enabled.
  2. Send a request: curl http://localhost:8080/.
  3. Look at the Collector log for spans with your service.name:
docker compose logs otel-collector | grep -i 'service.name'

If nothing arrives, check in this order: the endpoint and port match the protocol, the Collector pipeline for the signal lists the otlp receiver, the app can resolve the Collector’s hostname, and OTEL_SDK_DISABLED isn’t set. The troubleshooting guide expands on each.

Production notes

  • Pin agent and distribution versions, and update them deliberately.
  • Set service.name, service.version, and deployment.environment.name on every service, because dashboards depend on them.
  • Start with a sampling ratio rather than keeping everything (see the sampling guide).
  • On Kubernetes, use the Operator to inject the agent by annotation instead of editing every Dockerfile.

Popular Guides