API | Slack channel | Latest release: v1.1.0 (2025-10-11)
Trove is a minimal, modern alternative to tools.logging.
It's intended for library authors that want to emit rich logging without forcing their users to adopt any particular backend (e.g. Telemere, Timbre, μ/log, tools.logging, SLF4J, etc.).
It supports:
It's TINY (1 macro, 0 deps, ~100 loc), fast, and highly flexible.
Trove uses the same map-based logging API as Telemere.
trove/log!
to make your logging calls (see its docstring for options):(ns my-ns (:require [taoensso.trove :as trove]))
(trove/log! {:level :info, :id :auth/login, :data {:user-id 1234}, :msg "User logged in!"})
The above logging call expands to:
(when-let [log-fn trove/*log-fn*] ; Chosen backend fn
(log-fn ... "my-ns" :info :auth/login [line-num column-num]
{:msg "User logged in!", :data {:user-id 1234}} ...))
And the chosen backend then takes care of filtering and output.
Just set trove/*log-fn*
to an appropriate fn (see its docstring for fn args).
The default fn prints logs to *out*
or the JS console.
Alt fns are also available for some common backends, e.g.:
(ns my-ns
(:require
[taoensso.trove.x] ; x ∈ #{console telemere timbre mulog tools-logging slf4j} (default console)
[taoensso.trove :as trove]))
(trove/set-log-fn! (taoensso.trove.x/get-log-fn))
(trove/set-log-fn! nil) ; To noop all `trove/log!` calls
It's easy to write your own log-fn if you want to use a different backend or customise anything.
Structured logging sometimes involves expensive data collection or transformation, e.g.:
(trove/log! {:id ::my-event, :data (expensive) ...})
That's why Trove automatically delays any values that need runtime evaluation, allowing the backend to apply filtering before paying realization costs.
This explains the :lazy_
{:keys [msg data error kvs]}
arg given to truss/*log-fn*
.
A data-oriented pipeline can make a huge difference - supporting easier filtering, transformation, and analysis. It’s also usually faster, since you only pay for serialization if/when you need it. In a lot of cases you can avoid serialization altogether if your final target (DB, etc.) supports the relevant types.
The structured (data-oriented) approach is inherently more flexible, faster, and well suited to the tools and idioms offered by Clojure and ClojureScript.
You can help support continued work on this project and others, thank you!! 🙏
Copyright © 2025 Peter Taoussanis.
Licensed under EPL 1.0 (same as Clojure).
Can you improve this documentation? These fine people already did:
Peter Taoussanis & Michiel BorkentEdit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
Ctrl+k | Jump to recent docs |
← | Move to previous article |
→ | Move to next article |
Ctrl+/ | Jump to the search field |