Observability & Tracing · OpenTelemetry course

Lesson 01

OpenTelemetry: the architecture, from zero

You already carry a working model in your head. This lesson tests it, keeps the four things you got right, and sharpens the two edges that will otherwise trip you up in every config file you read.

The one win

By the end you can draw the OpenTelemetry pipeline and place every box correctly — especially the line where OpenTelemetry stops and Jaeger/Dynatrace begin. That single boundary explains most of the setup from your meeting.

Start from what you said

Your pre-assessment, near-verbatim:

“Each request has a trace ID … shared via gRPC or HTTP … then used by Jaeger or OpenTelemetry to display the traces of that request across several microservices. A trace ID is for the whole request and a span ID is just for the microservice itself.”

Four things there are correct: a trace ID per request; trace ID = the whole request; shipped over gRPC or HTTP; correlated across microservices. Hold onto those. Now the two edges.

Edge 1 — OpenTelemetry does not display anything

Predict first

If OpenTelemetry isn't the thing showing you the pretty waterfall UI… then what is OpenTelemetry responsible for? Guess in one word before reading on.

OpenTelemetry is a vendor-neutral standard + toolkit for producing and shipping telemetry. It generates the data inside your app and pushes it out. It has no storage and no UI. The thing that stores traces and draws them is a separate backend — Jaeger or Dynatrace in your project.[components]

┌──────────────────────── YOUR SERVICE (JVM) ────────────────────────┐ │ │ │ app code + auto-instrumentation │ │ │ "here is a span / metric / log" │ │ ▼ │ │ OTel API ──────► OTel SDK ──────► Exporter │ │ (what to record) (batch, sample, (serialise to OTLP) │ │ add service.name) │ │ └─────────────────────────────────────────────────┼──────────────────┘ │ OTLP gRPC :4317 or HTTP :4318 (POST /v1/traces) │ ┌───────────────────────┼───────────────────────┐ ▼ ▼ ▼ (optional) ┌────────────┐ ┌────────────┐ OTel COLLECTOR │ JAEGER │ │ DYNATRACE │ receive→process→export ───► │ store+UI │ │ store+UI │ │ └────────────┘ └────────────┘ └──────────────────────────────────────────────┘ ── OpenTelemetry's job ──────────────────────────►│◄──── backend's job ────► PRODUCE + SHIP │ STORE + DISPLAY

The vertical line is the whole lesson. Everything left of it is OpenTelemetry (neutral, swappable). Everything right of it is a backend you chose. [collector docs]

So your sentence becomes: “…shipped via OTLP over gRPC or HTTP, then used by Jaeger or Dynatrace (not OpenTelemetry) to display the request across several microservices.” The Collector in the middle is optional — in dev you often skip it and export straight to Jaeger, which speaks OTLP natively.[jaeger]

Which statement is true about OpenTelemetry?

Edge 2 — a span is one operation, not a service

Predict first

One request enters order-service. Inside, it runs a DB query and calls payment-service over HTTP. How many spans do you think that one service produces? One? Or more?

A span is a single unit of work — one operation with a start time, a duration, and a name.[traces] A single microservice usually emits many spans for one request. A trace is the whole tree of spans that share one trace ID; each span also has its own span ID and points to its parent span ID. That parent pointer is what builds the tree.

TRACE (trace_id = a0892f35… — the SAME id on every span below) order-service span kind ┌───────────────────────────────────────────────────────────┐ │ span: POST /orders id=f03067 parent=(none, root)│ SERVER │ ├── span: SELECT items id=7b12aa parent=f03067 │ INTERNAL/CLIENT │ └── span: POST /pay ───────►id=9c41de parent=f03067 │ CLIENT └───────────────────────────────────┼───────────────────────┘ │ trace_id travels in the │ W3C traceparent header ▼ payment-service ┌───────────────────────────────────────────────────────────┐ │ span: POST /pay id=2ef880 parent=9c41de │ SERVER │ └── span: charge card id=5a77c1 parent=2ef880 │ INTERNAL └───────────────────────────────────────────────────────────┘ Two services · ONE trace_id · SIX spans · linked by parent ids

The child span's parent = the caller's span id. The trace id is constant. That is literally how the waterfall gets drawn. [context propagation]

Answer to the prediction: that one order-service produced three spans. So “a span is just the microservice” → replace with “a span is one operation; a service emits as many spans as it does operations.”

