prompts/skills/clojure-trove/SKILL.md
Trove is a minimal logging facade for Clojure/Script supporting both traditional and structured logging. Use when writing libraries that need logging without forcing a backend choice, or when you need rich data-oriented logging with flexible filtering.
npx skillsauth add ramblurr/nix-devenv clojure-troveInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A minimal, modern logging facade for Clojure/Script. It's a lightweight alternative to tools.logging, designed for library authors who want rich logging without forcing users to adopt a specific backend.
Trove supports both traditional string-based logging and structured data-oriented logging, with rich filtering capabilities (by namespace, id, level, data, etc.). It's tiny (1 macro, 0 deps, ~100 loc) and works with Clojure, ClojureScript, GraalVM, and Babashka.
deps.edn:
com.taoensso/trove {:mvn/version "1.1.0"}
Leiningen:
[com.taoensso/trove "1.1.0"]
See https://clojars.org/com.taoensso/trove for the latest version.
Library author usage (emitting logs):
(ns my-library.core
(:require [taoensso.trove :as trove]))
;; Traditional logging (string messages)
(trove/log! {:level :info, :msg "User logged in"})
(trove/log! {:level :warn, :msg "Connection retry attempt"})
;; Structured logging (data-oriented)
(trove/log! {:level :info
:id :auth/login
:data {:user-id 1234, :session-id "abc123"}
:msg "User authenticated"})
Application user setup (choosing backend):
(ns my-app.core
(:require
[taoensso.trove :as trove]
[taoensso.trove.console] ; or .telemere, .timbre, .mulog, .tools-logging, .slf4j
))
;; Use default console backend (prints to *out* or js/console)
;; Default is already set, no action needed
;; Or switch to a different backend
(trove/set-log-fn! (taoensso.trove.telemere/get-log-fn))
;; Or disable all logging
(trove/set-log-fn! nil)
Trove is a facade - it provides a single logging API (trove/log!) that library authors use. Application users choose which backend handles those logs by setting trove/*log-fn*.
Key design features:
The log! macro accepts a map of options:
(trove/log!
{:level :info ; Required: :trace :debug :info :warn :error :fatal :report
:id :user/login ; Optional: keyword identifier for this event
:msg "User login" ; Optional: human-readable message
:data {:user-id 42} ; Optional: structured data map
:error ex}) ; Optional: exception/throwable
Standard levels from least to most severe:
:trace - Very detailed diagnostic info:debug - Debugging information:info - Informational messages (default):warn - Warning messages:error - Error conditions:fatal - Critical failures:report - Special high-priority reportsTraditional (message-focused):
(trove/log! {:level :info, :msg "Processing order #1234"})
(trove/log! {:level :error, :msg "Database connection failed", :error ex})
Structured (data-focused):
(trove/log! {:level :info
:id :order/process
:data {:order-id 1234, :user-id 567, :total 99.99}})
(trove/log! {:level :error
:id :db/connection-failed
:data {:host "db.example.com", :port 5432}
:error ex})
Structured logging is preferred because:
Use keyword IDs to categorize events:
;; Namespace-qualified keywords recommended
(trove/log! {:id :auth/login, :data {...}})
(trove/log! {:id :payment/success, :data {...}})
(trove/log! {:id ::order-complete, :data {...}}) ; Auto-namespaced
;; IDs enable powerful filtering
;; - Filter by ID prefix: :auth/*
;; - Track specific events
;; - Build metrics and dashboards
Trove automatically delays expensive data, so backends can filter before paying computation costs:
;; This expensive call only runs if the log passes filtering
(trove/log! {:level :debug
:data (expensive-computation)})
;; Use :let for shared bindings across lazy args
(trove/log! {:level :info
:let [result (expensive-call)]
:msg (format-result result)
:data (transform-result result)})
The :let bindings are only evaluated if the log passes filtering.
Trove includes adapters for common backends:
;; Console (default) - prints to *out* or js/console
(require '[taoensso.trove.console])
(trove/set-log-fn! (taoensso.trove.console/get-log-fn))
;; Telemere - modern structured logging
(require '[taoensso.trove.telemere])
(trove/set-log-fn! (taoensso.trove.telemere/get-log-fn))
;; Timbre - popular Clojure logging
(require '[taoensso.trove.timbre])
(trove/set-log-fn! (taoensso.trove.timbre/get-log-fn))
;; μ/log - structured events
(require '[taoensso.trove.mulog])
(trove/set-log-fn! (taoensso.trove.mulog/get-log-fn))
;; tools.logging - Java interop
(require '[taoensso.trove.tools-logging])
(trove/set-log-fn! (taoensso.trove.tools-logging/get-log-fn))
;; SLF4J - Java interop
(require '[taoensso.trove.slf4j])
(trove/set-log-fn! (taoensso.trove.slf4j/get-log-fn))
The default console backend supports filtering:
;; Only log :warn and above
(trove/set-log-fn!
(taoensso.trove.console/get-log-fn
{:min-level :warn}))
Use binding for temporary backend changes:
;; Disable logging in tests
(binding [trove/*log-fn* nil]
(run-tests))
;; Use custom backend in specific context
(binding [trove/*log-fn* my-custom-log-fn]
(perform-operation))
Implement a function matching the *log-fn* signature:
(defn my-log-fn
[ns coords level id lazy_]
;; ns - String namespace, e.g. "my-app.utils"
;; coords - [line column] or nil
;; level - Keyword: :trace :debug :info :warn :error :fatal :report
;; id - Keyword or nil, e.g. :auth/login
;; lazy_ - Map or delayed map: {:keys [msg data error kvs]}
;; Force lazy_ to get the actual values
(let [{:keys [msg data error kvs]} (force lazy_)]
;; Implement filtering
(when (should-log? level id)
;; Perform logging side effects
(send-to-backend {:level level :id id :msg msg :data data}))))
;; Configure it
(trove/set-log-fn! my-log-fn)
Key implementation notes:
lazy_ to access :msg, :data, :error, :kvsOverride the defaults:
(trove/log! {:level :info
:ns "custom.namespace"
:coords [100 50]
:msg "Override defaults"})
Use a different backend for specific logs:
(trove/log! {:level :info
:log-fn my-special-log-fn
:msg "Uses custom backend"})
Pass additional data to your custom log-fn:
(trove/log! {:level :info
:msg "Custom event"
:my-custom-key "value"
:another-key 123})
;; Your log-fn receives these in the :kvs key
(defn my-log-fn [ns coords level id lazy_]
(let [{:keys [kvs]} (force lazy_)]
(println "Custom keys:" (:my-custom-key kvs))))
As a library author, just use trove/log!:
(ns my-library.api
(:require [taoensso.trove :as trove]))
(defn process-data [data]
(trove/log! {:level :debug
:id ::process-start
:data {:count (count data)}})
(try
(let [result (do-processing data)]
(trove/log! {:level :info
:id ::process-complete
:data {:processed (count result)}})
result)
(catch Exception ex
(trove/log! {:level :error
:id ::process-failed
:error ex
:data {:count (count data)}})
(throw ex))))
Your users control the backend without changing your code.
In your application entry point:
(ns my-app.main
(:require
[taoensso.trove :as trove]
[taoensso.trove.telemere]
[my-library.api :as lib]))
(defn -main []
;; Configure logging backend once
(trove/set-log-fn! (taoensso.trove.telemere/get-log-fn))
;; All libraries using Trove now log to Telemere
(lib/process-data [...]))
Use when expressions for conditional logic:
(when (dev-mode?)
(trove/log! {:level :debug
:data (expensive-debug-info)}))
;; Or use level filtering in the backend
(trove/set-log-fn!
(taoensso.trove.console/get-log-fn {:min-level :info}))
Log-fn is synchronous: The *log-fn* runs on the calling thread. Implement async/backpressure for expensive operations in your log-fn.
Lazy evaluation: Values like :data and :msg may be wrapped in delay. Always force the lazy_ argument in custom log-fns.
Backend setup timing: Set *log-fn* before any logging occurs. Do this early in application startup.
Nil log-fn: When *log-fn* is nil, all logging noops. This is intentional - useful for disabling logs.
Map required: The log! macro requires a compile-time map. Variables won't work:
;; This works
(trove/log! {:level :info, :msg "ok"})
;; This fails
(let [opts {:level :info}]
(trove/log! opts)) ; Compile error!
ClojureScript console: In ClojureScript, the console backend checks for js/console existence before logging.
Use Trove when:
Don't use Trove when:
Trove is specifically designed for library authors. Application developers should typically use a full-featured backend directly.
testing
Use this OCP when executing or preparing to execute commands that change a live or important system, service reloads/restarts, package changes, deployments, migrations, firewall/network/access changes, credential rotation, NixOS switch/test/boot/deploy, or incident mitigation. It guides safe operations with a persisted ledger for scope, preflight, baseline, rollback, validation, and evidence.
development
Create new agent skills with proper structure, progressive disclosure, and bundled resources. Use when user wants to create, write, or build a new skill.
documentation
Naming conventions for workflow documents in prompts/. Use when creating plans, PRDs, research reports, idea capture or other workflow documents. Triggers on (1) creating new planning documents, (2) naming PRDs or research reports, (3) questions about document organization in prompts/.
testing
Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions.