集成Opentelemery

102 阅读5分钟

熟悉Opentelemery接口

  • Resources For example, a process producing telemetry that is running in a container on Kubernetes has a process name, a pod name, a namespace, and possibly a deployment name. All four of these attributes can be included in the resource.

  • Semantic Conventions语义约定 一种特殊的Attribute, key值是固定的属性, 如key必须是service.name,约定好的.

resource配置

import (
    "context"
    "fmt"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/jaeger"
    sdkresource "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
    "log"
    "time"
)

func initTracer() func() {
    // 创建一个输出导出器
    exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
    if err != nil {
       log.Fatal(err)
    }

    tp := sdktrace.NewTracerProvider(
       sdktrace.WithBatcher(exporter),
       sdktrace.WithResource(sdkresource.NewWithAttributes(
          semconv.SchemaURL,
          semconv.ServiceNameKey.String("test-svc"),
          attribute.String("environment", "stag"),
          attribute.Int64("ID", 1),
       )),
    )
    // 设置全局TracerProvider
    otel.SetTracerProvider(tp)
    return func() {
       _ = tp.Shutdown(context.Background())
    }
}

resource其它方法

    r, _ := resource.New(
       context.Background(),
       resource.WithFromEnv(),      // Discover and provide attributes from OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME environment variables.
       resource.WithTelemetrySDK(), // Discover and provide information about the OpenTelemetry SDK used.
       resource.WithProcess(),      // Discover and provide process information.
       resource.WithOS(),           // Discover and provide OS information.
       resource.WithContainer(),    // Discover and provide container information.
       resource.WithHost(),         // Discover and provide host information.
       resource.WithAttributes(attribute.String("foo", "bar")), // Add custom resource attributes.
       // resource.WithDetectors(thirdparty.Detector{}), // Bring your own external Detector implementation.
    )

    tp := trace.NewTracerProvider(
       trace.WithBatcher(exporter,
          // Default is 5s. Set to 1s for demonstrative purposes.
          trace.WithBatchTimeout(time.Second)),
       trace.WithResource(r),
    )
func newResource() (*resource.Resource, error) {
    return resource.Merge(resource.Default(),
       resource.NewWithAttributes(semconv.SchemaURL,
          semconv.ServiceName("my-service"),
          semconv.ServiceVersion("0.1.0"),
       ))
}

生成span

trace api

    ctx, span := tracer.Start(
        ctx,
        name,
        trace.WithSpanKind(trace.SpanKindClient),
        trace.WithAttributes(attr...)
    )
    startOpts := append([]trace.SpanStartOption{
        trace.WithSpanKind(trace.SpanKindClient),
        trace.WithAttributes(attr...),
    },
        cfg.SpanStartOptions...,
    )

    ctx, span := tracer.Start(
        ctx,
        name,
        startOpts...,
    )

单进程集成Opentelemery

span添加属性和event

package main

import (
    "context"
    "fmt"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/jaeger"
    sdkresource "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
    "go.opentelemetry.io/otel/trace"
    "log"
    "time"
)

func initTracer() func() {
    // 创建一个输出导出器
    exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
    if err != nil {
       log.Fatal(err)
    }

    tp := sdktrace.NewTracerProvider(
       sdktrace.WithBatcher(
          exporter,
          // Default is 5s. Set to 1s for demonstrative purposes.
          sdktrace.WithBatchTimeout(time.Second),
       ),
       sdktrace.WithSampler(sdktrace.AlwaysSample()), //设置全量采样(默认)
       //sdktrace.WithSampler(sdktrace.TraceIDRatioBased(0.5)), //设置采样率为50%
       sdktrace.WithResource(sdkresource.NewWithAttributes(
          semconv.SchemaURL,
          semconv.ServiceNameKey.String("lcoal-test"),
          attribute.String("env", "dev"),
       )),
    )
    // 设置全局TracerProvider
    otel.SetTracerProvider(tp)
    return func() {
       _ = tp.Shutdown(context.Background())
    }
}

var tracer = otel.Tracer("example-local")

