Zero-Code Instrumentation in OpenTelemetry

Zero-Code Instrumentation in OpenTelemetry

How you can make a running application report traces, metrics and logs without changing a single line of its source code, how that trick works under the hood, and where it helps, where it stops, and how to use it wisely.

Imagine you move into an old house and want to know how much electricity each room uses. One way is to tear open the walls and rewire every circuit with built-in meters. Another way is to plug small smart meters into the sockets that already exist. The second way takes an afternoon, needs no builder, and leaves the house untouched. The meters still tell you what you need to know, though they cannot see inside the wires themselves.

Zero-code instrumentation is the smart-meter approach for software. A company may run hundreds of programs, some written years ago by people who have left. Asking every team to edit its code and add monitoring calls would take months. With zero-code instrumentation, you attach a helper to each program when it starts, and the helper begins to report what the program is doing. No source files are opened, no new version is built, and no developer has to be convinced.

In this guide we begin from zero. We explain what instrumentation is, how the zero-code approach compares with the others, and how it works inside for each language. Then we look at Kubernetes, configuration, the data you get, the limits you must know, and the habits that keep it safe and affordable. Every new word is explained when it first appears, and each big idea comes with an everyday comparison.

1What Is Zero-Code Instrumentation?

It is a way to collect telemetry from an application by attaching something to it from outside, instead of editing the application itself.

OpenTelemetry, often shortened to OTel, is an open-source project that gives software a standard way to produce telemetry. Telemetry is the stream of information a program sends out about itself: what it did, how long things took, what went wrong. People read this stream to understand and repair their systems. The three main kinds of telemetry, called signals, are traces, metrics and logs.

Instrumentation is the work of making a program produce that telemetry. Someone, or something, must notice “a web request arrived” and “the database call took 40 milliseconds” and turn each event into a record. Zero-code instrumentation is instrumentation that needs zero changes to your application’s source code. Instead, a helper is loaded alongside your program when it starts. The helper watches popular libraries that your program already uses, such as web frameworks and database drivers, and creates the records for you.

Other names for the same idea

You will meet several names. The OpenTelemetry documentation uses zero-code instrumentation. Older articles and many tools say automatic instrumentation or auto-instrumentation. Some people say agent-based instrumentation, because in Java the helper is called an agent. They all point at the same idea: telemetry without editing the code.

What exactly is “zero”?

The word “zero” refers to your source code. You still do some work. You install or download the helper, set a few settings, and restart the program. You may change the command that starts the program, or the container image, or a deployment file. What you do not do is open the program’s own files, add monitoring calls, or recompile it. This difference is the heart of the idea, and it explains both the strength and the limits of the approach.

Analogy

Think of a fitness tracker on your wrist. It tells you your steps, heart rate and sleep without you changing how you walk, run or sleep. You do not rebuild your body to be measured. The tracker sits on the outside and watches movement. Zero-code instrumentation is a tracker for software. It cannot know your private thoughts, which here means your business meaning, but it can tell you a great deal about the visible movement.

Where it fits in the OpenTelemetry family

OpenTelemetry has several parts that work together. The API is a small set of calls that code uses to create telemetry. The SDK is the engine that collects, processes and exports it. Instrumentation is the code that calls the API at the right moments. And the Collector is a separate program that receives and forwards the data. Zero-code instrumentation supplies the instrumentation part, and it brings an SDK along with it, so your program needs nothing else added.

i
Good to know

Zero-code does not mean zero effort and does not mean zero knowledge. You still choose settings, check the output and decide what to keep. It means that the first useful data can appear in minutes rather than weeks, and that no developer has to touch the code to get it.

2The Problem It Solves

Monitoring is most useful when it covers everything, and covering everything by hand is slow, uneven and easy to forget.

The coverage problem

Suppose a company has two hundred services. To add monitoring by hand, each team must learn the tools, edit its code, test the change, release it and fix mistakes. Some teams finish in a week. Others are busy with other work and do nothing for a year. After six months you have a map with half the cities missing. Requests enter the covered services and vanish into the dark ones, and traces break at every border.

The legacy problem

Old programs are a special difficulty. The code may be fragile, the original authors may be gone, and the team that owns it may be afraid to touch it. Sometimes the source is not even available, because the program is a purchased product. You cannot edit what you do not have. A method that works from outside is the only way to see inside such a program.

The speed problem

When something is on fire, you want to see the system now. Waiting for a development cycle to add monitoring is too slow. Teams that can switch on telemetry by changing a start-up setting can get data for a troubled service within an hour.

The consistency problem

When many teams write their own instrumentation, each makes different choices. One names a field one way and another names it differently. Zero-code instrumentation applies the same well-tested rules to every program, which gives the same names, the same structure and the same quality across the company.

Problem

Slow rollout

Hand-written monitoring takes many team-months across a large company, and finishing is never certain.

Problem

Untouchable code

Legacy or purchased programs cannot be changed safely or at all, but still need to be seen.

Problem

Urgent visibility

During an incident you need data within hours, not after the next release.

Problem

Uneven quality

Different teams produce different names and different detail, so data cannot be combined.

Analogy

A city that wants to track traffic has two choices. It can ask every shop owner to count passing cars by hand and report the numbers, or it can install cameras at the main junctions. The shop owners are busy and count differently. The cameras are fitted by one team in one pass and record in the same way everywhere. Zero-code instrumentation is the camera at the junction.

!
Watch out

Cameras at junctions show where traffic flows, but they cannot tell you why a particular driver turned left. In the same way, zero-code instrumentation shows the technical movement of requests, and it cannot know your business reasons. Chapters 4 and 13 explain how to add the missing meaning on top.

Who benefits, and how

Zero-code instrumentation is not only for developers. Each group in a company gains something different. Developers get traces without a detour from their feature work, and they can read the traces to find slow parts of their own service. Operations and reliability engineers get a uniform view of every service, which shortens the time to find the cause of an outage. Security teams get a map of which services talk to which, including calls that nobody remembered. Managers and architects get evidence for decisions: which services are busiest, which dependencies are slowest and where money is being spent.

The common thread is that the cost of getting a first, useful picture falls from months of coordinated effort to days of platform work. A smaller cost of entry means that more teams start, and a system with more teams started is easier to understand as a whole.

Example: the forgotten dependency

After switching on zero-code tracing, a company discovers that an old billing service still calls a reporting service that was supposed to be retired two years ago. Nobody had a record of the link, because it lived in code written by a team that had since moved on. The traces showed it within a day, and the team could plan a safe retirement.

3A Quick Refresher: Spans, the SDK and Libraries

Five small ideas make the rest of this guide easy: span, trace, library, SDK and exporter.

Traces and spans

A span is one timed step of work, such as “handle this web request” or “run this database query”. It has a name, a start time, an end time, a status and a set of labels called attributes. A trace is the family tree of spans that belong to one request as it travels through the system. Parent spans contain child spans, and the tree shows where the time went.

Libraries: where the interesting events happen

Most programs do not talk to the network or the database directly. They use libraries, which are ready-made packages written by others: a web framework to accept requests, an HTTP client to make calls, a database driver to run queries, a message client to use a queue. The events we care about happen inside these libraries. That is the key to zero-code instrumentation. If a helper can watch the library, it does not need to touch the application code that calls the library.

The SDK and the exporter

The SDK is the part that gathers spans, applies rules such as sampling, and hands them on. An exporter is the final piece that sends the data out of the program, usually to the OpenTelemetry Collector or straight to a monitoring tool. The usual language for this journey is OTLP, the OpenTelemetry Protocol. In zero-code setups, the helper brings its own SDK and exporter, so you only need to say where to send the data.

TermPlain meaningEveryday comparison
SpanOne timed step of workOne stop on a delivery round
TraceAll the spans for one requestThe whole delivery route
LibraryA package your program uses for common tasksA tool borrowed from a shared workshop
InstrumentationCode that watches something and creates telemetryA sensor on a machine
SDKThe engine that collects and processes telemetryThe control room that receives sensor signals
ExporterThe part that sends data outThe radio that sends the signals to headquarters

A very small picture of one request

Consider a shopper pressing “Buy”. The orders service receives a web request, asks the database to save an order and calls the payment service over HTTP. With instrumentation, three spans appear: one for handling the web request, one for the database query and one for the outgoing HTTP call. The spans are linked as parent and children. This is exactly what a zero-code helper produces, with no changes to the orders service.

flowchart TD
A["Span: handle web request in orders service"] --> B["Span: database query"]
A --> C["Span: HTTP call to payment service"]
C --> D["Span: handle request in payment service"]
Figure 1: The spans that a zero-code helper creates for one request, each tied to a library call.
Analogy

A hospital monitors a patient with sensors on the body, not by asking the patient to write down their own pulse. The sensors attach to places where the signals appear: the wrist, the chest, the finger. Libraries are those places in software. The helper attaches to the web framework, the database driver and the HTTP client, because the signals appear there.

i
Good to know

Because the helper follows the standard naming rules from OpenTelemetry’s semantic conventions, the spans and attributes it creates use agreed names such as the HTTP method and the status code. This is why data from different languages can be shown on one dashboard.

4The Spectrum: Zero-Code, Library and Manual

Instrumentation is not one choice but a range. Understanding the range tells you when zero-code is enough and when it is not.

Three levels of effort

At one end is zero-code instrumentation: you attach a helper from outside and change no source code. In the middle is library instrumentation: you add instrumentation packages to your code, and write a small amount of set-up code to turn them on. At the other end is manual instrumentation: you write calls to the OpenTelemetry API yourself, deciding which spans to create and which attributes to add. These levels are not rivals. They are tools that you can combine in one program.

flowchart LR
Z["Zero-code: attach a helper, no code change"] --> L["Library: add packages and small set-up code"]
L --> M["Manual: write API calls for custom spans and attributes"]
Figure 2: More effort buys more control. Most real systems use zero-code for breadth and manual for depth.
QuestionZero-codeLibraryManual
Change source code?NoA little set-upYes
Time to first dataMinutes to hoursHours to daysDays to weeks
Coverage of common librariesWide and automaticChosen one by oneOnly what you write
Business meaningNone by itselfLimitedFull control
Control over detailSettings onlyMediumComplete
Best forQuick breadth, legacy, uniform coverageTeams that own their codeKey business operations

What zero-code cannot know

The helper sees libraries, not intentions. It can see that a database query ran and how long it took. It cannot know that this query was “check whether the discount code is valid”. It sees an HTTP request to a payment service but not that the amount was large or that the customer was a new one. These business details live in your code, and only manual instrumentation can capture them. The mix is therefore usual: zero-code for broad coverage, then manual additions in the few places where meaning matters.

Analogy

Consider a sports match. A broadcast camera that follows the ball (zero-code) tells you who has it, where it went and how fast. A commentator who knows the team’s tactics (manual) tells you why the coach changed the plan. You need the camera for everyone to see the game, and you add the commentator for the moments that need explanation.

i
Rule of thumb

Start with zero-code to see the whole system quickly. Look at the traces to find the important operations. Then add manual spans and attributes only for those, rather than instrumenting everything by hand.

5How the Trick Works: Four Techniques

A program does not change because you wish it to. Each language offers a different door through which a helper can slip in.

Why the technique differs by language

Languages are built differently. Some run on a virtual machine that allows code to be rewritten as it loads. Some are interpreted and let you swap functions while the program runs. Some compile to machine code with no hooks at all. Because of this, there is no single method, and OpenTelemetry uses a few. Knowing the four techniques helps you understand the strengths and failures of each.

Technique 1

Bytecode instrumentation

Used by Java and other virtual-machine languages. A helper called an agent changes the compiled instructions of library classes as they are loaded, adding code that records telemetry.

Technique 2

Monkey patching

Used by interpreted languages such as Python and Node.js. The helper replaces library functions with wrapped versions that record telemetry and then call the original.

Technique 3

Runtime profiler hooks

Used by .NET. A profiler interface lets a helper rewrite code as the runtime prepares it, in a similar spirit to bytecode work.

Technique 4

eBPF

Used for compiled languages such as Go. Small programs run safely inside the operating system’s kernel and observe the target program from outside, without changing it at all.

Bytecode in simple words

When a Java program is built, it is turned into bytecode, a compact set of instructions that the Java Virtual Machine runs. Java allows an agent to be loaded before the program starts, and that agent is allowed to look at each class as it is loaded and change its instructions. The agent finds the part of a web framework that handles requests and adds a few instructions at the start and the end: “begin a span” and “end the span”. The framework’s own file is unchanged on disk. The change happens in memory only.

Monkey patching in simple words

The name sounds silly, but the idea is easy. In Python and JavaScript, a function is just a value stored under a name, and you can replace it. A helper takes the library’s function that sends an HTTP request, stores it safely, and puts in its place a new function. The new function starts a span, calls the original function, ends the span and returns the same result. Your code still calls the same name and gets the same answer, but a span now appears.

eBPF in simple words

eBPF is a technology in the Linux kernel that lets small, verified programs run inside the operating system and watch what happens, such as network calls and function entries. A helper built on eBPF can see that a Go program accepted an HTTP request and made a database call, without being loaded into the program at all. This is useful for languages that give no friendly hooks. The price is limits: it needs a suitable Linux kernel and elevated permissions, and it may see less detail than an in-process agent.

TechniqueWhere it livesStrengthWeakness
BytecodeInside the program’s virtual machineRich detail, wide library supportStartup cost, version compatibility
Monkey patchingInside the program’s interpreterSimple, works for many librariesMust match library versions and load order
Profiler hooksInside the .NET runtimeGood coverage without code changesNeeds environment set-up
eBPFOutside the program, in the kernelWorks for compiled code, no restart of code pathsLinux only, higher privileges, less detail
Analogy

Four ways to learn what a shop sells without asking the owner. You can send a secret shopper who stands at the till and writes down each sale (monkey patching). You can fit the cash register itself with a counter, which changes it while it is being set up (bytecode and profiler hooks). Or you can watch the shop from a camera across the street (eBPF). Each method sees something different, and each has its own limits.

!
Watch out

Because zero-code changes behaviour at the edges of your program, it can in rare cases conflict with other tools that do the same trick, such as a commercial monitoring agent. Run only one agent of this kind per program unless the documentation says that two can live together.

A first test, whatever the language

Whichever technique your language uses, the first test is the same, and it is worth doing before any rollout. Start the program with the helper attached and a console exporter turned on, so that spans are printed to the screen instead of sent over the network. Send one simple request to the program, such as opening a page. If the helper works, you will see at least one printed span with the method, route and status code of the request, and, if the page uses a database, another span for the query.

This tiny test checks three things at once. It confirms that the helper loaded, that it recognises your libraries and that spans are created. If it fails, you have narrowed the problem to the machine in front of you, without any network, Collector or backend in the way. Only after this works should you point the helper at a real Collector.

1

Attach and print

Start the program with the helper and the console exporter on a test machine.

2

Send one request

Trigger a simple request and watch for printed spans.

3

Connect to a Collector

Point the helper at a test Collector and confirm that the same spans arrive there.

4

Check the backend

Look for the service name in your tracing tool and open a trace.

6The Java Agent in Depth

Java was the first language to get full zero-code support, and its agent is the most widely used and the most complete.

