Structured logs and telemetry for Clojure/Script
Find a file
Peter Taoussanis ffea1a30ed [fix] Fix broken AOT support, add AOT tests
Thanks to @AdamFrey for reporting this issue!
Ref. <https://clojurians.slack.com/archives/C06ALA6EEUA/p1713805333272469>

Previously:

  Attempting to run AOT'd code using Telemere would result in errors like:
  "Attempting to call unbound fn: #'taoensso.telemere.handlers.open-telemetry/handler:open-telemetry-logger"

The approach I was using of conditionally requiring namespaces and then aliasing vars seems to be inherently
fragile under AOT, and was leading to the remote source var being unbound.

With this commit I've now switched to a simpler approach - where we conditionally require namespaces *without*
the need for any aliasing.
2024-04-24 13:22:30 +02:00
.github/workflows [new] Add Clojure v1.12, JVM v21 to tests 2024-03-11 11:36:26 +01:00
bb [new] Add initial code, content 2024-03-08 10:35:10 +01:00
doc [new] Add initial code, content 2024-03-08 10:35:10 +01:00
imgs [doc] Update images 2024-04-17 11:00:27 +02:00
public [new] Add basic shadow-cljs setup 2024-03-28 16:24:08 +01:00
resources/signal-docstrings [nop] Misc housekeeping 2024-04-18 12:05:32 +02:00
slf4j [fix] Fix broken AOT support, add AOT tests 2024-04-24 13:22:30 +02:00
src/taoensso [fix] Fix broken AOT support, add AOT tests 2024-04-24 13:22:30 +02:00
test/taoensso [fix] Fix broken AOT support, add AOT tests 2024-04-24 13:22:30 +02:00
wiki [fix] Fix broken AOT support, add AOT tests 2024-04-24 13:22:30 +02:00
.gitignore [new] Add archiving file handler 2024-04-05 17:51:14 +02:00
bb.edn [new] Add initial code, content 2024-03-08 10:35:10 +01:00
CHANGELOG.md v1.0.0-beta1 (2024-04-19) 2024-04-19 13:04:47 +02:00
examples.cljc [doc] Documentation work 2024-04-19 12:41:11 +02:00
FUNDING.yml [new] Add initial code, content 2024-03-08 10:35:10 +01:00
LICENSE.txt [nop] Add initial LICENSE.txt 2023-08-28 12:13:21 +02:00
project.clj [fix] Fix broken AOT support, add AOT tests 2024-04-24 13:22:30 +02:00
README.md v1.0.0-beta1 (2024-04-19) 2024-04-19 13:04:47 +02:00
SECURITY.md [new] Add initial code, content 2024-03-08 10:35:10 +01:00
shadow-cljs.edn [nop] Bump deps 2024-04-24 12:41:50 +02:00
shadow-cljs.sh [new] Add basic shadow-cljs setup 2024-03-28 16:24:08 +01:00

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 support for system-level config (JVM props, ENV vars, 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).