在生产环境,往往调用的链路都是多级的,下面我们将给大家提供一个最基础的多级调用。其中一个细节是traceId的透传,如果不透传,下游是服务拿到traceId,也就无法把整个链路串起来。
具体看代码。
clientA
package main
import (
"context"
"flag"
"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
"go.opentelemetry.io/otel/trace"
"io"
"log"
"net/http"
"net/http/httptrace"
"time"
)
func initProvider() func() {
//采用本地打印的exporter暴露数据
exporter, err := stdout.New(stdout.WithPrettyPrint())
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return tp, nil
}
func sendReq(url string, ctx context.Context) {
// 构造一个trace client
client := http.Client{
Transport: otelhttp.NewTransport(
http.DefaultTransport,
// 必须引入这个参数,不然trace不透传
otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace {
return otelhttptrace.NewClientTrace(ctx)
}),
),
}
tr := otel.Tracer("example/client")
err := func(ctx context.Context) error {
// 初始化一个span 名称叫say hello
ctx, span := tr.Start(ctx, "say hello", trace.WithAttributes(semconv.PeerService("ExampleService")))
defer span.End()
ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx))
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
res, err := client.Do(req)
if err != nil {
panic(err)
}
_, err = io.ReadAll(res.Body)
_ = res.Body.Close()
return err
}(ctx)
if err != nil {
log.Fatal(err)
}
}
func doClient(url string, ctx context.Context) {
// 不断请求数据
for {
sendReq(url, ctx)
time.Sleep(3 * time.Second)
}
}
func main() {
shutdown := initProvider()
defer shutdown()
url := flag.String("server", "http://localhost:8070/hello", "server url")
flag.Parse()
bag, _ := baggage.Parse("username=xxx")
ctx := baggage.ContextWithBaggage(context.Background(), bag)
doClient(*url, ctx)
}
serverB
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"net/http"
"net/http/httptrace"
"time"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
"go.opentelemetry.io/otel/trace"
)
// provider和client一样
func initProvider() func() {
//采用本地打印的exporter暴露数据
exporter, err := stdout.New(stdout.WithPrettyPrint())
if err != nil {
return nil, err
}
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithBatcher(exporter),
)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
return tp, nil
}
func handler() {
helloHandler := func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
sendReq("http://localhost:8071/hello1", ctx)
}
otelHandler := otelhttp.NewHandler(http.HandlerFunc(helloHandler), "Hello")
http.Handle("/hello", otelHandler)
}
// sendReq 和client的sendReq方法一样
func sendReq(url string, ctx context.Context) {
// 构造一个trace client
client := http.Client{
Transport: otelhttp.NewTransport(
http.DefaultTransport,
otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace {
return otelhttptrace.NewClientTrace(ctx)
}),
),
}
tr := otel.Tracer("example/client")
err := func(ctx context.Context) error {
ctx, span := tr.Start(ctx, "say hello", trace.WithAttributes(semconv.PeerService("ExampleService")))
defer span.End()
ctx = httptrace.WithClientTrace(ctx, otelhttptrace.NewClientTrace(ctx))
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
res, err := client.Do(req)
if err != nil {
panic(err)
}
_, err = io.ReadAll(res.Body)
_ = res.Body.Close()
return err
}(ctx)
if err != nil {
log.Fatal(err)
}
}
func main() {
flag.Parse()
shutdown := initProvider()
defer shutdown()
handler()
_ := http.ListenAndServe(":8070", nil)
}
serverC
package main
import (
"context"
"flag"
"fmt"
"io"
"log"
"net/http"
"net/http/httptrace"
"time"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/baggage"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.17.0"
"go.opentelemetry.io/otel/trace"
)
func main() {
flag.Parse()
shutdown := initProvider()
defer shutdown()
handler()
_ := http.ListenAndServe(":8071", nil)
}
func handler() {
helloHandler1 := func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
span := trace.SpanFromContext(ctx)
bag := baggage.FromContext(ctx)
span.AddEvent("handling this...", trace.WithAttributes(uk.String(bag.Member("username").Value())))
_, _ = io.WriteString(w, "Hello, world!\n")
}
otelHandler1 := otelhttp.NewHandler(http.HandlerFunc(helloHandler1), "Hello1")
http.Handle("/hello1", otelHandler1)
}
通过以上代码,您就可以实现clientA->serverB->serverC的调用了。