API | Slack channel | Latest release: v1.0.0 (2025-08-21)
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:
- Both traditional and structured logging
- Both Clojure and ClojureScript
- Richer filtering capabilities (by namespace, id, level, data, etc.)
It's TINY (1 macro, 0 deps, ~100 loc), fast, and highly flexible.
Trove uses the same map-based logging API as Telemere.
- Include the (tiny) dependency in your project or library.
- Use
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*
.
- Traditional logging outputs strings (messages).
- Structured logging in contrast outputs data. It retains rich data types and (nested) structures throughout the logging pipeline from logging callsite → filters → middleware → handlers.
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).