func main() {
    cleanup := initTracer()
    defer cleanup()
    ctx := context.WithValue(context.Background(), "userID", 1)
    // 开始一个新的 span
    ctx, span := tracer.Start(ctx, "client-span")
    defer span.End()
    var msg string = funcA(ctx)
    fmt.Println(msg)
    time.Sleep(time.Second)
}

func funcA(ctx context.Context) string {
    ctx, span := tracer.Start(trace.ContextWithRemoteSpanContext(ctx), trace.WithLinks())
    defer span.End()

    var attrs []attribute.KeyValue
    attrs = append(attrs, attribute.String("k1", "v1"))
    attrs = append(attrs, attribute.Bool("k2", false))
    attrs = append(attrs, attribute.StringSlice("k2", []string{"v1", "v2"}))
    span.SetAttributes(attrs...)
    span.AddEvent("this is an event")
    time.Sleep(time.Second)
    return "VIP"
}
export OTEL_RESOURCE_ATTRIBUTES='service.version=1.0,deployment.environment=dev,service.name=local-test-svcname,newrelic.service=local-test-svc,dd.service=local-test-svc'

image.png

go sdk trace采样率支持

type Sampler
    func AlwaysSample() Sampler
    func NeverSample() Sampler
    func ParentBased(root Sampler, samplers ...ParentBasedSamplerOption) Sampler
    func TraceIDRatioBased(fraction float64) Sampler

Resouce自动携带其它选项

export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"

A base endpoint URL for any signal type, with an optionally-specified port number. Helpful for when you’re sending more than one signal to the same endpoint and want one environment variable to control the endpoint.
Default value:
* gRPC: "http://localhost:4317"
* HTTP: "http://localhost:4318"
package main

import (
    "context"
    "fmt"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/exporters/jaeger"
    sdkresource "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
    "log"
    "time"
)

func newResource() (*sdkresource.Resource, error) {
    return sdkresource.Merge(sdkresource.Default(),
       sdkresource.NewWithAttributes(semconv.SchemaURL,
          semconv.ServiceName("my-service"),
          semconv.ServiceVersion("0.1.0"),
       ))
}
func initTracer() func() {
    // 创建一个输出导出器
    exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
    if err != nil {
       log.Fatal(err)
    }

    r, _ := sdkresource.New(
       context.Background(),
       sdkresource.WithFromEnv(),                                  // Discover and provide attributes from OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME environment variables.
       sdkresource.WithTelemetrySDK(),                             // Discover and provide information about the OpenTelemetry SDK used.
       sdkresource.WithProcess(),                                  // Discover and provide process information.
       sdkresource.WithOS(),                                       // Discover and provide OS information.
       sdkresource.WithContainer(),                                // Discover and provide container information.
       sdkresource.WithHost(),                                     // Discover and provide host information.
       sdkresource.WithAttributes(attribute.String("foo", "bar")), // Add custom resource attributes.
       // resource.WithDetectors(thirdparty.Detector{}), // Bring your own external Detector implementation.
    )

    // 创建 TracerProvider
    tp := sdktrace.NewTracerProvider(
       sdktrace.WithBatcher(exporter,
          // Default is 5s. Set to 1s for demonstrative purposes.
          sdktrace.WithBatchTimeout(time.Second)),
       sdktrace.WithResource(r),
    )

    // 设置全局TracerProvider
    otel.SetTracerProvider(tp)
    return func() {
       _ = tp.Shutdown(context.Background())
    }
}

var tracer = otel.Tracer("example-local")

func main() {
    cleanup := initTracer()
    defer cleanup()
    ctx := context.WithValue(context.Background(), "userID", 1)
    // 开始一个新的 span
    ctx, span := tracer.Start(ctx, "client-span")
    defer span.End()
    var msg string = funcA(ctx)
    fmt.Println(msg)
    time.Sleep(time.Second)
}

func funcA(ctx context.Context) string {
    ctx, span := tracer.Start(ctx, "funcA")
    defer span.End()
    span.SetAttributes(attribute.String("age", "22"))
    span.AddEvent("this is an event")
    time.Sleep(time.Second)
    return "VIP"
}

http集成Opentelemery携带Baggage

  • client/main.go
package main