What the agent is

The OpenTelemetry Java agent is a single file, a JAR, that you download from the project’s releases. It is an instrumentation agent, a special kind of Java program that the Java Virtual Machine, or JVM, can load before it runs your application. The agent bundles everything needed: a copy of the OpenTelemetry SDK, an exporter, and a large collection of instrumentation for popular libraries. It supports hundreds of libraries and frameworks, covering web servers, HTTP clients, database drivers, message systems, remote procedure calls and more.

How you attach it

You attach the agent by adding a flag to the command that starts the program, telling the JVM to load the agent file first. When the start command is hard to change, as in many containers and managed platforms, you can pass the same flag through a standard environment variable that the JVM reads on its own. Either way, the application is not edited, not rebuilt and not repackaged.

sequenceDiagram
participant J as JVM
participant A as OpenTelemetry agent
participant L as Library classes
participant O as Backend or Collector
J->>A: Load the agent before the application starts
A->>A: Read settings and start the SDK
J->>L: Load library classes
A->>L: Rewrite chosen classes to add telemetry
J->>J: Run the application as normal
L->>A: Report spans, metrics and logs
A->>O: Export data using OTLP
Figure 3: The agent loads first, rewrites library classes as they are loaded, and exports what they report.

What happens at startup

1

The JVM starts and loads the agent

Before your first line of code runs, the JVM calls the agent’s entry point and gives it a chance to register itself.

2

The agent reads its settings

It reads environment variables and system properties that say where to send data, how to name the service and which parts to switch on or off.

3

It starts the SDK and exporters

An internal SDK is created, with the chosen sampler, propagators and exporters ready for the first span.

4

It watches class loading

As the JVM loads classes, the agent checks each one against a list of known libraries and versions, and rewrites the ones that match.

5

The application runs

From then on, calls into instrumented libraries create spans and metrics without the application knowing.

What it does for you

The agent creates a span for every incoming web request and a span for each outgoing call. It records database queries, message sends and receives, and remote procedure calls. It passes the trace identity along with each outgoing request, using a propagation format such as W3C Trace Context, so that traces join across services. It records metrics about the runtime, such as memory and thread counts, and about request durations. And it can add the trace and span identifiers to log records so that logs link to traces.

Turning parts on and off

Not every instrumentation is wanted. The agent lets you disable the whole agent, disable instrumentation for one library, or turn off all instrumentation and then enable only a chosen few. Disabling noisy parts is a common way to reduce volume and cost. It also lets you avoid trouble if one library’s instrumentation misbehaves with your version.

i
Good to know

The Java agent can be extended with your own small plug-in that adds instrumentation for a library it does not know, or changes how it handles certain data. This is still not a change to your application, and it is a good fit for company-wide rules.

Analogy

Think of a new employee who arrives at a factory before the shift starts, reads the safety notes, and fits small counters to the main machines as the workers bring them online. The factory does not change its blueprint, and the workers carry on as usual. The Java agent is that employee, and the counters are the instructions it adds in memory.

7Python and Node.js

Interpreted languages let a helper replace functions while the program runs. The set-up is light, but a few details decide whether it works.

Python

Python’s route has three steps, all done outside your code. First, you install a package named opentelemetry-distro together with an OTLP exporter package. The distro bundles the SDK and a sensible default configuration. Second, you run a command called opentelemetry-bootstrap. It looks at the packages that are already installed in your environment, such as a web framework or a database driver, and installs the matching instrumentation packages, one per library found. Third, you start your program through a wrapper command named opentelemetry-instrument, placed in front of your usual start command.

The wrapper sets up the SDK, loads every instrumentation package it finds, patches the libraries, and then starts your program as normal. Your own files are not opened. This design means the set of instrumented libraries depends on what is installed, so after you add a new library you should run the bootstrap step again.

!
Watch out

Some Python servers start several worker processes by copying a main process, a method called forking. Telemetry components that were created before the fork, such as background export threads, do not survive the copy. In such servers, the SDK may need to be started inside each worker, which usually means a small hook in the server’s own configuration file. Read the documentation for your server before you trust the first test.

Node.js

For Node.js, you install the package @opentelemetry/auto-instrumentations-node. It contains instrumentation for many popular libraries, including web frameworks, HTTP clients, database drivers and message clients. To switch it on without editing code, you tell Node to load a registration file before your application, using a start-up option. A common method is to set that option in the NODE_OPTIONS environment variable, so that every Node process in the container picks it up.

Node.js has two module systems, the older require style and the newer import style. The older one is easy to patch because the loader can be intercepted. The newer one needs a special loader hook, and the start-up flag is different. Check which style your application uses, and use the matching instructions. You can also choose which instrumentations to enable with an environment variable, which is handy when you want only a few.

PointPythonNode.js
Main packageopentelemetry-distro plus exporter@opentelemetry/auto-instrumentations-node
How libraries are foundA bootstrap command installs matching packagesA bundle includes many instrumentations
How it is switched onStart through the opentelemetry-instrument wrapperLoad a registration file at start-up
TechniqueMonkey patchingModule loader interception and patching
Common trapForking servers need per-worker set-upBundled code cannot be patched

The bundler trap in Node.js

Many JavaScript projects are packed into a single file by a bundler before they run. A bundler pulls the code of every library into one place and removes the module boundaries. Monkey patching works by intercepting the moment a library is loaded by name. If the libraries are already merged into one file, there is no such moment, and the patching silently does nothing. The usual cure is to leave the instrumented libraries out of the bundle, so that they are loaded normally from the folder of installed packages.

Analogy

Imagine a switchboard operator who can listen in on calls only when a call is connected through the main exchange. If two neighbouring offices are wired directly to each other, the operator never sees their calls. A bundler wires libraries directly to your code, and the helper, standing at the exchange, hears nothing.

i
Good to know

In both languages, the quickest test is to start the program with a console exporter, which prints spans to the screen. If a request produces printed spans, the patching works. If nothing prints, check the load order, the bundler and the library versions before you look anywhere else.

8.NET, Go, PHP and Other Languages

Not every language offers the same door. Support and maturity differ, so it pays to know what to expect.

.NET

The OpenTelemetry .NET automatic instrumentation uses the runtime’s profiler interface, a feature that lets a native component watch and rewrite code as the .NET runtime prepares it. It also uses a start-up hook that runs before your application’s own entry point. You install the instrumentation using a provided script or package, and then set a group of environment variables that tell the runtime to load the profiler. Once these are set, ASP.NET Core requests, outgoing HTTP calls and many database and messaging clients are covered. It runs on both Windows and Linux. As with Java, you do not recompile your application.

Go

Go compiles to native machine code and offers no easy place to attach a helper while the program runs. For this reason, zero-code support for Go is built on eBPF. A separate process, often run beside the application as a sidecar, uses eBPF to observe the Go program from the kernel. It can see incoming web requests, outgoing calls and some database and messaging activity, and it creates spans from what it sees. This needs Linux, a recent enough kernel and special privileges. The project is under active development, and a related effort contributed to OpenTelemetry brings eBPF-based instrumentation for several languages at the network level. There are also newer approaches that change the program during the build. Because this area is moving fast, check the current state before you plan around it.

PHP

PHP can be instrumented without code changes by installing the OpenTelemetry extension, a small native module loaded by the PHP engine, together with packages that provide instrumentation for common frameworks and libraries. The extension lets the packages hook into function calls. Once installed and configured, requests handled by supported frameworks produce spans.

Web servers and other places

Instrumentation also exists for web servers, such as Apache HTTP Server and Nginx, through loadable modules. These create spans for each request they handle and pass trace identity onward to the services behind them. Some other languages, such as Ruby, offer instrumentation packages that need a small amount of set-up code in the application. That is close to zero-code, but not fully. Always read the status for your language rather than assume.

