Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

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:

bash
1
pip install scailog

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():

python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
from scailog import log, pi

event_id = log.info(
    "SCAI-CHECKOUT-PAID-001",
    "order paid",
    subject="usr_8842",  # required because a pi() field is present
    fields={
        "method": "card",
        "email": pi("jan@example.nl", "direct_identifier"),
        "ip": pi("203.0.113.7", "online_identifier"),
    },
)

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:

python
1
2
3
4
5
6
7
8
9
from scailog import pi
from scailog.sdk import Logger

log = Logger(tenant="acme", service="checkout", environment="prod")
log.info("SCAI-CHECKOUT-PAID-001", "order paid",
         subject="usr_8842",
         fields={"email": pi("jan@example.nl", "direct_identifier")})
log.flush()   # best-effort drain before exit
log.close()

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):

python
1
2
3
4
5
6
log.trace("SCAI-CACHE-MISS-001", "cache miss")
log.debug("SCAI-CACHE-EVICT-001", "evicted key")
log.info("SCAI-USER-LOGIN-001", "user logged in", subject="usr_8842")
log.warn("SCAI-RATE-NEAR-001", "approaching rate limit")
log.error("SCAI-DB-TXN-FAIL-001", "transaction rolled back")
log.fatal("SCAI-BOOT-CONFIG-001", "missing required config")

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:

python
1
2
log.info("bad_site", "nope")
# ValueError: invalid call_site_id: 'bad_site' (pattern ^[A-Z0-9]+(-[A-Z0-9]+){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:

python
1
2
3
4
5
6
from scailog import context, log, pi

with context(request_id=req.id, trace_id=req.trace, subject=user.id):
    # picks up subject + request_id + trace_id from the ambient context
    log.info("SCAI-API-HANDLED-001", "handled",
             fields={"email": pi(user.email, "direct_identifier")})

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.

Updated 2026-07-13 09:41:43 View source (.md) rev 2