phel-lang / phel-log
Data-driven logging library for Phel Lang. Inspired by Timbre + Monolog. Levels, namespace filtering, pluggable appenders, PSR-3 adapter, Monolog handler bridge.
Fund package maintenance!
Requires
- php: >=8.4
- phel-lang/phel-lang: ^0.37
- psr/log: ^3.0
Requires (Dev)
- monolog/monolog: ^3.0
Suggests
- monolog/monolog: Bridge phel-log events into Monolog handlers (Slack, Syslog, Elasticsearch, ...)
README
Data-driven logging library for Phel Lang. Inspired by Timbre (Clojure) and Monolog (PHP).
- Pure-data config: one map, swap it at runtime.
- Levels with namespace + per-appender filters.
- Pluggable appenders: console, file, in-memory, Monolog handler bridge.
- Pluggable formatters: line, JSON, custom fn.
- Processors (middleware) for masking / enriching events.
- PSR-3
LoggerInterfaceadapter for Symfony / Laravel / Slim interop.
(ns my-app.users
(:require phel.log :as log))
(log/info "user logged in" {:user-id 42})
;; 2026-05-16T10:21:33.123Z [INFO ] my-app.users - user logged in array (...)
Install
composer require phel-lang/phel-log
Requires PHP 8.4+ and phel-lang/phel-lang 0.37+.
Optional: monolog/monolog 3.x for the Monolog handler bridge.
Configure
(log/set-config!
{:min-level :debug
:appenders [(log/console-appender)
(log/file-appender {:path "/var/log/my-app.log"
:formatter :json
:min-level :warn})]
:ns-filter {:allow ["my-app.**"]
:deny ["my-app.noisy.**"]}
:processors [(fn [ev] (assoc ev :data (merge (:data ev) {:trace-id (request-id)})))]
:base-data {:app "my-app" :env "prod"}})
Levels
:trace -> :debug -> :info -> :warn -> :error -> :fatal -> :report
Each level has a macro: log/trace, log/debug, ..., log/report. The call-site
namespace is captured at macro-expand time, no extra plumbing.
(log/debug "raw payload" {:body request-body})
(log/error "failed to charge card" {:order-id 7 :error e})
(log/report "deployment finished" {:sha "abc123"}) ; always loud
Appenders
| Appender | Purpose |
|---|---|
console-appender |
stdout for info-, stderr for warn+ |
file-appender |
append-only, lock-on-write |
memory-appender |
capture events into an atom (tests / REPL) |
monolog-appender |
bridge any Monolog\Handler\HandlerInterface |
All appenders share the same shape:
{:name :my-appender
:min-level :warn ; optional, per-appender override
:formatter :line ; or :json, or (fn [event] string)
:write! (fn [event line] ...)}
Write your own by returning a map of the same shape; everything in
log/write-event! is data-driven.
Formatters
:line- human one-liner (timestamp, level, ns, message, data).:json- one JSON object per line; pipe straight into Loki / ELK / CloudWatch.(fn [event] string)- anything you want; the event is a plain map.
{:level :info
:ns "my-app.users"
:time-ms 1715846400123
:message "user logged in"
:data {:user-id 42}
:error nil}
Monolog handler bridge
Re-use every existing Monolog handler (Slack, Sentry, Syslog, RotatingFile, ...):
(let [slack (php/new \Monolog\Handler\SlackWebhookHandler
webhook-url
"#alerts"
"phel-bot"
true
nil
false
false
(php/-> \Monolog\Level (Error)))]
(log/update-config!
{:appenders [(log/console-appender)
(log/monolog-appender {:handler slack
:channel "my-app"
:min-level :error})]}))
PSR-3 adapter
Drop the Phel logger into a PSR-3-aware PHP framework:
use Phel\PhelLog\PsrLogger; $dispatcher = static function (string $level, string $message, array $ctx): void { \Phel::run('my-bootstrap', 'log-from-psr', $level, $message, $ctx); }; $logger = new PsrLogger($dispatcher, channel: 'symfony'); $container->set(\Psr\Log\LoggerInterface::class, $logger);
Docs
Full index at docs/README.md — start with
Quickstart, then Config /
Appenders. Integrations:
Monolog bridge, PSR-3.
Contributing
Run tests:
composer test
Conventional commits, PR template in .github/PULL_REQUEST_TEMPLATE.md.