// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A target and the fields bound to it: what a module or a request holds and
/// writes through.
///
/// A logger is a value. It is passed in, it can be copied, and a library that
/// was handed none writes nothing — there is no default one to inherit.
pub struct Logger {
hub : Hub
target : String
fields : Array[(String, Value)]
}
///|
/// A logger writing to `hub` under `target`.
pub fn Logger::new(hub : Hub, target : String) -> Logger {
{ hub, target, fields: [], }
}
///|
/// The target it writes under.
pub fn Logger::target(self : Logger) -> String {
self.target
}
///|
/// The same logger with `key` bound, so every line it writes carries it.
pub fn Logger::bound(self : Logger, key : String, value : Value) -> Logger {
let fields = self.fields.copy()
let mut replaced = false
for i, (name, _) in fields {
if name == key {
fields[i] = (key, value)
replaced = true
}
}
if !replaced {
fields.push((key, value))
}
{ hub: self.hub, target: self.target, fields, }
}
///|
/// The same logger under another target, which is what a submodule is.
pub fn Logger::under(self : Logger, target : String) -> Logger {
{ hub: self.hub, target, fields: self.fields, }
}
///|
/// Whether a message at `level` would be written.
///
/// Worth asking before assembling a message that costs something: a logger
/// cannot skip work its caller has already done.
pub fn Logger::enabled(self : Logger, level : Level) -> Bool {
self.hub.enabled(level)
}
///|
/// Writes a message, with fields if there are any.
pub fn Logger::log(
self : Logger,
level : Level,
msg : String,
fields~ : Array[(String, Value)] = [],
) -> Unit {
if !self.enabled(level) {
return
}
let all = self.fields.copy()
for pair in fields {
all.push(pair)
}
self.hub.write(level, self.target, msg, fields=all)
}
///|
/// Writes at `trace`.
pub fn Logger::trace(
self : Logger,
msg : String,
fields~ : Array[(String, Value)] = [],
) -> Unit {
self.log(trace, msg, fields~)
}
///|
/// Writes at `debug`.
pub fn Logger::debug(
self : Logger,
msg : String,
fields~ : Array[(String, Value)] = [],
) -> Unit {
self.log(debug, msg, fields~)
}
///|
/// Writes at `info`.
pub fn Logger::info(
self : Logger,
msg : String,
fields~ : Array[(String, Value)] = [],
) -> Unit {
self.log(info, msg, fields~)
}
///|
/// Writes at `warn`.
pub fn Logger::warn(
self : Logger,
msg : String,
fields~ : Array[(String, Value)] = [],
) -> Unit {
self.log(warn, msg, fields~)
}
///|
/// Writes at `error`.
pub fn Logger::error(
self : Logger,
msg : String,
fields~ : Array[(String, Value)] = [],
) -> Unit {
self.log(error, msg, fields~)
}
///|
/// Writes at `fatal`. It does not abort: what to do after the last line is the
/// caller's to decide, and a library that ends the process takes that decision
/// away.
pub fn Logger::fatal(
self : Logger,
msg : String,
fields~ : Array[(String, Value)] = [],
) -> Unit {
self.log(fatal, msg, fields~)
}