OpenTelemetry Go 語言教程
本教程將演示如何在 Go 中使用 OpenTelemetry,我們將手寫一個簡單的應用程序,並向外發送鏈路追蹤和指標數據。
準備示例應用程序
創建一個扔骰子的程序。
在本地新建一個dice
目錄,並進入該目錄下。
mkdir dice
cd dice
執行 go mod 初始化。
go mod init dice
在同一目錄下創建 main.go
文件,並添加以下代碼。
package main
import (
"log"
"net/http"
)
func main() {
http.HandleFunc("/roll", roll)
log.Fatal(http.ListenAndServe(":8080", nil))
}
在同目錄下另外創建一個名爲 roll.go
的文件,並向該文件添加以下代碼:
package main
import (
"fmt"
"math/rand"
"net/http"
)
func roll(w http.ResponseWriter, r *http.Request) {
number := 1 + rand.Intn(6)
_, _ = fmt.Fprintln(w, number)
}
使用以下命令構建並運行應用程序:
go run .
使用瀏覽器打開 http://127.0.0.1:8080/roll 確保程序能夠正常運行。
添加 OpenTelemetry 測量儀器
接下來,我們將展示如何在示例應用程序中添加 OpenTelemetry 測量儀器。
引入依賴
在你的 Go 項目中安裝以下依賴包。
go get "go.opentelemetry.io/otel" \
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric" \
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace" \
"go.opentelemetry.io/otel/propagation" \
"go.opentelemetry.io/otel/sdk/metric" \
"go.opentelemetry.io/otel/sdk/resource" \
"go.opentelemetry.io/otel/sdk/trace" \
"go.opentelemetry.io/otel/semconv/v1.24.0" \
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
這裏安裝的是 OpenTelemety SDK 組件和 net/http
測量儀器。如果要對不同的庫進行網絡請求檢測,則需要安裝相應的儀器庫。
初始化 OpenTelemetry SDK
首先,我們將初始化 OpenTelemetry SDK。任何想導出追蹤數據的應用程序都必須完成這一步初始化。
新建一個otel.go
文件,並在其中添加以下代碼。
package main
import (
"context"
"errors"
"time"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/stdout/stdoutmetric"
"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/metric"
"go.opentelemetry.io/otel/sdk/trace"
)
// setupOTelSDK 引導 OpenTelemetry pipeline。
// 如果沒有返回錯誤,請確保調用 shutdown 進行適當清理。
func setupOTelSDK(ctx context.Context) (shutdown func(context.Context) error, err error) {
var shutdownFuncs []func(context.Context) error
// shutdown 會調用通過 shutdownFuncs 註冊的清理函數。
// 調用產生的錯誤會被合併。
// 每個註冊的清理函數將被調用一次。
shutdown = func(ctx context.Context) error {
var err error
for _, fn := range shutdownFuncs {
err = errors.Join(err, fn(ctx))
}
shutdownFuncs = nil
return err
}
// handleErr 調用 shutdown 進行清理,並確保返回所有錯誤信息。
handleErr := func(inErr error) {
err = errors.Join(inErr, shutdown(ctx))
}
// 設置傳播器
prop := newPropagator()
otel.SetTextMapPropagator(prop)
// 設置 trace provider.
tracerProvider, err := newTraceProvider()
if err != nil {
handleErr(err)
return
}
shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)
otel.SetTracerProvider(tracerProvider)
// 設置 meter provider.
meterProvider, err := newMeterProvider()
if err != nil {
handleErr(err)
return
}
shutdownFuncs = append(shutdownFuncs, meterProvider.Shutdown)
otel.SetMeterProvider(meterProvider)
return
}
func newPropagator() propagation.TextMapPropagator {
return propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
)
}
func newTraceProvider() (*trace.TracerProvider, error) {
traceExporter, err := stdouttrace.New(
stdouttrace.WithPrettyPrint())
if err != nil {
return nil, err
}
traceProvider := trace.NewTracerProvider(
trace.WithBatcher(traceExporter,
// 默認爲 5s。爲便於演示,設置爲 1s。
trace.WithBatchTimeout(time.Second)),
)
return traceProvider, nil
}
func newMeterProvider() (*metric.MeterProvider, error) {
metricExporter, err := stdoutmetric.New()
if err != nil {
return nil, err
}
meterProvider := metric.NewMeterProvider(
metric.WithReader(metric.NewPeriodicReader(metricExporter,
// 默認爲 1m。爲便於演示,設置爲 3s。
metric.WithInterval(3*time.Second))),
)
return meterProvider, nil
}
如果不使用 tracing ,則可以省略相應的 TracerProvider 的初始化代碼;
如果不使用 metrics,則可以省略 MeterProvider 的初始化代碼。
測量 HTTP server
現在,我們已經初始化了 OpenTelemetry SDK,可以測量 HTTP 服務器了。
按如下代碼修改 main.go
,加入設置 OpenTelemetry SDK 的代碼,並使用 otelhttp 儀器庫測量 HTTP 服務器:
package main
import (
"context"
"errors"
"log"
"net"
"net/http"
"os"
"os/signal"
"time"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func newHTTPHandler() http.Handler {
mux := http.NewServeMux()
// handleFunc 是 mux.HandleFunc 的替代品,。
// 它使用 http.route 模式豐富了 handler 的 HTTP 測量
handleFunc := func(pattern string, handlerFunc func(http.ResponseWriter, *http.Request)) {
// 爲 HTTP 測量配置 "http.route"。
handler := otelhttp.WithRouteTag(pattern, http.HandlerFunc(handlerFunc))
mux.Handle(pattern, handler)
}
// Register handlers.
handleFunc("/roll", roll)
// 爲整個服務器添加 HTTP 測量。
handler := otelhttp.NewHandler(mux, "/")
return handler
}
func main() {
if err := run(); err != nil {
log.Fatalln(err)
}
}
func run() (err error) {
// 平滑處理 SIGINT (CTRL+C) .
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
defer stop()
// 設置 OpenTelemetry.
otelShutdown, err := setupOTelSDK(ctx)
if err != nil {
return
}
// 妥善處理停機,確保無泄漏
defer func() {
err = errors.Join(err, otelShutdown(context.Background()))
}()
// 啓動 HTTP server.
srv := &http.Server{
Addr: ":8080",
BaseContext: func(_ net.Listener) context.Context { return ctx },
ReadTimeout: time.Second,
WriteTimeout: 10 * time.Second,
Handler: newHTTPHandler(),
}
srvErr := make(chan error, 1)
go func() {
srvErr <- srv.ListenAndServe()
}()
// 等待中斷.
select {
case err = <-srvErr:
// 啓動 HTTP 服務器時出錯.
return
case <-ctx.Done():
// 等待第一個 CTRL+C.
// 儘快停止接收信號通知.
stop()
}
// 調用 Shutdown 時,ListenAndServe 會立即返回 ErrServerClosed。
err = srv.Shutdown(context.Background())
return
}
添加自定義測量
測量庫可以捕捉系統邊緣的遙測數據,例如入站和出站 HTTP 請求,但無法捕捉應用程序中的情況。因此需要編寫一些自定義的手動儀器。
修改 roll.go
,使用 OpenTelemetry API 包含定製的測量儀器:
package main
import (
"fmt"
"math/rand"
"net/http"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
)
var (
tracer = otel.Tracer("roll")
meter = otel.Meter("roll")
rollCnt metric.Int64Counter
)
func init() {
var err error
rollCnt, err = meter.Int64Counter("dice.rolls",
metric.WithDescription("The number of rolls by roll value"),
metric.WithUnit("{roll}"))
if err != nil {
panic(err)
}
}
func roll(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "roll") // 開始 span
defer span.End() // 結束 span
number := 1 + rand.Intn(6)
rollValueAttr := attribute.Int("roll.value", number)
span.SetAttributes(rollValueAttr) // span 添加屬性
// 搖骰子次數的指標 +1
rollCnt.Add(ctx, 1, metric.WithAttributes(rollValueAttr))
_, _ = fmt.Fprintln(w, number)
}
運行應用程序
使用以下命令構建並運行應用程序:
go mod tidy
export OTEL_RESOURCE_ATTRIBUTES="service.
go run .
使用瀏覽器中打開 http://127.0.0.1:8080/roll。向服務器發送請求時,你會在控制檯顯示的鏈路跟蹤中看到兩個 span。由儀器庫生成的 span 跟蹤向 /roll 路由發出請求的生命週期。名爲 roll 的 span 是手動創建的,它是前面提到的 span 的子 span。
將鏈路追蹤數據發送至 Jaeger
如果覺着控制檯看的 span 不夠直觀,可以選擇將鏈路追蹤的數據發送至 Jaeger,通過 Jaeger UI 查看。
啓動 Jaeger
Jaeger 官方提供的 all-in-one 是爲快速本地測試而設計的可執行文件。它包括 Jaeger UI、jaeger-collector、jaeger-query 和 jaeger-agent,以及一個內存存儲組件。
啓動 all-in-one 的最簡單方法是使用發佈到 DockerHub 的預置鏡像(只需一條命令行)。
docker run --rm --name jaeger \
-e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
-p 6831:6831/udp \
-p 6832:6832/udp \
-p 5778:5778 \
-p 16686:16686 \
-p 4317:4317 \
-p 4318:4318 \
-p 14250:14250 \
-p 14268:14268 \
-p 14269:14269 \
-p 9411:9411 \
jaegertracing/all-in-one:1.55
然後你可以使用瀏覽器打開 http://localhost:16686 訪問 Jaeger UI。
容器公開以下端口:
我們這裏使用 HTTP 協議的4318
端口上報鏈路追蹤數據。
上報至 Jaeger
安裝 otlptracehttp
依賴包。
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp
修改otel.go
代碼,新增以下函數。
func newJaegerTraceProvider(ctx context.Context) (*trace.TracerProvider, error) {
// 創建一個使用 HTTP 協議連接本機Jaeger的 Exporter
traceExporter, err := otlptracehttp.New(ctx,
otlptracehttp.WithEndpoint("127.0.0.1:4318"),
otlptracehttp.WithInsecure())
if err != nil {
return nil, err
}
traceProvider := trace.NewTracerProvider(
trace.WithBatcher(traceExporter,
// 默認爲 5s。爲便於演示,設置爲 1s。
trace.WithBatchTimeout(time.Second)),
)
return traceProvider, nil
}
並且按如下代碼修改設置 trace provider 部分。
// 設置 trace provider.
//tracerProvider, err := newTraceProvider()
tracerProvider, err := newJaegerTraceProvider(ctx)
if err != nil {
handleErr(err)
return
}
shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)
otel.SetTracerProvider(tracerProvider)
再次構建並啓動程序。
go run .
嘗試訪問一次 http://127.0.0.1:8080/roll ,確保修改後的服務能夠正常運行。
使用 Jaeger UI
使用瀏覽器打開 http://127.0.0.1:16686 的 Jaeger UI 界面。在屏幕左側的 service 下拉框中選中 dice
後查找,即可看到上報的 trace 數據。
點擊右側的 trace 數據,即可查看詳情。
完整代碼請查看 https://github.com/Q1mi/dice
參考資料
-
https://opentelemetry.io/docs/languages/go/getting-started/
-
https://www.jaegertracing.io/docs/1.55/getting-started/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/NE2Rr8bIadKtjXpogrMh-Q