Language or serverMain mechanismHow it is attached
JavaBytecode agentA start-up flag pointing to the agent file
PythonMonkey patchingA wrapper command in front of the start command
Node.jsModule loader patchingA start-up option that loads a registration file
.NETRuntime profiler hooksEnvironment variables and an installed instrumentation
GoeBPF from outside the processA separate, privileged observer process
PHPNative extension with packagesInstall the extension and instrumentation packages
Apache and NginxLoadable server modulesEnable the module in the server configuration
Analogy

Different buildings have different doors for a delivery person. One has a ground-floor loading bay, another a staff entrance, and a third only a window on the first floor. The delivery person is skilled in every method but must be told which door each building has. Language runtimes are the buildings, and each one has its own door for instrumentation.

!
Watch out

Maturity varies. Java and several other languages have broad, long-tested support. Newer or harder languages have narrower coverage and more caveats. Check the status labels in the documentation, and test with your real libraries before you promise coverage to anyone.

9Kubernetes, Containers and Serverless

Modern programs run in containers. The OpenTelemetry Operator can attach zero-code helpers to thousands of them from one place.

The problem at scale

Attaching a Java agent to one program by hand is easy. Attaching it to four hundred programs, in a mix of languages, each started by a different team, is not. You would have to change four hundred images or start commands. The OpenTelemetry Operator for Kubernetes solves this by making the attachment part of the platform rather than part of each program.

How the Operator does it

The Operator is a program that runs inside your Kubernetes cluster. It adds a new kind of resource called Instrumentation, in which you describe the helper: where to send data, which sampler to use, which propagators and which resource labels to add, and which language images to use. Then, you put an annotation, a small label, on the workload that you want instrumented. The annotation names the language, for example instrumentation.opentelemetry.io/inject-java for Java. Equivalent annotations exist for Node.js, Python, .NET, Go, Apache HTTP Server, Nginx and others.

When a pod is created, a component called an admission webhook in the Operator notices the annotation and changes the pod’s definition before it starts. It adds an init container, a short-lived helper that runs first and copies the agent files into a shared folder. It then adds environment variables to the application container so that the language starts with the agent attached. Your image is not rebuilt, and the developer does not change the start command.

sequenceDiagram
participant U as Platform engineer
participant K as Kubernetes API
participant W as Operator webhook
participant P as Pod
U->>K: Create Instrumentation resource
U->>K: Add inject annotation to a workload
K->>W: New pod is being created
W->>K: Add init container and environment variables
K->>P: Start the init container
P->>P: Copy agent files to a shared folder
K->>P: Start the application with the agent attached
Figure 4: The Operator adds the helper to a pod while it is being created, without touching the application image.
1

Install the Operator

Install it in the cluster. It normally needs a way to create certificates for its webhook, which is often provided by a certificate manager, unless you configure another method.

2

Deploy a Collector

Run a Collector, as an agent per node or as a gateway, so that the instrumented pods have somewhere close to send data.

3

Create an Instrumentation resource

Describe the endpoint, sampler and propagators once. All workloads that point to it share these settings.

4

Annotate workloads

Add the language annotation to a test workload first. Restart its pods, because the change applies only when pods are created.

5

Check and widen

Confirm traces in your tool, then roll out the annotation to more workloads, namespace by namespace.

Other places that run programs

Outside Kubernetes, the same ideas apply. On plain virtual machines, you can set the environment variables in the service definition. In Docker images, you can add the agent file and variables in the build. For serverless functions, the project publishes layers for some cloud function platforms, which are add-on packages that bring in the language helper and a lightweight Collector. Each platform has its own details, but the pattern is the same: the helper is added beside the code, never inside it.

Analogy

A hotel manager wants every room fitted with a smoke detector. Visiting every room with a ladder takes weeks. Instead, the building has a standard installation step that runs whenever a room is prepared for a guest, and a detector is added automatically. The Operator is that standard step for pods.

i
Good to know

If a pod has several containers, tell the Operator which container to instrument, using the container name annotation, so that it does not attach the helper to a side-car or tool container by mistake.

Rolling out without Kubernetes

Many companies still run programs on plain servers or in simple container setups, and zero-code works well there too. On a virtual machine, you place the helper files in a known folder, then add the environment variables and the start-up flag to the service definition, such as the file that your service manager reads. After a restart of the service, the helper is active. Because the settings are in one file per service, a configuration management tool can roll them out to many machines at once.

In a container image, you copy the helper files into the image during the build and set the variables in the image or in the deployment file. This makes the image self-contained and predictable, because every copy of the container has exactly the same helper version. The cost is that an upgrade of the helper means rebuilding images. Teams often choose a middle path: a shared base image per language that already includes the helper, which each team builds on.

Option

Virtual machines

Install the helper in a shared folder and change the service definition. A restart activates it.

Option

Container images

Bake the helper into the image or a shared base image, so every container has the same version.

Option

Platform-as-a-service

Use the platform’s setting for environment variables and start options, if it exposes them.

Option

Serverless layers

Add the published layer to the function and set the variables in the function’s configuration.

i
Good to know

Wherever you run, keep the helper version in one place per environment, so an upgrade is one change and not hundreds. A single source of truth for the version is the best protection against fleets drifting apart.

10Configuring It with Environment Variables

Because you cannot edit the code, settings come from outside. OpenTelemetry defines a shared family of environment variables that work across languages.

Why environment variables

An environment variable is a named setting that is supplied to a program when it starts. It is available to every language, it is easy to set in containers, service definitions and deployment files, and it needs no code. OpenTelemetry has standardised a set of names that most language helpers understand, so that one knowledge carries from Java to Python to Node.js.

VariableWhat it controls
OTEL_SERVICE_NAMEThe name of the service, the most important label on all data
OTEL_RESOURCE_ATTRIBUTESExtra labels, such as the environment and version, as key and value pairs
OTEL_EXPORTER_OTLP_ENDPOINTWhere to send data, usually a Collector address
OTEL_EXPORTER_OTLP_PROTOCOLWhich OTLP form to use, such as gRPC or HTTP
OTEL_TRACES_EXPORTER, OTEL_METRICS_EXPORTER, OTEL_LOGS_EXPORTERWhich exporter each signal uses, or none to switch it off
OTEL_TRACES_SAMPLER and OTEL_TRACES_SAMPLER_ARGHow many traces to keep, such as a parent-based ratio sampler and its percentage
OTEL_PROPAGATORSWhich trace-header formats to read and write
OTEL_SDK_DISABLEDA switch to turn the SDK off

The settings that matter first

If you set only three things, set the service name, the endpoint and the environment label. Without a service name, data is grouped under a placeholder such as “unknown_service”, which makes dashboards confusing. Without an endpoint, the helper tries a default address on the local machine, which usually does not exist in a container. The OTLP default ports are 4317 for gRPC and 4318 for HTTP. Without an environment label, you cannot tell production from testing.

Language-specific switches

Beyond the shared names, each helper has its own settings. The Java agent, for example, can disable individual instrumentations, turn on debug logging for the agent itself, or capture selected HTTP headers when you ask. Node.js lets you list the instrumentations to enable. Always keep a record of which settings are shared and which are specific, so you can reuse the shared ones across languages.

Identity

Name and labels

Set the service name and resource attributes so every signal says who sent it and from where.

Destination

Endpoint and protocol

Point to a Collector near the application, and choose the protocol that your network allows.

Volume

Sampler and exporters

Control how much data is kept, and which signals are sent at all.

Joining

Propagators

Choose header formats that match your neighbours, so traces connect across services.

Analogy

