std/log
Structured entries and call-time sink services. See std-2.md §6/§7. Level(..), Entry(..) export constructors; Sink(E,N) hides its constructor. Level { Debug Info Warning Failure } Entry { Entry(level: Level, message: String, fields: List((String,String)), at: Option(time.Instant)) } Sink(E,N) { Sink((Level)->Bool, (Entry)->Nil ? E ! N, (N)->Nil) } Interface: make(E,N): (accept: (Level)->Bool, send: (Entry)->Nil ? E ! N)->Sink(E,N) silent(N): ()->Sink(Never,N) filter(E,N): (sink: Sink(E,N), accept: (Level)->Bool)->Sink(E,N) minimum(E,N): (sink: Sink(E,N), level: Level)->Sink(E,N) enabled(E,N): (level: Level)->Bool ! Sink(E,N) write(E,N): (entry: Entry)->Nil ? E ! N | Sink(E,N) lazy(E,F,N): (level: Level, message: ()->String ? F ! N)->Nil ? E | F ! N | Sink(E,N) format: (entry: Entry)->String stderr/stdout: () -> Sink for the corresponding stream file: (path: String) -> Sink appending formatted lines timestamped: (entry: Entry) -> Entry, reading the Unix clock
Level order is Debug < Info < Warning < Failure. Entry strings are arbitrary; fields preserve order and duplicate keys. Timestamps are explicit Unix instants and are never added or replaced by delivery. No clock is read.
make stores callbacks without calling them. enabled checks accept once. write checks the current sink once, then sends the original entry once only if accepted. False returns Nil. Delivery preserves the original error, with no retry, buffering, deduplication, task spawn, or failure suppression. lazy checks once, then calls message once and sends an entry with no fields or timestamp. Rejection calls neither callback; a message error skips send. Eager write arguments still evaluate before filtering.
filter checks the new predicate before the old predicate; send is unchanged. minimum accepts levels at or above its threshold. silent always rejects. These factories are pure. enabled followed by write performs two checks. Sink’s E is covariant and its stored N invariant. The unused (N)->Nil marker is never called. Replacements need equal service arguments; N cannot contain the same Sink(E,N) recursively. Capture a concrete downstream sink instead.
format returns lowercase level, space, quoted message, optional “ @” and signed decimal Unix nanoseconds, then ordered quoted key=value pairs. It adds no final LF. Quotes/backslashes and LF/CR/tab are escaped; other C0/C1 controls and U+2028/U+2029 use minimal lowercase \u{h}. Other scalars remain. Formatting is deterministic and linear in text length, plus integer printing.
Service laws compare delivery observations, not closure equality: accepted entries arrive in sequential call order; filter(s,always_true) preserves delivery; minimum(minimum(s,a),b) uses threshold max(a,b). No callback termination guarantee is added. Generic client use awaits the §7 compiler IO adapters are explicit and use the same sink service.
Level
Level = Debug | Info | Warning | Failure Entry
Entry = ( Sink
Sink(E, N) = ((Level) -> Bool, (Entry) <- (Fail(E), ..N) -> Nil, (N) -> Nil) make
make = <E, N>( silent
silent = <N>() -> Sink(Never, N) { filter
filter = <E, N>(sink : Sink(E, N), accept : (Level) -> Bool) -> Sink(E, N) { minimum
minimum = <E, N>(sink : Sink(E, N), level : Level) -> Sink(E, N) { enabled
enabled = <E, N>(level : Level) <- (sink : Sink(E, N)) -> Bool { write
write = <E, N>(entry : Entry) <- (sink : Sink(E, N)) { lazy
lazy = <E, N>(level : Level, message : () <- N -> String) <- ( format
format = (entry : Entry) -> String { stderr
A sink that writes each formatted entry to standard error.
stderr = () { stdout
A sink that writes each formatted entry to standard output.
stdout = () { file
A sink that appends each formatted entry to a file.
file = (path : String) { timestamped
Adds the current Unix time to an entry.
timestamped = (entry : Entry) { Entry(..entry, at = Some(time.now())) }