ℹ️ v1.7 adds FList/FMap/FNull variants, list/map/null constructors, vstr/vint/vfloat/vbool/vnull raw helpers, native typed JSON output, and public format_event_* helpers.

ℹ️ v1.6 adds child loggers, filter_event_sink, public emit(LogEvent), and public level_to_int. No breaking changes.

ℹ️ v1.5 adds instanced logger context, inspect, tap_time, level_from_string, set_level_from_env, get_level, append_context, and soft deprecation warnings for legacy field helpers.

ℹ️ v1.4 adds 4 new OTP levels (Notice, Critical, Alert, Emergency), beam_event_sink, multi-sink dispatch, and dev()/prod() presets.

⚠️ v1.3 breaking change: fields changed from List(#(String, String)) to List(#(String, FieldValue)). See docs/migration_v1_3.md.

woof logo

Package Version Hex Docs Built with Gleam License: MIT

woof

A straightforward logging library for Gleam.
Dedicated to Echo, my dog.

woof gets out of your way: import it, call info(...), and you’re done. Structured fields, namespaces, scoped context, typed events - all there when you need them, invisible when you don’t.

Install

gleam add woof

Quick start

import woof

pub fn main() {
  woof.info("Server started", [woof.str("host", "0.0.0.0"), woof.int("port", 3000)])
  woof.warning("Cache almost full", [woof.int("usage_pct", 92)])
  woof.error("Connection lost", [woof.str("host", "db-primary")])
}
[INFO] 10:30:45 Server started
  host: 0.0.0.0
  port: 3000
[WARN] 10:30:46 Cache almost full
  usage_pct: 92
[ERROR] 10:30:47 Connection lost
  host: db-primary

No setup, no builder chains, no ceremony.

Typed fields

Fields carry their original Gleam types through the entire pipeline. Pattern-match on them in event sinks, assert on them in tests.

woof.info("Payment processed", [
  woof.str("order_id", "ORD-42"),
  woof.int("amount_cents", 4999),
  woof.float("tax_rate", 8.5),
  woof.bool("express", True),
])

Nested data

Lists, nested objects, and explicit null pass through as typed values:

woof.info("order", [
  woof.str("id", "ORD-42"),
  woof.list("items", [woof.vstr("widget"), woof.vstr("gadget")]),
  woof.map("address", [
    #("city", woof.vstr("Bologna")),
    #("zip",  woof.vstr("40121")),
  ]),
  woof.null("coupon"),
])

JSON output emits real types: "items":["widget","gadget"], "address":{...}, "coupon":null. Numbers are numbers, booleans are booleans.

Testing capture typed events

let #(sink, get) = woof.test_sink()
woof.set_sink(woof.silent_sink)
woof.set_event_sink(sink)

process_payment(order_id: "ORD-99", amount: 0)

let assert [event] = get()
event.level   |> should.equal(woof.Error)
event.message |> should.equal("Payment rejected")
event.fields  |> should.equal([
  #("order_id", woof.FString("ORD-99")),
  #("reason",   woof.FString("zero amount")),
])

One-call setup

pub fn main() {
  woof.dev()   // Debug level, Text format, colors Auto, stdout
  // - or -
  woof.prod()  // Info level, Json format, OTP logger
}

Or wire up sinks explicitly:

woof.set_sinks([woof.beam_logger_sink, my_metrics_sink])
woof.set_event_sink(woof.beam_event_sink) // structured typed fields to OTP

Instanced loggers with context

Pass a fixed set of fields through a logger instance - no global state needed:

let db = woof.new("database") |> woof.set_context([woof.str("component", "db")])
db |> woof.log(woof.Info, "Connected", [woof.str("host", "localhost")])
// → namespace: "database", fields: component="db", host="localhost"

Build hierarchies with child (inherits parent context, dot-joined namespace):

let http   = woof.new("http")
let router = woof.child(http, "router")   // namespace: "http.router"

Ideal for JS async code where global context is unreliable.

Selective sink routing

Wrap a sink with a predicate to send only matching events somewhere:

woof.set_event_sink(woof.filter_event_sink(
  fn(e) { woof.level_to_int(e.level) >= woof.level_to_int(woof.Error) },
  pagerduty_sink,
))

Debugging helpers

fetch_user(id)
|> woof.inspect("user")          // logs string repr at Debug, passes value through
|> woof.tap_time("after_fetch")  // logs monotonic_ms as Int field at Debug
|> transform()

Level from environment variable

pub fn main() {
  let _ = woof.set_level_from_env("LOG_LEVEL")  // reads LOG_LEVEL, falls back silently
  // ...
}
LOG_LEVEL=warning ./my_app   # sets Warning level at startup

Parse or inspect the level anywhere:

woof.level_from_string("critical")  // Ok(Critical)
woof.get_level()                    // current Level

Documentation

DocumentContents
docs/guide.mdFull reference: levels, formats, sinks, context, BEAM integration, API table
docs/migration_v1_3.mdUpgrading from v1.2 - what changed and how to fix it
roadmap.mdFuture releases v1.7 to v2.0
CHANGELOG.mdRelease history
hexdocs.pm/woofGenerated module reference

Requirements


Made with Gleam 💜

Search Document