Logging is synchronous and needs no setup:
LogInfo("Hello, world!")
LogWarn("Request {0} took {1:.2f}ms",42,3.125)The default destination is stderr, threshold Info, format
{timestamp} [{level}] {message}. Levels, from least to most severe, are Trace,
Debug, Info, Warn, Error and Fatal. Fatal does not exit. All six functions accept
an optional leading Logger or LoggerHandle.
Numbered message fields use the same scalar format modifiers as f-strings;
{{/}} escape braces. Repeated fields reuse the value. Literal formats receive
compile-time checks; dynamic bad patterns produce logging diagnostics. STRING,
BOOLEAN, INTEGER, BYTE, UINT32, UINT64, DOUBLE, Error values (type name) and
LogFieldValue choices are supported. LogArgument(value) constructs a choice
for heterogeneous argument lists passed to checked WriteLog.
Nominal values with OPERATOR Display
, including resource records,
are borrowed and rendered after filtering; numeric formats reject for their text.
Deferred functions may return these values without a manual LogArgument wrapper.
minimum=10
deferred=() => minimum*2
SetLogFormat("{level} {message}")
LogDebug("filtered {0}",deferred)
SetLogLevel(LogLevel.Debug)
LogDebug("computed {0} twice: {0}",deferred)Ordinary expressions evaluate normally, even when filtered. A zero-argument
callable argument instead runs once, synchronously, after filtering. Its declared
errors remain the caller’s checked errors. Captures are independent readonly
snapshots established when constructing the callable; capture construction is
ordinary eager work. Reuse a constructed closure in a hot loop. A named function
or noncapturing lambda can borrow static callable data without allocation.
LogCallable(handler,label) supplies a function label instead of invoking it.
Configuration and handles
DefaultLogConfig() returns an independent initialized record with fields Level,
Format, Name, Path and Console. Console is LogConsole.None, StandardError or
StandardOutput. CreateLogger(config) creates a scoped resource;
CreateLogger(path) appends to that file with console output disabled.
Resources close automatically. Use Flush(logger) or Close(logger) where a
checked finishing result or early closure matters.
GetLogConfig, SetLogConfig, SetLogLevel and SetLogFormat accept a logger
owner, handle, or the default logger when omitted. Formats/configuration are
validated before replacement; an unsuccessful file replacement keeps the old
configuration. LogEnabled([logger,] level) also returns false for a service with
no destination.
Metadata fields are {timestamp}, {date}, {time} (UTC), {level}, {message},
{file}, {line}, {function} and {logger}. Source positions are inserted
automatically by severity calls; function names use normalized compiler spelling.
WriteLog(logger,level,pattern,values) has no automatic caller metadata.
DefaultLogger() and LogHandle(owner) return copyable access capabilities.
They refer to the service without owning its lifetime; ordinary data still has
independent values. SetDefaultLogger(TAKE owner) promotes its ownership to the
program. A program-owned handle may select an already promoted service. Replacing
the selection keeps earlier program-owned services alive for existing handles.
Program cleanup finishes children and ordinary locals, then closes these services.
Stale/closed handles are checked and cannot access a reused service slot.
Application destinations
AddLogSink([logger,] handler) registers a SUB taking LogRecord. Its fields are
Timestamp (Instant), Level, Message, File, Line, FunctionName, Logger and Formatted.
Handlers run in registration order. They may suspend and use ordinary file,
database or network APIs. There is no logging thread or asynchronous record queue.
Stateful destinations use CreateLogSink(context,handler[,finish]), then transfer
the sink to AddLogSink. Context is an independent owned argument; named
resources require TAKE. The handler takes readonly or BYREF context followed by
LogRecord; an optional finish SUB takes the same context/mode. A finish callback
runs on checked Flush/Close and automatic service closure; normal resource cleanup
then destroys the context. Finish is not a substitute for owned cleanup.
TYPE CountedOutput
Count AS INTEGER
END TYPE
config=DefaultLogConfig()
config.Console=LogConsole.None
SetLogConfig(config)
sink=CreateLogSink(CountedOutput {Count=0},
SUB(BYREF context AS CountedOutput,record AS LogRecord)
context.Count+=1
PRINT context.Count;": ";record.Message
END SUB)
AddLogSink(TAKE sink)
LogInfo("first")
LogInfo("second")AddLogSink([logger,] context,handler) is the direct form without a finisher.
ClearLogSinks([logger]) drops application sinks; the configured Path output is
separate. Mutable context access is exclusive throughout suspension. Another
task’s logger operation queues until the active operation finishes; cancellation
unregisters its wait and releases owned values normally. Same-service recursive
logging reports an error instead of deadlocking. Application cycles between
different services still require normal deadlock avoidance.
Severity calls report format/destination failures to stderr and continue other
destinations. Checked WriteLog returns LoggingError after attempting destinations;
configuration, Flush and Close also declare LoggingError. Deferred computation
errors propagate unchanged, including a deferred LoggingError. Cancellation
remains a distinct task outcome. Automatic cleanup reports finishing failures and
still destroys owned contexts. Allocation failure remains fatal.