# Traces, logs and sensitive data

> What the node filters before storing — secrets, headers, query strings, IPs, SQL literals, hashed user ids — and what not to turn on in your instrumentation.

Spans and logs can carry more than you meant to send: a token in a URL, a header copied by an instrumentation, an email in a log line. Belvador filters on your node before anything is stored, but the safest data is data your app never sends.

## What your node filters before storing

The node runs the same scrubber as for errors (see [Privacy](https://belvador.dev/en/docs/privacy#layer-2-your-node-before-storing)) over every resource, span, event, link and log record:

- **Secrets by name** — attributes such as `password`, `token`, `api_key`, `authorization` or `secret`, at any depth, become `[Filtered]`.
- **HTTP headers** — header attributes (`http.request.header.*`, `http.response.header.*`) are dropped unless they are harmless: `content-type`, `content-length`, `content-encoding`, `accept`, `accept-encoding`, `accept-language`, `user-agent`, `cache-control`, `x-request-id`, `traceparent`.
- **URLs** — user names and passwords are removed from `url.full` and similar attributes, and query parameters with sensitive names are filtered; `url.query` is filtered the same way.
- **IP addresses** — client and peer addresses are masked (last part of IPv4 zeroed, IPv6 cut to its first 48 bits); public IPv4 addresses inside log bodies and other text are masked the same way, while private and loopback addresses stay readable.
- **User ids** — `user.id` and `enduser.id` are replaced by a salted hash, so you can still count and group by user.
- **SQL** — string and number literals in `db.query.text` (and the older `db.statement`) become `?`.
- **Size** — over-long values are cut and end with `…[truncated]`; the [limits](https://belvador.dev/en/docs/reference/otlp#limits) say where.

These follow the project's **Data protection** settings: if an owner turns a group off there, that kind of value is stored as sent.

## What not to turn on

OpenTelemetry instrumentations capture little by default. Leave these off unless you have a reason and have checked the data:

- request and response **headers** (`OTEL_INSTRUMENTATION_HTTP_CAPTURE_HEADERS_*`, `OTEL_INSTRUMENTATION_HTTP_*_HEADERS` and similar),
- request and response **bodies**,
- database **parameter values**,
- **GenAI prompt and completion content** (`OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT` and similar),
- the Node.js **`process`** resource detector, which copies the full command line into every span — the [Node.js recipe](https://belvador.dev/en/docs/otel/node#what-the-variables-above-set) leaves it out.

## Log lines

Log bodies are free text. The node filters secret-looking values in structured attributes and known token patterns, but it cannot know that a sentence contains a customer's address. Log identifiers, not personal data, and prefer structured fields (`logger.info({ orderId }, "paid")`) so filtering rules can see names.

## Where it lives

Spans and log records are stored only on your node and kept for the retention you set (see [Sampling and cost](https://belvador.dev/en/docs/otel/sampling-and-cost)). Belvador's edge handles each request in transit and, while your node is offline, keeps it encrypted to your node's key; Belvador's database receives only metadata such as service names, SDK names and counts.
