Add full-observability example: metrics and tracing together
All checks were successful
Publish / publish (push) Successful in 19s
All checks were successful
Publish / publish (push) Successful in 19s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
107
examples/full-observability/main.go
Normal file
107
examples/full-observability/main.go
Normal file
@@ -0,0 +1,107 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user