Skip to content

Observability

The gateway exposes three observable surfaces:

  1. Structured JSON logs to stdout (visible via docker logs).
  2. Prometheus metrics at /metrics.
  3. The OpenRouter completion ID returned to the client, which can be cross-referenced against upstream logs.

No tracing is emitted in v1.0.0. No request bodies or message content are ever logged.

Structured JSON logs

src/smart_gateway/observability.py::setup_logging configures a JSON formatter. Each line is a single JSON object:

{
  "level": "INFO",
  "message": "Routing decision made",
  "logger": "smart_gateway",
  "timestamp": "2026-08-23T10:11:12.345Z",
  "task_id": "task-17",
  "decision": {
    "action": "ROUTE",
    "model": "deepseek_v4_flash",
    "cause": "score_flash"
  }
}

Standard fields:

Field Notes
level INFO, WARNING, ERROR.
message Free-text.
logger Always smart_gateway.
timestamp ISO-8601 UTC.
task_id The routing task ID (or *_fallback for fallback events).
decision Action, model, cause.

Routing-event-specific messages:

Message When
Routing decision made Always emitted on a chat completions call.
OpenRouter API failed after retries with status <code> Retryable upstream failure exhausted.
OpenRouter API error: <code> Non-retryable upstream failure.
OpenRouter connection failed: <reason> Transport failure.

Tail the logs

docker logs -f --tail 200 smart-supervisor-production-gateway-1

Filter for routing decisions:

docker logs smart-supervisor-production-gateway-1 \
  | grep '"Routing decision made"'

Filter for failures:

docker logs smart-supervisor-production-gateway-1 \
  | grep -E '"FAILED"|"FALLBACK_GLM_FAIL"'

Save a 24-hour audit trail

docker logs --since 24h smart-supervisor-production-gateway-1 \
  > /srv/apps/smart-supervisor-production/backups/log-$(date -u +%Y%m%dT%H%M%SZ).jsonl

Prometheus metrics

See API reference → Metrics for the full metric list. Three metrics are emitted:

  • sgw_requests_total{model, status}
  • sgw_routing_decisions_total{action, model}
  • sgw_request_latency_seconds (histogram)

What is intentionally absent

  • No request body in logs. Even redacted message content is not logged. Audit trails rely on task_id, decision.cause, and the OpenRouter completion ID.
  • No PII in metrics. Metric labels are model IDs and HTTP statuses only.
  • No tracing. v1.0.0 does not emit OpenTelemetry spans. The JSON-formatter logs are the audit trail.
  • No log shipping. The gateway writes to stdout; you are expected to collect stdout via your container runtime.

How to debug a routing decision

  1. Find the request's task ID. Clients should set X-Task-Id or metadata.task_id. If neither is set, the gateway derives one from the timestamp.
  2. Grep the logs for the task ID.
  3. Look at the decision.cause field. Map it to a rule (see Architecture → Routing pipeline).
  4. If decision.cause is score_flash or score_pro, the log line shows the score in decision.score (added by the policy module). The dry-run endpoint /v1/route/decision is a more convenient way to inspect scores.
  5. If the cause is all_models_unhealthy, check the OpenRouter connectivity and the model health registry state.

See also

  • Reference → Metrics — metric reference.
  • The operator runbook at docs-internal/operations/daily-operations.md describes how the operator uses these surfaces on the production VPS (operator-only).