Skip to content
Back to skills

Clojure Telemere

ASecurity

Structured telemetry library for Clojure/Script. Use when working with logging, tracing, structured logging, events, signal handling, observability, or replacing Timbre/tools.logging.

  • 666 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added February 8, 2026
documentationgojavadebugginggitapiperformance

Works with

  • api

Security analysis

A100/100

Scanned February 12, 2026

npx -y skills add majiayu000/claude-skill-registry --skill clojure-telemere --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Clojure Telemere?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Clojure Telemere
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/majiayu000-clojure-telemere/badge)](https://www.skillsdirectory.com/skills/majiayu000-clojure-telemere)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: clojure-telemere
description: Structured telemetry library for Clojure/Script. Use when working with logging, tracing, structured logging, events, signal handling, observability, or replacing Timbre/tools.logging.
---

# Telemere

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.

## Setup

deps.edn:
```clojure
com.taoensso/telemere {:mvn/version "1.2.1"}
```

Leiningen:
```clojure
[com.taoensso/telemere "1.2.1"]
```

See https://clojars.org/com.taoensso/telemere for the latest version.

Namespace setup:
```clojure
(ns my-app
  (:require [taoensso.telemere :as tel]))
```

## Quick Start

Telemere works out-of-the-box with no config needed. Signals print to console by default:

```clojure
(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_ ...]}
```

## Core Signal Creators

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:
```clojure
;; 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
```

## Signal Options

All signal creators accept a map of options:

```clojure
(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 created

## Filtering

Filtering happens at multiple stages for efficiency:

```clojure
;; 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.

## Signal Handlers

Handlers process created signals (write to console, file, DB, etc.):

```clojure
;; 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!)
```

### Included Handlers

Console handlers (output as formatted text, edn, or JSON):
```clojure
;; 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)

## Interop

### SLF4J (Java Logging)

1. Add dependencies:
   - `org.slf4j/slf4j-api` (v2+)
   - `com.taoensso/telemere-slf4j`

2. SLF4J calls automatically become Telemere signals

Verify: `(tel/check-interop)` => `{:slf4j {:telemere-receiving? true}}`

### tools.logging

1. Add `org.clojure/tools.logging` dependency
2. Call `(tel/tools-logging->telemere!)` or set env config

Verify: `(tel/check-interop)` => `{:tools-logging {:telemere-receiving? true}}`

### System Streams

Redirect `System/out` and `System/err` to Telemere:
```clojure
(tel/streams->telemere!)
```

### OpenTelemetry

See [references/config.md](references/config.md#opentelemetry) for OpenTelemetry integration.

## Common Patterns

### Message Building

```clojure
;; 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)])
```

### Tracing Nested Operations

```clojure
(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))))
```

### Dynamic Context

```clojure
;; 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"))
```

### Error Handling

```clojure
;; 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))
```

## Key Gotchas

1. **Always call `stop-handlers!`** on shutdown to flush buffers and close resources. Use `tel/call-on-shutdown!` for JVM shutdown hooks.

2. **Signals are filtered before creation** - data in `:let`, `:data`, `:msg`, `:do` is only evaluated if the signal passes filters.

3. **Handler filters are additive** - handlers can be MORE restrictive than call filters, not less.

4. **Messages are lazy** - message building only happens if the signal is created and handled.

5. **`:error` value != `:error` level** - signals can have error values at any level, and vice versa.

6. **Cache validators** - use `tel/validator`, `tel/decoder`, etc. once, not per signal.

## Performance

Telemere is highly optimized:
- Filtered signals: ~350 nsecs/call
- Compile-time elision: 0 nsecs (completely removed)
- Handler dispatch is typically async with backpressure control

Tips for performance:
- Use compile-time filtering for hot paths
- Use sampling for high-volume signals
- Use rate limiting for expensive operations
- Cache validators/transformers outside signal calls

## Detailed References

- [Getting Started](references/getting-started.md) - Setup, usage, default config
- [Architecture](references/architecture.md) - How signal flow works
- [Config](references/config.md) - Filtering, handlers, interop configuration
- [Handlers](references/handlers.md) - Writing and configuring handlers
- [FAQ](references/faq.md) - Common questions (vs Timbre, vs μ/log, etc.)
- [Tips](references/tips.md) - Best practices for observable systems

## External References

- GitHub: https://github.com/taoensso/telemere
- Wiki: https://github.com/taoensso/telemere/wiki
- API Docs: https://cljdoc.org/d/com.taoensso/telemere/
- Slack: https://www.taoensso.com/telemere/slack

## Internal Help

Telemere includes extensive docstrings accessible from your REPL:
- `tel/help:signal-creators` - Creating signals
- `tel/help:signal-options` - All signal options
- `tel/help:signal-content` - Signal map content
- `tel/help:filters` - Filtering and transformations
- `tel/help:handlers` - Handler management
- `tel/help:handler-dispatch-options` - Handler dispatch configuration
- `tel/help:environmental-config` - JVM/env/classpath configuration

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.