birch

A logging library for Gleam with cross-platform support.

The name “birch” comes from birch trees, whose white bark gleams in the light.

Quick Start

import birch as log

pub fn main() {
  log.info("Application starting")
  log.debug("Debug info", [#("key", "value")])
}

Named Loggers

import birch as log

let logger = log.new("myapp.database")
logger |> log.logger_info("Connected", [])

Configuration

import birch as log
import birch/level
import birch/handler/console

log.configure([
  log.config_level(level.Debug),
  log.config_handlers([console.handler()]),
])

Types

Global configuration for the default logger. Re-exported from config module for convenience.

pub type Config =
  config.GlobalConfig
pub type LogHandler =
  handler.Handler
pub type LogLevel =
  level.Level
pub type LogMetadata =
  List(#(String, String))

Values

pub fn config_context(
  ctx: List(#(String, String)),
) -> config.ConfigOption

Create a configuration option to set the default context metadata.

pub fn config_handlers(
  handlers: List(handler.Handler),
) -> config.ConfigOption

Create a configuration option to set the global handlers.

pub fn config_level(lvl: level.Level) -> config.ConfigOption

Create a configuration option to set the global log level.

pub fn config_on_error(
  callback: fn(handler.HandlerError) -> Nil,
) -> config.ConfigOption

Create a configuration option to set the global error callback.

This callback is invoked when any handler encounters an error. It’s useful for monitoring and alerting on handler failures.

Example:

import birch as log

log.configure([
  log.config_on_error(fn(err) {
    io.println("Handler " <> err.handler_name <> " failed: " <> err.error)
  }),
])
pub fn config_sampling(
  sample_config: config.SampleConfig,
) -> config.ConfigOption

Create a configuration option to set sampling.

Example:

import birch as log
import birch/level
import birch/sampling

// Log only 10% of debug messages
log.configure([
  log.config_sampling(sampling.config(level.Debug, 0.1)),
])
pub fn configure(options: List(config.ConfigOption)) -> Nil

Configure the global logging settings.

Example:

import birch as log
import birch/level
import birch/handler/console

log.configure([
  log.config_level(level.Debug),
  log.config_handlers([console.handler()]),
  log.config_context([#("app", "myapp")]),
])
pub fn debug(message: String) -> Nil

Log a debug message using the default logger.

pub fn debug_lazy(message_fn: fn() -> String) -> Nil

Log a debug message with lazy evaluation using the default logger. The message function is only called if debug level is enabled and sampled.

pub fn debug_m(
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log a debug message with metadata using the default logger.

pub fn default_config() -> config.GlobalConfig

Default configuration: Info level, console handler, no context, no error callback, no sampling.

pub fn error(message: String) -> Nil

Log an error message using the default logger.

pub fn error_m(
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log an error message with metadata using the default logger.

pub fn error_result(message: String, result: Result(a, e)) -> Nil

Log an error message with an associated Result using the default logger.

If the result is an Error, the error value is automatically included in the metadata under the “error” key.

Example

import birch as log

case file.read("config.json") {
  Ok(content) -> parse_config(content)
  Error(_) as result -> {
    log.error_result("Failed to read config file", result)
    use_defaults()
  }
}
pub fn error_result_m(
  message: String,
  result: Result(a, e),
  metadata: List(#(String, String)),
) -> Nil

Log an error message with an associated Result and metadata.

pub fn fatal(message: String) -> Nil

Log a fatal message using the default logger.

pub fn fatal_m(
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log a fatal message with metadata using the default logger.

pub fn fatal_result(message: String, result: Result(a, e)) -> Nil

Log a fatal message with an associated Result using the default logger.

If the result is an Error, the error value is automatically included in the metadata under the “error” key.

pub fn fatal_result_m(
  message: String,
  result: Result(a, e),
  metadata: List(#(String, String)),
) -> Nil

Log a fatal message with an associated Result and metadata.

pub fn get_config() -> config.GlobalConfig

Get the current global configuration. Returns the configured settings, or defaults if not configured.

pub fn get_level() -> level.Level

Get the current global log level.

Returns the configured log level, or Info if not configured.

pub fn get_scope_context() -> List(#(String, String))

Get the current scope context.

Returns the metadata from all active scopes, with inner scope values taking precedence (appearing first in the list).

Returns an empty list if called outside of any scope.

pub fn info(message: String) -> Nil

Log an info message using the default logger.

pub fn info_lazy(message_fn: fn() -> String) -> Nil

Log an info message with lazy evaluation using the default logger.

pub fn info_m(
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log an info message with metadata using the default logger.

pub fn is_scoped_context_available() -> Bool

Check if scoped context is available on the current platform.

  • On Erlang: Always returns True (uses process dictionary)
  • On Node.js: Returns True (uses AsyncLocalStorage)
  • On other JavaScript runtimes: Returns False

When not available, with_scope still works but context may not propagate to nested async operations.

pub fn level_from_string(s: String) -> Result(level.Level, Nil)

Parse a string into a log level. Case-insensitive. Returns Error for unrecognized strings.

pub fn level_to_string(lvl: level.Level) -> String

Convert a log level to its string representation.

pub fn logger_debug(
  lgr: logger.Logger,
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log a debug message using a specific logger.

pub fn logger_error(
  lgr: logger.Logger,
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log an error message using a specific logger.

pub fn logger_error_result(
  lgr: logger.Logger,
  message: String,
  result: Result(a, e),
  metadata: List(#(String, String)),
) -> Nil

Log an error message with an associated Result using a specific logger.

pub fn logger_fatal(
  lgr: logger.Logger,
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log a fatal message using a specific logger.

pub fn logger_fatal_result(
  lgr: logger.Logger,
  message: String,
  result: Result(a, e),
  metadata: List(#(String, String)),
) -> Nil

Log a fatal message with an associated Result using a specific logger.

pub fn logger_info(
  lgr: logger.Logger,
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log an info message using a specific logger.

pub fn logger_log(
  lgr: logger.Logger,
  log_level: level.Level,
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log a message at the specified level using a specific logger.

pub fn logger_trace(
  lgr: logger.Logger,
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log a trace message using a specific logger.

pub fn logger_warn(
  lgr: logger.Logger,
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log a warning message using a specific logger.

pub fn new(name: String) -> logger.Logger

Create a new named logger.

The logger inherits the global configuration (level, handlers, context). Named loggers allow you to organize logs by component:

let db_logger = log.new("myapp.database")
let http_logger = log.new("myapp.http")
pub fn reset_config() -> Nil

Reset the global configuration to defaults.

pub fn set_level(lvl: level.Level) -> Nil

Set the global log level at runtime.

This changes the log level for all new log operations immediately. Other configuration (handlers, context) is preserved.

Example:

import birch as log
import birch/level

// Enable debug logging for troubleshooting
log.set_level(level.Debug)

// Later, reduce verbosity
log.set_level(level.Warn)
pub fn silent(name: String) -> logger.Logger

Create a silent logger (no handlers). Useful for library code where the consumer controls logging.

pub fn timestamp() -> String

Get the current timestamp in ISO 8601 format. Useful for custom formatting or external logging.

pub fn trace(message: String) -> Nil

Log a trace message using the default logger.

pub fn trace_m(
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log a trace message with metadata using the default logger.

pub fn warn(message: String) -> Nil

Log a warning message using the default logger.

pub fn warn_m(
  message: String,
  metadata: List(#(String, String)),
) -> Nil

Log a warning message with metadata using the default logger.

pub fn with_caller_id_capture(
  lgr: logger.Logger,
) -> logger.Logger

Enable caller ID capture for a 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”

Example

import birch as log

// Enable caller ID capture for debugging concurrent code
let logger =
  log.new("myapp.worker")
  |> log.with_caller_id_capture()
pub fn with_context(
  logger: logger.Logger,
  context: List(#(String, String)),
) -> logger.Logger

Add context metadata to a logger. This metadata will be included in all subsequent log messages.

pub fn with_handler(
  lgr: logger.Logger,
  handler: handler.Handler,
) -> logger.Logger

Add a handler to a logger.

pub fn with_level(
  logger: logger.Logger,
  min_level: level.Level,
) -> logger.Logger

Set the minimum log level for a logger.

pub fn with_scope(
  context: List(#(String, String)),
  work: fn() -> a,
) -> a

Execute a function with the given context applied.

All logs made within the scope (directly or through nested calls) will include the scoped context metadata.

Scopes can be nested, with inner scopes adding to (and potentially shadowing) the outer scope’s context.

Example

import birch as log

pub fn handle_request(request_id: String) {
  log.with_scope([#("request_id", request_id)], fn() {
    // All logs in this block include request_id
    log.info("processing request")
    do_work()
    log.info("request complete")
  })
}

Platform Support

  • Erlang: Uses the process dictionary. Each process has its own scope.
  • JavaScript (Node.js): Uses AsyncLocalStorage for async context propagation.
  • JavaScript (Other): Falls back to simple storage; may not propagate to async operations.
pub fn with_time_provider(
  lgr: logger.Logger,
  provider: fn() -> String,
) -> logger.Logger

Set a custom time provider for a logger.

This is primarily useful for testing, allowing deterministic timestamps.

Example

import birch as log

// For testing - fixed timestamp
let test_logger =
  log.new("test")
  |> log.with_time_provider(fn() { "2024-01-01T00:00:00.000Z" })
pub fn without_caller_id_capture(
  lgr: logger.Logger,
) -> logger.Logger

Disable caller ID capture for a logger.

pub fn without_time_provider(lgr: logger.Logger) -> logger.Logger

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

Search Document