This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/archive/DESIGN-0.33.0.md
2026-03-20 16:08:12 +00:00

8.1 KiB

DESIGN-0.33.0 — Observability

Operate the platform without reading Go source code. Six changesets covering structured logging, Prometheus metrics, OpenAPI docs, Grafana dashboards, alerting rules, and a built-in admin dashboard.

Depends on: v0.32.0 (Multi-Replica HA, current HEAD).

Design decision: Zero new database migrations. All metrics are in-memory per-pod, scraped by Prometheus. Existing tables (provider_health, tool_health_windows, usage_log, audit_log) continue serving the admin dashboard via stores.

Library choices:

  • log/slog (Go 1.22 stdlib) — zero-dep structured logging
  • prometheus/client_golang v1.20+ — canonical Go Prometheus client
  • Hand-curated OpenAPI 3.0.3 YAML — avoids annotation sprawl across 97 handlers
  • Swagger UI v5 via CDN — single HTML embed, zero build step

What Already Existed

These required no structural changes, only instrumentation:

  • Health accumulator — in-memory provider+tool metrics, 60s DB flush
  • Hub.ConnCount() — active WebSocket connection count
  • database.DB.Stats() — Go stdlib DB pool metrics
  • K8s probes/healthz/ready + /healthz/live in Helm chart
  • Middleware skip paths/metrics already in SkipPaths list
  • google/uuid — already in go.mod for request ID generation

CS0 — Structured Logging + Request ID

Foundation changeset. All subsequent work emits structured logs.

server/logging/logger.go (new)

Init(format, level) configures the global slog logger:

  • format=jsonslog.NewJSONHandler(os.Stdout, opts)
  • format=textslog.NewTextHandler(os.Stdout, opts)
  • Levels: debug, info, warn, error

server/middleware/request_id.go (new)

Generates UUID per request. Reuses existing google/uuid. Honors inbound X-Request-Id header for trace propagation. Sets request_id in Gin context and echoes header on response.

Middleware chain rewrite

Changed gin.Default()gin.New() with explicit chain:

RequestID → Prometheus → Logger → Recovery → CORS

Logger middleware rewritten from log.Printf to slog.Info("request", ...) with structured fields: method, path, status, latency_ms, client_ip, request_id, user_id.

Config additions

  • LogFormat (env: LOG_FORMAT, default: text)
  • LogLevel (env: LOG_LEVEL, default: info)

CS1 — Prometheus /metrics Endpoint

server/metrics/metrics.go (new)

All metric definitions in one file using promauto:

Metric Type Labels
switchboard_http_requests_total Counter method, path_pattern, status
switchboard_http_request_duration_seconds Histogram method, path_pattern
switchboard_websocket_connections Gauge
switchboard_completion_tokens_total Counter direction, model_id
switchboard_completions_total Counter provider_config_id, model_id, status
switchboard_completion_duration_seconds Histogram provider_config_id, model_id
switchboard_provider_status Gauge provider_config_id
switchboard_db_open_connections Gauge
switchboard_db_in_use_connections Gauge
switchboard_db_idle_connections Gauge
switchboard_db_wait_count Gauge
switchboard_db_wait_duration_seconds Gauge
switchboard_task_executions_total Counter status
switchboard_sandbox_executions_total Counter entry_point, status

server/metrics/db_collector.go (new)

Background goroutine reads database.DB.Stats() every 15s, updates Prometheus gauges.

server/middleware/prometheus.go (new)

Gin middleware. Uses c.FullPath() for path_pattern label to avoid cardinality explosion from path parameters (e.g. /channels/:id not /channels/abc123).

Instrumentation points

  • events/ws.goWebSocketConnections.Inc()/Dec() on connect/disconnect
  • handlers/completion.go — token counters in logUsage(), completion duration/status in recordHealth()
  • handlers/stream_loop.go — same instrumentation in recordHealthFn()
  • health/accumulator.goProviderStatus gauge updated in flush()

Route

/metrics mounted via promhttp.Handler() (no auth, standard scraping).


CS2 — OpenAPI Spec + Swagger UI

server/static/openapi.yaml (new)

Hand-curated OpenAPI 3.0.3 spec covering core API groups: Auth, Channels, Completions, Health, Metrics, WebSocket Tickets, Admin (stats, health, usage, dashboard).

server/static/swagger.html (new)

Minimal HTML loading Swagger UI v5 from unpkg CDN, pointing at openapi.yaml. Zero build step.

Routes

  • GET /api/docs → serves embedded swagger.html
  • GET /api/docs/openapi.yaml → serves embedded openapi.yaml

Both files embedded via //go:embed directives.


CS3 — Grafana Dashboard + Alerting Rules + Helm

Static files + Helm templates. No Go code changes.

chart/dashboards/switchboard-overview.json (new)

Grafana dashboard with 10 panels:

  • Request rate, error rate, latency percentiles
  • WebSocket connections, completion rate/latency by provider
  • Tokens/min by model, provider status
  • DB connection pool, task executions

Template variables: $datasource, $namespace, $pod.

chart/alerting/switchboard-rules.yaml (new)

Source PrometheusRule with 6 alerts:

Alert Condition Severity
SwitchboardPodRestart Restarts in 1h warning
SwitchboardProviderDown Status > 2 for 5m critical
SwitchboardDBPoolExhaustion In-use/open > 80% for 5m warning
SwitchboardHighErrorRate 5xx rate > 5% for 5m warning
SwitchboardTaskFailureRate Error rate > 25% for 10m warning
SwitchboardNoCompletions Zero completions for 15m critical

Helm templates (new, all gated enabled: false by default)

  • chart/templates/servicemonitor.yaml — ServiceMonitor CRD
  • chart/templates/grafana-dashboard-configmap.yaml — ConfigMap for Grafana sidecar discovery
  • chart/templates/prometheusrule.yaml — PrometheusRule CRD

Helm modifications

  • chart/values.yamllogging section + monitoring section
  • chart/templates/configmap.yamlLOG_FORMAT, LOG_LEVEL env vars
  • chart/templates/services.yaml — Prometheus scrape annotations

CS4 — Admin Observability Dashboard

Built-in admin page for real-time health without Grafana dependency.

server/handlers/dashboard_admin.go (new)

GET /api/v1/admin/dashboard aggregating:

  • Provider health summaries (from HealthStore)
  • 24h usage totals (from UsageStore)
  • DB pool stats (database.DB.Stats())
  • WebSocket connection count (Hub.ConnCount())
  • Recent errors (from AuditStore)
  • Process uptime

Frontend

  • SCAFFOLDING.dashboard — stat cards grid + content container
  • ADMIN_LOADERS.dashboardUI.loadAdminDashboard()
  • ADMIN_SECTIONS.monitoringdashboard added as first section
  • Dashboard auto-refreshes every 30s
  • CSS: provider grid, DB pool bar gauge, error list

Admin template

Monitoring tab now lands on /admin/dashboard instead of /admin/usage.


CS5 — Wiring, docker-compose, ICD Tests

docker-compose.yml

Added LOG_FORMAT and LOG_LEVEL env vars with defaults.

ICD tests (crud/observability.js)

Test Assertion
GET /metrics 200, contains switchboard_* metrics
GET /api/docs 200, contains swagger-ui
GET /api/docs/openapi.yaml 200, contains openapi: and paths:
X-Request-Id generation Response includes 36-char UUID header
X-Request-Id passthrough Client-sent header echoed back
GET /admin/dashboard 200, has uptime, ws_connections, provider_health

Verification

  1. LOG_FORMAT=json docker compose up → JSON log lines on stdout
  2. curl localhost:8080/metrics | grep switchboard_ → all metrics present
  3. curl -I localhost:8080/api/v1/channelsX-Request-Id header
  4. /api/docs in browser → Swagger UI renders
  5. /admin → Monitoring → Dashboard shows live stat cards
  6. helm lint chart/ --set monitoring.serviceMonitor.enabled=true → passes
  7. ICD test suite passes (existing + observability tests)