Think of the settings label on a washing machine: a few knobs for temperature, spin and programme. You do not open the machine to change how it works, you turn the knobs. Environment variables are the knobs of the helper. A small number of well-chosen settings covers nearly every use.

!
Watch out

Environment variables can contain secrets, such as authentication headers for a vendor. Do not paste them into shared files or logs. Supply them through your platform’s secret store, and give the least access that works.

One plan, three environments

The same settings do not suit every environment. A short planning table avoids the common mistake of copying production settings into development, or the reverse. The values below are illustrative, and you should choose your own after measuring.

SettingDevelopmentStagingProduction
SamplerKeep every traceKeep a large shareKeep a small share, plus errors through the Collector
ExporterConsole, to see spans quicklyOTLP to a test CollectorOTLP to a nearby Collector
Debug logging of the helperOn when investigatingOff, switched on briefly if neededOff
Captured headersA few for testingAs few as neededOnly approved, safe ones
Environment labeldevelopmentstagingproduction

Keep these differences in the deployment files, not in the application. Because zero-code is controlled entirely by settings, switching an environment’s behaviour is a matter of changing variables, and the same application build can move from one stage to the next unchanged. This is one of the most practical benefits of the approach.

11What Telemetry You Actually Get

Knowing what arrives, and what does not, saves you from both disappointment and false confidence.

Traces

The most visible output is traces. For each incoming request, the helper creates a server span with the method, route and status code. For each outgoing call, it creates a client span and adds the trace headers so that the next service continues the same trace. Database calls become spans with the database system and operation. Message sends and receives become producer and consumer spans. When all your services are instrumented, the spans join into trees that follow a request across the whole system.

Metrics

The helper also produces metrics. The common ones are request counts and durations by route and status code, plus metrics about the language runtime, such as memory use, garbage collection and thread counts. These give you the classic “rate, errors and duration” view of each service without any extra code. The exact set depends on the language and version.

Logs

For logs, the help is usually correlation rather than collection. Many helpers add the trace and span identifiers to the log lines written by popular logging libraries. That lets you click from a log line to the trace that produced it. Some helpers can also send logs through OpenTelemetry directly, if you switch that on. Check your language’s status, since log support has matured later than traces.

SignalWhat you usually getWhat you usually do not get
TracesSpans for web requests, outgoing calls, database and messaging operationsSpans for your own business steps
MetricsRequest rate and duration, runtime healthBusiness counts such as orders placed
LogsTrace identifiers on log lines, sometimes log exportBetter log messages or new log statements

Standard names

The helper follows the OpenTelemetry semantic conventions, which are the agreed names for attributes, spans and metrics. For example, it records the HTTP method and the response status code under the agreed names, uses seconds for durations and uses low-cardinality route templates in span names. This is why data from a Java service and a Python service can appear on the same chart.

Sensible privacy defaults

Helpers are designed not to collect everything. HTTP headers and request bodies are normally not captured unless you request specific headers. Database statements are sanitised in many cases, so that literal values are replaced with placeholders. These defaults reduce the risk of leaking private data, but they are not a guarantee, so you should still check what leaves your network, as the security chapter explains.

Analogy

A traffic camera records the plate number, speed and direction of each car, but not what is in the boot or who is driving. The details it records follow a fixed form, so the police in two different cities can read each other’s records. The helper’s output has the same standard form, and similar limits on what it looks at.

i
Good to know

If you do not see a span you expected, the usual cause is that the library or its version is not on the supported list. The documentation of each helper includes a list of supported libraries and versions, and it is the first place to look.

12How It Works Inside: The Life of a Request

Follow one web request through an instrumented program to see exactly where each piece of telemetry is born.

The data flow

flowchart LR
Req["Incoming request"] --> Hook1["Instrumented web framework"]
Hook1 --> App["Your application code"]
App --> Hook2["Instrumented database driver"]
App --> Hook3["Instrumented HTTP client"]
Hook1 --> SDK["Embedded SDK: sampler, processor"]
Hook2 --> SDK
Hook3 --> SDK
SDK --> Exp["OTLP exporter"]
Exp --> Col["Collector"]
Col --> Back["Backend"]
Figure 5: Instrumented libraries report to the embedded SDK, which exports to the Collector.
1

The request arrives

The instrumented web framework reads any incoming trace headers, rebuilds the parent context and starts a server span.

2

The sampler decides

The sampler looks at the parent decision and its own rule, and chooses whether to record. A dropped span still carries the context forward.

3

Your code runs

The current span is stored in the context, which follows the code even as work moves between threads or callbacks.

4

Library calls create child spans

When your code calls the database or another service, the instrumented library starts a child span, adds trace headers to outgoing requests and ends the span on completion.

5

The response goes out

The framework records the status code, marks errors, and ends the server span.

6

Spans are batched and exported

Finished spans go to a batching processor that groups them and sends them in the background, so your request is not delayed.

Staying safe with versions

Instrumentation is written against particular versions of each library. If a library changes its internals, the instrumentation might break or, worse, break the application. The Java agent protects against this with a check often called muzzle. Before it rewrites a class, it checks that the library version has the shape the instrumentation expects. If it does not, the agent leaves that library alone instead of risking a crash. Other languages use version ranges in their packages for the same purpose. This is why the supported-versions list matters.

Context across threads

One of the harder jobs of the helper is keeping the context correct when work jumps between threads, thread pools, callbacks and asynchronous tasks. If the context is lost, a child span appears as the start of a new trace, and the tree breaks. The Java agent wraps common executors and frameworks to carry the context along. Python and Node.js use language features designed for the same purpose. Custom thread handling in your own code is the usual place where the context gets lost.

Flushing on shutdown

Because spans are batched, a few may still be waiting when the program stops. The helper normally registers a shutdown step that sends remaining data before the process ends. For programs that are killed abruptly, the last moments of data may be lost. In short-lived jobs and serverless functions, make sure the program has time to flush, or use a shorter batching delay.

Analogy

A relay race follows a baton from runner to runner. The helper is a team of officials stationed at each handover who note the time and pass a coloured ribbon, the context, from hand to hand. If a runner hands the baton over in a hidden corridor where no official stands, the ribbon is dropped, and the record shows a new race. Thread hand-offs are those hidden corridors.

A troubleshooting guide

When something does not work, a methodical approach is faster than guessing. Match the symptom to the likely cause in the table below, then work through the checks.

SymptomLikely causesFirst check
No spans at allHelper not loaded, wrong start option, settings typo, SDK disabledLook for the helper’s start-up message and try a console exporter
Spans for some libraries onlyUnsupported library version, bundler hiding libraries, load orderCompare versions with the supported list
Traces broken between servicesDifferent header formats, a proxy removing headers, lost context in threadsCompare propagator settings and inspect headers at each border
Everything under “unknown_service”No service name setSet the service name variable
Data never reaches the backendWrong endpoint or protocol, blocked network path, wrong credentialsCheck the Collector’s receiver counters and the helper’s error logs
Too much dataNo sampling, noisy instrumentation, health checksSet a sampler and disable the noisy parts
Application slower to startMany classes or modules rewrittenAllow for the extra time, and disable unneeded instrumentation
Duplicate spansTwo agents or two SDKs activeRemove the extra agent or the extra SDK dependency

A good habit is to change one thing at a time and test after each change. Because zero-code involves many pieces, such as the helper, the settings, the network and the backend, changing several at once makes it impossible to know which change fixed or broke the result.

13Adding Manual Touches on Top

The best systems use zero-code for breadth and add a few manual details where business meaning matters.

One SDK, shared

The helper brings its own SDK and registers it globally. If your own code needs to add spans or attributes, you should depend only on the small API package and not on a second SDK. The API calls are routed to the SDK that the helper has already started. This means your manual spans appear in the same trace tree, with the same sampling and the same exporter, as the automatic ones. Adding a second SDK by mistake often causes duplicate or missing data.

