Skip to content

Observability

PrivaCI emits a structured, machine-readable event stream so a run can be piped straight into log aggregation, audited after the fact, or watched live. Every significant lifecycle moment is one JSON object on its own line on stdout. stderr is reserved for unexpected, uncaught failures.

For the per-command flags see cli-reference.md; for the on-disk audit trail see state-schema.md.

The stdout event stream

Each line is a complete JSON object terminated by \n, with no ANSI color codes or other decoration. That means you can pipe stdout through jq:

privaci run --config mask-rules.yaml | jq -c 'select(.event == "table.end")'

Every event carries a common envelope:

Field Description
timestamp ISO-8601 UTC with microseconds (e.g. 2026-06-10T21:14:09.123456+00:00).
level debug, info, warning, or error.
event The event identifier (see the catalog below).
run_id The run UUID, present from run.start onward.

Event-specific fields are merged into the same object. Ordinary (non-lifecycle) log lines are rendered as {"event": "log", "message": "...", ...}.

Example

{"timestamp":"2026-06-10T21:14:09.000123+00:00","level":"info","event":"run.start","run_id":"018f...","engine_version":"0.1.0","config_hash":"...","salt_fingerprint":"...","source_db_hash":"...","commercial_layer_present":false}
{"timestamp":"2026-06-10T21:14:09.450000+00:00","level":"info","event":"table.start","schema_name":"public","table_name":"users","estimated_rows":1000}
{"timestamp":"2026-06-10T21:14:11.500000+00:00","level":"info","event":"table.progress","schema_name":"public","table_name":"users","rows_processed":5000,"rows_per_sec":2380.95,"percent_complete":100.0}
{"timestamp":"2026-06-10T21:14:11.900000+00:00","level":"info","event":"table.end","schema_name":"public","table_name":"users","rows_processed":1000,"duration_ms":1450.2,"status":"done"}
{"timestamp":"2026-06-10T21:14:12.000000+00:00","level":"info","event":"run.end","run_id":"018f...","status":"succeeded","duration_ms":3000.0,"tables_processed":1,"rows_processed":1000,"errors":0}

Event catalog

Event Level Key fields
run.start info engine_version, config_hash, salt_fingerprint, source_db_hash, commercial_layer_present
preflight.ok / preflight.fail info / error checks (array of {name, status, detail})
schema.cloned info tables_created, schemas_created
table.start info schema_name, table_name, estimated_rows
table.progress info rows_processed, rows_per_sec, percent_complete
table.end info rows_processed, duration_ms, status
column.masked info schema_name, table_name, column_name, action, rows_affected
cycle_break info tables, deferred_constraint
polymorphic_fk_warning warning table_id, message
implied_fk_warning warning source_column_path, message
skipped_object info schema_name, object_name, kind
new_table info schema_name, table_name, reason
binary_fallback warning schema_name, table_name, unsupported_types
warning warning message
error error message, exit_code
run.end info status, duration_ms, tables_processed, rows_processed, errors

table.progress throttling

For long-running tables, table.progress is emitted at most once every two seconds per table so the stream stays readable on multi-million-row loads.

PII redaction

No event payload ever contains a raw column value. Any value-bearing field is collapsed to a non-reversible marker: *** followed by at most the first eight characters of the value (e.g. john@acme.com***john@acm). Empty or null values render as ***. A captured stdout file is therefore safe to share for debugging or audit.

Log level

Control verbosity with --log-level or the PRIVACI_LOG_LEVEL environment variable (default info):

privaci run --log-level debug --config mask-rules.yaml
PRIVACI_LOG_LEVEL=warning privaci run --config mask-rules.yaml

At debug, additional trace events appear; at warning or error, only the matching severities are emitted.

Optional Prometheus metrics

Metrics are off by default — no network port is opened unless you ask for one. Pass --prometheus-port to serve the Prometheus exposition format on /metrics:

privaci run --prometheus-port 9100 --config mask-rules.yaml
# then: curl http://localhost:9100/metrics

Exposed series:

Metric Type Labels
privaci_run_rows_processed_total counter table
privaci_run_duration_seconds histogram
privaci_run_errors_total counter type
privaci_table_progress_ratio gauge table

The endpoint requires the optional prometheus-client package:

pip install prometheus-client

If the package is missing, --prometheus-port exits with a configuration error (exit code 3) explaining how to install it.