{"id":43471737,"url":"https://github.com/tylerbutler/birch","last_synced_at":"2026-02-21T06:35:30.315Z","repository":{"id":333243974,"uuid":"1123552128","full_name":"tylerbutler/birch","owner":"tylerbutler","description":"Logs that gleam. ✨🪵✨ A comprehensive logging library in Gleam that targets both Erlang and JavaScript.","archived":false,"fork":false,"pushed_at":"2026-02-14T06:46:58.000Z","size":388,"stargazers_count":1,"open_issues_count":4,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-14T13:42:42.695Z","etag":null,"topics":["gleam","logging"],"latest_commit_sha":null,"homepage":"","language":"Gleam","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tylerbutler.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-27T05:38:05.000Z","updated_at":"2026-02-14T06:36:32.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tylerbutler/birch","commit_stats":null,"previous_names":["tylerbutler/birch"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/tylerbutler/birch","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerbutler%2Fbirch","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerbutler%2Fbirch/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerbutler%2Fbirch/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerbutler%2Fbirch/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tylerbutler","download_url":"https://codeload.github.com/tylerbutler/birch/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tylerbutler%2Fbirch/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29481953,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-15T11:35:25.641Z","status":"ssl_error","status_checked_at":"2026-02-15T11:34:57.128Z","response_time":118,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gleam","logging"],"created_at":"2026-02-03T07:11:06.013Z","updated_at":"2026-02-15T21:01:04.513Z","avatar_url":"https://github.com/tylerbutler.png","language":"Gleam","funding_links":[],"categories":[],"sub_categories":[],"readme":"# birch - logs that gleam ✨🪵✨\n\nA logging library for Gleam with cross-platform support.\n\nThe name \"birch\" comes from birch trees, whose white bark gleams in the light.\n\n[![Package Version](https://img.shields.io/hexpm/v/birch)](https://hex.pm/packages/birch)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/birch/)\n\n## Features\n\n- **Cross-platform**: Works on both Erlang and JavaScript targets\n- **Zero-configuration startup**: Just import and start logging\n- **Structured logging**: Key-value metadata on every log message\n- **Multiple handlers**: Console, file, JSON, async, or custom handlers\n- **Log rotation**: Size-based and time-based rotation for file handlers\n- **Color support**: Colored output for TTY terminals\n- **Lazy evaluation**: Avoid expensive string formatting when logs are filtered\n- **Scoped context**: Request-scoped metadata that propagates automatically\n- **Sampling**: Probabilistic sampling and rate limiting for high-volume scenarios\n\n## Quick Start\n\n```gleam\nimport birch as log\n\npub fn main() {\n  // Simple logging\n  log.info(\"Application starting\")\n  log.debug(\"Debug message\")\n  log.error(\"Something went wrong\")\n\n  // With metadata\n  log.info_m(\"User logged in\", [#(\"user_id\", \"123\"), #(\"ip\", \"192.168.1.1\")])\n}\n```\n\n## Installation\n\nAdd `birch` to your `gleam.toml`:\n\n```toml\n[dependencies]\nbirch = \"\u003e= 0.1.0\"\n```\n\n## Global Configuration\n\nConfigure the default logger with custom settings:\n\n```gleam\nimport birch as log\nimport birch/level\nimport birch/handler/console\nimport birch/handler/json\n\npub fn main() {\n  // Configure with multiple options\n  log.configure([\n    log.config_level(level.Debug),\n    log.config_handlers([console.handler(), json.handler()]),\n    log.config_context([#(\"app\", \"myapp\"), #(\"env\", \"production\")]),\n  ])\n\n  // All logs now include the context and go to both handlers\n  log.info(\"Server starting\")\n}\n```\n\n### Runtime Level Changes\n\nChange the log level at runtime without reconfiguring everything:\n\n```gleam\nimport birch as log\nimport birch/level\n\n// Enable debug logging for troubleshooting\nlog.set_level(level.Debug)\n\n// Later, reduce verbosity\nlog.set_level(level.Warn)\n\n// Check current level\nlet current = log.get_level()\n```\n\n## Named Loggers\n\nCreate named loggers for different components:\n\n```gleam\nimport birch as log\n\npub fn main() {\n  let db_logger = log.new(\"myapp.database\")\n  let http_logger = log.new(\"myapp.http\")\n\n  db_logger |\u003e log.logger_info(\"Connected to database\", [])\n  http_logger |\u003e log.logger_info(\"Server started on port 8080\", [])\n}\n```\n\n## Logger Context\n\nAdd persistent context to a logger:\n\n```gleam\nimport birch as log\n\npub fn handle_request(request_id: String) {\n  let logger = log.new(\"myapp.http\")\n    |\u003e log.with_context([\n      #(\"request_id\", request_id),\n      #(\"service\", \"api\"),\n    ])\n\n  // All logs from this logger include the context\n  logger |\u003e log.logger_info(\"Processing request\", [])\n  logger |\u003e log.logger_info(\"Request complete\", [#(\"status\", \"200\")])\n}\n```\n\n## Scoped Context\n\nAutomatically attach metadata to all logs within a scope:\n\n```gleam\nimport birch as log\n\npub fn handle_request(request_id: String) {\n  log.with_scope([#(\"request_id\", request_id)], fn() {\n    // All logs in this block include request_id automatically\n    log.info(\"Processing request\")\n    do_work()  // Logs in nested functions also include request_id\n    log.info(\"Request complete\")\n  })\n}\n```\n\nScopes can be nested, with inner scopes adding to outer scope context:\n\n```gleam\nlog.with_scope([#(\"request_id\", \"123\")], fn() {\n  log.info(\"Start\")  // request_id=123\n\n  log.with_scope([#(\"step\", \"validation\")], fn() {\n    log.info(\"Validating\")  // request_id=123 step=validation\n  })\n\n  log.info(\"Done\")  // request_id=123\n})\n```\n\n### Platform Support\n\n- **Erlang**: Uses process dictionary. Each process has isolated context.\n- **Node.js**: Uses AsyncLocalStorage. Context propagates across async operations.\n- **Other JS runtimes**: Falls back to stack-based storage.\n\nCheck availability with `log.is_scoped_context_available()`.\n\n## Log Levels\n\nSix log levels are supported, from least to most severe:\n\n| Level   | Use Case                                |\n|---------|----------------------------------------|\n| `Trace` | Very detailed diagnostic information   |\n| `Debug` | Debugging information during development |\n| `Info`  | Normal operational messages (default)  |\n| `Warn`  | Warning conditions that might need attention |\n| `Error` | Error conditions that should be addressed |\n| `Fatal` | Critical errors preventing continuation |\n\nSet the minimum level for a logger:\n\n```gleam\nimport birch as log\nimport birch/level\n\nlet logger = log.new(\"myapp\")\n  |\u003e log.with_level(level.Debug)  // Log Debug and above\n```\n\n## Handlers\n\n### Console Handler\n\nThe default handler outputs to stdout with colors:\n\n```gleam\nimport birch/handler/console\n\nlet handler = console.handler()\n// or with configuration\nlet handler = console.handler_with_config(console.ConsoleConfig(\n  color: True,\n  target: handler.Stdout,\n))\n```\n\n### JSON Handler\n\nFor log aggregation systems:\n\n```gleam\nimport birch/handler/json\n\nlet handler = json.handler()\n```\n\nOutput:\n```json\n{\"timestamp\":\"2024-12-26T10:30:45.123Z\",\"level\":\"info\",\"logger\":\"myapp\",\"message\":\"Request complete\",\"method\":\"POST\",\"path\":\"/api/users\"}\n```\n\n#### Custom JSON Format\n\nUse the builder pattern to customize JSON output:\n\n```gleam\nimport birch/handler/json\nimport gleam/json as j\n\nlet custom_handler =\n  json.standard_builder()\n  |\u003e json.add_custom(fn(_record) {\n    [\n      #(\"service\", j.string(\"my-app\")),\n      #(\"version\", j.string(\"1.0.0\")),\n    ]\n  })\n  |\u003e json.build()\n  |\u003e json.handler_with_formatter()\n```\n\n### File Handler\n\nWrite to files with optional rotation:\n\n```gleam\nimport birch/handler/file\n\n// Size-based rotation\nlet handler = file.handler(file.FileConfig(\n  path: \"/var/log/myapp.log\",\n  rotation: file.SizeRotation(max_bytes: 10_000_000, max_files: 5),\n))\n\n// Time-based rotation (daily)\nlet handler = file.handler(file.FileConfig(\n  path: \"/var/log/myapp.log\",\n  rotation: file.TimeRotation(interval: file.Daily, max_files: 7),\n))\n\n// Combined rotation (size OR time)\nlet handler = file.handler(file.FileConfig(\n  path: \"/var/log/myapp.log\",\n  rotation: file.CombinedRotation(\n    max_bytes: 50_000_000,\n    interval: file.Daily,\n    max_files: 10,\n  ),\n))\n```\n\n### Async Handler\n\nWrap any handler for non-blocking logging:\n\n```gleam\nimport birch/handler/async\nimport birch/handler/console\n\n// Make console logging async\nlet async_console =\n  console.handler()\n  |\u003e async.make_async(async.default_config())\n\n// With custom configuration\nlet config =\n  async.config()\n  |\u003e async.with_queue_size(5000)\n  |\u003e async.with_flush_interval(50)\n  |\u003e async.with_overflow(async.DropOldest)\n\nlet handler = async.make_async(console.handler(), config)\n\n// Before shutdown, ensure all logs are written\nasync.flush()\n```\n\n### Null Handler\n\nFor testing or disabling logging:\n\n```gleam\nimport birch/handler\n\nlet handler = handler.null()\n```\n\n## Custom Handlers\n\nCreate custom handlers with the handler interface:\n\n```gleam\nimport birch/handler\nimport birch/formatter\n\nlet my_handler = handler.new(\n  name: \"custom\",\n  write: fn(message) {\n    // Send to external service, etc.\n  },\n  format: formatter.human_readable,\n)\n```\n\n### Error Callbacks\n\nHandle errors from handlers without crashing:\n\n```gleam\nimport birch/handler\nimport birch/handler/file\n\nlet handler =\n  file.handler(config)\n  |\u003e handler.with_error_callback(fn(err) {\n    io.println(\"Handler \" \u003c\u003e err.handler_name \u003c\u003e \" failed: \" \u003c\u003e err.error)\n  })\n```\n\n## Lazy Evaluation\n\nAvoid expensive operations when logs are filtered:\n\n```gleam\nimport birch as log\n\n// The closure is only called if debug level is enabled\nlog.debug_lazy(fn() {\n  \"Expensive debug info: \" \u003c\u003e compute_debug_info()\n})\n```\n\n## Error Result Helpers\n\nLog errors with automatic metadata extraction:\n\n```gleam\nimport birch as log\n\ncase file.read(\"config.json\") {\n  Ok(content) -\u003e parse_config(content)\n  Error(_) as result -\u003e {\n    // Automatically includes error value in metadata\n    log.error_result(\"Failed to read config file\", result)\n    use_defaults()\n  }\n}\n\n// With additional metadata\nlog.error_result_m(\"Database query failed\", result, [\n  #(\"query\", \"SELECT * FROM users\"),\n  #(\"table\", \"users\"),\n])\n```\n\n## Sampling\n\nFor high-volume logging, sample messages probabilistically:\n\n```gleam\nimport birch as log\nimport birch/level\nimport birch/sampling\n\n// Log only 10% of debug messages\nlog.configure([\n  log.config_sampling(sampling.config(level.Debug, 0.1)),\n])\n\n// Debug messages above the threshold are always logged\n// Messages at or below Debug level are sampled at 10%\n```\n\n## Testing Support\n\n### Custom Time Providers\n\nUse deterministic timestamps in tests:\n\n```gleam\nimport birch as log\n\nlet test_logger =\n  log.new(\"test\")\n  |\u003e log.with_time_provider(fn() { \"2024-01-01T00:00:00.000Z\" })\n```\n\n### Caller ID Capture\n\nTrack which process/thread created each log:\n\n```gleam\nimport birch as log\n\nlet logger =\n  log.new(\"myapp.worker\")\n  |\u003e log.with_caller_id_capture()\n\n// Log records will include:\n// - Erlang: PID like \"\u003c0.123.0\u003e\"\n// - JavaScript: \"main\", \"pid-N\", or \"worker-N\"\n```\n\n## Output Formats\n\n### Human-Readable (default)\n\n```\n2024-12-26T10:30:45.123Z | INFO  | myapp.http | Request complete | method=POST path=/api/users\n```\n\n### JSON\n\n```json\n{\"timestamp\":\"2024-12-26T10:30:45.123Z\",\"level\":\"info\",\"logger\":\"myapp.http\",\"message\":\"Request complete\",\"method\":\"POST\",\"path\":\"/api/users\"}\n```\n\n## Library Authors\n\nFor library code, create silent loggers that consumers can configure:\n\n```gleam\n// In your library\nimport birch as log\n\nconst logger = log.silent(\"mylib.internal\")\n\npub fn do_something() {\n  logger |\u003e log.logger_debug(\"Starting operation\", [])\n  // ...\n}\n```\n\nConsumers control logging by adding handlers to the logger.\n\n## Comparison with Other Logging Libraries\n\nSeveral logging libraries exist in the Gleam ecosystem. Here's how they compare:\n\n| Feature | birch | [glight](https://hexdocs.pm/glight/) | [glogg](https://hexdocs.pm/glogg/) | [palabres](https://hexdocs.pm/palabres/) |\n|---------|-------|--------|-------|----------|\n| Erlang target | ✅ | ✅ | ✅ | ✅ |\n| JavaScript target | ✅ | ❌ | ✅ | ✅ |\n| Console output | ✅ | ✅ | ❌ | ✅ |\n| File output | ✅ | ✅ | ❌ | ❌ |\n| JSON output | ✅ | ✅ | ✅ | ✅ |\n| File rotation | ✅ | ❌ | ❌ | ❌ |\n| Colored output | ✅ | ✅ | ❌ | ✅ |\n| Structured metadata | ✅ | ✅ | ✅ | ✅ |\n| Typed metadata values | ❌ | ❌ | ✅ | ✅ |\n| Named loggers | ✅ | ❌ | ❌ | ❌ |\n| Logger context | ✅ | ✅ | ✅ | ❌ |\n| Scoped context | ✅ | ❌ | ❌ | ❌ |\n| Lazy evaluation | ✅ | ❌ | ❌ | ❌ |\n| Custom handlers | ✅ | ❌ | ❌ | ❌ |\n| Sampling | ✅ | ❌ | ❌ | ❌ |\n| Stacktrace capture | ❌ | ❌ | ✅ | ❌ |\n| Erlang logger integration | ✅ | ✅ | ❌ | ❌ |\n| Wisp integration | ❌ | ❌ | ❌ | ✅ |\n| Zero-config startup | ✅ | ❌ | ❌ | ✅ |\n\n### When to Choose Each Library\n\n- **birch**: Applications needing file rotation, scoped context propagation, lazy evaluation, custom handlers, or Erlang logger integration with cross-platform support.\n- **glight**: Erlang-only applications that want a minimal wrapper around Erlang's standard logger module.\n- **glogg**: Applications requiring typed metadata fields (Int, Float, Bool, Duration) or stacktrace capture.\n- **palabres**: Wisp web applications that benefit from built-in middleware integration.\n\n## Development\n\nSee [DEV.md](DEV.md) for development setup, testing, and contribution guidelines.\n\n## License\n\nMIT License - see [LICENSE](LICENSE) for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylerbutler%2Fbirch","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftylerbutler%2Fbirch","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftylerbutler%2Fbirch/lists"}