# Jaeger 入門：用 OpenTelemetry 串起分散式追蹤

> 程式只對 OpenTelemetry 產生 span、用 OTLP 送進 Jaeger，要換 Grafana Tempo 只改 exporter；含 gortex 接法與 Docker 範本。

原文：https://sheng.page/posts/jaeger-distributed-tracing-with-otel/ · 發布：2026-06-07 · 作者：Sheng

一個請求打進來，經過 gateway、好幾個 service、資料庫，最後回應。哪一段慢了？log 散在各台機器很難拼。分散式追蹤把這條請求的每一段串成一棵 span 樹，Jaeger 就是拿來收集與檢視這棵樹的工具。這篇講 Jaeger 是什麼、OpenTelemetry 為什麼是關鍵、gortex 的 `Tracer` 介面怎麼接上，以及怎麼用 Docker 在本機快速跑起來。

## 分散式追蹤要解什麼

微服務下，一個外部請求會散成內部好幾個呼叫，出問題時看單台的 log 只看到片段。分散式追蹤的單位是 span：一段有名字、有起訖時間的工作，像一次 HTTP handler、一次 DB query。span 之間有 parent / child 關係，串起來就是一個 trace，一棵完整的請求樹，看這棵樹就知道時間花在哪、哪一段報錯。

關鍵是 trace context 要能跨服務傳遞：上游把 trace ID 放進 header，下游接著用，這條 trace 才接得起來。gortex 在程式內部用 `context` 傳 span（見[可觀測性那篇](/posts/gortex-observability-health-tracing/)），跨服務則靠 OTel 的 propagation。

## Jaeger 是什麼

Jaeger 是 CNCF 的分散式追蹤平台，負責收集、儲存、查詢 span，並提供一個 Web UI 把 trace 畫成時間軸瀑布圖。正式部署會拆成幾個角色：collector 收 span、儲存後端存資料、query 加 UI 給你查。

開發階段不用拆這麼細，官方有一個 all-in-one image，把 collector、query、UI 包在一個 container 裡、用記憶體存資料，起來就能用，重開就清空，正好適合本機開發。

## OpenTelemetry：共通語言

這裡的關鍵字是 OpenTelemetry（OTel）。它是一套廠商中立的可觀測性標準：一組 instrumentation 的 API / SDK，加上一個傳輸協定 OTLP。你的程式只對 OTel 產生 span，要送去哪個後端是另一回事。

重點在於 instrumentation 與後端解耦。今天送 Jaeger，明天想換 Grafana Tempo 或 Zipkin，改的是 exporter 設定，不是你滿地的 instrumentation 程式。

gortex 把這個解耦做進框架：它只依賴一個 `Tracer` 介面，附一個 `OTelTracerAdapter` 把 gortex span 轉成 OTel span（severity 對映、tags、status 都搬過去）。所以「gortex → OTel → Jaeger」這條路是現成的，你的業務碼只看到 `Tracer` 介面。設定大致長這樣：

```go
import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"

    gotel "github.com/yshengliao/gortex/observability/otel"
    "github.com/yshengliao/gortex/observability/tracing"
)

// 標準 OTel SDK：OTLP exporter 指到 Jaeger 的 4317
exp, _ := otlptracegrpc.New(ctx,
    otlptracegrpc.WithEndpoint("localhost:4317"),
    otlptracegrpc.WithInsecure(),
)
otel.SetTracerProvider(sdktrace.NewTracerProvider(sdktrace.WithBatcher(exp)))

// gortex 端：包一層 adapter，span 就會流進 OTel → Jaeger
tracer := gotel.NewOTelTracerAdapter(tracing.NewSimpleTracer(), "gortex")
```

exporter 是標準 OTel SDK 的東西，不是 gortex 自己發明的；gortex 只負責把自家 span 轉成 OTel span。

## 用 Docker 在 OrbStack 上快速起

最快的方式是 compose，用現成範本（[jaeger](https://github.com/yshengliao/docker-compose-templates/tree/main/jaeger)，跑 `jaegertracing/all-in-one:latest`）。在 [OrbStack](/posts/orbstack-docker-and-vms-on-macos/) 上 `docker compose up -d`，幾秒就好：

```bash
git clone https://github.com/yshengliao/docker-compose-templates
cd docker-compose-templates/jaeger
docker compose up -d
```

幾個會用到的 port：

- `16686`：Jaeger UI，瀏覽器開 `http://localhost:16686` 就能查 trace。
- `4317`：OTLP gRPC、`4318`：OTLP HTTP，你的程式 exporter 指這裡。
- `9411`：Zipkin 相容端點。

範本已經設了 `COLLECTOR_OTLP_ENABLED=true`，OTLP 收 trace 預設開著。把上面那段的 exporter 指到 `localhost:4317`，發幾個請求，UI 上就能看到 trace 一條條進來。

## 換成 Grafana 也一樣

不想用 Jaeger UI，想統一在 Grafana 看？同一套 OTel instrumentation 不用動。Grafana 這邊用 Tempo 當 trace 後端，Tempo 一樣收 OTLP：把 exporter 的 endpoint 從 Jaeger 改成 Tempo，再在 Grafana 接 Tempo data source，trace 就出現在 Grafana 裡，還能跟 metrics、log 並排看。

Jaeger、Grafana Tempo 與 Zipkin 都能接在同一套 OTel instrumentation 後面。gortex 只依賴 adapter 介面，所以換 tracing backend 是維運設定的變動，不用重寫應用程式。


## 補充筆記

- 分散式追蹤的單位是 span，串成 trace（請求樹）；要靠 trace context 跨服務傳遞才接得起來。
- Jaeger 是 CNCF 的追蹤平台，all-in-one image 把 collector / query / UI 包成一個 container，最適合本機開發。
- OpenTelemetry 是廠商中立標準（API/SDK 加 OTLP），讓 instrumentation 與後端解耦；gortex 用 `Tracer` 介面加 `OTelTracerAdapter` 接上。
- 用 [jaeger 範本](https://github.com/yshengliao/docker-compose-templates/tree/main/jaeger) `docker compose up -d`：UI 在 `16686`，OTLP 在 `4317` / `4318`，exporter 指過去即可。
- 後端可換成 Grafana Tempo（一樣收 OTLP），instrumentation 一行不動，這是 OTel 標準加 adapter 模式的好處。

原始碼與更多框架細節：[yshengliao/gortex](https://github.com/yshengliao/gortex)。

---

> Sheng 撰寫，Claude 協助文字與範例 · 環境 Jaeger all-in-one + OpenTelemetry + Docker（OrbStack） · 列入 20260613 blog 翻新計劃，新漆未乾。