PII Redaction
Automatically redact personally identifiable information from traces before storage.
Neatlogs provides two layers of PII redaction: client-side masking that runs in your process before data leaves, and server-side redaction powered by a Presidio-based microservice that runs on the backend before data is stored.
Client-Side Masking
Use the mask parameter in neatlogs.init() to redact sensitive data before it's exported from your application:
def redact(span):
attrs = span.get("attributes", {})
for key in list(attrs):
if "email" in key or "phone" in key:
attrs[key] = "***"
return span
neatlogs.init(mask=redact)The mask function receives the full span dict and must return the modified dict. It runs once per span before export.
The mask replaces attribute values with whatever you wish to return, so it is not limited to redaction — returning a transformed string (for example, a translation of the input/output) is equally valid.
Per-Span Masking
Apply a mask to specific spans only using @span(mask=fn) or with trace(..., mask=fn). Per-span masks take precedence over the global mask:
@neatlogs.span(kind="TOOL", mask=redact_pii)
def lookup_customer(email: str):
...A per-span mask applies only to that span, not to its peer/child spans. To mask every span in a trace with the same masking function, use the global neatlogs.init(mask=fn).
Server-Side Redaction
Server-side redaction runs automatically on the Neatlogs backend using a Presidio-based microservice. It detects and redacts PII entities (names, emails, phone numbers, addresses, etc.) from span payloads before they are stored.
Configuration
Server-side redaction is configured at the team level in the Neatlogs dashboard under Settings > PII Redaction. You can configure:
- Enabled/Disabled: toggle redaction on or off for all projects in the team
- Entity types: which PII types to detect (e.g.,
PERSON,EMAIL_ADDRESS,PHONE_NUMBER) - Operator: how to redact detected entities:
replace: replace with the entity type label (e.g.,John→<PERSON>)mask: mask characters (e.g.,John→****)redact: remove the entity entirely
- Span types: which span kinds to apply redaction to (e.g., only
LLMandTOOLspans)
SDK Overrides
Override the team-level settings per project using neatlogs.init():
# Enable redaction regardless of the dashboard setting
neatlogs.init(pii_enabled=True)
# Only redact PII in LLM and TOOL spans
neatlogs.init(pii_span_types=["LLM", "TOOL"])
# Disable redaction for a dev/test environment
neatlogs.init(pii_enabled=False)When pii_enabled or pii_span_types is not set (default), the team-level dashboard configuration is used.
What Gets Redacted
Server-side redaction covers all user-visible span fields:
| Category | Fields |
|---|---|
| Core I/O | input_value, output_value |
| LLM content | completion_text, intermediate_steps |
| Prompt templates | prompt_template, prompt_template_variables, user_prompt_template, user_prompt_template_variables |
| Tool fields | tool_input, tool_output, tool_parameters, tool_call_function_arguments |
| Retrieval | retrieval_query, retrieval_documents |
| Reranker | reranker_query, reranker_input_documents, reranker_output_documents |
| MCP | mcp_request_argument, mcp_response_value |
Graceful Degradation
If the PII redaction microservice is unavailable or times out, the original data is stored unchanged. Ingestion is never blocked. A warning is logged server-side when this occurs.
When to Use Which
| Approach | Use when |
|---|---|
Client-side mask | You need to redact data before it leaves your infrastructure |
| Server-side redaction | You want automatic, team-wide PII detection without modifying application code |
| Both | Defense in depth: mask known fields client-side, let the server catch anything else |
