diff --git a/examples/basic-tracing/main.go b/examples/basic-tracing/main.go new file mode 100644 index 0000000..1c8b032 --- /dev/null +++ b/examples/basic-tracing/main.go @@ -0,0 +1,83 @@ +// 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 +}