Go SDK
The Go SDK (scailabs.ai/scailog-go) is the Go emit side of ScaiLog:
scailog.New plus scailog.PI give you GDPR-native structured logging that
ships entries over a Unix socket to the local scailog-agent. It is stdlib-only
(zero external dependencies) and wire-compatible with the Python, TypeScript, and
.NET SDKs — a Go-emitted entry is encrypted at ingest and decrypts correctly
through DSAR.
Install#
The Go module is not on a public proxy yet, so go get cannot resolve it.
It is distributed as a source archive on
scailabs.ai/downloads and used with a
replace directive — no proxy, no network, two lines:
1 | |
1 2 3 | |
Then import "scailabs.ai/scailog-go" and go build — offline. When the module
is later served from a ScaiLabs proxy, drop the replace line and it resolves
normally, no code change. USAGE.md inside the archive repeats these steps.
Emit a log entry#
Construct a logger with scailog.New, then call a level method. Each level takes
a context.Context, a call_site_id, a constant message, a field map
(scailog.F), and any number of per-call options. Wrap personal-information
values in scailog.PI:
1 2 3 4 5 6 7 8 | |
Multi-tenant services#
Tenant is a default, not a binding. A process serving many tenants carries it
on the context that every log call already takes:
1 2 3 4 5 6 | |
Precedence: WithTenantOpt, then the context, then Options.Tenant. With none
of them the entry is rejected with a TENANT_REQUIRED diagnostic and an empty
id returned, rather than being labelled with an invented tenant.
defer log.Close()
eventID := log.Info(context.Background(), "SCAI-CHECKOUT-PAID-001", "order paid", scailog.F{ "method": "card", "email": scailog.PI("jan@example.nl", "direct_identifier"), "ip": scailog.PI("203.0.113.7", "online_identifier"), }, scailog.Subject("usr_8842"), // required whenever a field is PI-marked )
log.Flush(3 * time.Second) // best-effort drain before exit
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | |
Contract rules#
The SDK enforces the shared ScaiLog contract before an entry leaves the process.
On a violation the entry is rejected: the method returns "" and a PII-free
diagnostic (the error code, service, and offending detail — never a field value)
is written to stderr, so the offending PII is never emitted.
site(call_site_id) must match^[A-Z0-9]+(-[A-Z0-9]+){2,}$; theAUTO-/SCAILOG-prefixes are reserved. An empty site auto-derives anAUTO-<hash>id (wire-identical to the other SDKs) rather than being dropped.scailog.PI(value, type)marks a field as personal information; markers attach to fields only, never the message.- A PI-marked field requires a subject (
SUBJECT_REQUIRED). - Reserved field names (
event_id,msg, …) are rejected the same way.
Context propagation#
Subject, trace id, and request id ride on the context.Context and are used as
defaults when a call doesn't pass them explicitly:
1 2 3 | |
The helpers are scailog.WithSubject, scailog.WithTraceID, and
scailog.WithRequestID. Explicit per-call options (scailog.Subject(...),
scailog.TraceID(...), scailog.RequestID(...)) override the context.
Configuration#
scailog.Options{Tenant, Service, Environment, SocketPath, QueueSize} configures
the logger. Environment defaults to prod, SocketPath defaults to
$SCAILOG_SOCKET or /run/scailog/agent.sock, and QueueSize defaults to
10,000.
Entries are queued and sent by a background sender as NDJSON over the socket. If
the queue is full or the socket is unavailable, the entry is written to stderr in
the same NDJSON shape (with PI values redacted) rather than being silently
dropped. Call log.Flush(timeout) before a short-lived process exits, and
log.Close() to shut down the background sender.