// Full observability: Prometheus metrics and OpenTelemetry tracing together. package main import ( "context" "fmt" "log" "math/rand/v2" "net/http" "os" "os/signal" "strconv" "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() // --- Tracing --- shutdown, err := obsx.SetupTracer(ctx, obsx.TracerConfig{ ServiceName: "full-observability", 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) } }() // --- Metrics --- metrics := obsx.NewMetrics(obsx.MetricsConfig{ Namespace: "myapp", Subsystem: "orders", }) requests := metrics.Counter("requests_total", "Total requests", "method", "status") duration := metrics.Histogram("request_duration_seconds", "Latency", nil, "method") inflight := metrics.Gauge("inflight_requests", "In-flight requests") // --- Routes --- mux := http.NewServeMux() mux.HandleFunc("POST /orders", func(w http.ResponseWriter, r *http.Request) { inflight.WithLabelValues().Inc() defer inflight.WithLabelValues().Dec() start := time.Now() ctx, span := obsx.StartSpan(r.Context(), "HandleCreateOrder") defer span.End() orderID, err := createOrder(ctx) status := http.StatusCreated if err != nil { status = http.StatusInternalServerError span.RecordError(err) span.SetStatus(codes.Error, err.Error()) http.Error(w, err.Error(), status) } else { span.SetStatus(codes.Ok, "") w.WriteHeader(status) fmt.Fprintf(w, `{"order_id": %q}`+"\n", orderID) } requests.WithLabelValues("POST", strconv.Itoa(status)).Inc() duration.WithLabelValues("POST").Observe(time.Since(start).Seconds()) }) mux.Handle("GET /metrics", metrics.Handler()) // --- Server --- srv := &http.Server{Addr: ":8080", Handler: mux} go func() { <-ctx.Done() _ = srv.Shutdown(context.Background()) }() log.Println("listening on :8080 (try POST /orders, GET /metrics)") if err := srv.ListenAndServe(); err != http.ErrServerClosed { log.Fatal(err) } } // createOrder simulates order creation with a child span. func createOrder(ctx context.Context) (string, error) { _, span := obsx.StartSpan(ctx, "db.InsertOrder") defer span.End() span.SetAttributes(attribute.String("db.statement", "INSERT INTO orders ...")) // Simulate DB latency. time.Sleep(time.Duration(rand.IntN(80)) * time.Millisecond) orderID := fmt.Sprintf("ord-%d", rand.IntN(100000)) span.SetAttributes(attribute.String("order.id", orderID)) return orderID, nil }