Three small additions

Add

Attributes on the current span

Fetch the span that the helper created for the current request and add business labels, such as the customer tier or the number of items in the cart.

Add

Spans around key steps

Wrap an important internal step, such as “calculate discount”, in its own span so it shows in the trace tree. In Java, an annotation can do this with a single line above the method.

Add

Custom metrics

Use the API to count business events, such as orders placed, and let the helper’s SDK export them with the rest.

flowchart TD
Z["Layer 1: zero-code helper covers libraries"] --> M["Layer 2: manual API calls add business meaning"]
M --> S["Shared SDK exports one consistent trace"]
Figure 6: Layers combine. The helper covers the many, and your calls explain the few.

Naming your own additions

Custom attributes should follow the same grammar as the standard ones: lowercase, with dots for groups and underscores for words in a group. Put them under a prefix that you own, such as your company name, and never invent names inside the standard groups. This avoids clashes with names that may be added in the future.

A short method for deciding what to add

1

Look at real traces

Find the slowest and most important requests, and note which steps are invisible.

2

Name the questions

Write the questions you could not answer, such as “which customer tier is slow?”

3

Add the smallest change

Add one attribute or one span that answers each question, and no more.

4

Check the cost

Make sure new attributes do not carry private data or an unbounded number of values.

Analogy

A museum buys an audio guide that explains every famous painting in the same style. The curator then records a few extra notes for the paintings that have a special story. The standard guide gives breadth in one purchase, and the curator’s notes give the personality. Zero-code is the standard guide and manual spans are the curator’s notes.

!
Watch out

Adding the API dependency is a code change, even if a small one, so this step is no longer zero-code. That is fine and expected. Keep the changes few and valuable, so that the benefit of fast, uniform coverage is not lost.

14Performance, Overhead and Scaling

A helper that watches everything is never completely free. Knowing where the cost appears lets you keep it small and predictable.

Where the cost comes from

Zero-code instrumentation costs something in four places. The first is start-up time. In the Java case, the agent examines and rewrites classes as they load, which can make the application take noticeably longer to start. The second is memory: the SDK, the buffers for waiting spans and the instrumentation itself all use space. The third is processor time: each span means a little extra work to create, fill and export. The fourth is network: every span, metric and log that leaves the program uses bandwidth.

The size of these costs depends on the language, the version, the number of libraries instrumented and the amount of traffic. For many typical services, the extra processor and memory use is modest, but it is not zero, and there are no universal numbers you can trust. The only reliable method is to measure your own service with and without the helper, using a realistic load.

CostWhat drives itHow to reduce it
Start-up timeNumber of classes or modules rewrittenDisable instrumentation you do not need; allow for it in health-check timeouts
MemorySDK buffers, batch queues, instrumentation codeSet queue limits; give containers enough memory headroom
Processor timeSpans created per requestSample, and switch off noisy instrumentation
NetworkVolume of exported telemetrySend to a nearby Collector; sample; drop low-value spans

Sampling: the biggest lever

Most of the cost scales with the number of spans you create and export. Sampling means keeping only a share of traces. A common production choice is a parent-based sampler with a ratio, such as keeping one trace in ten, set through the environment variables. The decision is made at the start of a trace and passed along, so that all services keep or drop the same traces. Sampling at the application is cheap, because a dropped trace costs almost nothing to process. For smarter choices, such as always keeping errors, you can add tail sampling in the Collector.

Noisy instrumentation

Some instrumentation creates a very large number of spans. A program that reads from a database inside a tight loop may create thousands of tiny database spans for one request. Health-check endpoints can create a span every few seconds from every pod. These add cost and clutter without adding insight. Most helpers let you disable instrumentation for a specific library, or ignore requests to chosen paths. Review your first traces for such noise and remove it early.

Scaling to many services

Because the helper lives inside each program, its cost is paid once per instance, and it grows with your fleet. Two hundred services with five instances each means a thousand helpers sending data. A Collector close to the workloads helps. It receives data over a fast local link, batches it, and handles retries and delivery to the destination, so that the application does not need to. In Kubernetes, a Collector per node is a common design.

flowchart LR
P1["Pod with helper"] --> N["Node Collector"]
P2["Pod with helper"] --> N
P3["Pod with helper"] --> N
N --> G["Gateway Collector: sampling, redaction"]
G --> B["Backend"]
Figure 7: Many instrumented pods send to a nearby Collector, which forwards to a gateway for shared rules.
Analogy

A delivery company that tracks every parcel with a scanner knows that scanning has a cost: a few seconds per parcel, and a battery to charge. For a small town, this is invisible. For a huge sorting centre, it matters, so the company scans only what it must and sends all the scans to a central office through one fast link. Sampling and a nearby Collector play those roles.

!
Watch out

When you roll out zero-code to a whole fleet at once, you may see a sudden rise in memory, start-up time and network use. Roll out in steps, watch the numbers on the first group, and keep resource requests and health-check timeouts generous enough for the new start-up behaviour.

A simple way to measure the overhead

Because no universal figure exists, run a small experiment on one representative service. First, run the service without the helper under a steady, realistic load for ten or fifteen minutes, and record start-up time, memory, processor use and the response times that matter, such as the 95th percentile. Second, run the same service, with the same load and the same machine size, with the helper attached and your chosen sampler. Third, compare the two sets of numbers.

Look at the difference rather than the absolute values. If the extra processor use is a few percent and the added start-up time fits inside your health-check timeout, the helper is a good fit. If the difference is larger, find out why: perhaps one instrumentation is very noisy, the sampler keeps everything, or the container has too little memory for the buffers. Fix the cause, repeat the test, and write down the result so that the next team can reuse the numbers as a guide, not as a promise.

i
Good to know

Repeat the experiment after major upgrades of the helper or of your main libraries. Overheads can change from one version to the next, usually for the better, but a new instrumentation that is on by default may add spans you did not have before.

15Security, Monitoring and Cost

A helper that sits inside every program is powerful. That power calls for careful permissions, regular checks and a watch on the bill.

Security: what the helper can see and do

The helper runs with the same permissions as your application. It can read whatever the application can read. That is acceptable when you trust the source, but it also means that a compromised helper would be as dangerous as a compromised library. Treat the agent files as part of your software supply chain: download them from the official project, pin a specific version, check its integrity, and, in larger organisations, serve them from an internal mirror.

Safety

Pin versions

Use an exact version of the agent and instrumentation packages, and upgrade deliberately after testing.

Safety

Limit captured data

Leave header and body capture off unless needed, and name only the specific safe headers you want.

Safety

Redact at the Collector

Remove or mask sensitive attributes in a central place, so a single rule protects every service.

Safety

Minimal privileges

Give eBPF-based observers and Operator components only the permissions they need, and review them.

Private data in telemetry

Even with safe defaults, private data can slip through. A web address may include a token in its query part. A database statement may include a name if it was not parameterised. A span attribute that you add by hand may include an email. Treat telemetry as data that leaves your system, and review samples of it regularly. Use Collector processors to delete or mask sensitive fields, and prefer allow-lists for the most sensitive destinations.

Monitoring the helper itself

A silent failure is the main danger: the application works perfectly, but no telemetry arrives. Build simple checks. Confirm that each service appears in your tracing tool, with recent spans. Watch the Collector’s counters for data received from each source. Look at the application’s logs for messages from the helper about failed exports. Most helpers also have a debug setting that prints more detail about what they are doing, which is useful in a test place but too noisy for permanent use.

