Observability
The system uses two observability layers: application logs for infrastructure and request-level events, and LLM tracing for AI-specific signals (token usage, retrieval quality, qualification outcomes).
Application Logs
Logs are emitted as structured JSON by the shared/telemetry package. The service is tagged as growth-chat-api in all log entries.
The log destination (log aggregation provider) is configured via environment variables — see ADR-008 for the provider decision and backend/.env.example for the relevant variable names.
What is logged:
- Request lifecycle (session start, message received, response dispatched)
- Qualification state changes
- Handoff dispatch attempts and outcomes
- RAG retrieval results (chunk count, whether threshold was met)
- Rate limit events (429 responses)
- Backend startup and shutdown
- Analytics provider failures (degraded, not fatal)
What is NOT logged (PII protection):
- Message content from visitors
- Visitor name or email in log body (only in the handoff subsystem’s structured fields, which go to the database)
- Raw API responses
Log retention is 30 days, managed by the log provider.
LLM Tracing (Langfuse)
LLM calls, RAG retrievals, and embedding spans are traced via Langfuse (see ADR-007). Tracing activates only when Langfuse credentials are present in the environment. If credentials are absent, the analytics provider silently uses a no-op implementation — the chat continues normally but no traces are recorded.
Trace structure per chat request:
Trace (session_id)
└── Span: chat_request
├── Generation: LLM call (model, input messages, token counts, latency)
├── Retriever span: retrieve_knowledge (query, chunks returned, scores) [if RAG triggered]
└── Embedding span: embed query (model, input text) [if RAG triggered]
Events to monitor in Langfuse:
| Event | Significance |
|---|---|
qualification_state_changed |
Visitor progressed through qualification stages |
handoff_dispatched |
A lead was sent to Slack and CRM |
handoff_partial_failure |
One channel (Slack or CRM) failed after retries |
handoff_total_failure |
Both channels failed — email fallback triggered |
rag_retrieved |
RAG returned chunks above the relevance threshold |
rag_no_result |
RAG found no chunks above threshold for a query |
session_expired |
Session closed due to inactivity TTL |
Langfuse is hosted on the EU cloud instance for GDPR data residency compliance (all compute in Frankfurt).
Health Monitoring
Two endpoints are available for external health checks:
| Endpoint | Use | Normal state |
|---|---|---|
GET /health |
Process liveness (Docker HEALTHCHECK) | 200 {"status": "ok"} always, if the process is up |
GET /ready |
Application readiness (Fly.io HTTP check) | 200 {"status": "ready"} when the app has finished starting; 503 during startup or shutdown |
Fly.io polling: /ready is checked every 30 seconds, 5-second timeout, 30-second grace on start. Three consecutive failures trigger a machine restart.
Interpreting /ready 503:
- At startup: normal — wait for the grace period to pass.
- After a successful deploy: transient — clears once the database connection pool is established.
- Persistent 503 post-deploy: startup failure — check
flyctl logsfor errors (common causes: missing required secret, database connection failure, Langfuse client failure).
Cost Monitoring
The system has a configurable monthly LLM cost ceiling (default: $50/month). This is not enforced at the application level — it is a soft cap that feeds the alerting system.
Monitor in Langfuse:
- Token consumption per session — outlier sessions may indicate prompt injection or abuse.
- Total daily token spend — compare against the monthly budget to project overage risk.
rag_retrievedcall rate — a spike may indicate over-triggering of RAG, which increases cost.
The per-session token budget (default: 16,000 cumulative tokens) is enforced at the application level. Sessions that exceed the budget receive a graceful closure and cannot continue.
Alerting
The following alerts should be configured in the log provider and/or Fly.io:
| Alert | Source | Trigger condition |
|---|---|---|
| Backup job failure | Fly.io machine exit event | Backup machine exits with non-zero code — configure once the backup job is implemented |
| Persistent readiness failure | Fly.io health check | /ready returns 503 for >3 consecutive checks |
| Monthly LLM spend threshold | Langfuse / log provider | Spend exceeds 80% of MONTHLY_COST_CAP_USD |
| Rate limit spike | Application logs | 429 response rate exceeds baseline |
| Total handoff failure | Application logs / Langfuse | handoff_total_failure event emitted |
Specific alert configuration (thresholds, notification channels, escalation paths) is maintained in the team’s private runbook.