Documentation/Reference

Logging

On this page

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.

From the BASIL documentation. View source ↗

Find your way around BASIL

Type to search the manual.