PlanCover Docs
Operations

Observability

Langfuse for the model, Sentry for the process, structured logs for everything else.

Three signals, each answering a different question.

Langfuse

Why did the assistant answer this way? Traces, retrieval, tool calls, evaluations.

Sentry

What broke? Exceptions and performance for the service itself.

Structured logs

What happened, in order? One JSON line per event, carrying a request id.

Request correlation

Every response carries a request id, generated at the outermost middleware so it exists even when the request fails before reaching a route. The same id appears in the log line, the Sentry event and the trace, which is what makes a report of "it failed around 3pm" actionable.

Each turn gets its own server-generated Langfuse trace id. The request id is kept as trace metadata rather than reused, because a caller can supply it. Sentry events carry the trace id as langfuse_trace_id, together with conversation_id and channel, and so does each turn's turn.completed log line. Langfuse groups a conversation's turns into one session.

The caller appears as one pseudonym everywhere: the Langfuse user, the Sentry user and the user_id field of a log line. It is a keyed hash of their user_ref, never the value itself — for a member, user_ref is the chat session id, and that is also their bearer. The hash is keyed by ENCRYPTION_KEY; in dev without one there is no pseudonym, so traces and Sentry events have no user and log lines a null user_id.

One line per turn

Every chat turn ends with exactly one turn.completed log line, including turns that fail or where the client hangs up:

{"event": "turn.completed", "outcome": "stop", "duration_ms": 4120,
 "prompt_tokens": 2310, "completion_tokens": 188, "tool_calls": 1, "tool_errors": 0,
 "retrieval_hits": 4, "request_id": "…", "conversation_id": "…", "langfuse_trace_id": "…"}

outcome is the finish reason (stop, awaiting_confirmation, …), error (with error_code), or cancelled. Build latency, error-rate and token dashboards from it.

Sampling

SettingDefaultNotes
LANGFUSE_SAMPLE_RATE1.0Fraction of turns traced. Lower it in prod if volume outgrows the plan.
SENTRY_TRACES_SAMPLE_RATE0.1Performance only. Errors are always sent, and health probes are never sampled.

Redaction

Non-negotiable

Access tokens, OTPs and PlanCover tool URLs never reach a prompt, graph state, a log line, a trace or an error report. Redaction happens by whole key segment, so a nested access_token is removed wherever it appears rather than only at the top level.

Langfuse is an observability and evaluation tool. It is not the application conversation database — conversation state lives in Postgres.

What is worth alerting on

SignalWhy it matters
Readiness failuresThe service is up but cannot serve
Tool error rate (turn.completed → tool_errors)An upstream PlanCover API is degraded
outcome=error rate by error_codeTurns failing in front of members
p95 duration_msThe member is watching a blank screen
Groundedness scoreAnswers are drifting from retrieved evidence

On this page