QuestionHow to check
Is the helper loaded?Look for its start-up message in the application logs
Is data leaving the program?Use a console exporter in testing, or the Collector’s receiver counters
Is data reaching the backend?Search for the service name and a recent trace
Are exports failing?Check the helper’s logs and the Collector’s failure counters
Is the cost under control?Track spans per second per service and data sent per day

Cost: automatic means a lot

The speed of zero-code is also its financial risk. Because it instruments everything it knows, a large fleet can produce a very large stream of spans, and many monitoring tools charge by volume. Control the cost with sampling, with disabling noisy instrumentation, with dropping health-check traffic, and with removing labels that have too many different values. Track the volume per service so that a team that suddenly doubles its data is noticed early.

i
Good to know

Ask for a cost estimate before the first fleet-wide rollout. Instrument a small, typical group of services, measure the data volume per service per day, and multiply by the number of services. This simple estimate avoids unpleasant surprises on the first invoice.

Analogy

A security camera system that records every doorway generates vast video files. Without a plan, the storage fills and the bill grows. A sensible owner chooses which doors matter, records only on motion, and deletes old footage. Sampling, filtering and retention are the same habits for telemetry.

A rollout safety checklist

Before you roll the helper out beyond the pilot group, tick off a short list. It takes minutes and prevents most of the painful surprises.

  • Identity: every service has a clear service name, an environment label and a version label.
  • Destination: the endpoint and protocol are correct and reachable from the workloads, and a Collector is ready to receive.
  • Volume: a sampler is set, noisy instrumentation is reviewed, and an estimate of data per day exists.
  • Privacy: header capture is limited, sample telemetry has been inspected and redaction rules are in the Collector.
  • Compatibility: supported library versions are confirmed, and no other agent is attached.
  • Resources: memory requests and health-check timeouts allow for the helper’s overhead.
  • Rollback: there is a quick way to switch the helper off, such as removing the annotation or setting the disable variable.

16Advantages, Disadvantages and Trade-offs

Zero-code is a strong default, not a universal answer. An honest list of both sides helps you use it well.

Advantages

  • No source code changes, so no development cycle is needed.
  • Fast first data, often within hours.
  • Works for legacy and purchased software that you cannot edit.
  • Uniform, standard naming across teams and languages.
  • Wide coverage of popular libraries in one step.
  • Settings can be changed from outside, per environment.

Disadvantages

  • It cannot see business meaning without manual additions.
  • It covers only supported libraries and versions.
  • It adds start-up time, memory and processor use.
  • It can produce more data than you need, raising cost.
  • It can clash with other agents or with bundlers.
  • Support is uneven across languages, and some need privileges.

Trade-offs you will face

ChoiceBenefitCost
Instrument everything automaticallyMaximum coverage with least effortHigh volume and some noise
Enable only chosen librariesLower cost and less noiseMore blind spots, more work to choose
Use the Operator for fleet rolloutOne central switch for many workloadsA new component to run, and pods must restart
Bake the agent into imagesPredictable, versioned, offline-friendlyImages must be rebuilt for upgrades
Add manual spansBusiness-level insightCode changes and care with naming
High sampling rateMore complete dataHigher cost and load
Low sampling rateLower costRare problems may be missed
Analogy

A ready-made suit fits most people well enough, costs little and is ready today. A tailored suit fits perfectly but takes weeks and costs more. Many people buy the ready-made suit and pay a tailor for a few small changes. Zero-code is the ready-made suit, and manual instrumentation is the tailor’s adjustment.

i
Rule of thumb

Use zero-code as the default for every service. Reach for manual instrumentation only where a question about the business cannot be answered from the automatic data.

17Patterns, Anti-patterns and Best Practices

Teams that adopt zero-code well repeat a few habits. Teams that struggle repeat a few mistakes.

Patterns that work

Pattern

Pilot, then widen

Start with a few typical services, check data and overhead, then roll out by namespace or team.

Pattern

Central settings

Keep endpoint, sampler and propagators in one shared place, such as the Operator’s Instrumentation resource.

Pattern

Collector nearby

Send data to a Collector close to the workloads, and do sampling and redaction there.

Pattern

Golden dashboards

Build a standard dashboard of rate, errors and duration that works for every instrumented service.

Pattern

Targeted manual additions

Add a few spans and attributes only where traces show a business gap.

Pattern

Versioned rollouts

Pin the helper’s version, test upgrades in a safe place, and upgrade on purpose.

Anti-patterns to avoid

ANTI-PATTERN 01Avoid
What it looks like

The helper is switched on for the whole fleet at once, with default settings and no sampling.

Why it hurts

Memory, start-up time and data volume jump together. Health checks fail and the monitoring bill surprises everyone.

Do this instead

Pilot on a few services, measure, set sampling, and widen in steps.

ANTI-PATTERN 02Avoid
What it looks like

Two instrumentation agents, such as a commercial one and the OpenTelemetry one, are attached to the same program.

Why it hurts

They may rewrite the same classes in conflicting ways, causing crashes, missing spans or double counting.

Do this instead

Use one agent per program, unless the documentation clearly supports a combination.

ANTI-PATTERN 03Avoid
What it looks like

A team adds a full second SDK to its code to create manual spans, while the helper already runs one.

Why it hurts

Two SDKs can export duplicates or lose the link between automatic and manual spans.

Do this instead

Depend only on the API package in application code, and let the helper provide the SDK.

ANTI-PATTERN 04Avoid
What it looks like

Nobody checks whether data arrives after the rollout. The helper may silently be disabled by a typo in a setting.

Why it hurts

The first time anyone notices is during an incident, when the data is needed most.

Do this instead

Add a check that every service has recent spans, and alert when one goes quiet.

Best practices at a glance

1

Set identity first

Always set the service name, environment and version before anything else.

2

Match the propagators

Make sure all services read and write the same trace-header formats, or traces will break at the borders.

3

Sample on purpose

Choose a sampler and a rate based on traffic and budget, not on the default.

4

Remove noise early

Disable instrumentation and ignore paths that add cost without insight.

5

Test with a console exporter

Confirm spans appear before you connect to the network and the backend.

6

Review regularly

Revisit supported versions, volumes and captured data every quarter.

Common mistakes

!
Mistake

Setting the variables on the wrong container in a multi-container pod, so the helper is attached to a side-car and not to the application.

!
Mistake

Annotating a Kubernetes workload and expecting running pods to change. The helper is added only when a new pod is created, so the pods must be restarted.

!
Mistake

Assuming that a missing span means a bug in the helper, when the library version is simply not supported. Check the supported list first.

Keeping the system healthy over time

Adoption is not a one-time event. Libraries change, new services appear and old services are retired, and the helper needs attention to keep up. A light routine is enough. Each quarter, review the list of helper versions in use and move laggards forward. Check that new services were created with the helper attached, perhaps by making it part of the template that every new service starts from. Review the data volume per service and the top noisy spans. And look at a few real traces with fresh eyes, asking whether they still answer the questions the business asks.

It also helps to name an owner. A platform team that owns the Operator settings, the shared base images and the standard dashboards can answer questions and make changes once for everyone. Without an owner, the system slowly drifts, with services on many different versions and settings, and the original promise of uniform coverage fades.

Example: the quarterly review

A platform team reviews its fleet each quarter. It finds that nine services run a three-year-old agent version, two have sampling switched off and one new service was never annotated. The team upgrades the old versions in a planned change, restores sampling, and annotates the new service, all within one afternoon. Without the routine, each problem would have surfaced separately and at a worse time.

18Real-World Walkthrough and Industry Usage

Let us see zero-code in action in an imaginary company, then look at where the same ideas appear in practice.

ShopFast: from blind to visible in a week

