// OpenTelemetry tracing with nested spans and graceful shutdown. package main import ( "context" "fmt" "log" "math/rand/v2" "net/http" "os" "os/signal" "time" "git.codelab.vc/pkg/obsx" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/codes" ) func main() { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt) defer stop() shutdown, err := obsx.SetupTracer(ctx, obsx.TracerConfig{ ServiceName: "basic-tracing", ServiceVersion: "0.1.0", Endpoint: "localhost:4317", }) if err != nil { log.Fatal(err) } defer func() { if err := shutdown(context.Background()); err != nil { log.Printf("tracer shutdown: %v", err) } }() mux := http.NewServeMux() mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) { ctx, span := obsx.StartSpan(r.Context(), "HandleGetUser") defer span.End() userID := r.PathValue("id") span.SetAttributes(attribute.String("user.id", userID)) user, err := fetchUser(ctx, userID) if err != nil { span.RecordError(err) span.SetStatus(codes.Error, err.Error()) http.Error(w, err.Error(), http.StatusInternalServerError) return } span.SetStatus(codes.Ok, "") fmt.Fprintln(w, user) }) srv := &http.Server{Addr: ":8080", Handler: mux} go func() { <-ctx.Done() _ = srv.Shutdown(context.Background()) }() log.Println("listening on :8080 (try GET /users/42)") if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Fatal(err) } } // fetchUser simulates a database query with its own child span. func fetchUser(ctx context.Context, id string) (string, error) { ctx, span := obsx.StartSpan(ctx, "db.QueryUser") defer span.End() span.SetAttributes(attribute.String("db.statement", "SELECT * FROM users WHERE id = $1")) // Simulate DB latency. time.Sleep(time.Duration(rand.IntN(50)) * time.Millisecond) _ = ctx // context carries the span for further nesting if needed return fmt.Sprintf(`{"id": %q, "name": "Alice"}`, id), nil }