Heim >Backend-Entwicklung >Golang >[Sprache] Tracer: Einfache Implementierung von Open Telemetry, Golang und Jagger

[Sprache] Tracer: Einfache Implementierung von Open Telemetry, Golang und Jagger

Patricia Arquette
Patricia ArquetteOriginal
2025-01-16 14:14:59547Durchsuche

Tracer

Tracer ist Teil von Observability, das eine wichtige Rolle bei der Implementierung der Microservices-Architektur spielt und einen „Trace“-Überblick über die in einer Anwendungslogik laufenden Prozesse bietet.

Einfach ausgedrückt liefert Tracer im Webservice eine Vorstellung davon, wie lange die Ausführungszeit einer Logik dauert, indem er ein Trace-Signal aussendet. Später wird dieser Tracer visualisiert und in Form einer verschachtelten Spanne angezeigt, nachdem ein Signal über den Exporter an den Collector übertragen wurde. [OpenTelemetry: Spuren]

OpenTelemetry

Um Traces-Signale übertragen zu können, die später vom Collector gesammelt werden können, benötigt der Webservice OpenTelemetry als Bibliothek, die zu einem Standard-Observability-Protokoll geworden ist, das normalerweise OpenTelemetry Protocol (OTLP) genannt wird. [OpenTelemetry: Sprache – Los]

Jäger

Die Visualisierung von Traces-Signalen ist wirklich erforderlich, um einen Überblick darüber zu geben, welche Prozesse im Webservice ablaufen. Jaeger ist eine Open-Source-Plattform, die OTLP durch Nutzung des HTTP- oder gRPC-Kommunikationsprotokolls unterstützt. [Jaeger]

Implementierung in Golang

Die Implementierung von Tracer in der Programmiersprache Golang implementiert einen einfachen Fall, in dem der Webservice nur Daten mit unterschiedlichen Antwortdauern zurückgibt. Die verwendete Bibliothek ist:

  • Chi: HTTP Framework
  • OpenTelemetry: Telemetriesignalisierung

Richten Sie OpenTelemetry als Telemetriemodul ein

Implementieren Sie das Telemetriemodul im Verzeichnis pkg/telemetry/telemetry.go:

package telemetry

import (
    "context"
    "errors"
    "time"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/sdk/resource"
    "go.opentelemetry.io/otel/sdk/trace"
)

// enumeration constant for which protocol used
const (
    HTTP uint8 = iota
    GRPC
)

// setup client to connect web-service with Jaeger
func SetupTraceClient(ctx context.Context, protocol uint8, endpoint string) otlptrace.Client {
    switch protocol {
    case GRPC:
        return otlptracegrpc.NewClient(otlptracegrpc.WithEndpoint(endpoint), otlptracegrpc.WithInsecure(), otlptracegrpc.WithCompressor("gzip"))
    default:
        return otlptracehttp.NewClient(otlptracehttp.WithEndpoint(endpoint), otlptracehttp.WithInsecure(), otlptracehttp.WithCompression(otlptracehttp.NoCompression))
    }
}

func setupTraceProvider(ctx context.Context, traceClient otlptrace.Client) (*trace.TracerProvider, error) {
    // set resource
    res, err := resource.New(
        ctx,
        resource.WithFromEnv(),
    )
    if err != nil {
        return nil, err
    }

    // init trace exporter
    traceExporter, err := otlptrace.New(ctx, traceClient)
    if err != nil {
        return nil, err
    }

    // init trace exporter
    traceProvider := trace.NewTracerProvider(
        trace.WithBatcher(
            traceExporter,
            trace.WithBatchTimeout(time.Duration(time.Second*3)),
        ),
        trace.WithResource(res), // Discover and provide attributes from OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME environment variables.
    )

    return traceProvider, nil
}

func SetupTelemetrySDK(ctx context.Context, traceClient otlptrace.Client) (func(context.Context) error, error) {
    var err error
    var shutdownFuncs []func(context.Context) error

    shutdown := func(ctx context.Context) error {
        var err error
        for _, fn := range shutdownFuncs {
            err = errors.Join(err, fn(ctx))
        }
        shutdownFuncs = nil
        return err
    }

    handleErr := func(inErr error) {
        err = errors.Join(inErr, shutdown(ctx))
    }

    traceProvider, err := setupTraceProvider(ctx, traceClient)
    if err != nil {
        handleErr(err)
        return shutdown, err
    }

    shutdownFuncs = append(shutdownFuncs, traceProvider.Shutdown)
    otel.SetTracerProvider(traceProvider)

    return shutdown, nil
}

Dann richten Sie die Telemetriekonfiguration in der Hauptfunktion main.go ein:

