Plattform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Modelle Tools & Services
Lösungen
Organisationen Entwickler Internet Service Provider Managed Service Provider AI-in-a-Box
Ressourcen
Support Documentation Blog Downloads
Unternehmen
Über uns Forschung Karriere Investieren Kontakt
Anmelden

Go SDK

The Go SDK (github.com/scailabs/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#

bash
1
go get github.com/scailabs/scailog-go

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:

go
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import (
    "context"
    "time"

    scailog "github.com/scailabs/scailog-go"
)

log := scailog.New(scailog.Options{Tenant: "acme", Service: "checkout"})
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

Each level method returns the entry's ULID eventID (or "" if the entry was rejected by a contract check — see below). Plain fields (method) are stored in cleartext and must stay PI-free; scailog.PI fields are encrypted at ingest and never enter the plaintext full-text index. When a call has no fields, pass nil.

Log levels#

Six methods with the same signature Level(ctx, site, msg string, fields F, opts ...EmitOption) string:

go
1
2
3
4
5
6
log.Trace(ctx, "SCAI-CACHE-MISS-001", "cache miss", nil)
log.Debug(ctx, "SCAI-CACHE-EVICT-001", "evicted key", nil)
log.Info(ctx, "SCAI-USER-LOGIN-001", "user logged in", nil, scailog.Subject("usr_8842"))
log.Warn(ctx, "SCAI-RATE-NEAR-001", "approaching rate limit", nil)
log.Error(ctx, "SCAI-DB-TXN-FAIL-001", "transaction rolled back", nil)
log.Fatal(ctx, "SCAI-BOOT-CONFIG-001", "missing required config", nil)

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,}$; the AUTO- / SCAILOG- prefixes are reserved. An empty site auto-derives an AUTO-<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:

go
1
2
3
ctx = scailog.WithRequestID(scailog.WithSubject(ctx, user.ID), req.ID)
// picks up subject + request id from the context
log.Info(ctx, "SCAI-API-HANDLED-001", "handled", nil)

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.

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