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_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_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_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_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 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_m(
message: String,
metadata: List(#(String, String)),
) -> Nil
Log a trace message with metadata 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.