- name
- telemere
- description
- Structured logging and telemetry for Clojure/Script with tracing and performance monitoring
# Telemere
Structured logging and telemetry library for Clojure and ClojureScript. Next-generation successor to Timbre with unified API for logging, tracing, and performance monitoring.
## Overview
Telemere provides a unified approach to application observability, handling traditional logging, structured telemetry, distributed tracing, and performance monitoring through a single consistent API.
**Key Features:**
- Structured data throughout pipeline (no string parsing)
- Compile-time signal elision (zero runtime cost for disabled signals)
- Runtime filtering (namespace, level, ID, rate limiting, sampling)
- Async and sync handler dispatch
- OpenTelemetry, SLF4J, and tools.logging interoperability
- Zero-configuration defaults
- ClojureScript support
**Artifact:** `com.taoensso/telemere`
**Latest Version:** 1.1.0
**License:** EPL-1.0
**Repository:** https://github.com/taoensso/telemere
## Installation
Add to `deps.edn`:
```clojure
{:deps {com.taoensso/telemere {:mvn/version "1.1.0"}}}
```
Or Leiningen `project.clj`:
```clojure
[com.taoensso/telemere "1.1.0"]
```
Import in namespace:
```clojure
(ns my-app
(:require [taoensso.telemere :as t]))
```
## Core Concepts
### Signals
Signals are structured telemetry events represented as Clojure maps with standardized attributes. They preserve data types throughout the logging pipeline rather than converting to strings.
Signal attributes include: namespace, level, ID, timestamp, thread info, line number, form data, return values, custom data maps.
### Default Configuration
Out-of-the-box settings:
- Minimum level: `:info`
- Handler: Console output to `*out*` or browser console
- Automatic interop with SLF4J, tools.logging when present
### Filtering Philosophy
Two-stage filtering:
1. **Call-time** (compile + runtime): Determines if signal is created
2. **Handler-time** (runtime): Determines which handlers process signal
Effective filtering reduces noise and improves performance.
## API Reference
### Signal Creation
#### log!
Traditional and structured logging.
```clojure
;; Basic logging with level
(t/log! :info "Processing started")
(t/log! :warn "High memory usage")
(t/log! :error "Database connection failed")
;; With message arguments
(t/log! :info ["User logged in:" {:user-id 123}])
;; Structured data
(t/log! {:level :info
:data {:user-id 123 :action "login"}})
;; With ID for filtering
(t/log! {:id :user-action
:level :info
:data {:user-id 123}})
```
**Levels (priority order):**
`:trace` < `:debug` < `:info` < `:warn` < `:error` < `:fatal` < `:report`
**Options:**
- `:level` - Signal level (keyword)
- `:id` - Signal ID for filtering (keyword)
- `:data` - Structured data map
- `:msg` - Message string or vector
- `:error` - Exception/error object
- `:ctx` - Context map
- `:sample-rate` - Signal sampling (0.0-1.0)
- `:rate-limit` - Rate limiting spec
- `:run` - Form to evaluate and include result
#### event!
ID and level-based event logging.
```clojure
;; Simple event
(t/event! :user-signup)
(t/event! :payment-processed)
;; With level
(t/event! :cache-miss :warn)
;; With data
(t/event! :user-signup
{:data {:user-id 123 :email "user@example.com"}})
;; With level and data
(t/event! :slow-query :warn
{:data {:duration-ms 1200 :query "SELECT ..."}})
```
Events are filtered by ID, making them ideal for metrics and tracking specific occurrences.
#### trace!
Tracks form execution with nested flow tracking.
```clojure
;; Basic tracing
(t/trace! :fetch-user
(fetch-user-from-db user-id))
;; Returns form result while logging execution
(def user
(t/trace! :fetch-user
(fetch-user-from-db 123)))
;; With data
(t/trace! {:id :process-order
:data {:order-id 456}}
(process-order 456))
;; Nested tracing shows parent-child relationships
(t/trace! :outer
(do
(t/trace! :inner-1 (step-1))
(t/trace! :inner-2 (step-2))))
```
Trace signals include execution time and return value. Nested traces maintain parent-child relationships.
#### spy!
Execution tracing with return value capture.
```clojure
;; Spy on expression
(t/spy! :debug
(+ 1 2 3))
;;=> 6 (also logs the expression and result)
;; Spy in pipeline
(->> data
(map inc)
(t/spy! :debug) ; See intermediate value
(filter even?))
;; With custom ID
(t/spy! {:id :computation :level :trace}
(* 42 (expensive-calc)))
```
Spy always returns the form result, making it useful in pipelines.
#### error!
Error logging with exception handling.
```clojure
;; Log error
(t/error! (ex-info "Failed" {:reason :timeout}))
;; With ID
(t/error! :db-error
(ex-info "Connection lost" {:host "db.example.com"}))
;; With additional data
(t/error! {:id :api-error
:data {:endpoint "/users" :status 500}}
(ex-info "API failed" {}))
```
Returns the error object.
#### catch->error!
Catch and log exceptions.
```clojure
;; Basic error catching
(t/catch->error!
(risky-operation))
;; With ID
(t/catch->error! :db-operation
(db-query))
;; With data
(t/catch->error! {:id :api-call
:data {:endpoint "/users"}}
(http-request "/users"))
;; Returns nil on error, result on success
(if-let [result (t/catch->error! (fetch-data))]
(process result)
(handle-error))
```
Catches exceptions, logs them, and returns nil. Returns form result if no exception.
#### signal!
Low-level signal creation with full control.
```clojure
;; Full signal specification
(t/signal!
{:kind :log
:level :info
:id :custom-event
:ns (str *ns*)
:data {:key "value"}
:msg "Custom message"
:run (do-something)})
```
Most use cases are better served by higher-level functions.
### Configuration
#### set-min-level!
Set global or namespace-specific minimum level.
```clojure
;; Global minimum level
(t/set-min-level! :warn)
;; Namespace-specific
(t/set-min-level! 'my.app.core :debug)
(t/set-min-level! 'my.app.* :info)
;; Per-namespace map
(t/set-min-level!
[['my.app.* :info]
['my.app.db :debug]
['noisy.library.* :error]])
```
Signals below minimum level are filtered at call-time.
#### set-ns-filter!
Configure namespace filtering.
```clojure
;; Allow only specific namespaces
(t/set-ns-filter! {:allow #{"my.app.*"}})
;; Disallow specific namespaces
(t/set-ns-filter! {:disallow #{"noisy.library.*"}})
;; Combined
(t/set-ns-filter!
{:allow #{"my.app.*"}
:disallow #{"my.app.test.*"}})
```
Namespace patterns support wildcards (`*`).
#### with-min-level
Temporarily override minimum level.
```clojure
;; Enable debug logging for block
(t/with-min-level :debug
(t/log! :debug "Debug info") ; Logged
(process-data))
;; Nested overrides
(t/with-min-level :warn
(t/with-min-level :trace ; Inner level applies
(t/log! :trace "Trace info")))
```
Scope is thread-local and dynamic.
#### with-signal
Capture last signal for testing.
```clojure
;; Capture signal map
(def sig
(t/with-signal
(t/log! {:level :info :data {:x 1}})))
(:level sig) ;;=> :info
(:data sig) ;;=> {:x 1}
;; Test signal creation
(let [sig (t/with-signal
(t/event! :test-event {:data {:y 2}}))]
(assert (= :test-event (:id sig)))
(assert (= {:y 2} (:data sig))))
```
Returns signal map instead of nil.
#### with-signals
Capture all signals from form.
```clojure
;; Capture multiple signals
(def sigs
(t/with-signals
(t/log! :info "First")
(t/log! :warn "Second")
(t/event! :third)))
(count sigs) ;;=> 3
(map :level sigs) ;;=> (:info :warn :info)
```
Returns vector of signal maps.
### Handlers
Handlers process signals and route them to destinations (console, files, databases, analytics).
#### add-handler!
Register signal handler.
```clojure
;; Console handler (built-in)
(t/add-handler! :my-console
(t/handler:console))
;; Custom handler function
(t/add-handler! :custom
(fn [signal]
(println "Custom:" (:msg signal))))
;; With filtering
(t/add-handler! :error-only
(t/handler:console)
{:min-level :error})
;; With async dispatch
(t/add-handler! :async-log
(fn [signal] (log-to-db signal))
{:async {:buffer-size 1024
:n-threads 2}})
;; With sampling
(t/add-handler! :sampled
(t/handler:console)
{:sample-rate 0.1}) ; 10% of signals
```
**Handler Options:**
- `:min-level` - Minimum signal level
- `:ns-filter` - Namespace filter
- `:id-filter` - ID filter
- `:sample-rate` - Sampling rate (0.0-1.0)
- `:rate-limit` - Rate limiting spec
- `:async` - Async dispatch config
- `:middleware` - Transform functions
#### remove-handler!
Remove handler by ID.
```clojure
(t/remove-handler! :my-console)
(t/remove-handler! :custom)
```
#### handler:console
Built-in console handler with formatting.
```clojure
;; Default text format
(t/handler:console)
;; JSON format
(t/handler:console {:format :json})
;; EDN format
(t/handler:console {:format :edn})
;; Custom format function
(t/handler:console
{:format (fn [signal]
(pr-str (:data signal)))})
```
#### handler:stream
Output to Java OutputStream or Writer.
```clojure
;; File output
(t/add-handler! :file
(t/handler:stream
(io/output-stream "app.log")
{:format :json}))
;; With rotation (requires additional setup)
(t/add-handler! :rotating-file
(rotating-file-handler "logs/app.log"))
```
### Filtering Utilities
#### check-min-level
Check if level passes minimum threshold.
```clojure
(t/check-min-level :info) ;;=> true/false
Ver no GitHub