Skip to content

taoensso/trove

Repository files navigation

Taoensso open source
API | Slack channel | Latest release: v1.0.0 (2025-08-21)

Clj tests Cljs tests Graal tests bb tests

Trove

Modern logging facade for Clojure/Script

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.

To log

Trove uses the same map-based logging API as Telemere.

  1. Include the (tiny) dependency in your project or library.
  2. 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.

To choose a backend

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.

What about expensive data?

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*.

Why structured logging?

  • 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.

Funding

You can help support continued work on this project and others, thank you!! 🙏

License

Copyright © 2025 Peter Taoussanis.
Licensed under EPL 1.0 (same as Clojure).