Maison >développement back-end >Golang >[Langue] Tracer : implémentation simple de la télémétrie ouverte, de Golang et de Jagger

[Langue] Tracer : implémentation simple de la télémétrie ouverte, de Golang et de Jagger

Patricia Arquette
Patricia Arquetteoriginal
2025-01-16 14:14:59547parcourir

Traceur

Tracer fait partie d'Observability qui joue un rôle important dans la mise en œuvre de l'architecture des microservices et fournit un aperçu « Trace » des processus exécutés dans une logique d'application.

En termes simples, dans Webservice, Tracer fournira une idée de la durée du temps d'exécution d'une logique en émettant un signal Trace. Plus tard, ce Tracer sera visualisé et vu sous la forme d'une travée imbriquée après avoir transmis un signal via l'exportateur vers le collecteur. [OpenTelemetry : Traces]

OpenTélémétrie

Pour pouvoir transmettre des signaux Traces qui pourront ensuite être collectés par le collecteur, le Webservice nécessite OpenTelemetry comme bibliothèque qui est devenue un protocole d'observabilité standard généralement appelé OpenTelemetry Protocol (OTLP). [OpenTelemetry : Langue - Aller]

Jaeger

La visualisation des signaux Traces est vraiment nécessaire pour fournir un aperçu des processus qui se produisent dans le Webservice. Jaeger est une plateforme Open Source qui prend en charge OTLP en utilisant le protocole de communication HTTP ou gRPC. [Jaeger]

Implémentation à Golang

L'implémentation de Tracer dans le langage de programmation Golang implémentera un cas simple où le Webservice ne renverra que des données avec des durées de réponse différentes. La bibliothèque qui sera utilisée est :

  • Chi : Framework HTTP
  • OpenTelemetry : signalisation de télémétrie

Configurer OpenTelemetry en tant que module de télémétrie

Implémentez le module Télémétrie dans le répertoire 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
}

Ensuite, configurez la configuration de la télémétrie dans la fonction principale main.go :

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>Utilisez Tracer dans le gestionnaire du fichier router/router.go pour transmettre les signaux Traces :<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)
}

Déploiements

Configuration Docker pour ce service Web de build, utilisant le mécanisme d'image de construction multi-étapes dans le fichier Docker :

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"]

Ensuite, la construction de l'image sera réalisée via le fichier docker-compose.yaml avec la configuration suivante :

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

Dans service.jaeger.ports, le port exposé est le port de :

  • 16686 : Tableau de bord Jaeger
  • 4317 : Jaeger OTLP Protobuf avec protocole gRPC
  • 4318 : Jaeger OTLP Protobuf/JSON avec protocole HTTP
  • 9411 : Collectionneur Zipkin

Exécutez l'application qui a été définie dans docker-compose.yaml, vous pouvez utiliser la commande :

docker compose up --build

Une fois l'application exécutée, vous pouvez essayer d'accéder à l'application sur le point de terminaison http://127.0.0.1:8080/api/user, si le service Web et l'application sont connectés, le nom du service apparaîtra comme sur l'image.

[Langue] Tracer : implémentation simple de la télémétrie ouverte, de Golang et de Jagger

un span apparaîtra pour définir le temps nécessaire à l'exécution d'un processus.

[Langue] Tracer : implémentation simple de la télémétrie ouverte, de Golang et de Jagger

Test de charge

Essayons maintenant d'utiliser l'outil CLI hey [https://github.com/rakyll/hey] pour exécuter un test de charge. La commande suivante peut être utilisée pour effectuer un simple test de charge :

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
}

La commande exécutera un test de charge pour 100 requêtes par seconde (RPS) pendant 10 minutes. Les résultats qui apparaîtront sur la page Jaeger UI ressembleront à ce qui suit.

[Langue] Tracer : implémentation simple de la télémétrie ouverte, de Golang et de Jagger

Si le test de charge est terminé, il y aura un rapport des résultats du test de charge.

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>
  
  
  Projet GitHub
</h2>

<p>Pour ceux qui veulent essayer ou voir le code complet, vous pouvez cloner le référentiel suivant : https://github.com/wahyurudiyan/otel-jaeger.</p>


          

            
        

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn