-
-
Notifications
You must be signed in to change notification settings - Fork 4
1 Getting started
Telemere is a structured telemetry library and next-generation replacement for Timbre. It helps enable the creation of Clojure/Script systems that are highly observable, robust, and debuggable.
Its key function is to help:
- Capture data in your running Clojure/Script programs, and
- Facilitate processing of that data into useful information / insight.
The basic unit of data in Telemere is the signal.
Signals include traditional log messages, structured log messages, and events. Telemere doesn't make a hard distinction between these - they're all just signals with various attributes.
And they're represented by plain Clojure/Script maps with those attributes (keys).
Fundamentally all signals:
- Occur or are observed at a particular location in your code (file, namespace, line, column).
- Occur or are observed within a particular program state / context.
- Convey something of value about that program state / context.
Signals may be independently valuable, valuable in the aggregate (e.g. statistically), or valuable in association with other related signals (e.g. while tracing the flow of some logical activity).
The basic tools of Telemere are:
- Signal creators to conditionally create signal maps at points in your code.
- Signal handlers to conditionally handle those signal maps (analyse, write to console/file/queue/db, etc.).
This is just a generalization of traditional logging which:
- Conditionally creates message strings at points in your code.
- Usually dumps those message strings somewhere for future parsing by human eyes or automated tools.
The parsing of traditional log messages is often expensive, fragile, and lossy. So a key principle of structured logging is to avoid parsing, by instead preserving data types and structures whenever possible.
Telemere embraces this principle by making such preservation natural and convenient.
Not all data is equally valuable.
Too much low-value data is often actively harmful: expensive to process, to store, and to query. Adding noise just interferes with better data, harming your ability to understand your system.
Telemere embraces this principle by making effective filtering likewise natural and convenient:
Telemere uses the term filtering as the superset of both random sampling and other forms of data exclusion/reduction.
To conclude- Telemere handles structured and traditional logging, tracing, and basic performance monitoring with a simple unified API that:
- Preserves data types and structures with rich signals, and
- Offers effective noise reduction with signal filtering.
Its name is a combination of telemetry and telomere:
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.
Telomere derives from the Greek télos (end) and méros (part). It refers to a genetic feature commonly found at the end of linear chromosomes that helps to protect chromosome integrity.
Add the relevant dependency to your project:
Leiningen: [com.taoensso/telemere "x-y-z"] ; or
deps.edn: com.taoensso/telemere {:mvn/version "x-y-z"}
And setup your namespace imports:
(ns my-app (:require [taoensso.telemere :as t]))
Telemere is configured sensibly out-the-box.
See section 3-Config for customization.
Default minimum level: :info
(signals with lower levels will no-op).
Default signal handlers:
Signal handlers process created signals to do something with them (analyse them, write them to console/file/queue/db, etc.)
Platform | Condition | Handler |
---|---|---|
Clj | Always |
Console handler that prints signals to *out* or *err* . |
Cljs | Always | Console handler that prints signals to the browser console. |
Clj | OpenTelemetry API present |
OpenTelemetry handler that emits signals as log records to a configured LoggerProvider . |
Default signal intakes:
Telemere can create signals from relevant external API calls, etc.
Platform | Condition | Signals from |
---|---|---|
Clj | SLF4J API and Telemere SLF4J backend present | SLF4J logging calls. |
Clj |
clojure.tools.logging present and tools-logging->telemere! called |
clojure.tools.logging logging calls. |
Clj |
streams->telemere! called |
Output to System/out and System/err streams. |
Run check-intakes
to help verify/debug:
(check-intakes) ; =>
{:tools-logging {:present? false}
:slf4j {:sending->telemere? true, :telemere-receiving? true}
:system/out {:sending->telemere? false, :telemere-receiving? false}
:system/err {:sending->telemere? false, :telemere-receiving? false}}
Use whichever signal creator is most convenient for your needs:
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 |
- See
help:signal-creators
for more info on signal creators. - See
help:signal-options
for signal options (shared by all creators). - See relevant docstrings (links above) for usage info.
- See examples.cljc for REPL-ready examples.
Use the with-signal
or (advanced) with-signals
utils to help test/debug the signals that you're creating:
(t/with-signal
(t/log!
{:let [x "x"]
:data {:x x}}
["My msg:" x]))
;; => {:keys [ns inst data msg_ ...]} ; The signal
-
with-signal
will return the last signal created by the given form. -
with-signals
will return all signals created by the given form.
Both have several options, see their docstrings (links above) for details.
A signal will be provided to a handler iff ALL of the following are true:
- Signal creation is allowed by compile-time filter config
- Signal creation is allowed by runtime filter config
- Signal handling is allowed by handler filter config
- Signal middleware does not suppress the signal (return nil)
- Handler middleware does not suppress the signal (return nil)
For 1-3, filtering may depend on (in order): Sample rate → namespace → kind → id → level → when form/fn → rate limit
Quick examples of some basic filtering:
(t/set-min-level! :info) ; Set global minimum level
(t/with-signal (t/event! ::my-id1 :info)) ; => {:keys [inst id ...]}
(t/with-signal (t/event! ::my-id1 :debug)) ; => nil (signal not allowed)
(t/with-min-level :trace ; Override global minimum level
(t/with-signal (t/event! ::my-id1 :debug))) ; => {:keys [inst id ...]}
;; Deny all signals in matching namespaces
(t/set-ns-filter! {:deny "some.nosy.namespace.*"})
- Filtering is always O(1), except for rate limits which are O(n_windows).
- Sample rates are multiplicative: if a signal is created with 20% sampling and a handler handles 50% of given signals, then 10% of possible signals will be handled. This multiplicative rate is helpfully reflected in each signal's final
:sample-rate
value. - See
help:signal-flow
for internal docs on signal flow. - See section 2-Architecture for a flowchart / visual aid.
Runtime signal filters can be configured with:
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) |
- See relevant docstrings (links above) for usage info.
- Compile-time filters are controlled by system-level config, see section 3-Config.
Telemere includes extensive internal help docstrings:
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 |