birch
A logging library for Gleam with cross-platform support.
The name “birch” comes from birch trees, whose white bark gleams in the light.
Features
- Cross-platform: Works on both Erlang and JavaScript targets
- Zero-configuration startup: Just import and start logging
- Structured logging: Key-value metadata on every log message
- Multiple handlers: Console, file, JSON, or custom handlers
- Log rotation: Size-based rotation for file handlers
- Color support: Colored output for TTY terminals
- Lazy evaluation: Avoid expensive string formatting when logs are filtered
Quick Start
import birch as log
pub fn main() {
// Simple logging
log.info("Application starting")
log.debug("Debug message")
log.error("Something went wrong")
// With metadata
log.info_m("User logged in", [#("user_id", "123"), #("ip", "192.168.1.1")])
}
Installation
Add birch to your gleam.toml:
[dependencies]
birch = ">= 0.1.0"
Named Loggers
Create named loggers for different components:
import birch as log
pub fn main() {
let db_logger = log.new("myapp.database")
let http_logger = log.new("myapp.http")
db_logger |> log.logger_info("Connected to database", [])
http_logger |> log.logger_info("Server started on port 8080", [])
}
Logger Context
Add persistent context to a logger:
import birch as log
pub fn handle_request(request_id: String) {
let logger = log.new("myapp.http")
|> log.with_context([
#("request_id", request_id),
#("service", "api"),
])
// All logs from this logger include the context
logger |> log.logger_info("Processing request", [])
logger |> log.logger_info("Request complete", [#("status", "200")])
}
Log Levels
Six log levels are supported, from least to most severe:
| Level | Use Case |
|---|---|
Trace | Very detailed diagnostic information |
Debug | Debugging information during development |
Info | Normal operational messages (default) |
Warn | Warning conditions that might need attention |
Error | Error conditions that should be addressed |
Fatal | Critical errors preventing continuation |
Set the minimum level for a logger:
import birch as log
import birch/level
let logger = log.new("myapp")
|> log.with_level(level.Debug) // Log Debug and above
Handlers
Console Handler
The default handler outputs to stdout with colors:
import birch/handler/console
let handler = console.handler()
// or with configuration
let handler = console.handler_with_config(console.ConsoleConfig(
color: True,
target: handler.Stdout,
))
JSON Handler
For log aggregation systems:
import birch/handler/json
let handler = json.handler()
Output:
{"timestamp":"2024-12-26T10:30:45.123Z","level":"info","logger":"myapp","message":"Request complete","method":"POST","path":"/api/users"}
File Handler
Write to files with optional rotation:
import birch/handler/file
let handler = file.handler(file.FileConfig(
path: "/var/log/myapp.log",
rotation: file.SizeRotation(max_bytes: 10_000_000, max_files: 5),
))
Null Handler
For testing or disabling logging:
import birch/handler
let handler = handler.null()
Custom Handlers
Create custom handlers with the handler interface:
import birch/handler
import birch/formatter
let my_handler = handler.new(
name: "custom",
write: fn(message) {
// Send to external service, etc.
},
format: formatter.human_readable,
)
Lazy Evaluation
Avoid expensive operations when logs are filtered:
import birch as log
// The closure is only called if debug level is enabled
log.debug_lazy(fn() {
"Expensive debug info: " <> compute_debug_info()
})
Output Formats
Human-Readable (default)
2024-12-26T10:30:45.123Z | INFO | myapp.http | Request complete | method=POST path=/api/users
JSON
{"timestamp":"2024-12-26T10:30:45.123Z","level":"info","logger":"myapp.http","message":"Request complete","method":"POST","path":"/api/users"}
Library Authors
For library code, create silent loggers that consumers can configure:
// In your library
import birch as log
const logger = log.silent("mylib.internal")
pub fn do_something() {
logger |> log.logger_debug("Starting operation", [])
// ...
}
Consumers control logging by adding handlers to the logger.
Development
# Build for default target (Erlang)
gleam build
# Build for JavaScript
gleam build --target javascript
# Run tests on Erlang
gleam test
# Run tests on JavaScript
gleam test --target javascript
# Check formatting
gleam format --check src test
# Format code
gleam format src test
# Generate docs
gleam docs build
Testing
This project uses:
- gleeunit - Standard test runner for Gleam
- qcheck - Property-based testing for more thorough test coverage
Unit tests are in test/birch_test.gleam and property tests are in test/property_test.gleam.
CI/CD
GitHub Actions runs on every push and PR:
- Tests on both Erlang and JavaScript targets
- Format checking
- Documentation build
Code Coverage
Note: Gleam currently has limited support for code coverage tools. Since Gleam compiles to Erlang source (rather than abstract format), integration with Erlang’s cover tool is challenging. We rely on comprehensive unit and property tests instead.
Comparison with Other Logging Libraries
Several logging libraries exist in the Gleam ecosystem. Here’s how they compare:
| Feature | birch | glight | glogg | palabres |
|---|---|---|---|---|
| Erlang target | ✅ | ✅ | ✅ | ✅ |
| JavaScript target | ✅ | ❌ | ✅ | ✅ |
| Console output | ✅ | ✅ | ❌ | ✅ |
| File output | ✅ | ✅ | ❌ | ❌ |
| JSON output | ✅ | ✅ | ✅ | ✅ |
| File rotation | ✅ | ❌ | ❌ | ❌ |
| Colored output | ✅ | ✅ | ❌ | ✅ |
| Structured metadata | ✅ | ✅ | ✅ | ✅ |
| Typed metadata values | ❌ | ❌ | ✅ | ✅ |
| Named loggers | ✅ | ❌ | ❌ | ❌ |
| Logger context | ✅ | ✅ | ✅ | ❌ |
| Scoped context | ✅ | ❌ | ❌ | ❌ |
| Lazy evaluation | ✅ | ❌ | ❌ | ❌ |
| Custom handlers | ✅ | ❌ | ❌ | ❌ |
| Sampling | ✅ | ❌ | ❌ | ❌ |
| Stacktrace capture | ❌ | ❌ | ✅ | ❌ |
| Erlang logger integration | ❌ | ✅ | ❌ | ❌ |
| Wisp integration | ❌ | ❌ | ❌ | ✅ |
| Zero-config startup | ✅ | ❌ | ❌ | ✅ |
When to Choose Each Library
- birch: Applications needing file rotation, scoped context propagation, lazy evaluation, or custom handler support.
- glight: Erlang-only applications that want integration with Erlang’s standard logger module.
- glogg: Applications requiring typed metadata fields (Int, Float, Bool, Duration) or stacktrace capture.
- palabres: Wisp web applications that benefit from built-in middleware integration.
License
MIT License - see LICENSE for details.