ShopFast is an imaginary online store with sixty services in Java, Python and Node.js, running on Kubernetes. Monitoring is patchy: a few services have hand-written metrics and no tracing at all. During a sale, checkout becomes slow, and nobody can say which service is to blame. Management asks for tracing everywhere within a week, and the developers are busy with the sale.

flowchart LR
Shop["Shopper"] --> GW["Gateway"]
GW --> Ord["Orders: Java"]
Ord --> Pay["Payment: Node.js"]
Ord --> Rec["Recommendations: Python"]
Ord --> DB["Orders database"]
Ord -. telemetry .-> OC["Collector"]
Pay -. telemetry .-> OC
Rec -. telemetry .-> OC
OC --> BE["Tracing backend"]
Figure 8: Three services in three languages, all instrumented without code changes, send to one Collector.
1

Day 1: set up the platform pieces

The platform team installs the Operator and a node-level Collector, and creates an Instrumentation resource with the endpoint, a ten percent parent-based sampler and the W3C propagators.

2

Day 2: pilot three services

They annotate the orders, payment and recommendations workloads, one per language, and restart them. Traces appear within minutes, with spans for web requests, outgoing calls and database queries.

3

Day 3: read the traces

A slow checkout trace shows that most of the time is spent in a single call from orders to recommendations, which in turn waits on an unindexed catalog query.

4

Day 4: remove noise

Health-check spans are dropped, and one chatty cache library is disabled, cutting data volume by about a third in their measurements.

5

Days 5 to 7: roll out and add meaning

They annotate the remaining namespaces step by step. Orders adds two business attributes, the customer tier and the cart size, so traces can be filtered by them.

By the end of the week, every service shows up in the tracing tool, and the team has found and fixed the real cause of the slow checkout. No team changed its service code except the small attribute addition in orders. The speed came from doing the work at the platform level.

Where the same ideas appear in industry

Vendor and cloud distributions

Several vendors and cloud providers package the OpenTelemetry agents with their own defaults. Examples include the AWS Distro for OpenTelemetry and distributions from Splunk and Elastic. They let customers use zero-code helpers with vendor-specific settings, while keeping the standard data format.

Serverless functions

For cloud functions, the project and cloud providers offer ready-made layers that add a language helper and a light Collector to a function without changing its code, which makes tracing of event-driven systems much quicker to set up.

Platform engineering teams

Teams that run internal platforms often make zero-code the default for every new workload, so that tracing exists from the first deployment. Product teams then add custom spans only where they need business insight.

Open-source and commercial backends

Backends such as Jaeger, Grafana Tempo, Honeycomb, Datadog, New Relic and Dynatrace accept OpenTelemetry data. Because the helper exports standard OTLP, a company can change backends without redoing its instrumentation.

Instrument the platform once, and every program running on it becomes visible.

19Frequently Asked Questions

Short answers to the questions people ask most often.

Q1What does zero-code instrumentation mean?
It means collecting telemetry from an application without changing its source code. A helper is attached from outside, usually when the program starts, and it watches common libraries to create spans, metrics and log links.
Q2Is it the same as automatic instrumentation?
Yes, in practice. The project’s documentation now prefers the term zero-code, but you will still see automatic instrumentation and auto-instrumentation in many articles and tools.
Q3Do I really not need to touch my code?
You do not edit your application’s source. You do install the helper, set environment variables, and often change a start command or deployment file. Adding manual spans later is optional and does need small code changes.
Q4Which languages are supported?
Java, Python, Node.js, .NET and PHP have helpers that work from outside the code. Go is supported through eBPF-based tools. Web servers such as Apache and Nginx have modules. Maturity varies, so check the documentation for your language.
Q5How does the Java agent change my classes?
The JVM loads the agent first. As classes load, the agent rewrites chosen library classes in memory to add telemetry calls. The files on disk are not changed.
Q6What is monkey patching?
It is replacing a library function at runtime with a wrapped version that records telemetry and then calls the original. Python and Node.js helpers use it to add spans without changing your code.
Q7How does the Kubernetes Operator attach the helper?
You add a language annotation to a workload. When a pod is created, a webhook adds an init container that copies the agent files and sets environment variables, so the application starts with the helper attached.
Q8Why do I need to restart pods after adding the annotation?
The Operator changes a pod only while it is being created. Existing pods stay as they were, so they must be recreated to pick up the helper.
Q9Which settings should I set first?
The service name, the endpoint of your Collector, and an environment label. Then choose a sampler and the trace-header formats that match your other services.
Q10Why do I see no spans from a library I use?
Most often, the library or its version is not supported. Other causes are a bundler that hides the library from patching, or a load order problem. Check the supported list and test with a console exporter.
Q11Does zero-code slow my application?
It adds some start-up time, memory and processor use. For many services the extra is modest, but you should measure your own service under realistic load and use sampling to control it.
Q12Can I use it together with manual instrumentation?
Yes, and this is the usual best approach. Use only the API package in your code, and the helper’s SDK will handle exporting both the automatic and the manual spans.
Q13Is it safe for private data?
The defaults avoid capturing bodies and most headers, and many helpers sanitise database statements. Still, check what leaves your network, and use Collector processors to remove sensitive fields.
Q14Can I run it with another monitoring agent?
It is risky to attach two agents that rewrite the same code. Use one per program unless the documentation of both says they can be combined.
Q15How do I turn off one noisy instrumentation?
Most helpers let you disable instrumentation for a named library, or ignore requests to chosen paths, using environment variables or settings. Review your first traces to find the noisy parts.
Q16Do traces join across services automatically?
Yes, if each service uses the helper with matching trace-header formats. The default W3C format is understood by all of them. If older services use another format, add that propagator too.
Q17What about programs I cannot restart?
Most helpers attach at start-up, so a restart is needed. eBPF-based tools can observe a running program from outside, though with more limits. Plan a restart window where you can.
Q18Where should I confirm the exact names and steps?
Use the OpenTelemetry documentation for your language and version. Package names, variable names and support levels change over time, so confirm them before you rely on them.
Q19Is zero-code instrumentation enough on its own?
For many teams it is a strong start and covers the technical flow of requests. It does not know your business meaning, so most teams add a few manual attributes and spans for the operations they care about most.
Q20How do I turn it off quickly if something goes wrong?
Use the disable variable for the SDK or the helper, remove the Kubernetes annotation and restart the pods, or remove the start flag. Plan this rollback before the rollout, and test it once.
Q21Does it work for programs I did not write?
Often yes, as long as they run in a supported language and use supported libraries, and you can change how they are started. This is one of its biggest strengths for legacy and purchased software.
Q22Should I wait for my language to be fully supported?
Not necessarily. Test the helper with your real libraries. If the coverage meets your needs, use it, and add library or manual instrumentation for the parts it misses.

20Summary and Key Takeaways

If you remember only a handful of ideas from this guide, make it these.

Key Takeaways

  • Zero-code means zero changes to source code. A helper attached from outside watches your libraries and creates traces, metrics and log links without editing the application.
  • The technique depends on the language. Java uses a bytecode agent, Python and Node.js patch functions at runtime, .NET uses runtime hooks, and Go relies on eBPF from outside the process.
  • Configuration lives in environment variables. Set the service name, endpoint, environment, sampler and propagators first, and pin the helper’s version.
  • Kubernetes makes it scale. The OpenTelemetry Operator attaches helpers through annotations and a webhook, but pods must be restarted to pick them up.
  • It sees libraries, not business meaning. Combine it with a few manual spans and attributes, using only the API so the helper’s SDK stays the single exporter.
  • It has a real cost. Expect extra start-up time, memory and data volume, and control them with measurement, sampling, noise removal and a nearby Collector.
  • Roll out carefully and keep watching. Pilot first, check that data arrives, redact sensitive fields at the Collector, and review supported versions and volumes regularly.