Aller au contenu principal

Traçage OpenTelemetry Go

Le SDK Go permet d’ajouter des spans applicatifs, de propager explicitement le contexte ou de couvrir des opérations hors de la collecte eBPF. Cet exemple sert GET /hello, exporte un span serveur en OTLP/HTTP et écrit un log JSON avec les mêmes IDs.

Exécuter l’exemple​

Prérequis : Go 1.25 minimum et une gateway Avuru Obs. Téléchargez dans un même dossier main.go, go.mod et go.sum. Les modules OpenTelemetry Go sont épinglés en 1.46.0 et doivent rester à des versions cohérentes.

Dans un terminal séparé :

kubectl -n avuruobs port-forward svc/avuruobs-gateway 4318:4318

Puis, dans le dossier de l’exemple :

export OTEL_SERVICE_NAME=avuru-go-example
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://127.0.0.1:4318/v1/traces
go mod download
go run .

Si l’ingestion impose une clé, configurez OTEL_EXPORTER_OTLP_TRACES_HEADERS avec Authorization=Bearer YOUR_INGEST_KEY pour votre projet de test. Depuis un autre namespace Kubernetes, utilisez le nom complet avuruobs-gateway.avuruobs.svc.cluster.local plutôt que localhost.

Appelez le service depuis un autre terminal :

curl --fail http://127.0.0.1:8081/hello
# hello from avuru-go-example

Source complète​

main.go
// A minimal OTLP/HTTP tracing example. Run against a test receiver.
package main

import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)

func run() error {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()

// Endpoint, protocol and headers use standard OTEL_EXPORTER_OTLP_* variables.
exporter, err := otlptracehttp.New(ctx)
if err != nil {
return fmt.Errorf("create OTLP exporter: %w", err)
}
res, err := resource.New(ctx, resource.WithFromEnv(), resource.WithAttributes(
attribute.String("service.version", "docs-example"),
))
if err != nil {
return fmt.Errorf("create resource: %w", err)
}
provider := sdktrace.NewTracerProvider(sdktrace.WithBatcher(exporter), sdktrace.WithResource(res))
defer func() {
flush, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := provider.Shutdown(flush); err != nil {
log.Printf("flush traces: %v", err)
}
}()
otel.SetTracerProvider(provider)
otel.SetTextMapPropagator(propagation.TraceContext{})
tracer := otel.Tracer("avuru-docs/go-example")
mux := http.NewServeMux()
mux.HandleFunc("GET /hello", func(w http.ResponseWriter, r *http.Request) {
parent := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
_, span := tracer.Start(parent, "GET /hello", trace.WithSpanKind(trace.SpanKindServer),
trace.WithAttributes(attribute.String("http.request.method", "GET"), attribute.String("http.route", "/hello")))
defer span.End()
span.SetAttributes(attribute.Int("http.response.status_code", http.StatusOK))
// Kubernetes stdout collection can correlate this line without a second log exporter.
line, err := json.Marshal(map[string]string{
"level": "INFO", "message": "hello request handled",
"trace_id": span.SpanContext().TraceID().String(),
"span_id": span.SpanContext().SpanID().String(),
})
if err != nil {
log.Printf("encode log: %v", err)
} else {
fmt.Println(string(line))
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
_, _ = fmt.Fprintln(w, "hello from avuru-go-example")
})
server := &http.Server{Addr: "127.0.0.1:8081", Handler: mux, ReadHeaderTimeout: 5 * time.Second}
serverErrors := make(chan error, 1)
go func() { serverErrors <- server.ListenAndServe() }()
log.Print("listening on http://127.0.0.1:8081/hello")
select {
case err := <-serverErrors:
if !errors.Is(err, http.ErrServerClosed) {
return fmt.Errorf("serve: %w", err)
}
case <-ctx.Done():
shutdown, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(shutdown); err != nil {
return fmt.Errorf("shutdown HTTP server: %w", err)
}
}
return nil
}

func main() {
if err := run(); err != nil {
log.Print(err)
os.Exit(1)
}
}

Le handler extrait le traceparent W3C avant de créer son span serveur. L’export se fait par lots. Ctrl+C arrête le serveur et vide les spans en attente avec un délai borné. Seul le tracing est configuré ici ; les métriques nécessitent leur propre meter provider et exporteur.

Vérifier dans Avuru Obs​

  1. Dans Traces, choisissez le projet destinataire et les 15 dernières minutes.
  2. Filtrez avuru-go-example et ouvrez GET /hello.
  3. Vérifiez le type serveur et http.route=/hello.
  4. Comparez l’ID de trace au log JSON du processus.

Le stdout d’un ordinateur local n’est pas envoyé automatiquement au collecteur Kubernetes. Les IDs démontrent le lien ; il faut une collecte pour transporter la ligne. Dans un pod avec collecte stdout active, l’agent du nœud peut la lire. Évitez un second export OTLP de la même ligne. Voir les logs corrélés.

Si le span n’arrive pas​

  • Lisez les erreurs de l’exporteur ; attendez un lot ou arrêtez le processus proprement.
  • L’URL spécifique HTTP comprend /v1/traces. Le port 4317 concerne gRPC.
  • Vérifiez clé, projet, santé de la gateway et fenêtre temporelle.
  • HTTPS dépend du schéma de l’endpoint ; ne désactivez pas la validation du certificat.
  • Les spans SDK et eBPF peuvent se recouvrir. Identifiez les opérations observées avant de tout activer.

Continuez avec les traces, PostgreSQL et la documentation Go OpenTelemetry.