Skip to content

taoensso/telemere

Repository files navigation

Taoensso open source
Documentation | Latest releases | Slack channel

Telemere logo

Structured telemetry library for Clojure/Script

Telemere is a next-generation replacement for Timbre. It handles structured and traditional logging, tracing, and basic performance monitoring with a simple unified API.

It helps enable Clojure/Script systems that are observable, robust, and debuggable - and it represents the refinement and culmination of ideas brewing over 12+ years in Timbre, Tufte, Truss, etc.

[Terminology] Telemetry derives from the Greek tele (remote) and metron (measure). It refers to the collection of in situ (in position) data, for transmission to other systems for monitoring/analysis. Logs are the most common form of software telemetry. So think of telemetry as the superset of logging-like activities that help monitor and understand (software) systems.

Latest release/s

Main tests Graal tests

See here for earlier releases.

Why Telemere?

Ergonomics

  • Elegant, lightweight API that's easy to use, easy to configure, and deeply flexible.
  • Sensible defaults to make getting started fast and easy.
  • Extensive beginner-oriented documentation, docstrings, and error messages.

Interop

Scaling

  • Hyper-optimized and blazing fast, see benchmarks.
  • An API that scales comfortably from the smallest disposable code, to the most massive and complex real-world production environments.

Flexibility

  • Config via plain Clojure vals and fns for easy customization, composition, and REPL debugging.
  • Unmatched environmental config support (JVM properties, environment variables, or classpath resources).
  • Expressive per-call and per-handler filtering at both runtime and compile-time.
  • Filter by namespace and id pattern, level, level by namespace pattern, etc.
  • Sampling, rate-limiting, and back-pressure monitoring.
  • Fully configurable a/sync dispatch (blocking, dropping, sliding, etc.).

Video demo

See for intro and usage:

Telemere demo video

Quick examples

(require '[taoensso.telemere :as t])

;; Start simple
(t/log! "This will send a `:log` signal to the Clj/s console")
(t/log! :info "This will do the same, but only when the current level is >= `:info`")

;; Easily construct messages
(let [x "constructed"] (t/log! :info ["Here's a" x "message!"]))

;; Attach an id
(t/log! {:level :info, :id ::my-id} "This signal has an id")

;; Attach arb user data
(t/log! {:level :info, :data {:x :y}} "This signal has structured data")

;; Capture for debug/testing
(t/with-signal (t/log! "This will be captured"))
;; => {:keys [location level id data msg_ ...]}

;; `:let` bindings available to `:data` and message, only paid for
;; when allowed by minimum level and other filtering criteria
(t/log!
  {:level :info
   :let [expensive-metric1 (last (for [x (range 100), y (range 100)] (* x y)))]
   :data {:metric1 expensive-metric1}}
  ["Message with metric value:" expensive-metric1])

;; With sampling 50% and 1/sec rate limiting
(t/log!
  {:sample-rate 0.5
   :rate-limit  {"1 per sec" [1 1000]}}
  "This signal will be sampled and rate limited")

;;; A quick taste of filtering...

(t/set-ns-filter! {:deny "taoensso.*" :allow "taoensso.sente.*"}) ; Set namespace filter
(t/set-id-filter! {:allow #{::my-particular-id "my-app/*"}})      ; Set id        filter

(t/set-min-level!       :warn) ; Set minimum level
(t/set-min-level! :log :debug) ; Set minimul level for `:log` signals

;; Set minimum level for `:event` signals originating in the "taoensso.sente.*" ns
(t/set-min-level! :event "taoensso.sente.*" :warn)

See examples.cljc for more REPL-ready snippets.

API overview

See relevant docstrings (links below) for usage info-

Signal creators

Name Signal kind Main arg Optional arg Returns
log! :log msg opts/level Signal allowed?
event! :event id opts/level Signal allowed?
error! :error error opts/id Given error
trace! :trace form opts/id Form result
spy! :spy form opts/level Form result
catch->error! :error form opts/id Form value or given fallback
signal! <arb> opts - Depends on opts

Signal filters

Global Dynamic Filters by
set-kind-filter! with-kind-filter Signal kind (:log, :event, etc.)
set-ns-filter! with-ns-filter Signal namespace
set-id-filter! with-id-filter Signal id
set-min-level with-min-level Signal level (minimum can be specified by kind and/or ns)

Internal help

Var Help with
help:signal-creators List of signal creators
help:signal-options Options for signal creators
help:signal-content Signal map content
help:signal-flow Ordered flow from signal creation to handling
help:signal-filters API for configuring signal filters
help:signal-handlers API for configuring signal handlers
help:signal-formatters Signal formatters for use by handlers

Example handler output

(t/log! {:id ::my-id, :data {:x1 :x2}} "My message") =>

Clj console handler

String output:

2024-04-11T10:54:57.202869Z INFO LOG Schrebermann.local examples(56,1) ::my-id - My message
    data: {:x1 :x2}

Cljs console handler

Chrome console:

Default ClojureScript console handler output

Cljs raw console handler

Chrome console, with cljs-devtools:

Raw ClojureScript console handler output

Clj file handler

MacOS terminal:

Default Clojure file handler output

Documentation

Observability tips

See here for general advice re: building and maintaining observable Clojure/Script systems.

Benchmarks

Telemere is highly optimized and offers terrific performance at any scale:

Compile-time filtering? Runtime filtering? Time? Trace? nsecs
✓ (elide) - - - 0
- - - 200
- - 280
- 650

Measurements:

  • Are ~nanoseconds per signal call (= milliseconds per 1e6 calls)
  • Exclude handler runtime (which depends on handler/s, is usually async)
  • Taken on a 2020 Macbook Pro M1, running OpenJDK 21

Tip: Telemere offers extensive per-call and per-handler filtering, sampling, and rate-limiting. Use these to ensure that you're not capturing useless/low-value information in production. See here for more tips!

Funding

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

License

Copyright © 2023-2024 Peter Taoussanis.
Licensed under EPL 1.0 (same as Clojure).