Skip to content

Data and privacy

polars-telemetry records your queries as written, literals included, because knowing which filter was slow is usually the point. This page lists where that content ends up and how to limit it.

What can carry your data

Data Example OpenTelemetry JSONL Console
Literals in predicates col("email") == "someone@example.com" span attributes plans —
Scan paths s3://bucket/customers.parquet span attributes plans —
Column names and keys col("customer_id") span attributes plans —
Source paths /srv/app/reports.py code.* attributes call_site file name
Labels nightly/revenue_by_region span attribute label header
Error messages polars' message, which can quote values span status failed header

Metrics carry none of this. Every metric dimension comes from a bounded set: node kinds, engine, direction and the plan fingerprint, which is a hash of the plan's shape without literals. The span attributes that can carry query content are listed in Spans and metrics.

Limiting it

Config(redaction=...) masks a query before any exporter receives it, including one you wrote. Redaction() masks literal values; each kind can be switched on or off:

import polars_telemetry
from polars_telemetry import Config, Redaction

polars_telemetry.install(Config(redaction=Redaction()))
Field Default Masks Becomes
strings on quoted text, except column and alias names "<str>"
numbers on numbers, including 1.0000e-9 <num>
temporal on dates, datetimes, times, durations <date>, <datetime>, <time>, <duration>
paths off files scanned or written <path>
call_site off the file, line and function that ran the query dropped
labels off labels set with label() dropped
custom none your own rule, applied to every expression and error message after the others whatever it returns

Masking keeps the structure and column names, so you can still see which filter was slow:

col("email") == "someone@example.com"   ->   col("email") == "<str>"
col("amount") > 60.0                    ->   col("amount") > <num>
col("placed") >= 2024-01-01             ->   col("placed") >= <date>

It works on polars' text form of expressions, so treat it as a precaution, not a compliance guarantee. include_plan stays off by default, and keeps the whole plan off spans. A masked profile says so in its redacted field, and the viewer shows it next to the query.

One setting per exporter

Wrap an exporter in redacted() to give it its own setting in place of the config's. A shared backend can get a masked copy while a file on the same machine keeps everything:

import polars_telemetry
from polars_telemetry import Config, Redaction, redacted
from polars_telemetry.export.file import FileExporter
from polars_telemetry.export.otel import OTelExporter

config = Config(redaction=Redaction())
polars_telemetry.install(
    config,
    exporter=[
        redacted(OTelExporter(config), Redaction(paths=True, call_site=True)),
        redacted(FileExporter("profiles/full.jsonl"), None),  # everything
    ],
)

Exporters left unwrapped follow config.redaction.

Where it goes

  • OpenTelemetry: wherever your SDK sends spans. Your tracing backend's access rules apply.
  • JSONL: a file on the machine that ran the query. The viewer reads it in the browser and uploads nothing.
  • Console: your terminal or wherever standard error is collected.
  • Viewer links: a shared link holds the profiles themselves, readable by anyone it reaches.