birch/logger
Logger type and operations.
A Logger is a named logging context with an associated level, handlers, and persistent metadata.
Types
A function that provides timestamps. Used for testing with deterministic timestamps.
pub type TimeProvider =
fn() -> String
Values
pub fn debug(
logger: Logger,
message: String,
metadata: List(#(String, String)),
) -> Nil
Log a debug message.
pub fn debug_lazy(
logger: Logger,
message_fn: fn() -> String,
metadata: List(#(String, String)),
) -> Nil
Log a debug message with lazy evaluation.
pub fn error(
logger: Logger,
message: String,
metadata: List(#(String, String)),
) -> Nil
Log an error message.
pub fn error_lazy(
logger: Logger,
message_fn: fn() -> String,
metadata: List(#(String, String)),
) -> Nil
Log an error message with lazy evaluation.
pub fn error_result(
logger: Logger,
message: String,
result: Result(a, e),
metadata: List(#(String, String)),
) -> Nil
Log an error message with an associated Result.
If the result is an Error, the error value is automatically included
in the metadata under the “error” key using string.inspect.
Example
case database.connect() {
Ok(conn) -> use_connection(conn)
Error(_) as result -> {
logger |> log.error_result("Database connection failed", result, [])
}
}
pub fn fatal(
logger: Logger,
message: String,
metadata: List(#(String, String)),
) -> Nil
Log a fatal message.
pub fn fatal_lazy(
logger: Logger,
message_fn: fn() -> String,
metadata: List(#(String, String)),
) -> Nil
Log a fatal message with lazy evaluation.
pub fn fatal_result(
logger: Logger,
message: String,
result: Result(a, e),
metadata: List(#(String, String)),
) -> Nil
Log a fatal message with an associated Result.
If the result is an Error, the error value is automatically included
in the metadata under the “error” key using string.inspect.
Example
case critical_init() {
Ok(state) -> run(state)
Error(_) as result -> {
logger |> log.fatal_result("Cannot start application", result, [])
panic as "Critical initialization failed"
}
}
pub fn get_context(logger: Logger) -> List(#(String, String))
Get the context metadata of a logger.
pub fn get_handlers(logger: Logger) -> List(handler.Handler)
Get the handlers attached to a logger.
pub fn info(
logger: Logger,
message: String,
metadata: List(#(String, String)),
) -> Nil
Log an info message.
pub fn info_lazy(
logger: Logger,
message_fn: fn() -> String,
metadata: List(#(String, String)),
) -> Nil
Log an info message with lazy evaluation.
pub fn is_caller_id_capture_enabled(logger: Logger) -> Bool
Check if caller ID capture is enabled for this logger.
pub fn log(
logger: Logger,
log_level: level.Level,
message: String,
metadata: List(#(String, String)),
) -> Nil
Log a message at the specified level.
Metadata is merged with the following priority (first wins):
- Call metadata (passed to this function)
- Scope context (from with_scope)
- Logger context (from with_context)
pub fn log_lazy(
logger: Logger,
log_level: level.Level,
message_fn: fn() -> String,
metadata: List(#(String, String)),
) -> Nil
Log a message using lazy evaluation. The message function is only called if the level is enabled.
Metadata is merged with the following priority (first wins):
- Call metadata (passed to this function)
- Scope context (from with_scope)
- Logger context (from with_context)
pub fn new(name: String) -> Logger
Create a new logger with the given name. Uses default level (Info) and console handler.
pub fn should_log(logger: Logger, log_level: level.Level) -> Bool
Check if a log level should be logged by this logger.
pub fn silent(name: String) -> Logger
Create a logger with no handlers (silent by default). Useful for library loggers that consumers can configure.
pub fn trace(
logger: Logger,
message: String,
metadata: List(#(String, String)),
) -> Nil
Log a trace message.
pub fn trace_lazy(
logger: Logger,
message_fn: fn() -> String,
metadata: List(#(String, String)),
) -> Nil
Log a trace message with lazy evaluation.
pub fn warn(
logger: Logger,
message: String,
metadata: List(#(String, String)),
) -> Nil
Log a warning message.
pub fn warn_lazy(
logger: Logger,
message_fn: fn() -> String,
metadata: List(#(String, String)),
) -> Nil
Log a warning message with lazy evaluation.
pub fn with_caller_id_capture(logger: Logger) -> Logger
Enable caller ID capture for this logger.
When enabled, log records will include the process/thread ID of the caller. This is useful for debugging concurrent applications.
- On Erlang: Captures the PID (e.g., “<0.123.0>”)
- On JavaScript: Captures “main”, “pid-N”, or “worker-N”
Note: This has a small performance cost (~1μs per log call) due to the FFI call to get the process/thread ID.
Example
let logger =
logger.new("myapp")
|> logger.with_caller_id_capture()
pub fn with_context(
logger: Logger,
context: List(#(String, String)),
) -> Logger
Add persistent context metadata to a logger. This metadata will be included in all log records from this logger.
pub fn with_handler(
logger: Logger,
handler: handler.Handler,
) -> Logger
Add a handler to a logger.
pub fn with_handlers(
logger: Logger,
handlers: List(handler.Handler),
) -> Logger
Replace all handlers on a logger.
pub fn with_level(
logger: Logger,
min_level: level.Level,
) -> Logger
Set the minimum level for a logger.
pub fn with_time_provider(
logger: Logger,
provider: fn() -> String,
) -> Logger
Set a custom time provider for a logger.
This is primarily useful for testing, allowing deterministic timestamps in test output.
Example
// For testing - fixed timestamp
let test_logger =
logger.new("test")
|> logger.with_time_provider(fn() { "2024-01-01T00:00:00.000Z" })
// For testing - incrementing counter
let counter = atomic.new(0)
let test_logger =
logger.new("test")
|> logger.with_time_provider(fn() {
let n = atomic.add(counter, 1)
"T" <> int.to_string(n)
})
pub fn without_caller_id_capture(logger: Logger) -> Logger
Disable caller ID capture for this logger.