Monitoring & Observability

Prometheus metrics, OpenTelemetry traces with per-request LLM cost, structured logs, Grafana dashboards, and health endpoints

Overview

SBproxy emits metrics, logs, and traces from one process. Metrics and logs are on by default; traces are opt-in per deployment. All three speak the same correlation triple, so a single inbound request stitches together across every signal without joining on timestamps.

Three pillars

  • Metrics - Prometheus / OpenMetrics on the /metrics endpoint, always on. The label set is closed and budgeted, so a fresh user-agent per request cannot blow up cardinality. High-cardinality identifiers (request id, agent id) live on traces and logs, never on metric labels.
  • Logs - Structured JSON to stdout and any number of configured sinks. See Request Logging for the schema, multi-sink dispatch, and redaction.
  • Traces - OpenTelemetry over OTLP (gRPC on 4317, HTTP on 4318). Each request produces a trace with spans for intake, policy enforcement, transforms, audit, and outbound calls.

Every log line and span attribute carries request_id, trace_id, and span_id. Click an outlier in a latency histogram and jump straight to its trace via exemplars.

Configure tracing

The observability schema lives under proxy.observability. Tracing is off until you enable it:

proxy:
  observability:
    log:
      level: info                  # debug | info | warn | error
      format: json                 # compact | pretty | json
    telemetry:
      enabled: true
      endpoint: "http://otel-collector:4317"
      transport: grpc              # grpc | http
      service_name: "sbproxy"
      sample_rate: 0.1             # head ratio for unsampled roots
      always_sample_errors: true   # 100% on 5xx and policy-block paths
      propagation: w3c             # w3c | b3 | jaeger
      resource_attrs:
        deployment.environment: "prod"

SBproxy extracts W3C traceparent on the way in and injects it on every outbound client it owns, so traces stay connected end to end.

AI spans with token counts and USD cost

The AI request span follows the OpenTelemetry GenAI semantic conventions (gen_ai.*) and dual-emits the OpenInference vocabulary (llm.*). Each AI span carries the provider and model, input and output tokens (with cache-read, cache-write, and reasoning splits), and a derived USD cost. Token counting happens at the gateway rather than trusting the upstream's self-report, and cost is computed from a pricing catalog whose revision is stamped on the span.

Because the spans are standard OTLP, these backends render a full LLM trajectory with no custom mapping:

  • Arize Phoenix and Langfuse - read gen_ai.* and OpenInference llm.* natively.
  • Grafana Tempo, Jaeger, Datadog, and Honeycomb - via OTLP, directly or through the OpenTelemetry Collector.

Dashboards and alerts

Eight Grafana dashboards ship as JSON you can import directly or provision through the Helm chart: overview (request rate, errors, latency), origins (per-origin traffic and health), ai-gateway (provider routing, tokens, latency), ai-value (per-credential token and USD spend), ai-bot-traffic (crawler and agent traffic), policy-verdicts (block rate and decision distribution), security (auth, WAF, rate limits), and judge-backend (LLM-as-judge evaluations). A matching SLO catalog ships PromQL recording and multi-window burn-rate alerting rules across page, ticket, and log-only tiers.

Health endpoints

Two endpoints on the management port (default 127.0.0.1:9091):

curl http://localhost:9091/healthz
# 200 OK, no body. Liveness only; the kubelet uses this to decide whether to restart the pod.

curl http://localhost:9091/readyz
# 200 OK with a JSON body listing each component status.
# 503 with the same body when a required dependency is unhealthy.

Reference stack in one command

A reference Compose stack boots Prometheus, Grafana, Tempo, Loki, an OpenTelemetry Collector, and Arize Phoenix, with the dashboards pre-provisioned:

cd examples/observability-stack
docker compose up -d
# Grafana at http://localhost:3000 (admin / admin)
# Prometheus at http://localhost:9090