import (
    "context"
    "fmt"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/baggage"
    "go.opentelemetry.io/otel/exporters/jaeger"
    "go.opentelemetry.io/otel/propagation"
    sdkresource "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
    "log"
    "net/http"
)

func initTracer() func() {
    // 创建一个输出导出器
    exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
    if err != nil {
       log.Fatal(err)
    }

    // 创建 TracerProvider
    tp := sdktrace.NewTracerProvider(
       sdktrace.WithBatcher(exporter),
       sdktrace.WithResource(sdkresource.NewWithAttributes(
          semconv.SchemaURL,
          semconv.ServiceNameKey.String("my_http_server"))),
    )

    // 设置全局TracerProvider
    otel.SetTracerProvider(tp)

    // 创建 TraceContext 和 Baggage Propagator
    tracePropagator := propagation.TraceContext{}
    baggagePropagator := propagation.Baggage{}
    compositePropagator := propagation.NewCompositeTextMapPropagator(tracePropagator, baggagePropagator)

    // 设置全局 Propagator
    otel.SetTextMapPropagator(compositePropagator)

    return func() {
       _ = tp.Shutdown(context.Background())
    }
}

func main() {
    cleanup := initTracer()
    defer cleanup()

    tracer := otel.Tracer("example-client")

    // 创建一个自定义的 Baggage
    userIDMember, _ := baggage.NewMember("userID", "1")
    sessionIDMember, _ := baggage.NewMember("sessionID", "xxxx")
    bag, _ := baggage.New(
       userIDMember,
       sessionIDMember,
    )

    // 将 Baggage 添加到上下文中
    ctx := baggage.ContextWithBaggage(context.Background(), bag)

    // 开始一个新的 span
    ctx, span := tracer.Start(ctx, "client-span")
    defer span.End()

    // 创建HTTP请求
    req, _ := http.NewRequestWithContext(ctx, "GET", "http://127.0.0.1:8080", nil)

    // 将上下文中的追踪信息和 Baggage 注入到HTTP请求的头部
    propagator := otel.GetTextMapPropagator()
    propagator.Inject(ctx, propagation.HeaderCarrier(req.Header))

    // 发送HTTP请求
    client := &http.Client{}
    res, err := client.Do(req)
    if err != nil {
       log.Fatalf("Failed to make request: %v", err)
    }
    defer res.Body.Close()

    fmt.Printf("Response status: %s\n", res.Status)
}

curl -X GET "http://localhost:8080" -H "traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-3c9e1d1a0d80c9b5-01"
curl -X GET "http://localhost:8080" -H "baggage: userID=12345,sessionID=abcde"
  • server/main.go
package main

import (
    "context"
    "go.opentelemetry.io/otel/attribute"
    "go.opentelemetry.io/otel/baggage"
    semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
    "log"
    "net/http"

    "github.com/gin-gonic/gin"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/jaeger"
    "go.opentelemetry.io/otel/propagation"
    sdkresource "go.opentelemetry.io/otel/sdk/resource"
    sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

type contextKey string

const (
    userIDKey    contextKey = "userID"
    sessionIDKey contextKey = "sessionID"
)

func initTracer() func() {
    // 创建 Jaeger 导出器
    exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
    if err != nil {
       log.Fatal(err)
    }

    // 创建 TracerProvider
    tp := sdktrace.NewTracerProvider(
       sdktrace.WithBatcher(exporter),
       sdktrace.WithResource(sdkresource.NewWithAttributes(
          semconv.SchemaURL,
          semconv.ServiceNameKey.String("my_http_server"))),
    )

    // 设置全局 TracerProvider
    otel.SetTracerProvider(tp)

    // 创建 TraceContext 和 Baggage Propagator
    tracePropagator := propagation.TraceContext{}
    baggagePropagator := propagation.Baggage{}
    compositePropagator := propagation.NewCompositeTextMapPropagator(tracePropagator, baggagePropagator)

    // 设置全局 Propagator
    otel.SetTextMapPropagator(compositePropagator)

    return func() {
       _ = tp.Shutdown(context.Background())
    }
}

func traceMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
       // 从请求头中提取追踪信息
       ctx := otel.GetTextMapPropagator().Extract(c.Request.Context(), propagation.HeaderCarrier(c.Request.Header))
       // 开始一个新的 Span
       tracer := otel.Tracer("example-server")
       ctx, span := tracer.Start(ctx, "server-span")
       defer span.End()

       // 获取 Baggage 项目
       reqBaggage := baggage.FromContext(ctx)
       userID := bag.Member("userID").Value()
       sessionID := bag.Member("sessionID").Value()

       // 将自定义值存储在 Gin 的上下文中
       c.Set(string(userIDKey), userID)
       c.Set(string(sessionIDKey), sessionID)

       span.SetAttributes(attribute.String(
          "userID",
          reqBaggage.Member("userID").Value()),
       )

       // 将上下文和 Span 传递到 Gin 的上下文中
       c.Request = c.Request.WithContext(ctx)

       // 继续处理请求
       c.Next()
    }
}

