prompts/skills/clojure-telemere/SKILL.md
Structured telemetry library for Clojure/Script. Use when working with logging, tracing, structured logging, events, signal handling, observability, or replacing Timbre/tools.logging.
npx skillsauth add ramblurr/nix-devenv clojure-telemereInstall 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.
Telemere is a next-generation structured telemetry library for Clojure/Script. It provides one unified API for traditional logging, structured logging, tracing, and basic performance monitoring.
deps.edn:
com.taoensso/telemere {:mvn/version "1.2.1"}
Leiningen:
[com.taoensso/telemere "1.2.1"]
See https://clojars.org/com.taoensso/telemere for the latest version.
Namespace setup:
(ns my-app
(:require [taoensso.telemere :as tel]))
Telemere works out-of-the-box with no config needed. Signals print to console by default:
(require '[taoensso.telemere :as tel])
;; Traditional logging (string messages)
(tel/log! {:level :info, :msg "User logged in!"})
;; Structured logging (explicit id and data)
(tel/log! {:level :info, :id :auth/login, :data {:user-id 1234}})
;; Mixed style (id, data, and message)
(tel/log! {:level :info
:id :auth/login
:data {:user-id 1234}
:msg "User logged in!"})
;; Trace execution with runtime tracking
(tel/trace! {:id ::my-id :data {:step "processing"}}
(do-some-work))
;; Check signal content for debugging
(tel/with-signal (tel/log! {...})) ; => {:keys [ns level id data msg_ ...]}
Telemere provides multiple signal creators optimized for different use cases. All accept an opts map:
| Function | Quick Args | Returns | Use Case |
|----------|-----------|---------|----------|
| log! | [?level msg] | nil | Traditional log messages |
| event! | [id ?level] | nil | Structured events |
| trace! | [?id form] | form result | Trace execution + timing |
| spy! | [?level form] | form result | Debug form values |
| error! | [?id error] | error | Log errors |
| catch->error! | [?id form] | value or fallback | Catch & log errors |
| signal! | [opts] | depends | Low-level, full control |
Examples:
;; log! - for messages
(tel/log! "Simple message")
(tel/log! :warn "Warning message")
(tel/log! {:level :info, :data {:x 1}} "Message with data")
;; event! - for structured events
(tel/event! ::user-login)
(tel/event! ::user-login :info)
(tel/event! ::user-login {:level :info, :data {:user-id 42}})
;; trace! - tracks runtime and return value
(tel/trace! (expensive-operation))
(tel/trace! ::complex-op (multi-step-process))
;; spy! - debug form values
(tel/spy! (+ 1 2)) ; => 3, logs the value
;; error! - log errors
(try
(risky-operation)
(catch Exception e
(tel/error! e)))
;; catch->error! - automatic error handling
(tel/catch->error! ::my-op
(risky-operation)) ; returns value or nil on error
All signal creators accept a map of options:
(tel/log!
{:level :debug
:id ::my-id
;; Filtering
:sample 0.75 ; 75% sampling (noop 25% of time)
:when (enabled?) ; conditional execution
:limit {"1/sec" [1 1000]
"5/min" [5 60000]}
;; Data and execution
:let [x (expensive-calc)] ; lazy bindings
:data {:result x} ; structured data
:do (inc-metric!) ; side effects
;; Context
:ctx {:user-id 123} ; arbitrary context
:parent trace-parent} ; tracing parent
"Message using bindings")
Key options:
:level - :trace, :debug, :info (default), :warn, :error, :fatal, or integer:id - qualified keyword for identifying this signal type:data - structured data map (preserved as data):msg - message string or vector to join:let - bindings available to :data and :msg:sample - random sampling rate (0.0 to 1.0):when - conditional execution:limit - rate limiting map:do - side effects to execute when signal is createdFiltering happens at multiple stages for efficiency:
;; Set minimum level globally
(tel/set-min-level! :warn) ; All signals
(tel/set-min-level! :log :debug) ; Just log! signals
;; Filter by namespace patterns
(tel/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"})
;; Filter by ID patterns
(tel/set-id-filter! {:allow #{::my-id "my-app/*"}})
;; Set level per namespace pattern
(tel/set-min-level! :log "taoensso.sente.*" :warn)
;; Transform signals (can modify or filter)
(tel/set-xfn!
(fn [signal]
(if (-> signal :data :skip-me?)
nil ; Filter out
(assoc signal :enriched true))))
;; Dynamic context overrides
(tel/with-min-level :trace
(tel/log! {:level :debug} "This will log"))
Filtering is O(1) except for rate limits (O(n-windows)). Compile-time filtering can completely elide signal calls for zero overhead.
Handlers process created signals (write to console, file, DB, etc.):
;; Add custom handler
(tel/add-handler! :my-handler
(fn [signal] (println "Got signal:" (:id signal))))
;; Add handler with filtering and async dispatch
(tel/add-handler! :my-handler
(fn
([signal] (save-to-db signal))
([] (close-db-connection))) ; Called on shutdown
{:async {:mode :dropping
:buffer-size 1024
:n-threads 1}
:priority 100
:min-level :info
:sample 0.5
:ns-filter {:disallow "noisy.namespace.*"}
:limit {"1/sec" [1 1000]}})
;; View current handlers
(tel/get-handlers)
;; Handler statistics
(tel/get-handlers-stats)
;; Remove handler
(tel/remove-handler! :my-handler)
;; Stop all handlers (IMPORTANT: call on shutdown!)
(tel/stop-handlers!)
Console handlers (output as formatted text, edn, or JSON):
;; Human-readable text (default)
(tel/add-handler! :console
(tel/handler:console
{:output-fn (tel/format-signal-fn {})}))
;; EDN output
(tel/add-handler! :console-edn
(tel/handler:console
{:output-fn (tel/pr-signal-fn {:pr-fn :edn})}))
;; JSON output (Clj needs JSON library)
(require '[jsonista.core :as json])
(tel/add-handler! :console-json
(tel/handler:console
{:output-fn (tel/pr-signal-fn
{:pr-fn json/write-value-as-string})}))
Other included handlers:
handler:file - Write to files (Clj only)handler:postal - Email via Postal (Clj only)handler:slack - Slack notifications (Clj only)handler:tcp-socket / handler:udp-socket - Network sockets (Clj only)handler:open-telemetry - OpenTelemetry integration (Clj only)Add dependencies:
org.slf4j/slf4j-api (v2+)com.taoensso/telemere-slf4jSLF4J calls automatically become Telemere signals
Verify: (tel/check-interop) => {:slf4j {:telemere-receiving? true}}
org.clojure/tools.logging dependency(tel/tools-logging->telemere!) or set env configVerify: (tel/check-interop) => {:tools-logging {:telemere-receiving? true}}
Redirect System/out and System/err to Telemere:
(tel/streams->telemere!)
See references/config.md for OpenTelemetry integration.
;; Fixed message
(tel/log! "User logged in")
;; Joined message vector
(tel/log! ["User" user-id "logged in"])
;; With preprocessing
(tel/log!
{:let [username (str/upper-case raw-name)
balance (parse-double raw-balance)]
:data {:username username
:balance balance}}
["User" username "balance:" (format "$%.2f" balance)])
(defn process-order [order-id]
(tel/trace! {:id ::process-order :data {:order-id order-id}}
(let [order (fetch-order order-id)
_ (tel/trace! {:id ::validate-order}
(validate-order order))
_ (tel/trace! {:id ::charge-payment}
(charge-payment order))]
(ship-order order))))
;; Set context for all signals in scope
(tel/with-ctx {:request-id request-id
:user-id user-id}
(tel/log! {:id ::processing} "Started")
(process-request)
(tel/log! {:id ::complete} "Done"))
;; Simple error logging
(try
(risky-op)
(catch Exception e
(tel/error! ::operation-failed e)))
;; Automatic error catching with fallback
(tel/catch->error! ::fetch-user
{:catch-val {:id nil :name "Guest"}}
(fetch-user-from-db user-id))
Always call stop-handlers! on shutdown to flush buffers and close resources. Use tel/call-on-shutdown! for JVM shutdown hooks.
Signals are filtered before creation - data in :let, :data, :msg, :do is only evaluated if the signal passes filters.
Handler filters are additive - handlers can be MORE restrictive than call filters, not less.
Messages are lazy - message building only happens if the signal is created and handled.
:error value != :error level - signals can have error values at any level, and vice versa.
Cache validators - use tel/validator, tel/decoder, etc. once, not per signal.
Telemere is highly optimized:
Tips for performance:
Telemere includes extensive docstrings accessible from your REPL:
tel/help:signal-creators - Creating signalstel/help:signal-options - All signal optionstel/help:signal-content - Signal map contenttel/help:filters - Filtering and transformationstel/help:handlers - Handler managementtel/help:handler-dispatch-options - Handler dispatch configurationtel/help:environmental-config - JVM/env/classpath configurationtesting
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.