Python SDK
The Python SDK is the reference ScaiLog SDK: from scailog import log, pi, and
you have a structured, GDPR-native logger. It emits entries over a Unix socket to
the local scailog-agent and marks personal information at the field level. This
page covers install, a full emit, the log levels, call_site_id rules, context
propagation, and what happens when the agent socket is unavailable.
Install#
The base install is the SDK only, and it is pure stdlib — zero third-party dependencies:
1 | |
Importing the SDK opens no socket and loads no heavy dependency; the process
logger is built lazily on first use. The running components (scailog-server,
scailog-agent, the CLI, the audit job) each pull their own extra —
pip install 'scailog[server]' and so on — but a service that only emits logs
needs none of them.
Emit a log entry#
log is a lazily-initialized process logger configured from the environment.
Call a level method with a call_site_id, a constant message, and structured
fields; wrap any personal-information value in pi():
1 2 3 4 5 6 7 8 9 10 11 12 | |
The call returns the entry's ULID event_id (a lexicographically sortable
string). Plain fields (method above) are stored in cleartext and must stay
PI-free; pi() fields are routed to the encrypted pi map and never enter the
plaintext full-text index. The message is a constant description — put variable
data in fields, never in the message.
Configuring the process logger#
log reads these environment variables (with defaults):
| Variable | Default | Meaning |
|---|---|---|
SCAILOG_TENANT |
default |
Tenant id |
SCAILOG_SERVICE |
unknown |
Service name |
SCAILOG_ENV |
prod |
Environment |
SCAILOG_SOCKET |
/run/scailog/agent.sock |
Agent Unix socket path |
When you need more than one configuration in a process, construct a Logger
explicitly:
1 2 3 4 5 6 7 8 9 | |
Log levels#
Six methods, in increasing severity, all with the same signature
level(site, message, *, subject=None, fields=None, trace_id=None, request_id=None):
1 2 3 4 5 6 | |
call_site_id rules#
The first argument is a developer-assigned call-site id and it is validated
before the entry is emitted. It must match ^[A-Z0-9]+(-[A-Z0-9]+){2,}$ — at
least three uppercase-alphanumeric segments separated by hyphens. An id that does
not match raises ValueError:
1 2 | |
The AUTO- and SCAILOG- prefixes are reserved: the SDK accepts them at runtime
(they are used for auto-derived and internal sites) but you should not assign
them yourself, and scailog-ci rejects them at build time. If you pass an empty
site, the SDK derives an AUTO-<hash> fallback rather than dropping the entry.
Two more contract checks raise ValueError:
- A reserved field name (
event_id,msg,subject, …) →reserved field name: '<name>'. - A
pi()field with no subject →SUBJECT_REQUIRED: entry has PI markers but no subject=.
Context propagation#
Request/trace ids and the data-subject assertion propagate through
contextvars, so handlers don't have to thread them through every call. Bind
them for the duration of a block with context(); the logger uses them as
defaults when you don't pass subject, trace_id, or request_id explicitly:
1 2 3 4 5 6 | |
An explicit keyword argument on the call always overrides the ambient context.
Stderr fallback when the agent is unavailable#
Entries are queued in process (bounded queue, default 10,000) and sent by a background thread as NDJSON over the Unix socket. ScaiLog never silently drops a log: if the queue overflows or the socket can't be reached, the entry is written to stderr in the same NDJSON shape instead.
Because stderr typically lands in container/journald logs, PI field values
are stripped before the fallback write — the field name and its pi_type are
kept (so the drop is auditable) but the value is replaced with
{"type": "...", "redacted": true}. This is deliberate: writing plaintext PI to
stderr would be the exact leak ScaiLog exists to prevent. Plain fields and the
message are PI-free by contract, so they pass through unchanged.