package main

import (
    "context"
    "fmt"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/go-chi/chi/v5"
    "github.com/wahyurudiyan/medium/otel-jaeger/config"
    "github.com/wahyurudiyan/medium/otel-jaeger/pkg/telemetry"
    "github.com/wahyurudiyan/medium/otel-jaeger/router"
)

func SetupTelemetry(ctx context.Context, config *config.Config) (func(context.Context) error, error) {
    otlpCli := telemetry.SetupTraceClient(ctx, telemetry.GRPC, config.JaegerGRPCEndpoint)
    shutdownFn, err := telemetry.SetupTelemetrySDK(ctx, otlpCli)
    return shutdownFn, err
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    cfg := config.Get()

    shutdownFn, err := SetupTelemetry(ctx, cfg)
    if err != nil {
        shutdownFn(ctx)
        panic(err)
    }

    r := chi.NewRouter()
    r.Route("/api", func(r chi.Router) {
        router.Router(r)
    })

    srv := http.Server{
        Addr:    "0.0.0.0:8080",
        Handler: r,
    }
    go func() {
        fmt.Println("Server running at port:", srv.Addr)
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            fmt.Printf("listen: %s\n", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    



<p>Verwenden Sie Tracer im Handler in der Datei router/router.go, um Traces-Signale zu übertragen:<br>
</p>

<pre class="brush:php;toolbar:false">package router

import (
    "encoding/json"
    "net/http"
    "time"

    "github.com/go-chi/chi/v5"
    "github.com/wahyurudiyan/medium/otel-jaeger/pkg/random"
    "go.opentelemetry.io/otel"
)

var (
    tracer = otel.Tracer("WebServer-Otel-Jaeger")
)

func getUserHandler(w http.ResponseWriter, r *http.Request) {
    _, span := tracer.Start(r.Context(), "GetUser")
    defer span.End()

    user := struct {
        Name     string
        Email    string
        Password string
    }{
        Name:     "John Doe",
        Email:    "john@email.com",
        Password: "Super5ecr3t!",
    }
    blob, _ := json.Marshal(&user)

    sleepDuration := time.Duration(time.Millisecond * time.Duration(random.GenerateRandNum()))
    time.Sleep(sleepDuration)

    w.Header().Add("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK)
    w.Write(blob)
}

func Router(router chi.Router) {
    router.Get("/user", getUserHandler)
}

Bereitstellungen

Docker-Konfiguration für diesen Build-Webservice unter Verwendung des Multi-Stage Build Image-Mechanismus in der Docker-Datei:

FROM golang:1.23.4 AS build
WORKDIR /src
COPY . .
RUN go get -v
RUN CGO_ENABLED=0 go build -o /bin/service .

FROM alpine:latest
WORKDIR /app
COPY --from=build /bin/service /bin/service
CMD ["/bin/service"]

Als nächstes wird der Image-Build über die Datei docker-compose.yaml mit der folgenden Konfiguration durchgeführt:

services:
  web-service:
    container_name: service
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      OTEL_SERVICE_NAME: service-otel-jaeger
      JAEGER_GRPC_ENDPOINT: jaeger:4317
    entrypoint: ["service"]
    ports:
      - 8080:8080

  jaeger:
    container_name: jaeger
    image: jaegertracing/all-in-one:latest
    environment:
      COLLECTOR_ZIPKIN_HOST_PORT: :9411
    ports:
      - 16686:16686 
      - 4317:4317 
      - 4318:4318 
      - 9411:9411

In service.jaeger.ports ist der offengelegte Port der Port für:

  • 16686: Jaeger-Dashboard
  • 4317: Jaeger OTLP Protobuf mit gRPC-Protokoll
  • 4318: Jaeger OTLP Protobuf/JSON mit HTTP-Protokoll
  • 9411: Zipkin-Sammler

Führen Sie die Anwendung aus, die in docker-compose.yaml definiert wurde. Sie können den folgenden Befehl verwenden:

docker compose up --build

Nachdem die Anwendung ausgeführt wurde, können Sie versuchen, die Anwendung auf dem Endpunkt http://127.0.0.1:8080/api/user aufzurufen. Wenn der Webservice und die Anwendung verbunden sind, wird der Dienstname wie im Bild angezeigt.

[Sprache] Tracer: Einfache Implementierung von Open Telemetry, Golang und Jagger

Es erscheint eine Zeitspanne, die angibt, wie lange es dauert, einen Prozess auszuführen.

[Sprache] Tracer: Einfache Implementierung von Open Telemetry, Golang und Jagger

Belastungstest

Jetzt versuchen wir, mit dem CLI-Tool hey [https://github.com/rakyll/hey] einen Auslastungstest durchzuführen. Mit dem folgenden Befehl kann ein einfacher Lasttest durchgeführt werden:

package telemetry

import (
    "context"
    "errors"
    "time"

    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/sdk/resource"
    "go.opentelemetry.io/otel/sdk/trace"
)

// enumeration constant for which protocol used
const (
    HTTP uint8 = iota
    GRPC
)

// setup client to connect web-service with Jaeger
func SetupTraceClient(ctx context.Context, protocol uint8, endpoint string) otlptrace.Client {
    switch protocol {
    case GRPC:
        return otlptracegrpc.NewClient(otlptracegrpc.WithEndpoint(endpoint), otlptracegrpc.WithInsecure(), otlptracegrpc.WithCompressor("gzip"))
    default:
        return otlptracehttp.NewClient(otlptracehttp.WithEndpoint(endpoint), otlptracehttp.WithInsecure(), otlptracehttp.WithCompression(otlptracehttp.NoCompression))
    }
}

func setupTraceProvider(ctx context.Context, traceClient otlptrace.Client) (*trace.TracerProvider, error) {
    // set resource
    res, err := resource.New(
        ctx,
        resource.WithFromEnv(),
    )
    if err != nil {
        return nil, err
    }

    // init trace exporter
    traceExporter, err := otlptrace.New(ctx, traceClient)
    if err != nil {
        return nil, err
    }

    // init trace exporter
    traceProvider := trace.NewTracerProvider(
        trace.WithBatcher(
            traceExporter,
            trace.WithBatchTimeout(time.Duration(time.Second*3)),
        ),
        trace.WithResource(res), // Discover and provide attributes from OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME environment variables.
    )

    return traceProvider, nil
}

func SetupTelemetrySDK(ctx context.Context, traceClient otlptrace.Client) (func(context.Context) error, error) {
    var err error
    var shutdownFuncs []func(context.Context) error

    shutdown := func(ctx context.Context) error {
        var err error
        for _, fn := range shutdownFuncs {
            err = errors.Join(err, fn(ctx))
        }
        shutdownFuncs = nil
        return err
    }

    handleErr := func(inErr error) {
        err = errors.Join(inErr, shutdown(ctx))
    }

    traceProvider, err := setupTraceProvider(ctx, traceClient)
    if err != nil {
        handleErr(err)
        return shutdown, err
    }

    shutdownFuncs = append(shutdownFuncs, traceProvider.Shutdown)
    otel.SetTracerProvider(traceProvider)

    return shutdown, nil
}

Der Befehl führt 10 Minuten lang einen Auslastungstest mit 100 Anfragen pro Sekunde (RPS) durch. Die Ergebnisse, die auf der Jaeger-UI-Seite angezeigt werden, sehen wie folgt aus.

[Sprache] Tracer: Einfache Implementierung von Open Telemetry, Golang und Jagger

Wenn der Lasttest abgeschlossen ist, wird ein Bericht über die Ergebnisse des Lasttests angezeigt.

package main

import (
    "context"
    "fmt"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/go-chi/chi/v5"
    "github.com/wahyurudiyan/medium/otel-jaeger/config"
    "github.com/wahyurudiyan/medium/otel-jaeger/pkg/telemetry"
    "github.com/wahyurudiyan/medium/otel-jaeger/router"
)

func SetupTelemetry(ctx context.Context, config *config.Config) (func(context.Context) error, error) {
    otlpCli := telemetry.SetupTraceClient(ctx, telemetry.GRPC, config.JaegerGRPCEndpoint)
    shutdownFn, err := telemetry.SetupTelemetrySDK(ctx, otlpCli)
    return shutdownFn, err
}

func main() {
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()

    cfg := config.Get()

    shutdownFn, err := SetupTelemetry(ctx, cfg)
    if err != nil {
        shutdownFn(ctx)
        panic(err)
    }

    r := chi.NewRouter()
    r.Route("/api", func(r chi.Router) {
        router.Router(r)
    })

    srv := http.Server{
        Addr:    "0.0.0.0:8080",
        Handler: r,
    }
    go func() {
        fmt.Println("Server running at port:", srv.Addr)
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            fmt.Printf("listen: %s\n", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    



<h2>
  
  
  Github-Projekt
</h2>

<p>Für diejenigen, die den vollständigen Code ausprobieren oder sehen möchten, können Sie das folgende Repository klonen: https://github.com/wahyurudiyan/otel-jaeger.</p>


          

            
        

Das obige ist der detaillierte Inhalt von[Sprache] Tracer: Einfache Implementierung von Open Telemetry, Golang und Jagger. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn