
The default advice for LLM observability is to log everything: every prompt, every completion, every tool call. That makes debugging easy, and it also creates a second copy of your users' personal data in a system that was never designed to hold it. Support conversations, account details and pasted documents end up in a tracing tool that most of the engineering team can search.
You do not have to choose between seeing what your app does and keeping personal data out of your logs. Most of what you need to run an LLM app is metadata, and the parts that do need content can be redacted, pseudonymised and kept for a short, defined time. Here is what we log, what we leave out, and how we decide.
Start with a per-call record that holds no content
Every model call should produce one structured record. None of these fields need the text of the prompt or the answer.
- Ids: a request id, a trace id that ties the call to the user action, a session or conversation id, and a pseudonymous user id.
- Versions: prompt template id and version, the model identifier the provider returned, sampling parameters, retrieval index version and app release.
- Usage: input and output token counts, cached tokens where the provider reports them, and how many retrieved passages were sent.
- Timing: total latency, time to first token when streaming, and time spent in retrieval and in each tool.
- Cost fields: model, tokens and provider are enough to compute cost later against whatever rate card applies. Store the inputs to the calculation, not a hard-coded price that goes stale.
- Outcome: finish reason, errors and retries, whether a guardrail fired, and what the user did next: accepted, regenerated, edited, escalated or left, plus any explicit feedback.
The OpenTelemetry semantic conventions for generative systems, which are still under active development, draw the same line. Metadata such as the model, token usage and finish reasons is recorded by default, while capturing prompt and response content is an explicit opt-in. That is a sensible default to copy even if you use a different tool.
What should you log from an LLM app for observability?
Log a structured metadata record for every model call, and treat prompt and response content as an opt-in exception. The record should hold a request id, a trace id, a pseudonymous user id, the prompt template version, the model identifier the provider returned, sampling parameters, input and output token counts, latency, the inputs needed to compute cost, the finish reason, errors, guardrail hits and what the user did next. Link each call into a trace with spans for retrieval (document ids and scores, not text), tool calls and guardrails, and attach quality scores such as faithfulness or citation checks. When you do need content, redact names, contact details, account numbers and other identifiers inside your service before the exporter writes anything, and keep the pseudonymisation key in a separate system. Give each field a purpose and a retention window, restrict content views to a small group, and remember that pseudonymised data is generally still personal data under the GDPR.
Trace the whole request, not only the model call
Problems in LLM apps rarely live in the model call alone. A wrong answer often starts with retrieval returning the wrong passage or a tool returning stale data. Structure each user request as one trace with child spans.
- One span per model call, carrying the record above.
- One span per retrieval, with the ids and scores of the documents returned and the index version, but not the document text.
- One span per tool call, with the tool name, status, latency and only the argument fields you have marked as non-sensitive.
- One span per guardrail or moderation step, recording which rule fired and what the app did about it.
Document ids and scores let you debug retrieval without copying your corpus into the log store. The documents already sit somewhere with access controls, so link to them instead. For agents that loop, the trace also shows how many steps a request took and where it went in circles, which usually tells you more than any single call.
Score quality without keeping the transcript
Quality signals belong in the record too: an automated faithfulness or relevance score, whether citations resolved, whether a refusal was correct, and any user feedback. The metrics in RAG evaluation before launch can be computed close to the call, on content that is then redacted or discarded, with only the scores stored.
You will still need real examples to improve the system. Treat them as a separate, deliberate dataset: sampled, redacted, reviewed and given its own retention, rather than an accidental archive of every conversation. Test sets for an eval-first release process are better built from a curated sample like this than from raw logs anyway.
How do you redact before anything is stored?
When content has to be captured, to debug a specific failure or feed a review queue, redact it before it is written, not afterwards. Once raw text enters a log pipeline it is copied into indexes, backups and exports that are hard to clean.
- Run redaction inside your service, before the exporter, replacing names, emails, phone numbers, account and card numbers, addresses and government ids with typed placeholders such as [EMAIL] or [ACCOUNT].
- Combine pattern rules for structured identifiers with an entity-recognition model for names and addresses, and test both against samples of your own traffic. Neither catches everything.
- Replace user and account ids with keyed hashes or tokens, and keep the key in a separate system with tightly restricted access.
- Keep personal data out of span names, metric labels and URLs. Those are copied everywhere and almost never redacted.
- Treat tool arguments, tool results and retrieved text as content. They often carry more personal data than the prompt does.
Be precise about what pseudonymisation achieves. Under the GDPR, pseudonymised data that you can link back to a person using information you hold separately is still personal data, so it still needs a lawful basis, a retention limit and access control. Anonymous data falls outside the regulation, but log data that carries stable ids is rarely anonymous.
Pseudonymisation reduces exposure. It does not take the data out of scope.
Retention windows and who can read logs
Different parts of the record deserve different lifetimes, and each should have a stated purpose written next to it.
- Metadata records and metrics: kept long enough for trend analysis and capacity planning, since they carry no content.
- Redacted content samples: a short window, with automatic deletion when it ends.
- Raw content, if you capture it at all: only behind a flag for a named incident, with a short expiry and a named owner.
- Records a regulation requires you to keep: a separate audit store with its own rules, because that is a different job from observability.
This maps directly onto two GDPR principles, data minimisation and storage limitation: keep only what a stated purpose needs, for as long as it needs it. If you cannot name the purpose of a field, stop logging it.
Access matters as much as retention. Most engineers need dashboards and traces without content. Limit content views to a small on-call or quality group, record who opened what, and keep the pseudonymisation key out of the observability tool entirely. If you use a hosted tracing vendor, check where the data is stored, whether it is used for anything else and how deletion works, and note the answers in your processing records.
Logs are also an attack surface. An injected instruction that makes the model echo a secret can land that secret in your traces, and text an attacker plants in a log line can mislead any LLM tool that later reads those logs. The OWASP Top 10 for LLM Applications treats sensitive information disclosure as its own risk, and our prompt injection testing checklist includes checks for exactly this kind of leak.
When observability is not enough
Observability logs exist to run and improve the system, so they should be lean and short-lived. Some products also need an audit trail: a durable, tamper-evident record of what the system did for each decision, kept for as long as a regulator expects. These are different stores with different rules, and merging them tends to make both worse. If your app touches regulated decisions, read what to record in an LLM audit trail for fintech alongside this post.
For a small internal tool with a handful of trusted users and no customer data, a simpler setup may be fine: metadata only, short retention, no content capture at all. The design above earns its effort once real customer data flows through the model.
Where to start
Search a recent slice of your current LLM logs for email addresses, phone numbers and account numbers. If you find any, that is the first fix: add redaction before the exporter and switch content capture off by default. Then make sure every call emits the metadata record above, so turning content off does not leave you blind.
Our MLOps and SI infrastructure team sets up GDPR-ready logging for LLM apps: per-call records, traces across retrieval and tools, redaction in the pipeline and retention that is enforced rather than promised. If you want a second opinion on what your logs hold today, Octalcode can go through them with you.