.NET SDK
The .NET SDK (ScaiLabs.ScaiLog, net8.0) is the .NET emit side of ScaiLog: a
Logger plus Pi.Mark give you GDPR-native structured logging that ships
entries over a Unix socket to the local scailog-agent. It uses only the base
class library (zero external dependencies) and is wire-compatible with the
Python, TypeScript, and Go SDKs — a .NET-emitted entry is encrypted at ingest and
decrypts correctly through DSAR.
Install#
1 | |
The package targets net8.0.
Emit a log entry#
Construct a Logger with your tenant and service, then call a level method with
a call_site_id, a constant message, an optional subject, and a fields
dictionary. Wrap any personal-information value in Pi.Mark:
1 2 3 4 5 6 7 8 9 10 11 12 | |
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; Pi.Mark fields are encrypted at ingest and
never enter the plaintext full-text index. Keep variable data in fields, not in
the message.
Log levels#
Six methods with the same signature
Level(string site, string message, string? subject = null, IDictionary<string, object?>? fields = null, string? traceId = null, string? requestId = null):
1 2 3 4 5 6 | |
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.Pi.Mark(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 an AsyncLocal and are used as
defaults when a call doesn't pass them explicitly. They flow across await
boundaries; push them with LogContext.Push, and dispose the scope (via using)
to restore the previous context:
1 2 3 4 5 | |
Explicit arguments on a call override the ambient context; nested pushes inherit unspecified values from the enclosing scope.
Configuration#
LoggerOptions { 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() before a short-lived process exits; disposing the
Logger (the using above) shuts down the background sender.