README.md

# birch

A logging library for Gleam with cross-platform support.

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

[![Package Version](https://img.shields.io/hexpm/v/birch)](https://hex.pm/packages/birch)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/birch/)

## 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

```gleam
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`:

```toml
[dependencies]
birch = ">= 0.1.0"
```

## Named Loggers

Create named loggers for different components:

```gleam
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:

```gleam
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:

```gleam
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:

```gleam
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:

```gleam
import birch/handler/json

let handler = json.handler()
```

Output:
```json
{"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:

```gleam
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:

```gleam
import birch/handler

let handler = handler.null()
```

## Custom Handlers

Create custom handlers with the handler interface:

```gleam
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:

```gleam
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

```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:

```gleam
// 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

```bash
# 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](https://hexdocs.pm/glight/) | [glogg](https://hexdocs.pm/glogg/) | [palabres](https://hexdocs.pm/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](LICENSE) for details.