
API | Slack channel | Latest release: v1.2.0 (2026-07-25)
Trove is a minimal, modern alternative to tools.logging that supports:
It's intended mostly for library authors that want to emit rich logging without forcing their users to adopt any particular backend.
Trove is TINY (0 deps, ~250 loc), fast, and highly flexible.
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.
If a library you use depends on Trove, you'll automatically get sensible library logging out-the-box (to *out* or the JS console).
This just works - no need for any config, or even a logging library.
But you may prefer to configure Trove to instead funnel logs to your preferred logging backend.
Trove provides out-the-box support for Telemere, Timbre, μ/log, tools.logging, and SLF4J.
Call trove/set-log-fn! as you like:
(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 {:bridge-ctx? true/false}))
(trove/set-log-fn! nil) ; To noop all `trove/log!` calls
Use {:bridge-ctx? true} if you'd like to allow library authors to update your backend's native context when relevant. Requires Telemere, Timbre (except on Babashka), μ/log, or SLF4J with an MDC-capable provider.
You can also easily write your own log-fn - see trove/*log-fn* for details.
trove/log! to make your logging calls.Trove uses the same map-based logging API as Telemere:
(ns my-ns (:require [taoensso.trove :as trove]))
(trove/log! {:level :info, :id :auth/login, :data {:user-id 1234}, :msg "User logged in!"})
Which expands to roughly:
(when-let [log-fn trove/*log-fn*] ; Chosen backend fn
(log-fn "my-ns" [line column] :info :auth/login ; Callsite info
{:msg "User logged in!", :data {:user-id 1234}})) ; Payload
The end user's chosen backend takes care of filtering and output.
Structured logging sometimes involves expensive data collection or transformation, e.g.:
(trove/log! {:id ::my-event, :data (expensive) ...})
That's why Trove automatically delays payload values that need runtime evaluation, allowing the backend to apply filtering before paying realization costs. See *log-fn* for payload details.
A context map may be attached to trove/log! output via trove/*ctx*:
(trove/with-ctx+ {:workflow/step "fetch-page"}
(trove/log! {:id :ingest/fetched, :data {:n 42}}))
Utils:
set-root-ctx! to modify root (default) contextwith-ctx to replace the current contextwith-ctx+ to update the current context
log!also takes:ctxand:ctx+options to replace or update the context for a single call.
The above context belongs to Trove, so affects only trove/log! calls.
Some backends have their own equivalent context, which you can also offer to update if end-users opt-in:
(trove/with-ctx+ {:workflow/step "fetch-page"}
(trove/with-ctx-bridge ; Merge Trove context into native backend context
(fetch-page)))
Custom backends can add bridge support with trove/add-ctx-bridge.
You can help support continued work on this project and others, thank you!! 🙏
Copyright © 2026 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 |