Compare commits

..

No commits in common. "master" and "v1.0.0" have entirely different histories.

34 changed files with 386 additions and 576 deletions

View file

@ -1,27 +0,0 @@
name: Cljs tests
on: [push, pull_request]
jobs:
tests:
strategy:
matrix:
java: ['21']
os: [ubuntu-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: 'corretto'
java-version: ${{ matrix.java }}
- uses: DeLaGuardo/setup-clojure@12.5
with:
lein: latest
- uses: actions/cache@v4
id: cache-deps
with:
path: ~/.m2/repository
key: deps-${{ hashFiles('main/project.clj') }}
restore-keys: deps-
- run: lein test-cljs
working-directory: main

View file

@ -2,7 +2,7 @@ name: Graal tests
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
tests: test:
strategy: strategy:
matrix: matrix:
java: ['17'] java: ['17']
@ -29,10 +29,5 @@ jobs:
key: deps-${{ hashFiles('main/project.clj') }} key: deps-${{ hashFiles('main/project.clj') }}
restore-keys: deps- restore-keys: deps-
- name: Run Graal tests - run: bb graal-tests
run: bb graal-tests
working-directory: main working-directory: main
# - name: Run Babashka tests
# run: bb bb-tests
# working-directory: main

View file

@ -1,4 +1,4 @@
name: Clj tests name: Main tests
on: [push, pull_request] on: [push, pull_request]
jobs: jobs:
@ -7,6 +7,7 @@ jobs:
matrix: matrix:
java: ['17', '19', '21'] java: ['17', '19', '21']
os: [ubuntu-latest] os: [ubuntu-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -14,14 +15,17 @@ jobs:
with: with:
distribution: 'corretto' distribution: 'corretto'
java-version: ${{ matrix.java }} java-version: ${{ matrix.java }}
- uses: DeLaGuardo/setup-clojure@12.5 - uses: DeLaGuardo/setup-clojure@12.5
with: with:
lein: latest lein: latest
- uses: actions/cache@v4 - uses: actions/cache@v4
id: cache-deps id: cache-deps
with: with:
path: ~/.m2/repository path: ~/.m2/repository
key: deps-${{ hashFiles('main/project.clj') }} key: deps-${{ hashFiles('main/project.clj') }}
restore-keys: deps- restore-keys: deps-
- run: lein test-clj
- run: lein test-all
working-directory: main working-directory: main

View file

@ -2,106 +2,6 @@ This project uses [**Break Versioning**](https://www.taoensso.com/break-versioni
--- ---
# `v1.2.1` (2025-12-16)
## 📦 Dependencies
Available on Clojars:
1. [Telemere](https://clojars.org/com.taoensso/telemere/versions/1.2.1) - main dep
2. [SLF4J provider](https://clojars.org/com.taoensso/telemere-slf4j/versions/1.2.1) - extra dep to [send Java logging](https://github.com/taoensso/telemere/wiki/3-Config#java-logging) to Telemere
This project uses [Break Versioning](https://www.taoensso.com/break-versioning).
## Release notes
This is a **hotfix release** to fix a regression in v1.2.0 that prevented errors from correctly appearing via the Timbre->Telemere appender.
This should be a safe upgrade for users of v1.2.0, apologies for the trouble! - Peter Taoussanis
---
# `v1.2.0` (2025-12-09)
## 📦 Dependencies
Available on Clojars:
1. [Telemere](https://clojars.org/com.taoensso/telemere/versions/1.2.0) - main dep
2. [SLF4J provider](https://clojars.org/com.taoensso/telemere-slf4j/versions/1.2.0) - extra dep to [send Java logging](https://github.com/taoensso/telemere/wiki/3-Config#java-logging) to Telemere
This project uses [Break Versioning](https://www.taoensso.com/break-versioning).
## Release notes
This is a **maintenance and feature release** that should be a safe upgrade for users of v1.1.x, though there have been a few small **changes to signal content** relevant to a very small number of users (see the ➤ items below).
Please **report any unexpected problems** on [GitHub](https://github.com/taoensso/telemere/issues) or the [Slack channel](https://www.taoensso.com/telemere/slack) 🙏 - [Peter Taoussanis](https://www.taoensso.com)
## Since `v1.1.0` (2025-08-22)
- ➤ **\[mod]** SLF4J->Telemere backend: move noisy stuff out of signal data \[e6ce33d]
- ➤ **\[mod]** Timbre shim API: move noisy `:vargs` out of signal data \[cc680b0
- \[mod] [fix] Timbre->Telemere appender: de-duplicate output formatting \[47af803]
- \[mod] [fix] Timbre->Telemere appender: fix callsite coords \[b56e1c4]
- \[fix] OpenTelemetry handler: add missing line info to output \[6155713]
- \[fix] Correctly handle nil `:run` opt \[8a3ae14]
- \[new] OpenTelemetry handler: support spans created outside Telemere \[a6fc4ad]
- \[new] [#68] Add config to skip host and/or thread info \[a883df3]
- \[doc] Clarify that signal content is lazy \[917b1b4]
---
# `v1.1.0` (2025-08-22)
## 📦 Dependencies
Available on Clojars:
1. [Telemere](https://clojars.org/com.taoensso/telemere/versions/1.1.0) - main dep
2. [SLF4J provider](https://clojars.org/com.taoensso/telemere-slf4j/versions/1.1.0) - extra dep to [send Java logging](https://github.com/taoensso/telemere/wiki/3-Config#java-logging) to Telemere
This project uses [Break Versioning](https://www.taoensso.com/break-versioning).
## Release notes
This is a **maintenance release** that fixes a few minor issues, improves docs, and adds some extra API flexibility. It should be a safe upgrade for all users of v1.x.
Please **report any unexpected problems** on [GitHub](https://github.com/taoensso/telemere/issues) or the [Slack channel](https://www.taoensso.com/telemere/slack) 🙏 - [Peter Taoussanis](https://www.taoensso.com)
## Since v1.0.1 (2025-05-27)
- \[fix] `:trace` level JS console logging \[b2a8b66]
- \[fix] Clj-kondo warnings for `with-signal/s` \[269c58d]
- \[new] `with-ctx/+` now takes `& body` instead of a single form (via Encore update)
---
# `v1.0.1` (2025-05-27)
## 📦 Dependencies
Available on Clojars:
1. [Telemere](https://clojars.org/com.taoensso/telemere/versions/1.0.1) - main dependency.
2. [SLF4J provider](https://clojars.org/com.taoensso/telemere-slf4j/versions/1.0.1) - additional dependency for users that want their Java logging [to go to](https://github.com/taoensso/telemere/wiki/3-Config#java-logging) Telemere.
This project uses [Break Versioning](https://www.taoensso.com/break-versioning).
## Release notes
This is a **hotfix release** that fixes a few issues, and improves some documentation. It should be a safe upgrade for all users of v1.0.0.
## Since `v1` (2025-04-30)
* \[fix] [#65] Fix broken callsite `:limit` option \[f08b60b]
* \[fix] Fix bad `signal-content-fn` parent formatting \[3746de8]
* \[doc] Add extra docs re: debugging filtering \[1bdb667]
* \[doc] [#64] Hide some unimportant vars from API docs (@marksto) \[2e0a293]
* \[doc] [#63] Add link to community Axiom handler (@marksto) \[9d040d7]
---
# `v1.0.0` (2025-04-30) # `v1.0.0` (2025-04-30)
## 📦 Dependencies ## 📦 Dependencies

211
README.md
View file

@ -1,73 +1,83 @@
<a href="https://www.taoensso.com/clojure" title="More stuff by @ptaoussanis at www.taoensso.com"><img src="https://www.taoensso.com/open-source.png" alt="Taoensso open source" width="340"/></a> <a href="https://www.taoensso.com/clojure" title="More stuff by @ptaoussanis at www.taoensso.com"><img src="https://www.taoensso.com/open-source.png" alt="Taoensso open source" width="340"/></a>
[**API**][cljdoc] | [**Wiki**][GitHub wiki] | [Slack][] | Latest release: [v1.2.1](../../releases/tag/v1.2.1) (2025-12-16) [**API**][cljdoc] | [**Wiki**][GitHub wiki] | [Latest releases](#latest-releases) | [Slack channel][]
[![Clj tests][Clj tests SVG]][Clj tests URL]
[![Cljs tests][Cljs tests SVG]][Cljs tests URL]
[![Graal tests][Graal tests SVG]][Graal tests URL]
# <img src="https://raw.githubusercontent.com/taoensso/telemere/master/imgs/telemere-logo.svg" alt="Telemere logo" width="360"/> # <img src="https://raw.githubusercontent.com/taoensso/telemere/master/imgs/telemere-logo.svg" alt="Telemere logo" width="360"/>
### Structured logs and telemetry for Clojure/Script ### Structured logs and telemetry for Clojure/Script
**Telemere** is the next-gen version of [Timbre](https://www.taoensso.com/timbre). It offers **one API** to cover: **Telemere** is a **pure Clojure/Script library** that offers an elegant and simple **unified API** to cover:
- **Traditional logging** (string messages) - **Traditional logging** (string messages)
- **Structured logging** (rich Clojure data types and structures) - **Structured logging** (rich Clojure data types and structures)
- **Events** (named thing happened, with optional data)
- **Tracing** (nested flow tracking, with optional data) - **Tracing** (nested flow tracking, with optional data)
- Basic **performance monitoring** (nested form runtimes) - Basic **performance monitoring** (nested form runtimes)
- Any combination of the above
It's pure Clj/s, small, **easy to use**, super fast, and **seriously flexible**: It's small, super fast, easy to learn, easy to use, and **absurdly flexible**.
Use it alone, or as part of a suite of complementary **observability tools** for modern Clojure/Script applications:
- [Telemere](https://www.taoensso.com/telemere) for logging, tracing, and general telemetry
- [Tufte](https://www.taoensso.com/tufte) for performance monitoring
- [Truss](https://www.taoensso.com/truss) for assertions and error handling
Together these help enable Clojure/Script systems that are **robust**, **fast**, and **easily debugged**.
See [here](../../wiki/1-Getting-started) for **full introduction** (concepts, terminology, getting started).
## Latest release/s
- `2025-04-30` `v1.0.0`: [release info](../../releases/tag/v1.0.0)
[![Main tests][Main tests SVG]][Main tests URL]
[![Graal tests][Graal tests SVG]][Graal tests URL]
<!--See [here][GitHub releases] for earlier releases.-->
## Next-gen observability
A key hurdle in building **observable systems** is that it's often inconvenient and costly to get out the kind of **detailed info** that we need when debugging.
Telemere's strategy to address this is to:
1. Provide **lean, low-fuss syntax** to let you conveniently convey program state.
2. Use the unique power of **Lisp macros** to let you **dynamically filter costs as you filter signals** (pay only for what you need, when you need it).
3. For those signals that *do* pass filtering: move costs from the callsite to a/sync handlers with explicit [threading and back-pressure semantics](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) and [performance monitoring](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats).
The effect is more than impressive micro-benchmarks. This approach enables a fundamental (qualitative) change in one's approach to observability.
It enables you to write code that is **information-verbose by default**.
## Quick examples
> (Or see [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cljc) for REPL-ready snippets)
<details open><summary>Create signals</summary><br/>
```clojure ```clojure
(tel/log! {:level :info, :id ::login, :data {:user-id 1234}, :msg "User logged in!"}) (require '[taoensso.telemere :as t])
```
Works great with: ;; (Just works / no config necessary for typical use cases)
- [Trove](https://www.taoensso.com/trove) for logging by **library authors** ;; Without structured data
- [Tufte](https://www.taoensso.com/tufte) for rich **performance monitoring** (t/log! :info "Hello world!") ; %> Basic log signal (has message)
- [Truss](https://www.taoensso.com/truss) for **assertions** and error handling (t/event! ::my-id :debug) ; %> Basic event signal (just id)
## Why structured logging? ;; With structured data
(t/log! {:level :info, :data {...}} "Hello again!")
- Traditional logging outputs **strings** (messages). (t/event! ::my-id {:level :debug, :data {...}})
- Structured logging in contrast outputs **data**. It retains **rich data types and (nested) structures** throughout the logging pipeline from logging callsite → filters → middleware → handlers.
A data-oriented pipeline can make a huge difference - supporting **easier filtering**, **transformation**, and **analysis**. Its also usually **faster**, since you only pay for serialization if/when you need it. In a lot of cases you can avoid serialization altogether if your final target (DB, etc.) supports the relevant types.
The structured (data-oriented) approach is inherently more flexible, faster, and well suited to the tools and idioms offered by Clojure and ClojureScript.
## Examples
See [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cljc) for REPL-ready snippets, or expand below:
<details><summary>Create signals</summary><br/>
```clojure
(require '[taoensso.telemere :as tel])
;; No config needed for typical use cases!!
;; Signals print to console by default for both Clj and Cljs
;; Traditional style logging (data formatted into message string):
(tel/log! {:level :info, :msg (str "User " 1234 " logged in!")})
;; Modern/structured style logging (explicit id and data)
(tel/log! {:level :info, :id :auth/login, :data {:user-id 1234}})
;; Mixed style (explicit id and data, with message string)
(tel/log! {:level :info, :id :auth/login, :data {:user-id 1234}, :msg "User logged in!"})
;; Trace (can interop with OpenTelemetry) ;; Trace (can interop with OpenTelemetry)
;; Tracks form runtime, return value, and (nested) parent tree ;; Tracks form runtime, return value, and (nested) parent tree
(tel/trace! {:id ::my-id :data {...}} (t/trace! {:id ::my-id :data {...}}
(do-some-work)) (do-some-work))
;; Check resulting signal content for debug/tests ;; Check resulting signal content for debug/tests
(tel/with-signal (tel/log! {...})) ; => {:keys [ns level id data msg_ ...]} (t/with-signal (t/event! ::my-id)) ; => {:keys [ns level id data msg_ ...]}
;; Getting fancy (all costs are conditional!) ;; Getting fancy (all costs are conditional!)
(tel/log! (t/log!
{:level :debug {:level :debug
:sample 0.75 ; 75% sampling (noop 25% of the time) :sample 0.75 ; 75% sampling (noop 25% of the time)
:when (my-conditional) :when (my-conditional)
@ -95,33 +105,33 @@ See [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cl
```clojure ```clojure
;; Set minimum level ;; Set minimum level
(tel/set-min-level! :warn) ; For all signals (t/set-min-level! :warn) ; For all signals
(tel/set-min-level! :log :debug) ; For `log!` signals specifically (t/set-min-level! :log :debug) ; For `log!` signals only
;; Set id and namespace filters ;; Set id and namespace filters
(tel/set-id-filter! {:allow #{::my-particular-id "my-app/*"}}) (t/set-id-filter! {:allow #{::my-particular-id "my-app/*"}})
(tel/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"}) (t/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"})
;; SLF4J signals will have their `:ns` key set to the logger's name ;; SLF4J signals will have their `:ns` key set to the logger's name
;; (typically a source class) ;; (typically a source class)
(tel/set-ns-filter! {:disallow "com.noisy.java.package.*"}) (t/set-ns-filter! {:disallow "com.noisy.java.package.*"})
;; Set minimum level for `log!` signals for particular ns pattern ;; Set minimum level for `event!` signals for particular ns pattern
(tel/set-min-level! :log "taoensso.sente.*" :warn) (t/set-min-level! :event "taoensso.sente.*" :warn)
;; Use transforms (xfns) to filter and/or arbitrarily modify signals ;; Use transforms (xfns) to filter and/or arbitrarily modify signals
;; by signal data/content/etc. ;; by signal data/content/etc.
(tel/set-xfn! (t/set-xfn!
(fn [signal] (fn [signal]
(if (-> signal :data :skip-me?) (if (-> signal :data :skip-me?)
nil ; Filter signal (don't handle) nil ; Filter signal (don't handle)
(assoc signal :transformed? true)))) (assoc signal :transformed? true))))
(tel/with-signal (tel/log! {... :data {:skip-me? true}})) ; => nil (t/with-signal (t/event! ::my-id {:data {:skip-me? true}})) ; => nil
(tel/with-signal (tel/log! {... :data {:skip-me? false}})) ; => {...} (t/with-signal (t/event! ::my-id {:data {:skip-me? false}})) ; => {...}
;; See `tel/help:filters` docstring for more filtering options ;; See `t/help:filters` docstring for more filtering options
``` ```
</details> </details>
@ -130,13 +140,13 @@ See [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cl
```clojure ```clojure
;; Add your own signal handler ;; Add your own signal handler
(tel/add-handler! :my-handler (t/add-handler! :my-handler
(fn (fn
([signal] (println signal)) ([signal] (println signal))
([] (println "Handler has shut down")))) ([] (println "Handler has shut down"))))
;; Use `add-handler!` to set handler-level filtering and back-pressure ;; Use `add-handler!` to set handler-level filtering and back-pressure
(tel/add-handler! :my-handler (t/add-handler! :my-handler
(fn (fn
([signal] (println signal)) ([signal] (println signal))
([] (println "Handler has shut down"))) ([] (println "Handler has shut down")))
@ -147,27 +157,27 @@ See [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cl
:min-level :info :min-level :info
:ns-filter {:disallow "taoensso.*"} :ns-filter {:disallow "taoensso.*"}
:limit {"1 per sec" [1 1000]} :limit {"1 per sec" [1 1000]}
;; See `tel/help:handler-dispatch-options` for more ;; See `t/help:handler-dispatch-options` for more
}) })
;; See current handlers ;; See current handlers
(tel/get-handlers) ; => {<handler-id> {:keys [handler-fn handler-stats_ dispatch-opts]}} (t/get-handlers) ; => {<handler-id> {:keys [handler-fn handler-stats_ dispatch-opts]}}
;; Add console handler to print signals as human-readable text ;; Add console handler to print signals as human-readable text
(tel/add-handler! :my-handler (t/add-handler! :my-handler
(tel/handler:console (t/handler:console
{:output-fn (tel/format-signal-fn {})})) {:output-fn (t/format-signal-fn {})}))
;; Add console handler to print signals as edn ;; Add console handler to print signals as edn
(tel/add-handler! :my-handler (t/add-handler! :my-handler
(tel/handler:console (t/handler:console
{:output-fn (tel/pr-signal-fn {:pr-fn :edn})})) {:output-fn (t/pr-signal-fn {:pr-fn :edn})}))
;; Add console handler to print signals as JSON ;; Add console handler to print signals as JSON
;; Ref. <https://github.com/metosin/jsonista> (or any alt JSON lib) ;; Ref. <https://github.com/metosin/jsonista> (or any alt JSON lib)
#?(:clj (require '[jsonista.core :as jsonista])) #?(:clj (require '[jsonista.core :as jsonista]))
(tel/add-handler! :my-handler (t/add-handler! :my-handler
(tel/handler:console (t/handler:console
{:output-fn {:output-fn
#?(:cljs :json ; Use js/JSON.stringify #?(:cljs :json ; Use js/JSON.stringify
:clj jsonista/write-value-as-string)})) :clj jsonista/write-value-as-string)}))
@ -179,28 +189,34 @@ See [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cl
### Ergonomics ### Ergonomics
- Elegant unified API that's **easy to use** and **deeply flexible**. - Elegant, lightweight API that's **easy to use**, **easy to configure**, and **deeply flexible**.
- Pure **Clojure vals and fns** for easy config, composition, and REPL debugging. - **Sensible defaults** to make getting started **fast and easy**.
- **Sensible defaults** to get started fast. - Extensive **beginner-oriented** [documentation][GitHub wiki], [docstrings](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere), and error messages.
- **Beginner-oriented** [documentation][GitHub wiki], [docstrings](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere), and error messages.
### Interop ### Interop
- **Interop ready** with [tools.logging](../../wiki/3-Config#toolslogging), [Java logging via SLF4J v2](../../wiki/3-Config#java-logging), [OpenTelemetry](../../wiki/3-Config#opentelemetry), and [Tufte](../../wiki/3-Config#tufte). - 1st-class **out-the-box interop** with [tools.logging](../../wiki/3-Config#toolslogging), [Java logging via SLF4J v2](../../wiki/3-Config#java-logging), [OpenTelemetry](../../wiki/3-Config#opentelemetry), and [Tufte](../../wiki/3-Config#tufte).
- [Timbre shim](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.timbre) for easy/gradual [migration from Timbre](../../wiki/5-Migrating). - Included [shim](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.timbre) for easy/gradual [migration from Timbre](../../wiki/5-Migrating).
- Extensive set of [handlers](../../wiki/4-Handlers#included-handlers) included out-the-box. - Extensive set of [handlers](../../wiki/4-Handlers#included-handlers) included out-the-box.
### Scaling ### Scaling
- Rich [filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) by namespace, id pattern, level, level by namespace pattern, etc. - Hyper-optimized and **blazing fast**, see [performance](#performance).
- Fully [configurable](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) **a/sync dispatch support** with per-handler [performance monitoring](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats). - An API that **scales comfortably** from the smallest disposable code, to the most massive and complex real-world production environments.
- Turn-key **sampling**, **rate limiting**, and **back-pressure monitoring**. - Auto [handler stats](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats) for debugging performance and other issues at scale.
- Highly optimized and [blazing fast](#performance)!
### Flexibility
- Config via plain **Clojure vals and fns** for easy customization, composition, and REPL debugging.
- Unmatched [environmental config](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:environmental-config) support: JVM properties, environment variables, or classpath resources. Per platform, or cross-platform.
- Unmatched [filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) support: by namespace, id pattern, level, level by namespace pattern, etc. At runtime and compile-time.
- Fully [configurable](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) **a/sync dispatch support**: blocking, dropping, sliding, etc.
- Turn-key **sampling**, **rate limiting**, and **back-pressure monitoring** with sensible defaults.
## Comparisons ## Comparisons
- Telemere [compared](../../wiki/5-Migrating#from-timbre) to [Timbre](https://www.taoensso.com/timbre) (Telemere's predecessor) - Telemere [compared](../../wiki/5-Migrating#from-timbre) to [Timbre](https://www.taoensso.com/timbre) (Telemere's predecessor)
- Telemere [compared](../../wiki/6-FAQ#how-does-telemere-compare-to-%CE%BClog) to [μ/log](https://github.com/BrunoBonacci/mulog) (structured micro-logging library) - Telemere [compared](../../wiki/6-FAQ#how-does-telemere-compare-to-mulog) to [Mulog](https://github.com/BrunoBonacci/mulog) (structured micro-logging library)
## Videos ## Videos
@ -218,21 +234,19 @@ See [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cl
## API overview ## API overview
See relevant docstrings (links below) for usage info-
### Creating signals ### Creating signals
80% of Telemere's functionality is available through one macro: [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) and a rich set of [opts](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options). | Name | Kind | Args | Returns |
| :---------------------------------------------------------------------------------------------------------- | :--------- | :--------------- | :--------------------------- |
Use that directly, or any of the wrapper macros that you find most convenient. They're **semantically equivalent** but have ergonomics slightly tweaked for different common use cases: | [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `:log` | `?level` + `msg` | nil |
| [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `:event` | `id` + `?level` | nil |
| Name | Args | Returns | | [`trace!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#trace!) | `:trace` | `?id` + `run` | Form result |
| :---------------------------------------------------------------------------------------------------------- | :------------------------- | :--------------------------- | | [`spy!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#spy!) | `:spy` | `?level` + `run` | Form result |
| [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `[opts]` or `[?level msg]` | nil | | [`error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#error!) | `:error` | `?id` + `error` | Given error |
| [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `[opts]` or `[id ?level]` | nil | | [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) | `:error` | `?id` | Form value or given fallback |
| [`trace!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#trace!) | `[opts]` or `[?id run]` | Form result | | [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `:generic` | `opts` | Depends on opts |
| [`spy!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#spy!) | `[opts]` or `[?level run]` | Form result |
| [`error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#error!) | `[opts]` or `[?id error]` | Given error |
| [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) | `[opts]` or `[?id error]` | Form value or given fallback |
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `[opts]` | Depends on opts |
### Internal help ### Internal help
@ -314,12 +328,12 @@ See [here](../../wiki/8-Community) for community resources.
- [Wiki][GitHub wiki] (getting started, usage, etc.) - [Wiki][GitHub wiki] (getting started, usage, etc.)
- API reference via [cljdoc][cljdoc] - API reference via [cljdoc][cljdoc]
- Extensive [internal help](#internal-help) (no need to leave your IDE) - Extensive [internal help](#internal-help) (no need to leave your IDE)
- Support via [Slack][] or [GitHub issues][] - Support via [Slack channel][] or [GitHub issues][]
- [General observability tips](../../wiki/7-Tips) (advice on building and maintaining observable Clojure/Script systems, and getting the most out of Telemere) - [General observability tips](../../wiki/7-Tips) (advice on building and maintaining observable Clojure/Script systems, and getting the most out of Telemere)
## Funding ## Funding
You can [help support][sponsor] continued work on this project and [others][my work], thank you!! 🙏 You can [help support][sponsor] continued work on this project, thank you!! 🙏
## License ## License
@ -331,11 +345,10 @@ Licensed under [EPL 1.0](LICENSE.txt) (same as Clojure).
[GitHub releases]: ../../releases [GitHub releases]: ../../releases
[GitHub issues]: ../../issues [GitHub issues]: ../../issues
[GitHub wiki]: ../../wiki [GitHub wiki]: ../../wiki
[Slack]: https://www.taoensso.com/telemere/slack [Slack channel]: https://www.taoensso.com/telemere/slack
[Peter Taoussanis]: https://www.taoensso.com [Peter Taoussanis]: https://www.taoensso.com
[sponsor]: https://www.taoensso.com/sponsor [sponsor]: https://www.taoensso.com/sponsor
[my work]: https://www.taoensso.com/clojure-libraries
<!-- Project --> <!-- Project -->
@ -344,9 +357,7 @@ Licensed under [EPL 1.0](LICENSE.txt) (same as Clojure).
[Clojars SVG]: https://img.shields.io/clojars/v/com.taoensso/telemere.svg [Clojars SVG]: https://img.shields.io/clojars/v/com.taoensso/telemere.svg
[Clojars URL]: https://clojars.org/com.taoensso/telemere [Clojars URL]: https://clojars.org/com.taoensso/telemere
[Clj tests SVG]: https://github.com/taoensso/telemere/actions/workflows/clj-tests.yml/badge.svg [Main tests SVG]: https://github.com/taoensso/telemere/actions/workflows/main-tests.yml/badge.svg
[Clj tests URL]: https://github.com/taoensso/telemere/actions/workflows/clj-tests.yml [Main tests URL]: https://github.com/taoensso/telemere/actions/workflows/main-tests.yml
[Cljs tests SVG]: https://github.com/taoensso/telemere/actions/workflows/cljs-tests.yml/badge.svg
[Cljs tests URL]: https://github.com/taoensso/telemere/actions/workflows/cljs-tests.yml
[Graal tests SVG]: https://github.com/taoensso/telemere/actions/workflows/graal-tests.yml/badge.svg [Graal tests SVG]: https://github.com/taoensso/telemere/actions/workflows/graal-tests.yml/badge.svg
[Graal tests URL]: https://github.com/taoensso/telemere/actions/workflows/graal-tests.yml [Graal tests URL]: https://github.com/taoensso/telemere/actions/workflows/graal-tests.yml

View file

@ -8,25 +8,23 @@
(require '[taoensso.telemere :as tel]) (require '[taoensso.telemere :as tel])
;; No config needed for typical use cases!! ;; (Just works / no config necessary for typical use cases)
;; Signals print to console by default for both Clj and Cljs
;; Traditional style logging (data formatted into message string): ;; Without structured data
(tel/log! {:level :info, :msg (str "User " 1234 " logged in!")}) (tel/log! :info "Hello world!") ; %> Basic log signal (has message)
(tel/event! ::my-id :debug) ; %> Basic event signal (just id)
;; Modern/structured style logging (explicit id and data) ;; With structured data
(tel/log! {:level :info, :id :auth/login, :data {:user-id 1234}}) (tel/log! {:level :info, :data {}} "Hello again!")
(tel/event! ::my-id {:level :debug, :data {}})
;; Mixed style (explicit id and data, with message string) ;; Trace (auto interops with OpenTelemetry)
(tel/log! {:level :info, :id :auth/login, :data {:user-id 1234}, :msg "User logged in!"})
;; Trace (can interop with OpenTelemetry)
;; Tracks form runtime, return value, and (nested) parent tree ;; Tracks form runtime, return value, and (nested) parent tree
(tel/trace! {:id ::my-id :data {...}} (tel/trace! {:id ::my-id :data {}}
(do-some-work)) (do-some-work))
;; Check resulting signal content for debug/tests ;; Check resulting signal content for debug/tests
(tel/with-signal (tel/log! {...})) ; => {:keys [ns level id data msg_ ...]} (tel/with-signal (tel/event! ::my-id)) ; => {:keys [ns level id data msg_ ...]}
;; Getting fancy (all costs are conditional!) ;; Getting fancy (all costs are conditional!)
(tel/log! (tel/log!
@ -49,25 +47,20 @@
;; Message string or vector to join as string ;; Message string or vector to join as string
["Something interesting happened!" formatted]) ["Something interesting happened!" formatted])
)
;; Set minimum level ;; Set minimum level
(tel/set-min-level! :warn) ; For all signals (tel/set-min-level! :warn) ; For all signals
(tel/set-min-level! :log :debug) ; For `log!` signals specifically (tel/set-min-level! :log :debug) ; For `log!` signals only
;; Set id and namespace filters ;; Set namespace and id filters
(tel/set-id-filter! {:allow #{::my-particular-id "my-app/*"}})
(tel/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"}) (tel/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"})
(tel/set-id-filter! {:allow #{::my-particular-id "my-app/*"}})
;; SLF4J signals will have their `:ns` key set to the logger's name ;; Set minimum level for `event!` signals for particular ns pattern
;; (typically a source class) (tel/set-min-level! :event "taoensso.sente.*" :warn)
(tel/set-ns-filter! {:disallow "com.noisy.java.package.*"})
;; Set minimum level for `log!` signals for particular ns pattern
(tel/set-min-level! :log "taoensso.sente.*" :warn)
;; Use transforms (xfns) to filter and/or arbitrarily modify signals ;; Use transforms (xfns) to filter and/or arbitrarily modify signals
;; by signal data/content/etc. ;; by signal data/contentel/etc.
(tel/set-xfn! (tel/set-xfn!
(fn [signal] (fn [signal]
@ -75,11 +68,13 @@
nil ; Filter signal (don't handle) nil ; Filter signal (don't handle)
(assoc signal :transformed? true)))) (assoc signal :transformed? true))))
(tel/with-signal (tel/log! {... :data {:skip-me? true}})) ; => nil (tel/with-signal (tel/event! ::my-id {:data {:skip-me? true}})) ; => nil
(tel/with-signal (tel/log! {... :data {:skip-me? false}})) ; => {...} (tel/with-signal (tel/event! ::my-id {:data {:skip-me? false}})) ; => {...}
;; See `tel/help:filters` docstring for more filtering options ;; See `tel/help:filters` docstring for more filtering options
;;;; README "More examples"
;; Add your own signal handler ;; Add your own signal handler
(tel/add-handler! :my-handler (tel/add-handler! :my-handler
(fn (fn

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

View file

@ -1,4 +1,4 @@
(defproject com.taoensso/telemere "1.2.1" (defproject com.taoensso/telemere "1.0.0"
:author "Peter Taoussanis <https://www.taoensso.com>" :author "Peter Taoussanis <https://www.taoensso.com>"
:description "Structured logs and telemetry for Clojure/Script" :description "Structured logs and telemetry for Clojure/Script"
:url "https://www.taoensso.com/telemere" :url "https://www.taoensso.com/telemere"
@ -10,15 +10,15 @@
:scm {:name "git" :url "https://github.com/taoensso/telemere"} :scm {:name "git" :url "https://github.com/taoensso/telemere"}
:dependencies :dependencies
[[com.taoensso/encore "3.159.0"]] [[com.taoensso/encore "3.145.0"]]
:test-paths ["test" #_"src"] :test-paths ["test" #_"src"]
:profiles :profiles
{;; :default [:base :system :user :provided :dev] {;; :default [:base :system :user :provided :dev]
:provided {:dependencies [[org.clojure/clojurescript "1.12.134"] :provided {:dependencies [[org.clojure/clojurescript "1.12.35"]
[org.clojure/clojure "1.11.4"]]} [org.clojure/clojure "1.11.4"]]}
:c1.12 {:dependencies [[org.clojure/clojure "1.12.3"]]} :c1.12 {:dependencies [[org.clojure/clojure "1.12.0"]]}
:c1.11 {:dependencies [[org.clojure/clojure "1.11.4"]]} :c1.11 {:dependencies [[org.clojure/clojure "1.11.4"]]}
:c1.10 {:dependencies [[org.clojure/clojure "1.10.3"]]} :c1.10 {:dependencies [[org.clojure/clojure "1.10.3"]]}
@ -47,18 +47,18 @@
:dependencies :dependencies
[[org.clojure/core.async "1.8.741"] [[org.clojure/core.async "1.8.741"]
[org.clojure/test.check "1.1.2"] [org.clojure/test.check "1.1.1"]
[org.clojure/tools.logging "1.3.0"] [org.clojure/tools.logging "1.3.0"]
[org.slf4j/slf4j-api "2.0.17"] [org.slf4j/slf4j-api "2.0.17"]
[com.taoensso/telemere-slf4j "1.2.1"] [com.taoensso/telemere-slf4j "1.0.0"]
#_[org.slf4j/slf4j-simple "2.0.16"] #_[org.slf4j/slf4j-simple "2.0.16"]
#_[org.slf4j/slf4j-nop "2.0.16"] #_[org.slf4j/slf4j-nop "2.0.16"]
#_[io.github.paintparty/bling "0.4.2"] #_[io.github.paintparty/bling "0.4.2"]
;;; For optional handlers ;;; For optional handlers
[io.opentelemetry/opentelemetry-api "1.57.0"] [io.opentelemetry/opentelemetry-api "1.49.0"]
[io.opentelemetry/opentelemetry-sdk-extension-autoconfigure "1.57.0"] [io.opentelemetry/opentelemetry-sdk-extension-autoconfigure "1.49.0"]
[io.opentelemetry/opentelemetry-exporter-otlp "1.57.0"] [io.opentelemetry/opentelemetry-exporter-otlp "1.49.0"]
#_[io.opentelemetry/opentelemetry-exporters-jaeger "0.9.1"] #_[io.opentelemetry/opentelemetry-exporters-jaeger "0.9.1"]
[metosin/jsonista "0.3.13"] [metosin/jsonista "0.3.13"]
[com.draines/postal "2.0.5"] [com.draines/postal "2.0.5"]

View file

@ -11,8 +11,8 @@ various keys:
- All signal creators offer the same options [2], and - All signal creators offer the same options [2], and
- All signal kinds can contain the same content [3] - All signal kinds can contain the same content [3]
Creators vary only in in their default `:kind` value and call APIs (expected Creators vary only in in their default options and call APIs (expected args
args and return values), making them more/less convenient for certain use cases: and return values), making them more/less convenient for certain use cases:
`log!` ------------- ?level + msg => nil `log!` ------------- ?level + msg => nil
`event!` ----------- id + ?level => nil `event!` ----------- id + ?level => nil

View file

@ -9,12 +9,12 @@ All options are available for all signal creator calls:
Defaults to `:auto` for tracing signals, and nil otherwise Defaults to `:auto` for tracing signals, and nil otherwise
`:msg` --------- Arb app-level ?message to incl. in signal: str or vec of strs to join (with `\space`), may be a delay `:msg` --------- Arb app-level ?message to incl. in signal: str or vec of strs to join (with `\space`), may be a delay
`:data` -------- Arb app-level ?data to incl. in signal: usu. a map, LAZY! [3] `:data` -------- Arb app-level ?data to incl. in signal: usu. a map
`:error` ------- Arb app-level ?error to incl. in signal: platform error [2] `:error` ------- Arb app-level ?error to incl. in signal: platform error [2]
`:run` --------- ?form to execute UNCONDITIONALLY; will incl. `:run-val` in signal `:run` --------- ?form to execute UNCONDITIONALLY; will incl. `:run-val` in signal
`:do` ---------- ?form to execute conditionally (iff signal allowed) and LAZILY [3], before establishing `:let` ?binding `:do` ---------- ?form to execute conditionally (iff signal allowed), before establishing `:let` ?binding
`:let` --------- ?bindings to establish conditionally (iff signal allowed) and LAZILY [3], BEFORE evaluating `:data` and `:msg` (useful!) `:let` --------- ?bindings to establish conditionally (iff signal allowed), BEFORE evaluating `:data` and `:msg` (useful!)
`:parent` ------ Custom ?{:keys [id uid]} to override auto (dynamic) parent signal tracing info `:parent` ------ Custom ?{:keys [id uid]} to override auto (dynamic) parent signal tracing info
`:root` -------- Custom ?{:keys [id uid]} to override auto (dynamic) root signal tracing info `:root` -------- Custom ?{:keys [id uid]} to override auto (dynamic) root signal tracing info
@ -25,7 +25,6 @@ All options are available for all signal creator calls:
`:coords` ------ Custom ?[line column] to override auto signal callsite info `:coords` ------ Custom ?[line column] to override auto signal callsite info
`:elidable?` --- Should signal be subject to compile-time elision? (default true) `:elidable?` --- Should signal be subject to compile-time elision? (default true)
`:allow?` ------ Custom override for usual runtime filtering (true => ALWAYS create signal)
`:trace?` ------ Should tracing be enabled for `:run` form? `:trace?` ------ Should tracing be enabled for `:run` form?
`:sample` ------ Sample ?rate ∈ℝ[0,1] for random signal sampling (0.75 => allow 75% of signals, nil => allow all) `:sample` ------ Sample ?rate ∈ℝ[0,1] for random signal sampling (0.75 => allow 75% of signals, nil => allow all)
@ -37,13 +36,9 @@ All options are available for all signal creator calls:
<kvs> ---------- Other arb app-level ?kvs to incl. in signal. Typically NOT included in <kvs> ---------- Other arb app-level ?kvs to incl. in signal. Typically NOT included in
handler output, so a great way to provide custom data/opts for use handler output, so a great way to provide custom data/opts for use
(only) by custom transforms/handlers. LAZY! [3] (only) by custom transforms/handlers.
If anything is unclear, please ping me (@ptaoussanis) so that I can improve these docs! If anything is unclear, please ping me (@ptaoussanis) so that I can improve these docs!
[1] `java.time.Instant` or `js/Date` [1] `java.time.Instant` or `js/Date`
[2] `java.lang.Throwable` or `js/Error` [2] `java.lang.Throwable` or `js/Error`
[3] Most Telemere signal content is evaluated CONDITIONALLY (iff signal allowed),
LAZILY (when signal is created), and on the HANDLING THREAD (not logging thread).
This allows efficient filtering, better control+monitoring of back pressure,
conditional effects, etc. Ref. <https://www.taoensso.com/telemere/flow> for visual!

View file

@ -31,7 +31,7 @@
(remove-ns (symbol (str *ns*))) (remove-ns (symbol (str *ns*)))
(:api (enc/interns-overview))) (:api (enc/interns-overview)))
(enc/assert-min-encore-version [3 159 0]) (enc/assert-min-encore-version [3 145 0])
;;;; Shared signal API ;;;; Shared signal API
@ -49,8 +49,6 @@
#?(:clj with-handler) #?(:clj with-handler+) #?(:clj with-handler) #?(:clj with-handler+)
add-handler! remove-handler! stop-handlers! add-handler! remove-handler! stop-handlers!
with-signal with-signals
^:dynamic *ctx* set-ctx! #?(:clj with-ctx) #?(:clj with-ctx+) ^:dynamic *ctx* set-ctx! #?(:clj with-ctx) #?(:clj with-ctx+)
^:dynamic *xfn* set-xfn! #?(:clj with-xfn) #?(:clj with-xfn+)) ^:dynamic *xfn* set-xfn! #?(:clj with-xfn) #?(:clj with-xfn+))
@ -71,13 +69,13 @@
(enc/defaliases (enc/defaliases
;; Encore ;; Encore
#?(:clj ^:no-doc enc/set-var-root!) #?(:clj enc/set-var-root!)
#?(:clj ^:no-doc enc/update-var-root!) #?(:clj enc/update-var-root!)
#?(:clj enc/get-env) #?(:clj enc/get-env)
#?(:clj enc/call-on-shutdown!) #?(:clj enc/call-on-shutdown!)
^:no-doc enc/chance enc/chance
enc/rate-limiter enc/rate-limiter
^:no-doc enc/newline enc/newline
sigs/comp-xfn sigs/comp-xfn
#?(:clj truss/keep-callsite) #?(:clj truss/keep-callsite)

View file

@ -1,5 +1,6 @@
(ns ^:no-doc taoensso.telemere.consoles (ns ^:no-doc taoensso.telemere.consoles
"Telemere -> console handlers." "Private ns, implementation detail.
Core console handlers, aliased in main Telemere ns."
(:require (:require
[taoensso.truss :as truss] [taoensso.truss :as truss]
[taoensso.encore :as enc] [taoensso.encore :as enc]

View file

@ -1,5 +1,6 @@
(ns ^:no-doc taoensso.telemere.files (ns ^:no-doc taoensso.telemere.files
"Telemere -> file handler." "Private ns, implementation detail.
Core file handler, aliased in main Telemere ns."
(:require (:require
[taoensso.truss :as truss] [taoensso.truss :as truss]
[taoensso.encore :as enc] [taoensso.encore :as enc]

View file

@ -38,10 +38,7 @@
(def enabled:otel-tracing? (def enabled:otel-tracing?
"Documented at `taoensso.telemere/otel-tracing?`." "Documented at `taoensso.telemere/otel-tracing?`."
(enc/get-env {:as :bool, :default present:otel?} (enc/get-env {:as :bool, :default present:otel?}
:taoensso.telemere/otel-tracing<.platform>)) :taoensso.telemere/otel-tracing<.platform>))))
(def enabled:incl-host-info? "Include `:host` info in signals by default?" (enc/get-env {:as :bool, :default true} :taoensso.telemere/incl-host-info))
(def enabled:incl-thread-info? "Include `:thread` info in signals by default?" (enc/get-env {:as :bool, :default true} :taoensso.telemere/incl-thread-info))))
(def uid-kind (def uid-kind
"Documented at `taoensso.telemere/*uid-fn*`." "Documented at `taoensso.telemere/*uid-fn*`."
@ -173,8 +170,8 @@
(defn default-trace-msg (defn default-trace-msg
[form value error nsecs] [form value error nsecs]
(if error (if error
(str (if (nil? form) "nil" form) " !> " (truss/ex-type error)) (str form " !> " (truss/ex-type error))
(str (if (nil? form) "nil" form) " => " (if (nil? value) "nil" value)))) (str form " => " value)))
(comment (comment
(default-trace-msg "(+ 1 2)" 3 nil 12345) (default-trace-msg "(+ 1 2)" 3 nil 12345)
@ -394,7 +391,7 @@
:signal! ; opts => allowed? / run result (value or throw) :signal! ; opts => allowed? / run result (value or throw)
'( [& opts-kvs] '( [& opts-kvs]
[{:as opts-map :keys [{:as opts-map :keys
[elidable? coords inst uid xfn xfn+ #_kvs+, [elidable? coords inst uid xfn xfn+,
sample kind ns id level when limit limit-by, sample kind ns id level when limit limit-by,
ctx ctx+ parent root trace?, do let data msg error run & kvs]}]) ctx ctx+ parent root trace?, do let data msg error run & kvs]}])
@ -402,7 +399,7 @@
'([opts-or-msg] '([opts-or-msg]
[level msg] [level msg]
[{:as opts-map :keys [{:as opts-map :keys
[elidable? coords inst uid xfn xfn+ #_kvs+, [elidable? coords inst uid xfn xfn+,
sample kind ns id level when limit limit-by, sample kind ns id level when limit limit-by,
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]} ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}
msg]) msg])
@ -412,7 +409,7 @@
[id level] [id level]
[id [id
{:as opts-map :keys {:as opts-map :keys
[elidable? coords inst uid xfn xfn+ #_kvs+, [elidable? coords inst uid xfn xfn+,
sample kind ns id level when limit limit-by, sample kind ns id level when limit limit-by,
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}]) ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}])
@ -420,7 +417,7 @@
'([opts-or-run] '([opts-or-run]
[id run] [id run]
[{:as opts-map :keys [{:as opts-map :keys
[elidable? coords inst uid xfn xfn+ #_kvs+, [elidable? coords inst uid xfn xfn+,
sample kind ns id level when limit limit-by, sample kind ns id level when limit limit-by,
ctx ctx+ parent root trace?, do let data msg error run & kvs]} ctx ctx+ parent root trace?, do let data msg error run & kvs]}
run]) run])
@ -429,7 +426,7 @@
'([opts-or-run] '([opts-or-run]
[level run] [level run]
[{:as opts-map :keys [{:as opts-map :keys
[elidable? coords inst uid xfn xfn+ #_kvs+, [elidable? coords inst uid xfn xfn+,
sample kind ns id level when limit limit-by, sample kind ns id level when limit limit-by,
ctx ctx+ parent root trace?, do let data msg error run & kvs]} ctx ctx+ parent root trace?, do let data msg error run & kvs]}
run]) run])
@ -438,7 +435,7 @@
'([opts-or-error] '([opts-or-error]
[id error] [id error]
[{:as opts-map :keys [{:as opts-map :keys
[elidable? coords inst uid xfn xfn+ #_kvs+, [elidable? coords inst uid xfn xfn+,
sample kind ns id level when limit limit-by, sample kind ns id level when limit limit-by,
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]} ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}
error]) error])
@ -448,7 +445,7 @@
[id run] [id run]
[{:as opts-map :keys [{:as opts-map :keys
[catch-val, [catch-val,
elidable? coords inst uid xfn xfn+ #_kvs+, elidable? coords inst uid xfn xfn+,
sample kind ns id level when limit limit-by, sample kind ns id level when limit limit-by,
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]} ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}
run]) run])
@ -457,7 +454,7 @@
'([] '([]
[opts-or-id] [opts-or-id]
[{:as opts-map :keys [{:as opts-map :keys
[elidable? coords inst uid xfn xfn+ #_kvs+, [elidable? coords inst uid xfn xfn+,
sample kind ns id level when limit limit-by, sample kind ns id level when limit limit-by,
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}]) ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}])
@ -527,16 +524,14 @@
clj? (not cljs?) clj? (not cljs?)
opts (merge {:kind :generic, :level :info} base-opts opts) opts (merge {:kind :generic, :level :info} base-opts opts)
{run-form :run} opts
run-form? (contains? opts :run)
run-form (get opts :run)
ns-form* (get opts :ns :auto) ns-form* (get opts :ns :auto)
ns-form (auto-> ns-form* (str *ns*)) ns-form (auto-> ns-form* (str *ns*))
show-run-val (get opts :run-val '_run-val) show-run-val (get opts :run-val '_run-val)
show-run-form show-run-form
(when run-form? (when run-form
(get opts :run-form (get opts :run-form
(if (and (if (and
(enc/list-form? run-form) (enc/list-form? run-form)
@ -569,15 +564,15 @@
id-form :id id-form :id
level-form :level} opts level-form :level} opts
trace? (get opts :trace? run-form?) trace? (get opts :trace? (boolean run-form))
_ _
(when-not (contains? #{true false nil} trace?) (when-not (contains? #{true false nil} trace?)
(truss/ex-info! (truss/ex-info!
(str "Signal needs compile-time `:trace?` value at " (str "Signal needs compile-time `:trace?` value at "
(sigs/format-callsite ns-form coords)))) (sigs/format-callsite ns-form coords))))
host-form (auto-> (get opts :host :auto) (when (and clj? enabled:incl-host-info?) `(enc/host-info))) host-form (auto-> (get opts :host :auto) (when clj? `(enc/host-info)))
thread-form (auto-> (get opts :thread :auto) (when (and clj? enabled:incl-thread-info?) `(enc/thread-info))) thread-form (auto-> (get opts :thread :auto) (when clj? `(enc/thread-info)))
inst-form (auto-> (get opts :inst :auto) `(enc/now-inst*)) inst-form (auto-> (get opts :inst :auto) `(enc/now-inst*))
parent-form (get opts :parent `*trace-parent*) parent-form (get opts :parent `*trace-parent*)
@ -607,24 +602,17 @@
(get opts :xfn `taoensso.telemere/*xfn*)) (get opts :xfn `taoensso.telemere/*xfn*))
kvs-form kvs-form
(let [base
(not-empty (not-empty
(dissoc opts (dissoc opts
:elidable? :coords :inst :uid :xfn :xfn+ :kvs+, :elidable? :coords :inst :uid :xfn :xfn+,
:sample :ns :kind :id :level :filter :when #_:limit #_:limit-by, :sample :ns :kind :id :level :filter :when #_:limit #_:limit-by,
:ctx :ctx+ :parent :trace?, :do :let :data :msg :error, :ctx :ctx+ :parent #_:trace?, :do :let :data :msg :error,
:run :run-form :run-val, :elide? :allow? #_:callsite-id, :run :run-form :run-val, :elide? :allow? #_:callsite-id,
:host :thread :otel/context))] :host :thread :otel/context))
(if-let [kvs+ (get opts :kvs+)] ; Undocumented
(if base
`(not-empty (conj ~base ~kvs+))
`(not-empty ~kvs+))
base))
_ ; Compile-time validation _ ; Compile-time validation
(do (do
(when (and run-form? error-form) ; Ambiguous source of error (when (and run-form error-form) ; Ambiguous source of error
(truss/ex-info! (truss/ex-info!
(str "Signal cannot have both `:run` and `:error` opts at " (str "Signal cannot have both `:run` and `:error` opts at "
(sigs/format-callsite ns-form coords)))) (sigs/format-callsite ns-form coords))))
@ -636,18 +624,18 @@
signal-form signal-form
(let [record-form (let [record-form
(let [clause [(if run-form? :run :no-run) (if clj? :clj :cljs)]] (let [clause [(if run-form :run :no-run) (if clj? :clj :cljs)]]
(case clause (case clause
[:run :clj ] `(Signal. 1 ~'__inst ~'__uid, ~'__ns ~coords ~host-form ~'__thread ~'__otel-context, ~sample-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~'_msg_, ~'_run-err '~show-run-form ~show-run-val ~'_end-inst ~'_run-nsecs) [:run :clj ] `(Signal. 1 ~'__inst ~'__uid, ~'__ns ~coords ~host-form ~'__thread ~'__otel-context1, ~sample-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~'_msg_, ~'_run-err '~show-run-form ~show-run-val ~'_end-inst ~'_run-nsecs)
[:run :cljs] `(Signal. 1 ~'__inst ~'__uid, ~'__ns ~coords ~sample-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~'_msg_, ~'_run-err '~show-run-form ~show-run-val ~'_end-inst ~'_run-nsecs) [:run :cljs] `(Signal. 1 ~'__inst ~'__uid, ~'__ns ~coords ~sample-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~'_msg_, ~'_run-err '~show-run-form ~show-run-val ~'_end-inst ~'_run-nsecs)
[:no-run :clj ] `(Signal. 1 ~'__inst ~'__uid, ~'__ns ~coords ~host-form ~'__thread ~'__otel-context, ~sample-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~msg-form, ~error-form nil nil nil nil) [:no-run :clj ] `(Signal. 1 ~'__inst ~'__uid, ~'__ns ~coords ~host-form ~'__thread ~'__otel-context1, ~sample-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~msg-form, ~error-form nil nil nil nil)
[:no-run :cljs] `(Signal. 1 ~'__inst ~'__uid, ~'__ns ~coords ~sample-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~msg-form, ~error-form nil nil nil nil) [:no-run :cljs] `(Signal. 1 ~'__inst ~'__uid, ~'__ns ~coords ~sample-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~msg-form, ~error-form nil nil nil nil)
(truss/ex-info! (truss/ex-info!
(str "Unexpected signal constructor args at " (str "Unexpected signal constructor args at "
(sigs/format-callsite ns-form coords))))) (sigs/format-callsite ns-form coords)))))
record-form record-form
(if-not run-form? (if-not run-form
record-form record-form
`(let [~(with-meta '_run-result {:tag `RunResult}) ~'__run-result `(let [~(with-meta '_run-result {:tag `RunResult}) ~'__run-result
~'_run-nsecs (.-run-nsecs ~'_run-result) ~'_run-nsecs (.-run-nsecs ~'_run-result)
@ -679,31 +667,17 @@
(do signal#))))) (do signal#)))))
;; Trade-off: avoid double `run-form` expansion ;; Trade-off: avoid double `run-form` expansion
run-fn-form (when run-form? `(fn [] ~run-form)) run-fn-form (when run-form `(fn [] ~run-form))
run-form* (when run-form? `(~'__run-fn-form)) run-form* (when run-form `(~'__run-fn-form))
binds-form-base into-let-form
`[~'__inst ~inst-form
~'__thread ~thread-form
~'__root0 ~root-form0 ; ?{:keys [id uid]}
~'__otel-context
~(when (and clj? enabled:otel-tracing?)
(if run-form?
`(otel-context+span ~'__id ~'__inst ~(get opts :otel/context `(otel-context)) ~(get opts :otel/span-kind))
(do (get opts :otel/context `(otel-context)))))
~'__uid
~(if (and clj? enabled:otel-tracing? trace?)
(auto-> uid-form `(or (otel-span-id ~'__otel-context) (com.taoensso.encore.Ids/genHexId16)))
(auto-> uid-form `(taoensso.telemere/*uid-fn* (if ~'__root0 false true))))]
binds-form-more
(enc/cond! (enc/cond!
(not trace?) ; Non-tracing signal (not trace?) ; Don't trace
`[~'__root1 ~'__root0 ; Retain, but don't establish `[~'__otel-context1 nil
~'__uid ~(auto-> uid-form `(taoensso.telemere/*uid-fn* (if ~'__root0 false true)))
~'__root1 ~'__root0 ; Retain, but don't establish
~'__run-result ~'__run-result
~(when run-form? ~(when run-form
`(let [t0# (enc/now-nano*)] `(let [t0# (enc/now-nano*)]
(truss/try* (truss/try*
(do (RunResult. ~run-form* nil (- (enc/now-nano*) t0#))) (do (RunResult. ~run-form* nil (- (enc/now-nano*) t0#)))
@ -711,9 +685,11 @@
;; Trace without OpenTelemetry ;; Trace without OpenTelemetry
(or cljs? (not enabled:otel-tracing?)) (or cljs? (not enabled:otel-tracing?))
`[~'__root1 (or ~'__root0 ~(when trace? `{:id ~'__id, :uid ~'__uid})) `[~'__otel-context1 nil
~'__uid ~(auto-> uid-form `(taoensso.telemere/*uid-fn* (if ~'__root0 false true)))
~'__root1 (or ~'__root0 ~(when trace? `{:id ~'__id, :uid ~'__uid}))
~'__run-result ~'__run-result
~(when run-form? ~(when run-form
`(binding [*trace-root* ~'__root1 `(binding [*trace-root* ~'__root1
*trace-parent* {:id ~'__id, :uid ~'__uid}] *trace-parent* {:id ~'__id, :uid ~'__uid}]
(let [t0# (enc/now-nano*)] (let [t0# (enc/now-nano*)]
@ -723,17 +699,20 @@
;; Trace with OpenTelemetry ;; Trace with OpenTelemetry
(and clj? enabled:otel-tracing?) (and clj? enabled:otel-tracing?)
`[~'__root1 `[~'__otel-context0 ~(get opts :otel/context `(otel-context)) ; Context
~'__otel-context1 ~(if run-form `(otel-context+span ~'__id ~'__inst ~'__otel-context0 ~(get opts :otel/span-kind)) ~'__otel-context0)
~'__uid ~(auto-> uid-form `(or (otel-span-id ~'__otel-context1) (com.taoensso.encore.Ids/genHexId16)))
~'__root1
(or ~'__root0 (or ~'__root0
~(when trace? ~(when trace?
`{:id ~'__id, :uid (or (otel-trace-id ~'__otel-context) (com.taoensso.encore.Ids/genHexId32))})) `{:id ~'__id, :uid (or (otel-trace-id ~'__otel-context1) (com.taoensso.encore.Ids/genHexId32))}))
~'__run-result ~'__run-result
~(when run-form? ~(when run-form
`(binding [*otel-context* ~'__otel-context `(binding [*otel-context* ~'__otel-context1
*trace-root* ~'__root1 *trace-root* ~'__root1
*trace-parent* {:id ~'__id, :uid ~'__uid}] *trace-parent* {:id ~'__id, :uid ~'__uid}]
(let [otel-scope# (.makeCurrent ~'__otel-context) (let [otel-scope# (.makeCurrent ~'__otel-context1)
t0# (enc/now-nano*)] t0# (enc/now-nano*)]
(truss/try* (truss/try*
(do (RunResult. ~run-form* nil (- (enc/now-nano*) t0#))) (do (RunResult. ~run-form* nil (- (enc/now-nano*) t0#)))
@ -750,8 +729,11 @@
(enc/if-not ~allow? (enc/if-not ~allow?
~run-form* ~run-form*
(let [~@binds-form-base (let [~'__inst ~inst-form
~@binds-form-more ~'__thread ~thread-form
~'__root0 ~root-form0 ; ?{:keys [id uid]}
~@into-let-form ; Inject conditional bindings
signal# ~signal-delay-form] signal# ~signal-delay-form]
(dispatch-signal! (dispatch-signal!

View file

@ -1,9 +1,7 @@
(ns taoensso.telemere.open-telemetry (ns taoensso.telemere.open-telemetry
"Telemere -> OpenTelemetry handler using `opentelemetry-java`, "OpenTelemetry handler using `opentelemetry-java`,
Ref. <https://github.com/open-telemetry/opentelemetry-java>, Ref. <https://github.com/open-telemetry/opentelemetry-java>,
<https://javadoc.io/doc/io.opentelemetry/opentelemetry-api/latest/index.html> <https://javadoc.io/doc/io.opentelemetry/opentelemetry-api/latest/index.html>"
Telemere will attempt to load this ns automatically when possible."
(:require (:require
[clojure.string :as str] [clojure.string :as str]
[clojure.set :as set] [clojure.set :as set]
@ -220,14 +218,14 @@
(if (or common-attrs trace-attrs) (if (or common-attrs trace-attrs)
(let [ab (Attributes/builder)] (let [ab (Attributes/builder)]
(when-let [ns (get signal :ns)] (.put ab "ns" (str ns))) (when-let [ns (get signal :ns)] (.put ab "ns" (str ns)))
(when-let [line (enc/get-in* signal [:coords 0])] (.put ab "line" (long line))) (when-let [line (get signal :line)] (.put ab "line" (long line)))
(when-let [attrs common-attrs] (put-attrs! ab attrs)) (when-let [attrs common-attrs] (put-attrs! ab attrs))
(when-let [attrs trace-attrs] (put-attrs! ab attrs)) (when-let [attrs trace-attrs] (put-attrs! ab attrs))
(.build ab)) (.build ab))
;; Common case ;; Common case
(when-let [ns (get signal :ns)] (when-let [ns (get signal :ns)]
(if-let [line (enc/get-in* signal [:coords 0])] (if-let [line (get signal :line)]
(Attributes/of ak-ns ns, ak-line (long line)) (Attributes/of ak-ns ns, ak-line (long line))
(Attributes/of ak-ns ns))))))) (Attributes/of ak-ns ns)))))))

View file

@ -1,5 +1,5 @@
(ns taoensso.telemere.postal (ns taoensso.telemere.postal
"Telemere -> email handler using `postal`, "Email handler using `postal`,
Ref. <https://github.com/drewr/postal>." Ref. <https://github.com/drewr/postal>."
(:require (:require
[taoensso.truss :as truss] [taoensso.truss :as truss]

View file

@ -1,5 +1,5 @@
(ns taoensso.telemere.slack (ns taoensso.telemere.slack
"Telemere -> Slack handler using `clj-slack`, "Slack handler using `clj-slack`,
Ref. <https://github.com/julienXX/clj-slack>" Ref. <https://github.com/julienXX/clj-slack>"
(:require (:require
[taoensso.truss :as truss] [taoensso.truss :as truss]

View file

@ -1,5 +1,5 @@
(ns taoensso.telemere.sockets (ns taoensso.telemere.sockets
"Telemere -> TCP/UDP socket handlers." "Basic TCP/UDP socket handlers."
(:require (:require
[taoensso.truss :as truss] [taoensso.truss :as truss]
[taoensso.encore :as enc] [taoensso.encore :as enc]

View file

@ -1,5 +1,5 @@
(ns taoensso.telemere.streams (ns taoensso.telemere.streams
"Standard streams -> Telemere interop." "Interop support for standard stream/s -> Telemere."
(:require (:require
[taoensso.encore :as truss] [taoensso.encore :as truss]
[taoensso.encore :as enc] [taoensso.encore :as enc]

View file

@ -35,7 +35,7 @@
(enc/format* pattern vargs) (enc/format* pattern vargs)
(enc/str-join " " (map arg-str) vargs)))] (enc/str-join " " (map arg-str) vargs)))]
[error msg vargs]) [error msg {:vargs vargs}])
(let [md (if (and (map? v0) (get (meta v0) :meta)) v0 nil) (let [md (if (and (map? v0) (get (meta v0) :meta)) v0 nil)
error (get md :err) error (get md :err)
@ -49,7 +49,7 @@
(enc/format* pattern vargs) (enc/format* pattern vargs)
(enc/str-join " " (map arg-str) vargs)))] (enc/str-join " " (map arg-str) vargs)))]
[error msg (when-not (empty? vargs) vargs)]))))) [error msg (when-not (empty? vargs) {:vargs vargs})])))))
(comment (comment
(parse-vargs true [ "hello %s" "stu"]) (parse-vargs true [ "hello %s" "stu"])
@ -63,13 +63,13 @@
[level format-msg? vargs] [level format-msg? vargs]
(truss/keep-callsite (truss/keep-callsite
`(when (impl/signal-allowed? {:kind :log, :level ~level, :id shim-id}) `(when (impl/signal-allowed? {:kind :log, :level ~level, :id shim-id})
(let [[error# msg# vargs#] (parse-vargs ~format-msg? ~vargs)] (let [[error# msg# data#] (parse-vargs ~format-msg? ~vargs)]
(tel/log! (tel/log!
{:allow? true {:allow? true
:level ~level :level ~level
:id shim-id :id shim-id
:error error# :error error#
:timbre/vargs vargs#} :data data#}
msg#) msg#)
nil))))) nil)))))
@ -190,11 +190,8 @@
:min-level nil :min-level nil
:fn :fn
(fn [data] (fn [data]
(let [{:keys [instant level context ?err msg-type vargs (let [{:keys [instant level context ?err output_
?ns-str ?file ?line ?column]} data ?ns-str ?file ?line ?column]} data]
format-msg? (enc/identical-kw? msg-type :f)
[_error msg vargs] (parse-vargs format-msg? vargs)]
(taoensso.telemere/signal! (taoensso.telemere/signal!
{:kind :timbre {:kind :timbre
@ -203,10 +200,9 @@
:ctx+ context :ctx+ context
:ns ?ns-str :ns ?ns-str
:coords (when ?line [?line ?column]) :file ?file
:file ?file ; Non-standard, goes to kvs :line ?line
:column ?column
:error ?err :error ?err
:msg (when msg-type msg) :msg (force output_)})))})
:timbre/vargs vargs})))})

View file

@ -1,5 +1,5 @@
(ns taoensso.telemere.tools-logging (ns taoensso.telemere.tools-logging
"tools.logging -> Telemere interop. "Interop support for tools.logging -> Telemere.
Telemere will attempt to load this ns automatically when possible. Telemere will attempt to load this ns automatically when possible.
Naming conventions: Naming conventions:

View file

@ -142,14 +142,14 @@
#?(:cljs #?(:cljs
(defn js-console-logger (defn js-console-logger
"Returns JavaScript console logger to match given signal level: "Returns JavaScript console logger to match given signal level:
`:debug` -> `js/console.debug`, `:trace` -> `js/console.trace`,
`:error` -> `js/console.error`, etc. `:error` -> `js/console.error`, etc.
Defaults to `js.console.log` for unmatched signal levels. Defaults to `js.console.log` for unmatched signal levels.
NB: assumes that `js/console` exists, handler constructors should check first!" NB: assumes that `js/console` exists, handler constructors should check first!"
[level] [level]
(case level (case level
:trace js/console.debug :trace js/console.trace
:debug js/console.debug :debug js/console.debug
:info js/console.info :info js/console.info
:warn js/console.warn :warn js/console.warn
@ -493,11 +493,11 @@
{:keys [chain trace]} em] {:keys [chain trace]} em]
(let [s+cause (enc/sb-appender sb (str nls "Caused: "))] (let [s+cause (enc/sb-appender sb (str nls "Caused: "))]
(s+ "Root: ") (s+ " Root: ")
(doseq [{:keys [type msg data]} (rseq chain)] (doseq [{:keys [type msg data]} (rseq chain)]
(s+cause type " - " msg) (s+cause type " - " msg)
(when data (when data
(s+ nl "data: " (enc/pr-edn* data))))) (s+ nl " data: " (enc/pr-edn* data)))))
(when trace (when trace
(s+ nl nl "Root stack trace:" nl) (s+ nl nl "Root stack trace:" nl)
@ -556,12 +556,8 @@
(defn- format-parent [ns {:keys [id uid]}] (defn- format-parent [ns {:keys [id uid]}]
(if id (if id
(if uid
{:id (symbol (format-id ns id)), :uid uid} {:id (symbol (format-id ns id)), :uid uid}
{:id (symbol (format-id ns id))}) {:id (symbol (format-id ns id))}))
(if uid
{:uid uid}
nil)))
(comment (str (format-parent (str *ns*) {:id ::id1 :uid "uid1"}))) (comment (str (format-parent (str *ns*) {:id ::id1 :uid "uid1"})))

View file

@ -33,9 +33,9 @@
(do (def t2s "2024-02-02T02:02:02.120Z") (def t2 (enc/as-inst t2s)) (def udt2 (enc/as-udt t2))) (do (def t2s "2024-02-02T02:02:02.120Z") (def t2 (enc/as-inst t2s)) (def udt2 (enc/as-udt t2)))
(do (def t3s "2024-03-03T03:03:03.130Z") (def t3 (enc/as-inst t3s)) (def udt3 (enc/as-udt t3))) (do (def t3s "2024-03-03T03:03:03.130Z") (def t3 (enc/as-inst t3s)) (def udt3 (enc/as-udt t3)))
(def ex-info-type (truss/ex-type (ex-info "" {}))) (def ex-info-type (truss/ex-type (truss/ex-info "" {})))
(def ex1 (ex-info "Ex1" {})) (def ex1 (truss/ex-info "Ex1" {}))
(def ex2 (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"}))) (def ex2 (truss/ex-info "Ex2" {:k2 "v2"} (truss/ex-info "Ex1" {:k1 "v1"})))
(def ex2-chain (truss/ex-chain :as-map ex2)) (def ex2-chain (truss/ex-chain :as-map ex2))
(defn ex1! [] (throw ex1)) (defn ex1! [] (throw ex1))
(defn ex1? [x] (= (truss/ex-root x) ex1))) (defn ex1? [x] (= (truss/ex-root x) ex1)))
@ -594,8 +594,7 @@
(let [{rv :value, [sv] :signals} (with-sigs (tel/log!? {:allow? false} "msg")) ] [(is (= rv nil)) (is (nil? sv))])]) (let [{rv :value, [sv] :signals} (with-sigs (tel/log!? {:allow? false} "msg")) ] [(is (= rv nil)) (is (nil? sv))])])
(testing "trace!" ; ?id + run => unconditional run result (value or throw) (testing "trace!" ; ?id + run => unconditional run result (value or throw)
[(let [{rv :value, [sv] :signals} (with-sigs (tel/trace! nil)) ] [(is (= rv nil)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id nil, :msg_ "nil => nil"}))]) [(let [{rv :value, [sv] :signals} (with-sigs (tel/trace! (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id nil, :msg_ "(+ 1 2) => 3"}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/trace! (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id nil, :msg_ "(+ 1 2) => 3"}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/trace! {:msg nil} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id nil, :msg_ nil}))]) (let [{rv :value, [sv] :signals} (with-sigs (tel/trace! {:msg nil} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id nil, :msg_ nil}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/trace! :id1 (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id :id1}))]) (let [{rv :value, [sv] :signals} (with-sigs (tel/trace! :id1 (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id :id1}))])
(let [{rv :value, [sv] :signals} (with-sigs (tel/trace! {:id :id1} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id :id1}))]) (let [{rv :value, [sv] :signals} (with-sigs (tel/trace! {:id :id1} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id :id1}))])
@ -727,7 +726,7 @@
(is (sm? (with-sig (-> (.atWarn sl) (.log "Hello"))) {:level :warn, :ns "my.class", :kind :slf4j, :msg_ "Hello", :inst enc/inst?}) "Fluent API: warn basics")]) (is (sm? (with-sig (-> (.atWarn sl) (.log "Hello"))) {:level :warn, :ns "my.class", :kind :slf4j, :msg_ "Hello", :inst enc/inst?}) "Fluent API: warn basics")])
(testing "Message formatting" (testing "Message formatting"
(let [msgp "x={},y={}", expected {:msg_ "x=1,y=2", :slf4j/args (fn [objs] (= (vec objs) ["1" "2"]))}] (let [msgp "x={},y={}", expected {:msg_ "x=1,y=2", :data {:slf4j/args ["1" "2"]}}]
[(is (sm? (with-sig (.info sl msgp "1" "2")) expected) "Legacy API: formatted message, raw args") [(is (sm? (with-sig (.info sl msgp "1" "2")) expected) "Legacy API: formatted message, raw args")
(is (sm? (with-sig (-> (.atInfo sl) (.setMessage msgp) (.addArgument "1") (.addArgument "2") (.log))) expected) "Fluent API: formatted message, raw args")])) (is (sm? (with-sig (-> (.atInfo sl) (.setMessage msgp) (.addArgument "1") (.addArgument "2") (.log))) expected) "Fluent API: formatted message, raw args")]))
@ -738,8 +737,8 @@
m2 (#'slf4j/est-marker! "M2") m2 (#'slf4j/est-marker! "M2")
cm (#'slf4j/est-marker! "Compound" "M1" "M2")] cm (#'slf4j/est-marker! "Compound" "M1" "M2")]
[(is (sm? (with-sig (.info sl cm "Hello")) {:slf4j/markers #{"Compound" "M1" "M2"}}) "Legacy API: markers") [(is (sm? (with-sig (.info sl cm "Hello")) {:data {:slf4j/marker-names #{"Compound" "M1" "M2"}}}) "Legacy API: markers")
(is (sm? (with-sig (-> (.atInfo sl) (.addMarker m1) (.addMarker cm) (.log))) {:slf4j/markers #{"Compound" "M1" "M2"}}) "Fluent API: markers")])) (is (sm? (with-sig (-> (.atInfo sl) (.addMarker m1) (.addMarker cm) (.log))) {:data {:slf4j/marker-names #{"Compound" "M1" "M2"}}}) "Fluent API: markers")]))
(testing "Errors" (testing "Errors"
[(is (sm? (with-sig (.warn sl "An error" ^Throwable ex1)) {:level :warn, :error ex1}) "Legacy API: errors") [(is (sm? (with-sig (.warn sl "An error" ^Throwable ex1)) {:level :warn, :error ex1}) "Legacy API: errors")
@ -754,15 +753,15 @@
;;;; Timbre shim ;;;; Timbre shim
(deftest _timbre-shim (deftest _timbre-shim
[(is (sm? (with-sig (timbre/log :warn "x1" nil "x2")) {:kind :log, :level :warn, :id timbre/shim-id, :msg_ "x1 nil x2", :timbre/vargs ["x1" nil "x2"], :ns string?})) [(is (sm? (with-sig (timbre/log :warn "x1" nil "x2")) {:kind :log, :level :warn, :id timbre/shim-id, :msg_ "x1 nil x2", :data {:vargs ["x1" nil "x2"]}, :ns string?}))
(is (sm? (with-sig (timbre/info "x1" nil "x2")) {:kind :log, :level :info, :id timbre/shim-id, :msg_ "x1 nil x2", :timbre/vargs ["x1" nil "x2"], :ns string?})) (is (sm? (with-sig (timbre/info "x1" nil "x2")) {:kind :log, :level :info, :id timbre/shim-id, :msg_ "x1 nil x2", :data {:vargs ["x1" nil "x2"]}, :ns string?}))
(is (sm? (with-sig (timbre/error "x1" nil "x2")) {:kind :log, :level :error, :id timbre/shim-id, :msg_ "x1 nil x2", :timbre/vargs ["x1" nil "x2"], :ns string?})) (is (sm? (with-sig (timbre/error "x1" nil "x2")) {:kind :log, :level :error, :id timbre/shim-id, :msg_ "x1 nil x2", :data {:vargs ["x1" nil "x2"]}, :ns string?}))
(is (sm? (with-sig (timbre/logf :warn "%s %s %s" "x1" nil "x2")) {:kind :log, :level :warn, :id timbre/shim-id, :msg_ "x1 nil x2", :timbre/vargs ["x1" nil "x2"], :ns string?})) (is (sm? (with-sig (timbre/logf :warn "%s %s %s" "x1" nil "x2")) {:kind :log, :level :warn, :id timbre/shim-id, :msg_ "x1 nil x2", :data {:vargs ["x1" nil "x2"]}, :ns string?}))
(is (sm? (with-sig (timbre/infof "%s %s %s" "x1" nil "x2")) {:kind :log, :level :info, :id timbre/shim-id, :msg_ "x1 nil x2", :timbre/vargs ["x1" nil "x2"], :ns string?})) (is (sm? (with-sig (timbre/infof "%s %s %s" "x1" nil "x2")) {:kind :log, :level :info, :id timbre/shim-id, :msg_ "x1 nil x2", :data {:vargs ["x1" nil "x2"]}, :ns string?}))
(is (sm? (with-sig (timbre/errorf "%s %s %s" "x1" nil "x2")) {:kind :log, :level :error, :id timbre/shim-id, :msg_ "x1 nil x2", :timbre/vargs ["x1" nil "x2"], :ns string?})) (is (sm? (with-sig (timbre/errorf "%s %s %s" "x1" nil "x2")) {:kind :log, :level :error, :id timbre/shim-id, :msg_ "x1 nil x2", :data {:vargs ["x1" nil "x2"]}, :ns string?}))
(is (sm? (with-sig (timbre/info ex1 "x1" "x2")) {:kind :log, :level :info, :error ex1, :msg_ "x1 x2", :timbre/vargs ["x1" "x2"]}) "First-arg error") (is (sm? (with-sig (timbre/info ex1 "x1" "x2")) {:kind :log, :level :info, :error ex1, :msg_ "x1 x2", :data {:vargs ["x1" "x2"]}}) "First-arg error")
(is (sm? (with-sig (timbre/spy :info "my-name" (+ 1 2))) {:kind :spy, :level :info, :id timbre/shim-id, :msg_ "my-name => 3", :ns string?})) (is (sm? (with-sig (timbre/spy :info "my-name" (+ 1 2))) {:kind :spy, :level :info, :id timbre/shim-id, :msg_ "my-name => 3", :ns string?}))
(is (sm? (tel/with-min-level :debug (with-sig (timbre/spy (+ 1 2)))) {:kind :spy, :level :debug, :id timbre/shim-id, :msg_ "(+ 1 2) => 3", :ns string?})) (is (sm? (tel/with-min-level :debug (with-sig (timbre/spy (+ 1 2)))) {:kind :spy, :level :debug, :id timbre/shim-id, :msg_ "(+ 1 2) => 3", :ns string?}))
@ -837,8 +836,8 @@
(testing "format-error-fn" (testing "format-error-fn"
(let [ex2-str ((utils/format-error-fn) ex2)] (let [ex2-str ((utils/format-error-fn) ex2)]
[(is (enc/str-starts-with? ex2-str [(is (enc/str-starts-with? ex2-str
#?(:clj "Root: clojure.lang.ExceptionInfo - Ex1\ndata: {:k1 \"v1\"}\n\nCaused: clojure.lang.ExceptionInfo - Ex2\ndata: {:k2 \"v2\"}\n\nRoot stack trace:\n" #?(:clj " Root: clojure.lang.ExceptionInfo - Ex1\n data: {:k1 \"v1\"}\n\nCaused: clojure.lang.ExceptionInfo - Ex2\n data: {:k2 \"v2\"}\n\nRoot stack trace:\n"
:cljs "Root: cljs.core/ExceptionInfo - Ex1\ndata: {:k1 \"v1\"}\n\nCaused: cljs.core/ExceptionInfo - Ex2\ndata: {:k2 \"v2\"}\n\nRoot stack trace:\n"))) :cljs " Root: cljs.core/ExceptionInfo - Ex1\n data: {:k1 \"v1\"}\n\nCaused: cljs.core/ExceptionInfo - Ex2\n data: {:k2 \"v2\"}\n\nRoot stack trace:\n")))
(is (enc/str-contains? ex2-str "Root stack trace:")) (is (enc/str-contains? ex2-str "Root stack trace:"))
(is (enc/str-contains? ex2-str "invoke") "Root stack trace includes content")])) (is (enc/str-contains? ex2-str "invoke") "Root stack trace includes content")]))

View file

@ -1,4 +1,4 @@
(defproject com.taoensso/telemere-slf4j "1.2.1" (defproject com.taoensso/telemere-slf4j "1.0.0"
:author "Peter Taoussanis <https://www.taoensso.com>" :author "Peter Taoussanis <https://www.taoensso.com>"
:description "Telemere backend/provider for SLF4J API v2" :description "Telemere backend/provider for SLF4J API v2"
:url "https://www.taoensso.com/telemere" :url "https://www.taoensso.com/telemere"
@ -16,9 +16,9 @@
:profiles :profiles
{:provided {:provided
{:dependencies {:dependencies
[[org.clojure/clojure "1.12.3"] [[org.clojure/clojure "1.12.0"]
[org.slf4j/slf4j-api "2.0.17"] [org.slf4j/slf4j-api "2.0.17"]
[com.taoensso/telemere "1.2.1"]]} [com.taoensso/telemere "1.0.0"]]}
:dev :dev
{:plugins {:plugins

View file

@ -1,5 +1,5 @@
(ns taoensso.telemere.slf4j (ns taoensso.telemere.slf4j
"SLF4Jv2 -> Telemere interop. "Interop support for SLF4Jv2 -> Telemere.
Telemere will attempt to load this ns automatically when possible. Telemere will attempt to load this ns automatically when possible.
To use Telemere as your SLF4J backend/provider, just include the To use Telemere as your SLF4J backend/provider, just include the
@ -63,13 +63,13 @@
(comment [(est-marker! "a1" "a2") (get-marker "a1") (= (get-marker "a1") (get-marker "a1"))]) (comment [(est-marker! "a1" "a2") (get-marker "a1") (= (get-marker "a1") (get-marker "a1"))])
(def ^:private get-marker-names (def ^:private marker-names
"Returns #{<MarkerName>}. Cached => assumes markers NOT modified after creation." "Returns #{<MarkerName>}. Cached => assumes markers NOT modified after creation."
;; We use `BasicMarkerFactory` so: ;; We use `BasicMarkerFactory` so:
;; 1. Our markers are just labels (no other content besides their name). ;; 1. Our markers are just labels (no other content besides their name).
;; 2. Markers with the same name are identical (enabling caching). ;; 2. Markers with the same name are identical (enabling caching).
(enc/fmemoize (enc/fmemoize
(fn get-marker-names [marker-or-markers] (fn marker-names [marker-or-markers]
(if (instance? org.slf4j.Marker marker-or-markers) (if (instance? org.slf4j.Marker marker-or-markers)
;; Single marker ;; Single marker
@ -82,12 +82,12 @@
(fn [acc ^org.slf4j.Marker in] (fn [acc ^org.slf4j.Marker in]
(if-not (.hasReferences in) (if-not (.hasReferences in)
(conj acc (.getName in)) (conj acc (.getName in))
(into acc (get-marker-names in)))) (into acc (marker-names in))))
acc (.iterator m)))) acc (.iterator m))))
;; Vector of markers ;; Vector of markers
(reduce (reduce
(fn [acc in] (into acc (get-marker-names in))) (fn [acc in] (into acc (marker-names in)))
#{} (truss/have vector? marker-or-markers)))))) #{} (truss/have vector? marker-or-markers))))))
(comment (comment
@ -97,9 +97,9 @@
ms [m1 m2]] ms [m1 m2]]
(enc/qb 1e6 ; [45.52 47.48 44.85] (enc/qb 1e6 ; [45.52 47.48 44.85]
(get-marker-names m1) (marker-names m1)
(get-marker-names cm) (marker-names cm)
(get-marker-names ms)))) (marker-names ms))))
;;;; Interop fns (called by `TelemereLogger`) ;;;; Interop fns (called by `TelemereLogger`)
@ -132,10 +132,11 @@
(org.slf4j.helpers.MessageFormatter/basicArrayFormat (org.slf4j.helpers.MessageFormatter/basicArrayFormat
msg-pattern args)) msg-pattern args))
:slf4j/args args ; Object[] :data
:slf4j/markers marker-names ; Usu. used for routing, filtering, xfns, etc. (enc/assoc-some nil
:data (when kvs {:slf4j/kvs kvs})}) :slf4j/marker-names marker-names
:slf4j/args (when args (vec args))
:slf4j/kvs kvs)})
nil) nil)
(defn- log! (defn- log!
@ -148,7 +149,7 @@
error (.getThrowable event) error (.getThrowable event)
msg-pattern (.getMessage event) msg-pattern (.getMessage event)
args (when-let [args (.getArgumentArray event)] args) args (when-let [args (.getArgumentArray event)] args)
marker-names (when-let [markers (.getMarkers event)] (get-marker-names (vec markers))) markers (when-let [markers (.getMarkers event)] (marker-names (vec markers)))
kvs (when-let [kvps (.getKeyValuePairs event)] kvs (when-let [kvps (.getKeyValuePairs event)]
(reduce (reduce
(fn [acc ^org.slf4j.event.KeyValuePair kvp] (fn [acc ^org.slf4j.event.KeyValuePair kvp]
@ -156,11 +157,11 @@
nil kvps))] nil kvps))]
(when-debug (println [:slf4j/fluent-log-call (sig-level level) logger-name])) (when-debug (println [:slf4j/fluent-log-call (sig-level level) logger-name]))
(normalized-log! logger-name level inst error msg-pattern args marker-names kvs))) (normalized-log! logger-name level inst error msg-pattern args markers kvs)))
;; Legacy API calls ;; Legacy API calls
([logger-name ^org.slf4j.event.Level level error msg-pattern args marker] ([logger-name ^org.slf4j.event.Level level error msg-pattern args marker]
(let [marker-names (when marker (get-marker-names marker))] (let [marker-names (when marker (marker-names marker))]
(when-debug (println [:slf4j/legacy-log-call (sig-level level) logger-name])) (when-debug (println [:slf4j/legacy-log-call (sig-level level) logger-name]))
(normalized-log! logger-name level (enc/now-inst*) error msg-pattern args marker-names nil)))) (normalized-log! logger-name level (enc/now-inst*) error msg-pattern args marker-names nil))))

View file

@ -83,7 +83,7 @@ deps.edn: com.taoensso/telemere {:mvn/version "x-y-z"}
And setup your namespace imports: And setup your namespace imports:
```clojure ```clojure
(ns my-app (:require [taoensso.telemere :as tel])) (ns my-app (:require [taoensso.telemere :as t]))
``` ```
# Default config # Default config
@ -127,34 +127,30 @@ Interop can be tough to get configured correctly so the [`check-interop`](https:
## Creating signals ## Creating signals
Telemere's signals are all created using the low-level `signal!` macro. You can use that directly, or one of the wrapper macros like `log!`. Use whichever signal creator is most convenient for your needs:
Several different wrapper macros are provided. The only difference between them: | Name | Kind | Args | Returns |
| :---------------------------------------------------------------------------------------------------------- | :--------- | :--------------- | :--------------------------- |
| [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `:log` | `?level` + `msg` | nil |
| [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `:event` | `id` + `?level` | nil |
| [`trace!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#trace!) | `:trace` | `?id` + `run` | Form result |
| [`spy!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#spy!) | `:spy` | `?level` + `run` | Form result |
| [`error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#error!) | `:error` | `?id` + `error` | Given error |
| [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) | `:error` | `?id` | Form value or given fallback |
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `:generic` | `opts` | Depends on opts |
1. They create signals with a different `:kind` value (which can be handy for filtering, etc.). - See relevant docstrings (links above) for usage info.
2. They have different positional arguments and/or return values optimised for concise calling in different use cases. - See [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) for more about signal creators.
- See [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) for options shared by all signal creators.
**NB:** ALL wrapper macros can also just be called with a single [opts](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) map! - See [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cljc) for REPL-ready examples.
See the linked docstrings below for more info:
| Name | Args | Returns |
| :---------------------------------------------------------------------------------------------------------- | :------------------------- | :--------------------------- |
| [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `[opts]` or `[?level msg]` | nil |
| [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `[opts]` or `[id ?level]` | nil |
| [`trace!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#trace!) | `[opts]` or `[?id run]` | Form result |
| [`spy!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#spy!) | `[opts]` or `[?level run]` | Form result |
| [`error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#error!) | `[opts]` or `[?id error]` | Given error |
| [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) | `[opts]` or `[?id error]` | Form value or given fallback |
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `[opts]` | Depends on opts |
## Checking signals ## Checking signals
Use the [`with-signal`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#with-signal) or (advanced) [`with-signals`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#with-signals) utils to help test/debug the signals that you're creating: Use the [`with-signal`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#with-signal) or (advanced) [`with-signals`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#with-signals) utils to help test/debug the signals that you're creating:
```clojure ```clojure
(tel/with-signal (t/with-signal
(tel/log! (t/log!
{:let [x "x"] {:let [x "x"]
:data {:x x}} :data {:x x}}
["My msg:" x])) ["My msg:" x]))
@ -169,7 +165,7 @@ Both have several options, see their docstrings (links above) for details.
## Filtering ## Filtering
A signal will be provided to a handler iff **ALL** of the following are true: A signal will be provided to a handler iff ALL of the following are true:
- 1. Signal **call filters** pass: - 1. Signal **call filters** pass:
- a. Compile time: sample rate, kind, ns, id, level, when form, rate limit - a. Compile time: sample rate, kind, ns, id, level, when form, rate limit
@ -182,22 +178,20 @@ A signal will be provided to a handler iff **ALL** of the following are true:
- 3. **Call transform** `(fn [signal]) => ?modified-signal` returns non-nil - 3. **Call transform** `(fn [signal]) => ?modified-signal` returns non-nil
- 4. **Handler transform** `(fn [signal]) => ?modified-signal` returns non-nil - 4. **Handler transform** `(fn [signal]) => ?modified-signal` returns non-nil
> 👉 Transform fns provides a flexible way to modify and/or filter signals by arbitrary signal data/content conditions (return nil to skip handling). > Transform fns provides a flexible way to modify and/or filter signals by arbitrary signal data/content conditions (return nil to skip handling).
> 👉 Call and handler filters are **additive** - so handlers can be *more* but not *less* restrictive than call filters allow. This makes sense: call filters decide if a signal can be created. Handler filters decide if a particular handler is allowed to handle a created signal.
Quick examples of some basic filtering: Quick examples of some basic filtering:
```clojure ```clojure
(tel/set-min-level! :info) ; Set global minimum level (t/set-min-level! :info) ; Set global minimum level
(tel/with-signal (tel/log! {:level :info ...})) ; => {:keys [inst id ...]} (t/with-signal (t/event! ::my-id1 :info)) ; => {:keys [inst id ...]}
(tel/with-signal (tel/log! {:level :debug ...})) ; => nil (signal not allowed) (t/with-signal (t/event! ::my-id1 :debug)) ; => nil (signal not allowed)
(tel/with-min-level :trace ; Override global minimum level (t/with-min-level :trace ; Override global minimum level
(tel/with-signal (tel/log! {:level :debug ...})) ; => {:keys [inst id ...]} (t/with-signal (t/event! ::my-id1 :debug))) ; => {:keys [inst id ...]}
;; Disallow all signals in matching namespaces ;; Disallow all signals in matching namespaces
(tel/set-ns-filter! {:disallow "some.nosy.namespace.*"}) (t/set-ns-filter! {:disallow "some.nosy.namespace.*"})
``` ```
- Filtering is always O(1), except for rate limits which are O(n_windows). - Filtering is always O(1), except for rate limits which are O(n_windows).

View file

@ -2,7 +2,7 @@ See below for config by topic-
# Filtering # Filtering
A signal will be provided to a handler iff **ALL** of the following are true: A signal will be provided to a handler iff ALL of the following are true:
- 1. Signal **call filters** pass: - 1. Signal **call filters** pass:
- a. Compile time: sample rate, kind, ns, id, level, when form, rate limit - a. Compile time: sample rate, kind, ns, id, level, when form, rate limit
@ -15,24 +15,10 @@ A signal will be provided to a handler iff **ALL** of the following are true:
- 3. **Call transform** `(fn [signal]) => ?modified-signal` returns non-nil - 3. **Call transform** `(fn [signal]) => ?modified-signal` returns non-nil
- 4. **Handler transform** `(fn [signal]) => ?modified-signal` returns non-nil - 4. **Handler transform** `(fn [signal]) => ?modified-signal` returns non-nil
> 👉 Transform fns provides a flexible way to modify and/or filter signals by arbitrary signal data/content conditions (return nil to skip handling). > Transform fns provides a flexible way to modify and/or filter signals by arbitrary signal data/content conditions (return nil to skip handling).
> 👉 Call and handler filters are **additive** - so handlers can be *more* but not *less* restrictive than call filters allow. This makes sense: call filters decide if a signal can be created. Handler filters decide if a particular handler is allowed to handle a created signal.
See [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) for more about filtering. See [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) for more about filtering.
## Debugging filters
Telemere offers a *lot* of filtering control, so real systems can get quite complex. There's a lot of tools to help debug, including:
| Util | |
| ------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ |
| [`with-signal`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#with-signal) | To see *last* signal created in body |
| [`with-signals`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#with-signals) | To see *all* signals created in body |
| [`get-filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-filters) | To see all call filters in current context |
| [`without-filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#without-filters) | To disable filters in body |
| [`get-handlers-stats`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats) | To see handler call stats |
# Signal handlers # Signal handlers
See section [4-Handlers](./4-Handlers). See section [4-Handlers](./4-Handlers).
@ -132,7 +118,7 @@ Telemere can easily incorporate Tufte performance data in its signals, just like
```clojure ```clojure
(let [[_ perf-data] (tufte/profiled <opts> <form>)] (let [[_ perf-data] (tufte/profiled <opts> <form>)]
(tel/log! {:perf-data perf-data} "Performance data")) (t/log! {:perf-data perf-data} "Performance data"))
``` ```
Telemere and Tufte work great together: Telemere and Tufte work great together:
@ -149,7 +135,7 @@ Telemere can easily incorporate Truss assertion failure information in its signa
The [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) signal creator can be particularly convenient for this: The [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) signal creator can be particularly convenient for this:
```clojure ```clojure
(tel/catch->error! <form-with-truss-assertion/s>) (t/catch->error! <form-with-truss-assertion/s>)
``` ```
Telemere also uses [Truss contextual exceptions](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#ex-info) when relevant. Telemere also uses [Truss contextual exceptions](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#ex-info) when relevant.

View file

@ -71,11 +71,11 @@ By default it writes formatted strings intended for human consumption:
```clojure ```clojure
;; Create a test signal ;; Create a test signal
(def my-signal (def my-signal
(tel/with-signal (t/with-signal
(tel/log! {:id ::my-id, :data {:x1 :x2}} "My message"))) (t/log! {:id ::my-id, :data {:x1 :x2}} "My message")))
;; Create console handler with default opts (writes formatted string) ;; Create console handler with default opts (writes formatted string)
(def my-handler (tel/handler:console {})) (def my-handler (t/handler:console {}))
;; Test handler, remember it's just a (fn [signal]) ;; Test handler, remember it's just a (fn [signal])
(my-handler my-signal) ; %> (my-handler my-signal) ; %>
@ -90,8 +90,8 @@ To instead writes signals as [edn](https://github.com/edn-format/edn):
```clojure ```clojure
;; Create console handler which writes signals as edn ;; Create console handler which writes signals as edn
(def my-handler (def my-handler
(tel/handler:console (t/handler:console
{:output-fn (tel/pr-signal-fn {:pr-fn :edn})})) {:output-fn (t/pr-signal-fn {:pr-fn :edn})}))
(my-handler my-signal) ; %> (my-handler my-signal) ; %>
;; {:inst #inst "2024-04-11T10:54:57.202869Z", :msg_ "My message", :ns "examples", ...} ;; {:inst #inst "2024-04-11T10:54:57.202869Z", :msg_ "My message", :ns "examples", ...}
@ -105,9 +105,9 @@ To instead writes signals as JSON:
;; Ref. <https://github.com/metosin/jsonista> (or any alt JSON lib) ;; Ref. <https://github.com/metosin/jsonista> (or any alt JSON lib)
#?(:clj (require '[jsonista.core :as jsonista])) #?(:clj (require '[jsonista.core :as jsonista]))
(def my-handler (def my-handler
(tel/handler:console (t/handler:console
{:output-fn {:output-fn
(tel/pr-signal-fn (t/pr-signal-fn
{:pr-fn {:pr-fn
#?(:cljs :json ; Use js/JSON.stringify #?(:cljs :json ; Use js/JSON.stringify
:clj jsonista/write-value-as-string)})})) :clj jsonista/write-value-as-string)})}))
@ -125,10 +125,9 @@ Telemere includes a handy mechanism for including arbitrary app-level data/opts
Any *non-standard* (app-level) keys you include in your signal constructor opts will automatically be included in created signals, e.g.: Any *non-standard* (app-level) keys you include in your signal constructor opts will automatically be included in created signals, e.g.:
```clojure ```clojure
(tel/with-signal (t/with-signal
(tel/log! (t/event! ::my-id
{... {:my-data-for-xfn "foo"
:my-data-for-xfn "foo"
:my-data-for-handler "bar"})) :my-data-for-handler "bar"}))
;; %> ;; %>
@ -251,7 +250,7 @@ If you're making a customizable handler for use by others, it's often handy to d
# Example output # Example output
```clojure ```clojure
(tel/log! {:id ::my-id, :data {:x1 :x2}} "My message") => (t/log! {:id ::my-id, :data {:x1 :x2}} "My message") =>
``` ```
## Clj console handler ## Clj console handler

View file

@ -76,18 +76,18 @@ Examples:
```clojure ```clojure
;; A fixed message (string arg) ;; A fixed message (string arg)
(tel/log! "A fixed message") ; %> {:msg "A fixed message"} (t/log! "A fixed message") ; %> {:msg "A fixed message"}
;; A joined message (vector arg) ;; A joined message (vector arg)
(let [user-arg "Bob"] (let [user-arg "Bob"]
(tel/log! ["User" (str "`" user-arg "`") "just logged in!"])) (t/log! ["User" (str "`" user-arg "`") "just logged in!"]))
;; %> {:msg_ "User `Bob` just logged in!` ...} ;; %> {:msg_ "User `Bob` just logged in!` ...}
;; With arg prep ;; With arg prep
(let [user-arg "Bob" (let [user-arg "Bob"
usd-balance-str "22.4821"] usd-balance-str "22.4821"]
(tel/log! (t/log!
{:let {:let
[username (clojure.string/upper-case user-arg) [username (clojure.string/upper-case user-arg)
usd-balance (parse-double usd-balance-str)] usd-balance (parse-double usd-balance-str)]
@ -100,10 +100,10 @@ Examples:
;; %> {:msg "User BOB has balance: $22" ...} ;; %> {:msg "User BOB has balance: $22" ...}
(tel/log! (str "This message " "was built " "by `str`")) (t/log! (str "This message " "was built " "by `str`"))
;; %> {:msg "This message was built by `str`"} ;; %> {:msg "This message was built by `str`"}
(tel/log! (format "This message was built by `%s`" "format")) (t/log! (format "This message was built by `%s`" "format"))
;; %> {:msg "This message was built by `format`"} ;; %> {:msg "This message was built by `format`"}
``` ```
@ -115,11 +115,11 @@ See also [`msg-skip`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/tao
See section [9-Authors](./9-Authors.md). See section [9-Authors](./9-Authors.md).
# How does Telemere compare to μ/log? # How does Telemere compare to Mulog?
> [μ/log](https://github.com/BrunoBonacci/mulog) is an excellent "micro-logging library" for Clojure that shares many of the same capabilities and objectives as Telemere. > [Mulog](https://github.com/BrunoBonacci/mulog) is an excellent "micro-logging library" for Clojure that shares many of the same capabilities and objectives as Telemere.
Some **similarities** between Telemere and μ/log: Some **similarities** between Telemere and Mulog:
- Both emphasize **structured data** rather than string messages - Both emphasize **structured data** rather than string messages
- Both offer **tracing** to understand (nested) program flow - Both offer **tracing** to understand (nested) program flow
@ -127,7 +127,7 @@ Some **similarities** between Telemere and μ/log:
- Both are **fast** and offer **async handling** - Both are **fast** and offer **async handling**
- Both offer a variety of **handlers** and are designed for ease of use - Both offer a variety of **handlers** and are designed for ease of use
Some particular **strengths of μ/log** that I'm aware of: Some particular **strengths of Mulog** that I'm aware of:
- More **established/mature** - More **established/mature**
- Wider **range of handlers** (incl. Kafka, Kinesis, Prometheus, Zipkin, etc.) - Wider **range of handlers** (incl. Kafka, Kinesis, Prometheus, Zipkin, etc.)
@ -137,7 +137,7 @@ Some particular **strengths of μ/log** that I'm aware of:
Some particular **strengths of Telemere**: Some particular **strengths of Telemere**:
- Both **Clj and Cljs support** (μ/log is Clj only) - Both **Clj and Cljs support** (Mulog is Clj only)
- Rich **filtering capabilities** (see [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters)) incl. compile-time elision - Rich **filtering capabilities** (see [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters)) incl. compile-time elision
- Rich **dispatch control** (see [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options)) - Rich **dispatch control** (see [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options))
- Rich **environmental config** (see [`help:environmental-config`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:environmental-config)) for all platforms - Rich **environmental config** (see [`help:environmental-config`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:environmental-config)) for all platforms
@ -148,7 +148,7 @@ Some particular **strengths of Telemere**:
**My subjective thoughts**: **My subjective thoughts**:
μ/log is an awesome, well-designed library with quality documentation and a solid API. It's **absolutely worth checking out** - you may well prefer it to Telemere! Mulog is an awesome, well-designed library with quality documentation and a solid API. It's **absolutely worth checking out** - you may well prefer it to Telemere!
The two libraries have many shared capabilities and objectives. The two libraries have many shared capabilities and objectives.

View file

@ -114,7 +114,7 @@ Consider the [differences](https://www.youtube.com/watch?v=oyLBGkS5ICk) between
Any non-standard [options](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) you give to a signal creator call will be added to the signal it creates: Any non-standard [options](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) you give to a signal creator call will be added to the signal it creates:
```clojure ```clojure
(tel/with-signal (tel/log! {:my-key "foo"} "My message"))) (t/with-signal (t/log! {:my-key "foo"} "My message")))
;; => {:my-key "foo", :kvs {:my-key "foo", ...}, ...} ;; => {:my-key "foo", :kvs {:my-key "foo", ...}, ...}
``` ```

View file

@ -22,6 +22,5 @@ Includes videos, tutorials, demo projects, etc.
Includes libraries or examples for handlers (see [Writing handlers](./4-Handlers#writing-handlers)), transforms, handler utils (e.g. formatters), tools for analyzing signals, etc. Includes libraries or examples for handlers (see [Writing handlers](./4-Handlers#writing-handlers)), transforms, handler utils (e.g. formatters), tools for analyzing signals, etc.
| Type | Description | | Type | Description |
| ------- | :------------------------------------------------------------ | | ---- | :------------------------------------------------------------ |
| Handler | [Axiom.co](https://github.com/marksto/telemere.axiom) handler |
| - | Your link here? [PRs](../wiki#contributions-welcome) welcome! | | - | Your link here? [PRs](../wiki#contributions-welcome) welcome! |

View file

@ -1,30 +1,17 @@
Are you a library author/maintainer that's considering **using Telemere in your library**? Are you a library author/maintainer that's considering **using Telemere in your library**?
You have **a few options** below- You have **two options** below-
# Options # Options
## 1. Common logging facade (basic logging only)
## Modern logging facade Many libraries need only basic logging. In these cases it can be beneficial to do your logging through a common logging facade like [tools.logging](https://github.com/clojure/tools.logging) or [SLF4J](https://www.slf4j.org/).
[Trove](https://www.taoensso.com/trove) is a minimal, modern alternative to [tools.logging](https://github.com/clojure/tools.logging) that supports all of Telemere's structured logging and rich filtering features. This'll limit you to basic features (e.g. no structured logging or [rich filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-filters)) - but your users will have the freedom to choose and configure their **preferred backend** ([incl. Telemere if they like](./3-Config#interop)).
Basically: ## 2. Telemere as a transitive dependency
1. You include the (very small) Trove dependency with your library Include [Telemere](https://clojars.org/com.taoensso/telemere) in your **library's dependencies**. Your library (and users) will then have access to the full Telemere API.
2. Your library logs using the [Trove API](https://github.com/taoensso/trove#to-choose-a-backend)
3. Your users then [choose](https://github.com/taoensso/trove#to-choose-a-backend) their preferred backend (Telemere, etc.)
This would be my first recommendation, and is what I'm planning to use for future updates to [Sente](https://www.taoensso.com/sente), [Carmine](https://www.taoensso.com/carmine), etc.
## Traditional logging facade (basic logging only)
Many libraries need only basic logging. In these cases it can be beneficial to do your logging through a common traditional logging facade like [tools.logging](https://github.com/clojure/tools.logging) or [SLF4J](https://www.slf4j.org/).
Though these'll limit you to basic features (e.g. no structured logging or [rich filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-filters)).
## Telemere as a transitive dependency
You could just include [Telemere](https://clojars.org/com.taoensso/telemere) in your **library's dependencies**. Your library (and users) will then have access to the full Telemere API.
Telemere's [default config](./1-Getting-started#default-config) is sensible (with println-like console output), so your users are unlikely to need to configure or interact with Telemere much unless they choose to. Telemere's [default config](./1-Getting-started#default-config) is sensible (with println-like console output), so your users are unlikely to need to configure or interact with Telemere much unless they choose to.