One request runs a handler, one DB query, one outbound call in a single service. How many spans?

The bit under “saved to a log”: three separate signals

You said the trace is “saved to a log which is then shared.” Close, but tracing is not logging. OpenTelemetry carries three separate kinds of telemetry, each its own data type on the same pipeline:[signals]

SIGNAL WHAT IT IS SHAPE ───────── ──────────────────────────────────────── ───────────────── TRACES the path of a request through the system spans in a tree METRICS a measurement captured at runtime numbers over time LOGS a recording of a discrete event timestamped text (+ BAGGAGE — OTel lists it beside these three, but it is the odd one out: key/values that RIDE ALONG the context to correlate the signals above. It carries no telemetry and is NEVER exported. Never put secrets/PII in it.)

A fair question: OpenTelemetry's own docs list four things — traces, metrics, logs and baggage. So why is baggage set apart above? Because the first three are telemetry: they have a data model, they are exported over OTLP, and a backend stores them. Baggage is none of those. It never leaves the request it is riding on, and no backend ever receives it. Same list in the docs; different job entirely. We come back to this properly in Lesson 2.[baggage]

Your project is “mainly for tracing,” so we live in the traces row for now — but the reason logs matter to you later is correlation: because a log line can carry the same trace ID, you can jump from a trace straight to the exact logs of that request. That's the payoff we build toward.[logs]

Which of these is never exported to a backend as telemetry?

Where does Spring fit? (one-line teaser)

You won't write OTel API calls by hand. In Spring Boot 3.x, your code emits Micrometer Observations; a bridge (micrometer-tracing-bridge-otel) turns each into an OTel span; Actuator auto-wires the exporter. That whole left-hand box gets set up for you — which is exactly why the meeting had “a lot of setup.” We dissect that stack in Lesson 3.[micrometer]

Prove it — recall drill

Everything the four questions ask about is in this one recap — the same two diagrams from above, condensed so you don't have to scroll back for them.

┌──────────────── YOUR SERVICE (JVM) ─────────────────┐ │ │ │ app code + instrumentation │ │ │ │ │ ▼ │ │ OTel API ─────► OTel SDK ─────► Exporter │ │ (what to (batch, sample, (serialise to │ │ record) service.name) OTLP) │ └──────────────────────┬───────────────────────────────┘ │ OTLP gRPC :4317 ──┤ or ├── HTTP :4318 (POST /v1/traces) ▼ (optional) COLLECTOR ─────► JAEGER / DYNATRACE store + display ── OpenTelemetry's job ──────►│◄────── the backend's job ── PRODUCE + SHIP │ STORE + DISPLAY TRACE trace_id = a0892f35… ← THE SAME on every span below ├── span id=f03067 parent=(none, root) POST /orders ├── span id=7b12aa parent=f03067 SELECT items └── span id=9c41de parent=f03067 POST /pay ──► payment-service

Cover the recap with your hand, then answer

Say each answer out loud before you look — retrieval is what makes it stick. Then click to reveal and check yourself against the diagram.

1. Name the four left-of-the-line components, in order, from app code to the wire.

Instrumentation (produces telemetry) → API (what to record) → SDK (batch, sample, tag with service.name) → Exporter (serialise to OTLP and send). [components]

2. What is the difference between a trace ID and a span ID?

The trace ID is constant across the entire request and every span in it. Each span ID identifies one operation; a span also stores its parent span ID so the tree can be reconstructed.

3. Which two default ports does OTLP use, and for what?

4317 = OTLP over gRPC; 4318 = OTLP over HTTP (traces POST to /v1/traces). [otlp spec]

4. Draw the boundary: what is OpenTelemetry's job, and where does Jaeger's begin?

OpenTelemetry: produce + ship (instrument → API → SDK → exporter → OTLP). Jaeger/Dynatrace: store + display. The optional Collector sits in between to receive/process/re-export.

Primary source — read this next

Spend 10 minutes on the single best page for this lesson: OpenTelemetry → Concepts → Components. It names every box in the diagram above in the project's own words. If you read one thing, read that.

I'm your teacher — interrogate me. Anything blurry? Ask, e.g.: “Why would I ever run a Collector instead of exporting straight to Jaeger?”, “Show me a real traceparent header”, or “What does the SDK actually do when it batches?” Follow-up questions are how the next lesson gets aimed.

Sources