Observability State & Gaps (as served, 2026-10)
traceo beginner 6 min read
ELI5
Both services now have a /metrics window. Through the MCP window you can see the tenant-lookup counters and little else, because the machinery that counts web requests is bolted onto a file production does not run. The engine’s window counts every request, but it hands the numbers over gift-wrapped as JSON, which a Prometheus scraper cannot read, and two of its dials stick at their first reading.
Technical Deep Dive
Source: traceo-cat/traceo_mcp_server/traceo_mcp_server/routes/health.py:1-45, middleware/metrics.py:30-90, auth/middleware.py:43-63, app.py:85-91, server.py:88-160, engine/src/engine/server.py:142-168 and :360-410, engine/src/engine/middleware/logging.py:1-25, traceo_mcp_server/traceo_mcp_server/logging_config.py:1-40, .github/workflows/deploy-observability.yml (origin/main); decision 0075; live scrapes of mcp.traceo.cat/metrics and engine.traceo.cat/metrics (twice) on 2026-10-25; traceo-cat origin/main 7d647ab
What each /metrics serves (live, 2026-10-25)
MCP (routes/health.py:33) | Engine (server.py:360) | |
|---|---|---|
| Content type | Prometheus text (PlainTextResponse, CONTENT_TYPE_LATEST) | application/json: the exposition text arrives as one JSON-encoded string |
http_requests_total / duration / failed | declared, no samples: MetricsMiddleware is only added in app.py:85 | populated per method, path and status by MetricsMiddleware |
| Tenant resolve | traceo_tenant_resolve_total{result="ok"|"fail"}, traceo_tenant_resolve_consecutive_failures (decision 0075) | — |
| Engine gauges | — | traceo_engine_uptime_seconds, traceo_jobs_total{status}: frozen at the first scrape’s values |
Three defects the v1.0 card could not have listed:
- MCP request metrics never increment.
server.py’s combined app installs onlyAuthMiddleware.MetricsMiddleware,SentryContextMiddlewareandLoggingMiddlewareare added only inapp.py, which is not deployed. - The engine’s
/metricsis not scrapeable as Prometheus text. The handler returns the string fromgenerate_latest(...).decode()withoutPlainTextResponse, so FastAPI JSON-encodes it. A Prometheus scraper expects plain text. - The engine gauges stick. Each scrape tries to register
Gauge(...)again, and theexcept ValueError: passon the duplicate skips the.set(). Two scrapes 15 s apart both readtraceo_engine_uptime_seconds 760.69, while/healthmoved from 775.7 to 775.9.
At the 2026-10-25 scrape, the MCP tenant-resolve counters read ok 0, fail 0, with a creation timestamp moments earlier, so the process had just restarted. A zero there means “no authenticated tenant request since boot”. It is not a fault on its own.
flowchart LR subgraph mcp["MCP as deployed (server.py)"] A1["AuthMiddleware"] -->|"record_tenant_resolve"| C1["traceo_tenant_resolve_*"] MM["MetricsMiddleware"]:::dead APP["app.py only"]:::dead --> MM end subgraph eng["Engine"] M2["MetricsMiddleware"] --> H2["http_requests_* (live)"] G2["uptime / jobs gauges"] -->|"set skipped after first scrape"| F2["frozen"] end C1 --> P["GET /metrics text/plain"] H2 --> J["GET /metrics application/json string"] G2 --> J classDef dead stroke-dasharray: 5 5Logging and correlation IDs
| Tier | In production |
|---|---|
| Engine | LoggingMiddleware (structlog) generates or propagates a correlation ID per request via contextvars, plus SentryContextMiddleware tags Sentry events with it |
| MCP | logging_config.py configures structlog JSON output (LOG_FORMAT=json default), but the request middleware that sets correlation IDs is not on the deployed path |
The v1.0 card said there were no correlation IDs anywhere. The engine has them. The MCP server has the code but does not run it in production.
Deploy observability
The deploy path is watched by deploy-observability.yml, which checks that both Railway services deployed origin/main. It runs daily since 0131 (proposed) and is covered in traceo-018.
mindmap root((Traceo observability)) MCP /metrics text OK tenant resolve counters OK http_requests empty no correlation middleware Engine /metrics served as JSON http_requests live uptime and jobs gauges frozen correlation IDs live Deploy daily Railway observer verdict artifact every run Errors Sentry SDK both tiers shared exception handlers on combined appKey Terms
- Tenant-resolve counters →
traceo_tenant_resolve_total{result}andtraceo_tenant_resolve_consecutive_failures, mirrored fromAuthMiddleware’s module globals onto Prometheus (0075). - Exposition format → the plain-text format Prometheus scrapes. A JSON-quoted copy of it is not the same thing.
- Correlation ID → a per-request identifier carried in contextvars, so log lines and Sentry events for one request can be joined.
Q&A
Q: Which deployed service serves /metrics, and what does each one actually emit? A: Both. MCP serves Prometheus text with live tenant-resolve counters and empty HTTP request series. The engine serves populated HTTP request series plus uptime and job gauges, but as a JSON string, and the gauges freeze after the first scrape.
Q: Why are the MCP server’s http_requests_ series empty?*
A: The metrics are registered in the shared registry, but MetricsMiddleware is only installed in app.py. Production runs server.py, whose combined app adds only AuthMiddleware.
Q: What can Prometheus not do with the engine’s /metrics response?
A: Parse it as exposition text. The handler returns a bare str, so FastAPI serves it as application/json, a quoted string with escaped newlines. Wrapping it in PlainTextResponse (as the MCP route does) would fix it.
Q: Which tier has correlation-ID middleware in production?
A: The engine (LoggingMiddleware). The MCP server’s equivalent sits only in the undeployed app.py.
Examples
You write an alert on rate(http_requests_failed_total{job="traceo-mcp"}[5m]) > 0. It never fires, because the series has no samples on the deployed MCP server. Alert on the tenant-resolve counters there instead, for example traceo_tenant_resolve_consecutive_failures > 0. Before pointing a scraper at the engine, fix its /metrics to return plain text.
neighbors on the map
- Docs Are Not the Deployment: The Corroborated False Description auditing whether a control (test, middleware, gate) actually runs
- FNP Observability & Prometheus Metrics monitoring FNP systems
- Deployment Topology, Proxy Conflict & CI after eu-1 finding where a smo1 component runs and how it deploys