birch/logger

Logger type and operations.

A Logger is a named logging context with an associated level, handlers, and persistent metadata.

Types

A logger instance with configuration and context.

pub opaque type Logger

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 get_level(logger: Logger) -> level.Level

Get the minimum level of 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):

  1. Call metadata (passed to this function)
  2. Scope context (from with_scope)
  3. 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):

  1. Call metadata (passed to this function)
  2. Scope context (from with_scope)
  3. Logger context (from with_context)
pub fn name(logger: Logger) -> String

Get the name of a logger.

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.

pub fn without_time_provider(logger: Logger) -> Logger

Clear the custom time provider, reverting to the default platform timestamp.

Search Document