func main() {
    cleanup := initTracer()
    defer cleanup()

    // 创建 Gin 引擎
    r := gin.Default()

    // 使用追踪中间件
    r.Use(traceMiddleware())

    // 定义一个处理函数
    r.GET("/", func(c *gin.Context) {
       // 从 Gin 上下文中提取自定义值
       userID, _ := c.Get(string(userIDKey))
       sessionID, _ := c.Get(string(sessionIDKey))

       // 响应客户端
       c.JSON(http.StatusOK, gin.H{
          "userID":    userID,
          "sessionID": sessionID,
       })
    })

    // 启动 HTTP 服务器
    if err := r.Run("localhost:8080"); err != nil {
       log.Fatalf("Failed to start server: %v", err)
    }
}

grpc集成Opentelemery

官方otelgrpc写好了中间件

  • proto/hello.proto
syntax = "proto3";

option go_package = ".;proto";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}
//protoc --go_out=./proto --go-grpc_out=./proto ./proto/*

  • client/main.go
package main

import (
    "context"
    "flag"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/jaeger"
    "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.25.0"
    "log"
    "time"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials/insecure"
    pb "otel-start/grpc-start/proto"

    "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
)

const (
    defaultName = "world"
)

var (
    addr = flag.String("addr", "localhost:50051", "the address to connect to")
    name = flag.String("name", defaultName, "Name to greet")
)

func initTracer() func() {
    exp, _ := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
    tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exp), sdktrace.WithResource(
        resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String("myserver"),
        ),
    ))
    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
    return func() {
        tp.Shutdown(context.Background())
    }
}

func main() {
    cleanUp := initTracer()
    defer cleanUp()
    flag.Parse()
    // Set up a connection to the server.
    conn, err := grpc.Dial(*addr,
        grpc.WithTransportCredentials(insecure.NewCredentials()),
        grpc.WithChainUnaryInterceptor(otelgrpc.UnaryClientInterceptor()),
    )
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // Contact the server and print out its response.
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())

}
  • server/main.go
package main

import (
    "context"
    "flag"
    "fmt"
    "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/jaeger"
    "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.25.0"
    "google.golang.org/grpc"
    "log"
    "net"
    pb "otel-start/grpc-start/proto"
)

var (
    port = flag.Int("port", 50051, "The server port")
)

// server is used to implement helloworld.GreeterServer.
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func initTracer() func() {
    exp, _ := jaeger.New(jaeger.WithCollectorEndpoint(jaeger.WithEndpoint("http://localhost:14268/api/traces")))
    tp := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exp), sdktrace.WithResource(
        resource.NewWithAttributes(
            semconv.SchemaURL,
            semconv.ServiceNameKey.String("myserver"),
        ),
    ))
    otel.SetTracerProvider(tp)
    otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(propagation.TraceContext{}, propagation.Baggage{}))
    return func() {
        tp.Shutdown(context.Background())
    }
}

func main() {
    cleanUp := initTracer()
    defer cleanUp()
    flag.Parse()
    lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer(
        grpc.ChainUnaryInterceptor(otelgrpc.UnaryServerInterceptor()),
    )
    pb.RegisterGreeterServer(s, &server{})
    log.Printf("server listening at %v", lis.Addr())
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}