Documents the exact hook point chosen for each of the 3 dead observability primitives (access log, request-duration histogram, tracer provider) and the 11 named business-health metrics, verified against the real current code rather than assumed — including a pre-existing SLA-run status data quality gap surfaced along the way (documented, not fixed here). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4.9 KiB
Data Model: Full Observability
No Prisma schema changes — every entity here is in-process or exported to an external observability sink, never persisted to Postgres.
Request Context Store
AsyncLocalStorage<RequestContextSnapshot>, populated once per request in
request-context.plugin.ts's existing onRequest hook (the same hook that already builds
request.reqContext), read by logger.ts's Pino mixin function on every subsequent log call
made anywhere during that request's handling.
| Field | Type | Notes |
|---|---|---|
requestId |
string |
Same value already assigned to request.reqContext.requestId |
correlationId |
string |
Same value already assigned to request.reqContext.correlationId |
Access Log Line (shape, not a stored entity)
Emitted once per completed request via the existing logger singleton from the new
onResponse hook.
| Field | Type | Notes |
|---|---|---|
method |
string |
HTTP method |
route |
string |
Parameterized route pattern (request.routeOptions.url), not the raw URL |
statusCode |
number |
Response status |
durationMs |
number |
reply.elapsedTime |
requestId / correlationId |
string |
Via the mixin, same as every other line for this request |
event |
string |
Fixed value "http_request_completed" — lets log queries filter to access-log lines specifically |
Log level: info for 2xx/3xx, warn for 4xx, error for 5xx — mirrors the existing
error-handler's own level choices (app.ts) so severity is consistent across both sources of
request-outcome logging.
Metrics (Prometheus, via prom-client)
All registered in infrastructure/observability/metrics.ts on the existing default registry
(metricsRegistry, already exposed at GET /metrics), all prefixed supporthub_ to match the
existing histogram and default-metrics prefix.
| Metric name | Type | Labels | Incremented/observed when |
|---|---|---|---|
supporthub_http_request_duration_seconds |
Histogram (existing, now actually observed) | method, route, status_code |
Every completed HTTP request |
supporthub_ai_session_outcomes_total |
Counter | outcome (resolved | escalated) |
An AI support session reaches a terminal resolved/escalated status |
supporthub_ticket_resolutions_total |
Counter | resolved_by (ai | human) |
A ticket reaches RESOLVED, labeled from the ticket's Resolution.resolvedBy |
supporthub_ticket_resolution_duration_seconds |
Histogram | — | A ticket reaches RESOLVED — observes resolvedAt - ticket.createdAt |
supporthub_ticket_first_response_duration_seconds |
Histogram | — | The first AGENT_MESSAGE is posted on a ticket — observes firstResponseAt - ticket.createdAt |
supporthub_sla_run_outcomes_total |
Counter | outcome (met | breached) |
An SLA run completes on time (met) or is flagged by the breach sweep (breached) |
supporthub_escalations_total |
Counter | reason |
An ESCALATION_TRIGGERED domain event fires (already published unconditionally today) |
supporthub_problems_created_total |
Counter | category_id (or uncategorized) |
A Problem row is created (at ticket-intake time) |
supporthub_known_error_lookups_total |
Counter | code |
A valid error code's known issues are looked up |
supporthub_knowledge_retrieval_outcomes_total |
Counter | matched (true | false) |
The AI's searchProductKnowledge tool call returns zero vs. one-or-more results |
supporthub_tool_invocations_total |
Counter | tool, outcome (success | failed) |
Every AI tool-call result, any tool |
Deliberately not separate metrics (per spec.md's Assumptions): "recurring problems" and
"most common errors" are read directly off supporthub_problems_created_total and
supporthub_known_error_lookups_total respectively via a monitoring stack's own topk/rate
query — no additional "top N" metric or logic is computed by this application.
Traces / Spans (exported, not persisted)
| Span | Parent | Attributes | Created in |
|---|---|---|---|
ticket.create |
(root) | ticket.id, product.externalProductId |
ticketing/tickets/service/tickets.service.ts |
ai.escalation |
ticket.create (if within the same request) or its own root (async paths) |
ticket.id, session.id |
ai-support/sessions/service/session.service.ts, around the escalation branch |
orchestration.assignment |
ai.escalation (via the TICKET_UPDATED/HUMAN_ESCALATION subscriber) |
ticket.id, strategy |
orchestration/orchestration + orchestration/assignments, wrapping the existing handleHumanEscalation call |
Span context propagation across the domain-event bus relies on the OpenTelemetry Context API's
own async-local propagation — since eventBus.publish(...) is awaited synchronously within
the same call chain (confirmed in tickets.service.ts/escalation.service.ts), no manual
context-carrying payload field is needed.