Compare commits

..

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

44 changed files with 966 additions and 1075 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,195 +2,91 @@ This project uses [**Break Versioning**](https://www.taoensso.com/break-versioni
--- ---
# `v1.2.1` (2025-12-16) # `v1.0.0-RC4` (2025-03-03)
## 📦 Dependencies ## 📦 Dependencies
Available on Clojars: Available on Clojars:
1. [Telemere](https://clojars.org/com.taoensso/telemere/versions/1.2.1) - main dep 1. [Telemere](https://clojars.org/com.taoensso/telemere/versions/1.0.0-RC4) - main dependency.
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 2. [SLF4J provider](https://clojars.org/com.taoensso/telemere-slf4j/versions/1.0.0-RC4) - 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). This project uses [Break Versioning](https://www.taoensso.com/break-versioning).
## Release notes ## 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. RC4 includes some minor fixes, minor usability improvements, and a few **breaking changes** (indicated by ➤) that should not affect most users.
This should be a safe upgrade for users of v1.2.0, apologies for the trouble! - Peter Taoussanis If no unexpected issues come up, RC4 will become **v1 stable** in March 2025.
--- Big thanks to everyone that's been helping test and give feedback. And as always, please **report any unexpected problems** on [GitHub](https://github.com/taoensso/telemere/issues) or the [Slack channel](https://www.taoensso.com/telemere/slack) 🙏
# `v1.2.0` (2025-12-09) Thanks for the patience awaiting v1 final! I hope and believe that it'll have been worth the time + effort.
## 📦 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)
## 📦 Dependencies
Available on Clojars:
1. [Telemere](https://clojars.org/com.taoensso/telemere/versions/1.0.0) - main dependency.
2. [SLF4J provider](https://clojars.org/com.taoensso/telemere-slf4j/versions/1.0.0) - 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 the first stable release of Telemere v1! 🍾🥳🎉
Sincere thanks to everyone that's been helping test and give feedback. As always, please **report any unexpected problems** on [GitHub](https://github.com/taoensso/telemere/issues) or the [Slack channel](https://www.taoensso.com/telemere/slack) 🙏
Telemere is part of a suite of practical and complementary **observability tools** for modern Clojure and ClojureScript applications:
- [Telemere](https://www.taoensso.com/telemere) for logging, tracing, and general telemetry
- [Tufte](https://www.taoensso.com/tufte) for performance monitoring ([v3 RC1 just released](https://github.com/taoensso/tufte/releases/tag/v3.0.0-RC1))
- [Truss](https://www.taoensso.com/truss) for assertions and error handling ([v2 recently released](https://github.com/taoensso/truss/releases/tag/v2.1.0))
New to Telemere? [Start here](https://github.com/taoensso/telemere/wiki/1-Getting-started)!
Upgrading from an earlier version? See the list of changes below👇
Cheers! :-)
\- [Peter Taoussanis](https://www.taoensso.com) \- [Peter Taoussanis](https://www.taoensso.com)
## Changes since `v1 RC1` ## Recent CHANGES
> See linked commits for more info: ### Since `v1.0.0-RC1` (2024-10-29)
In **v1 stable** (2025-04-30): #### ➤ Changes to API since `v1.0.0-RC1`
- \[fix] [#61] OpenTelemetry handler not cancelling timer on shutdown \[51e8a10]
- \[fix] [#32] Fix clj-kondo declaration typo (@icp1994) \[254cd64]
- \[new] Support `:host`, `:thread` override \[31a4fc2]
- \[new] Add callsite info to compile-time errors \[345b125]
- \[doc] Use consistent style for docstring opts \[94fec57]
In **v1 RC5** (2025-03-10): Please read these carefully in case you might be affected:
* \[mod] Rename `:rate-limit` -> `:limit` \[f37f54e] (RC5)
* \[mod] Rename `:sample-rate` -> `:sample` \[1f4b49a] (RC5)
* \[mod] Rename `:middleware` -> `:xfn` \[7cccf67] (RC5)
* \[mod] [#56] `utils/clean-signal-fn` exclude `:schema` by default \[c78eb07] (RC5)
* \[fix] [#57] File handling: use nio API to create missing parent dirs \[af45ffc] (RC5)
* \[fix] [#55] SLF4J signals should include `*ctx*` \[79173a6] (RC5)
* \[fix] [#32] Fix clj-kondo warnings \[c60f33e] (RC5)
* \[new] [#57] File handling: make file stream more robust \[82f4c31] (RC5)
In **v1 RC4** (2025-03-03): * ➤ **\[mod]** `log!`, `event!` now always return nil \[ac5feb4] (**RC4**)
* \[mod] `log!`, `event!` now always return nil \[ac5feb4] (RC4) * ➤ **\[mod]** Signal content: drop `:location`, add `:coords` \[fda22ce] (**RC3**)
* \[mod] [#51] Make default console handler sync by default \[78ed4d7] (RC4) * ➤ **\[mod]** Signal options: drop `:location`, add `:coords` \[1f99f71] (**RC3**)
* \[mod] [#52] `signal-preamble-fn` now ignores nil `:kind` (@marksto) \[634cc53] (RC4) * ➤ **\[mod]** OpenTelemetry: use standard attr names when possible \[bb715fb] (**RC3**)
* \[fix] [#52] `signal-preamble-fn` should use host info in signal (@marksto) \[410ed89] (RC4)
* \[new]Add `log!?`, `event!?` \[ac5feb4] (RC4)
* \[new] Alias `keep-callsite`, mention in `signal!` docs \[bfea515] (RC4)
* \[doc][#50] Expand docs for `set-min-level!` (via Encore update) (RC4)
* \[doc] Mention `:inst` monotonicity \[6b0e0b9] (RC4)
In **v1 RC3** (2025-02-27):
* \[mod] Signal content: drop `:location`, add `:coords` \[fda22ce] (RC3)
* \[mod] Signal options: drop `:location`, add `:coords` \[1f99f71] (RC3)
* \[mod] OpenTelemetry: use standard attr names when possible \[bb715fb] (RC3)
* \[fix] Timbre shim: rename `spy!` -> `spy` (@lvh) \[3a9ffc6] (RC3)
* \[fix] Timbre shim: don't attach empty `:vargs` data \[0e642ba] (RC3)
* \[fix] Fix environment val docs \[db26a5d] (RC3)
* \[fix] `spy!` docstring typo (@rafd) \[35606d9] (RC3)
* \[new] Use [Truss](https://www.taoensso.com/truss) v2 and [contextual exceptions](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#ex-info) when relevant (RC3)
* \[new] [#44] Open Telemetry handler: add span kind option (@farcaller) \[413cce8] (RC3)
* \[new] Reduced Cljs build sizes in some cases (RC3)
* \[doc]Timbre shim: document different `spy` error handling \[1517f30] (RC3)
* \[doc] [#43] ns filters work for SLF4J logger names (@lvh) \[db0498b] (RC3)
In **v1 RC2** (2024-12-24):
* \[mod] [#39] Discontinued separate "shell" library \[096c432] (RC2) * \[mod] [#39] Discontinued separate "shell" library \[096c432] (RC2)
* \[mod] Change return value of experimental [`with-signals`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#with-signals) \[cb6a5d9] (RC2) * \[mod] Change return value of experimental [`with-signals`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#with-signals) \[cb6a5d9] (RC2)
* \[mod] Remove rarely-used advanced options from [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) \[0de5c09] (RC2) * \[mod] Remove rarely-used advanced options from [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) \[0de5c09] (RC2)
#### Changes to default output since `v1.0.0-RC1`
* ➤ **\[mod]** [#52] `signal-preamble-fn` now ignores nil `:kind` (@marksto) \[634cc53] (**RC4**)
* \[mod] Remove "- " msg separator from default [preamble](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-preamble-fn) output \[d61f6c2] (RC2) * \[mod] Remove "- " msg separator from default [preamble](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-preamble-fn) output \[d61f6c2] (RC2)
* \[mod] [Postal handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.postal#handler:postal) now uses default preamble fn for email subject \[706a8b6] (RC2) * \[mod] [Postal handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.postal#handler:postal) now uses default preamble fn for email subject \[706a8b6] (RC2)
* \[mod] Default [`signal-content-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-content-fn): omit redundant parent/root id namespaces \[55323f1] (RC2) * \[mod] Default [`signal-content-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-content-fn): omit redundant parent/root id namespaces \[55323f1] (RC2)
* \[mod] Default [`signal-content-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-content-fn): swap `ctx`, `kvs` position \[b208532] (RC2) * \[mod] Default [`signal-content-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-content-fn): swap `ctx`, `kvs` position \[b208532] (RC2)
* \[mod] Default [`signal-content-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-content-fn): omit `:root` if it's same as parent \[0464285] (RC2) * \[mod] Default [`signal-content-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-content-fn): omit `:root` if it's same as parent \[0464285] (RC2)
* \[mod] Omit empty `:data`, `:ctx` from [signal content](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-content-fn) output \[d78663a] (RC2) * \[mod] Omit empty `:data`, `:ctx` from [signal content](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-content-fn) output \[d78663a] (RC2)
* \[fix] Broken signal string representation \[8c701d4] (RC2)
* \[fix] Trace formatting: always include root info \[f522307] (RC2) ### Other changes
* \[fix] Trace formatting: properly format nil ids \[68a894e] (RC2)
* \[fix] [#36] Fix missing cljdoc docstrings \[b58ec73] (RC2) * **\[mod]** [#51] Make default console handler sync by default \[78ed4d7] (**RC4**)
* \[mod] Update [`pr-signal-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#pr-signal-fn) to use [`clean-signal-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#clean-signal-fn) \[f70363091] (beta 23)
* \[mod] Rename `taoensso.telemere.api` -> `taoensso.telemere.shell` \[a9005e7f1] (beta 23)
* \[mod] Move dep: `com.taoensso/slf4j-telemere` -> [com.taoensso/telemere-slf4j](https://clojars.org/com.taoensso/telemere-slf4j) \[77ed27cfd] (beta 22)
* \[mod] Generalize "intake", rename -> "interop" \[ef678bcc] (beta 20)
* \[mod] Make `:host` output opt-in for default signal handlers \[88eb5211] (beta 20)
* \[mod] [OpenTelemetry handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry): rename (generalize) \[064ef323] (beta 19)
* \[mod] [OpenTelemetry handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry): revert #10 \[599236f4] (beta 18)
* \[mod] Decrease level of :on-init signals \[4d2b5d46] (beta 18)
* \[mod] Removed `*auto-stop-handlers?*` var (beta 15)
* \[mod] Removed `:needs-stopping?` [handler dispatch opt](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) (beta 15)
* \[mod] Cljs handlers MUST now include stop (0) arity (beta 15)
* \[mod] Users MUST now **manually call** [`stop-handlers!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#stop-handlers!) (beta 15)
* \[mod] SLF4J and `tools.logging` signals now have a custom `:kind` and no `:id` (beta 14)
* \[mod] Renamed `get-min-level` -> [`get-min-levels`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-min-levels) (beta 13)
* \[mod] Renamed `shut-down-handlers!` -> [`stop-handlers!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#stop-handlers!) (beta 13)
* \[mod] Changed default **handler back-pressure** mechanism from `:dropping` to `:blocking` (eaiser for most users to understand and detect; override when calling [`add-handler!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#add-handler!)) (beta 11)
* \[mod] [`pr-signal-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#pr-signal-fn) now takes only a **single opts map** (beta 10)
* \[mod] [User-level kvs](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) are **no longer included by default** in handler output. `:incl-kvs?` option has been added to [`format-signal-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#format-signal-fn) and [`pr-signal-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#pr-signal-fn) (beta 7)
* \[mod] Middleware must now be a **single fn**, use [`comp-middleware`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#comp-middleware) to create one fn from many (beta 7)
* \[mod] [OpenTelemetry handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry) is **no longer auto added** (beta 1)
* \[mod] Various API improvements to [included handlers](https://github.com/taoensso/telemere/wiki/4-Handlers#included-handlers) and [utils](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils)
## Recent additions
### Since `v1.0.0-RC1` (2024-10-29)
#### Misc improvements
* **\[new]** Add `log!?`, `event!?` \[ac5feb4] (**RC4**)
* **\[new]** Alias `keep-callsite`, mention in `signal!` docs \[bfea515] (**RC4**)
* **\[new]** Use [Truss](https://www.taoensso.com/truss) v2 and [contextual exceptions](https://cljdoc.org/d/com.taoensso/truss/CURRENT/api/taoensso.truss#ex-info) when relevant (**RC3**)
* **\[new]** [#44] Open Telemetry handler: add span kind option (@farcaller) \[413cce8] (**RC3**)
* **\[new]** Reduced Cljs build sizes in some cases (**RC3**)
* \[new] Add [`timbre->telemere`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.timbre#timbre->telemere-appender) appender and update docs \[ace6e2d] (RC2) * \[new] Add [`timbre->telemere`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.timbre#timbre->telemere-appender) appender and update docs \[ace6e2d] (RC2)
* \[new] All signal creators can now take single opts map \[d2386d6] (RC2) * \[new] All signal creators can now take single opts map \[d2386d6] (RC2)
* \[new] Add `& opts` support to [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!), [`signal-allowed?`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal-allowed?) \[a04f255] (RC2) * \[new] Add `& opts` support to [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!), [`signal-allowed?`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal-allowed?) \[a04f255] (RC2)
@ -200,12 +96,99 @@ In **v1 RC2** (2024-12-24):
* \[new] Allow manual `:run-val` override \[9dc883d] (RC2) * \[new] Allow manual `:run-val` override \[9dc883d] (RC2)
* \[new] [#34] Add new [`signal-preamble-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-preamble-fn) opts (@Knotschi) \[0822217] (RC2) * \[new] [#34] Add new [`signal-preamble-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#signal-preamble-fn) opts (@Knotschi) \[0822217] (RC2)
* \[new] Alias low-level formatters in utils ns \[9dc9a46] (RC2) * \[new] Alias low-level formatters in utils ns \[9dc9a46] (RC2)
#### Doc improvements
* **\[doc]** [#50] Expand docs for `set-min-level!` (via Encore update) (**RC4**)
* **\[doc]** Mention `:inst` monotonicity \[6b0e0b9] (**RC4**)
* **\[doc]** Timbre shim: document different `spy` error handling \[1517f30] (**RC3**)
* **\[doc]** [#43] ns filters work for SLF4J logger names (@lvh) \[db0498b] (**RC3**)
* \[doc] [#33] Add community examples link to [Bling Gist](https://gist.github.com/ptaoussanis/f8a80f85d3e0f89b307a470ce6e044b5) \[8cd4ca9] (RC2) * \[doc] [#33] Add community examples link to [Bling Gist](https://gist.github.com/ptaoussanis/f8a80f85d3e0f89b307a470ce6e044b5) \[8cd4ca9] (RC2)
* \[doc] Better document pattern of using [`trace!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#trace!)/[`spy!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#spy!) with [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) \[5c977a3] (RC2) * \[doc] Better document pattern of using [`trace!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#trace!)/[`spy!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#spy!) with [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) \[5c977a3] (RC2)
* \[doc] [#35] Emphasize that [signal opts](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) must be a compile-time map \[55720ac] (RC2) * \[doc] [#35] Emphasize that [signal opts](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) must be a compile-time map \[55720ac] (RC2)
* \[doc] Add [FAQ item](https://github.com/taoensso/telemere/wiki/6-FAQ#why-the-unusual-arg-order-for-event) re: [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) arg order \[822032d] (RC2) * \[doc] Add [FAQ item](https://github.com/taoensso/telemere/wiki/6-FAQ#why-the-unusual-arg-order-for-event) re: [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) arg order \[822032d] (RC2)
* \[doc] Document that `:msg` may be a delay \[13d9dbf] (RC2) * \[doc] Document that `:msg` may be a delay \[13d9dbf] (RC2)
### Earlier improvements
* \[new] Add `:ctx+`, `:middleware+` signal options \[5a8c407] (RC1)
* \[new] OpenTelemetry handler: try print map vals as EDN \[c1e1c1e] (RC1)
* \[new] [#28] OpenTelemetry handler: support custom signal attrs \[5ef4f12] (RC1)
* \[new] Simplify default OpenTelemetry providers code, expose SDK \[19548d3] (RC1)
* \[new] Add `dispatch-signal!` util \[5ac8725] (RC1)
* \[new] `writeable-file!`: resolve sym links, etc. \[9965450] (RC1)
* \[new] Extend IIFE-wrap to Clj \[d0ad99d] (RC1)
* \[new] Numerous improvements to docs and examples (RC1)
* \[new] Add `:rate-limit-by` option to all signal creators \[d9c358363] (beta 23)
* \[new] Add `clean-signal-fn` util \[be55f44a8] (beta 23)
* \[new] Add `signal-allowed?` util \[d12b0b145] (beta 23)
* \[new] Allow compile-time config of uid kind \[965c2277f] (beta 23)
* \[new] Avoid duplicated trace bodies \[c9e84e8b3] (beta 23)
* \[new] Cap length of displayed run-form when tracing \[85772f733] (beta 23)
* \[new] Added experimental [shell API](https://cljdoc.org/d/com.taoensso/telemere-shell/CURRENT/api/taoensso.telemere.api) for library authors \[ece51b2ef] (beta 22)
* \[new] Auto stop existing handler when replacing it (beta 22)
* \[new] Added `"(.*)"` wildcard syntax to kind/ns/id filters (beta 22)
* \[new] Internal and doc improvements: \[8066776a8], \[b4b06f324], \[3068ccf8d] (beta 21)
* \[new] OpenTelemetry handler: improve span interop \[84957c6d] (beta 20)
* \[new] OpenTelemetry handler: add experimental trace output \[67cb4941] (beta 18)
* \[new] Improve uid control, switch to nano-style by default \[5ab2736c] (beta 18)
* \[new] Add host info to signal content \[1cef1957] (beta 18)
* \[new] Add extra tracing info to signal content \[d635318f] (beta 18)
* \[new] Ongoing [API](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere) and [wiki](https://github.com/taoensso/telemere/wiki) doc improvements (beta 15)
* \[new] [#5] Added [comparison to Mulog](https://github.com/taoensso/telemere/wiki/6-FAQ#how-does-telemere-compare-to-mulog) (beta 15)
* \[new] SLF4J and `tools.logging` signals now have a namespace (from logger name) (beta 14)
* \[new] Added [`get-handlers-stats`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#get-handlers-stats) (beta 13)
* \[new] [`add-handler!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#add-handler!) can now specify per-handler `:drain-msecs` (beta 13)
* \[new] Added [`*auto-stop-handlers?*`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#*auto-stop-handlers?*) (beta 13)
* \[new] [`remove-handler!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#remove-handler!) now auto stops relevant handlers after removal (beta 13)
* \[new] [`with-handler`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#with-handler) and [`with-handler+`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#with-handler+) now auto stops relevant handlers after use (beta 12)
* \[new] (Advanced) Handler fns can now include `:dispatch-opts` metadata, useful for handler authors that want to set defaults for use by [`add-handler!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#add-handler!) (beta 8)
* \[new] Added [Slack handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.slack#handler:slack) (beta 8)
* \[new] Added [TCP](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:tcp-socket) and [UDP](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:udp-socket) socket handlers (beta 7)
* \[new] Clj [signal content](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) now includes `:thread {:keys [group name id]}` key (beta 7)
* \[new] Added [postal (email) handler](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.postal#handler:postal) (beta 5)
* \[new] Handlers now block to try drain their signal queues on shutdown (beta 3)
* \[new] Rate limiter performance improvements (via Encore) (beta 3)
## Recent fixes
### Since `v1.0.0-RC1` (2024-10-29)
* **\[fix]** [#52] `signal-preamble-fn` should use host info in signal (@marksto) \[410ed89] (**RC4**)
* **\[fix]** Timbre shim: rename `spy!` -> `spy` (@lvh) \[3a9ffc6] (**RC3**)
* **\[fix]** Timbre shim: don't attach empty `:vargs` data \[0e642ba] (**RC3**)
* **\[fix]** Fix environment val docs \[db26a5d] (**RC3**)
* **\[fix]** `spy!` docstring typo (@rafd) \[35606d9] (**RC3**)
* \[fix] Broken signal string representation \[8c701d4] (RC2)
* \[fix] Trace formatting: always include root info \[f522307] (RC2)
* \[fix] Trace formatting: properly format nil ids \[68a894e] (RC2)
* \[fix] [#36] Fix missing cljdoc docstrings \[b58ec73] (RC2)
### Earlier fixes
* \[fix] `signal-opts`: allow map forms as intended \[f7a5663] (RC1)
* \[fix] `uncaught->error!` wasn't working (@benalbrecht) \[7f52cb1] (RC1)
* \[fix] Regression affecting deprecated `rate-limiter*` (beta 25)
* \[fix] Don't try count non-list tracing bodies \[88f7a3c7d] (beta 24)
* \[fix] [#21] Work around issue with use in Cljs `core.async/go` bodies \[cbab57be6] (beta 23)
* \[fix] [#20] Wrong :arglists meta on `spy!` \[568906c96] (beta 23)
* \[fix] [#18] Support `{:uid :auto}` for non-tracing signal creators \[f52a04b4d] (beta 23)
* \[fix] Runtime Clj env config now works correctly in uberjars (beta 23)
* \[fix] Signal `:line` info missing for some wrapped-macro cases \[0f09b797e] (beta 22)
* \[fix] OpenTelemetry handler: use signal callsite Context as root span parent \[a8e92303] (beta 19)
* \[fix] [#16] OpenTelemetry handler: coerce line attrs (@flyingmachine) \[17349a08] (beta 19)
* \[fix] Decrease min Java version (11->8) (@flyingmachine) \[a1c50f10] (beta 19)
* \[fix] Broken handler ns and kind filters \[23194238] (beta 16)
* \[fix] [#10] OpenTelemetry handler: render keywords as plain strings \[6e94215e] (beta 15)
* \[fix] [#11] OpenTelemetry handler: signals without message fail \[863cea15] (beta 15)
* \[fix] [#14] File handler: Don't truncate gzip output \[2d4b0497] (beta 15)
* \[fix] Don't drop signals while draining async buffer during shutdown, add tests (via Encore) (beta 12, beta 13)
* \[fix] [`pr-signal-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#pr-signal-fn) wasn't realizing delayed messages, add tests \[cf72017a] (beta 11)
* \[fix] [`pr-signal-fn`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils#pr-signal-fn) broken custom pr-fn support, add tests \[e7cce0c1] (beta 10)
* \[fix] [#6] Missing root stack trace, add tests \[213c6470] (beta 9)
* \[fix] Broken AOT support, add tests \[ffea1a30] (beta 1)
* \[fix] SLF4J broken timestamps, add tests \[e222297a] (beta 1)
--- ---
# `v1.0.0-RC1` (2024-10-29) # `v1.0.0-RC1` (2024-10-29)

230
README.md
View file

@ -1,79 +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**.
It helps enable Clojure/Script systems that are easily **observable**, **robust**, and **debuggable** - and it represents the refinement and culmination of ideas brewing over 12+ years in [Timbre](https://www.taoensso.com/timbre), [Tufte](https://www.taoensso.com/tufte) and [Truss](https://www.taoensso.com/truss).
See [here](../../wiki/1-Getting-started) for **full introduction** (concepts, terminology, getting started).
## Latest release/s
- `2025-03-03` `v1.0.0-RC4`: [release info](../../releases/tag/v1.0.0-RC4)
[![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-rate 0.75 ; 75% sampling (noop 25% of the time)
:when (my-conditional) :when (my-conditional)
:limit {"1 per sec" [1 1000] :rate-limit {"1 per sec" [1 1000]
"5 per min" [5 60000]} ; Rate limit "5 per min" [5 60000]}
:limit-by my-user-ip-address ; Rate limit scope :rate-limit-by my-user-ip-address ; Optional rate-limit scope
:do (inc-my-metric!) :do (inc-my-metric!)
:let :let
@ -95,33 +99,34 @@ 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 middleware to:
;; by signal data/content/etc. ;; - Transform signals
;; - Filter signals by arb conditions (incl. data/content)
(tel/set-xfn! (t/set-middleware!
(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 :passed-through-middleware? 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,44 +135,44 @@ 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")))
{:async {:mode :dropping, :buffer-size 1024, :n-threads 1} {:async {:mode :dropping, :buffer-size 1024, :n-threads 1}
:priority 100 :priority 100
:sample 0.5 :sample-rate 0.5
:min-level :info :min-level :info
:ns-filter {:disallow "taoensso.*"} :ns-filter {:disallow "taoensso.*"}
:limit {"1 per sec" [1 1000]} :rate-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 +184,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 +229,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
@ -242,7 +251,7 @@ Detailed help is available without leaving your IDE:
| :---------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------- | | :---------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------- |
| [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) | Creating signals | | [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) | Creating signals |
| [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Options when creating signals | | [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Options when creating signals |
| [`help:signal-content`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) | Signal content (map given to transforms/handlers) | | [`help:signal-content`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) | Signal content (map given to middleware/handlers) |
| [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) | Signal filtering and transformation | | [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) | Signal filtering and transformation |
| [`help:handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handlers) | Signal handler management | | [`help:handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handlers) | Signal handler management |
| [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) | Signal handler dispatch options | | [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) | Signal handler dispatch options |
@ -271,7 +280,7 @@ Telemere is optimized for *real-world* performance. This means **prioritizing fl
Large applications can produce absolute *heaps* of data, not all equally valuable. Quickly processing infinite streams of unmanageable junk is an anti-pattern. As scale and complexity increase, it becomes more important to **strategically plan** what data to collect, when, in what quantities, and how to manage it. Large applications can produce absolute *heaps* of data, not all equally valuable. Quickly processing infinite streams of unmanageable junk is an anti-pattern. As scale and complexity increase, it becomes more important to **strategically plan** what data to collect, when, in what quantities, and how to manage it.
Telemere is designed to help with all that. It offers [rich data](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) and unmatched [filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) support - including per-signal and per-handler **sampling** and **rate limiting**, and zero cost compile-time filtering. Telemere is designed to help with all that. It offers [rich data](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) and unmatched [filtering](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) support - including per-signal and per-handler **sampling** and **rate-limiting**, and zero cost compile-time filtering.
Use these to ensure that you're not capturing useless/low-value/high-noise information in production! With appropriate planning, Telemere is designed to scale to systems of any size and complexity. Use these to ensure that you're not capturing useless/low-value/high-noise information in production! With appropriate planning, Telemere is designed to scale to systems of any size and complexity.
@ -305,7 +314,7 @@ You can also easily [write your own handlers](../../wiki/4-Handlers#writing-hand
## Community ## Community
My plan for Telemere is to offer a **stable core of limited scope**, then to focus on making it as easy for the **community** to write additional stuff like handlers, transforms, and utils. My plan for Telemere is to offer a **stable core of limited scope**, then to focus on making it as easy for the **community** to write additional stuff like handlers, middleware, and utils.
See [here](../../wiki/8-Community) for community resources. See [here](../../wiki/8-Community) for community resources.
@ -314,12 +323,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 +340,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 +352,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

@ -1,41 +1,39 @@
(ns examples (ns examples
"Basic Telemere usage examples that appear in the Wiki or docstrings." "Basic Telemere usage examples that appear in the Wiki or docstrings."
(:require [taoensso.telemere :as tel])) (:require [taoensso.telemere :as t]))
(comment (comment
;;;; README "Quick examples" ;;;; README "Quick examples"
(require '[taoensso.telemere :as tel]) (require '[taoensso.telemere :as t])
;; 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!")}) (t/log! :info "Hello world!") ; %> Basic log signal (has message)
(t/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}}) (t/log! {:level :info, :data {}} "Hello again!")
(t/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 {...}} (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-rate 0.75 ; 75% sampling (noop 25% of the time)
:when (my-conditional) :when (my-conditional)
:limit {"1 per sec" [1 1000] :rate-limit {"1 per sec" [1 1000]
"5 per min" [5 60000]} ; Rate limit "5 per min" [5 60000]}
:limit-by my-user-ip-address ; Rate limit scope :rate-limit-by my-user-ip-address ; Optional rate-limit scope
:do (inc-my-metric!) :do (inc-my-metric!)
:let :let
@ -49,128 +47,126 @@
;; 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 (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 namespace and id filters
(tel/set-id-filter! {:allow #{::my-particular-id "my-app/*"}}) (t/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"})
(tel/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"}) (t/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) (t/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 ;; Use middleware to:
(tel/set-min-level! :log "taoensso.sente.*" :warn) ;; - Transform signals
;; - Filter signals by arb conditions (incl. data/content)
;; Use transforms (xfns) to filter and/or arbitrarily modify signals (t/set-middleware!
;; by signal data/content/etc.
(tel/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 :passed-through-middleware? 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
;;;; README "More examples"
;; 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")))
{:async {:mode :dropping, :buffer-size 1024, :n-threads 1} {:async {:mode :dropping, :buffer-size 1024, :n-threads 1}
:priority 100 :priority 100
:sample 0.5 :sample-rate 0.5
:min-level :info :min-level :info
:ns-filter {:disallow "taoensso.*"} :ns-filter {:disallow "taoensso.*"}
:limit {"1 per sec" [1 1000]} :rate-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)}))
;;;; Docstring examples ;;;; Docstring examples
(tel/with-signal (tel/event! ::my-id)) (t/with-signal (t/event! ::my-id))
(tel/with-signal (tel/event! ::my-id :warn)) (t/with-signal (t/event! ::my-id :warn))
(tel/with-signal (t/with-signal
(tel/event! ::my-id (t/event! ::my-id
{:let [x "x"] ; Available to `:data` and `:msg` {:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x} :data {:x x}
:msg ["My msg:" x]})) :msg ["My msg:" x]}))
(tel/with-signal (tel/log! "My msg")) (t/with-signal (t/log! "My msg"))
(tel/with-signal (tel/log! :warn "My msg")) (t/with-signal (t/log! :warn "My msg"))
(tel/with-signal (t/with-signal
(tel/log! (t/log!
{:let [x "x"] ; Available to `:data` and `:msg` {:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x}} :data {:x x}}
["My msg:" x])) ["My msg:" x]))
(tel/with-signal (throw (tel/error! (ex-info "MyEx" {})))) (t/with-signal (throw (t/error! (ex-info "MyEx" {}))))
(tel/with-signal (throw (tel/error! ::my-id (ex-info "MyEx" {})))) (t/with-signal (throw (t/error! ::my-id (ex-info "MyEx" {}))))
(tel/with-signal (t/with-signal
(throw (throw
(tel/error! (t/error!
{:let [x "x"] ; Available to `:data` and `:msg` {:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x} :data {:x x}
:msg ["My msg:" x]} :msg ["My msg:" x]}
(ex-info "MyEx" {})))) (ex-info "MyEx" {}))))
(tel/with-signal (tel/trace! (+ 1 2))) (t/with-signal (t/trace! (+ 1 2)))
(tel/with-signal (tel/trace! ::my-id (+ 1 2))) (t/with-signal (t/trace! ::my-id (+ 1 2)))
(tel/with-signal (t/with-signal
(tel/trace! (t/trace!
{:let [x "x"] ; Available to `:data` and `:msg` {:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x}} :data {:x x}}
(+ 1 2))) (+ 1 2)))
(tel/with-signal (tel/spy! (+ 1 2))) (t/with-signal (t/spy! (+ 1 2)))
(tel/with-signal (tel/spy! :debug (+ 1 2))) (t/with-signal (t/spy! :debug (+ 1 2)))
(tel/with-signal (t/with-signal
(tel/spy! (t/spy!
{:let [x "x"] ; Available to `:data` and `:msg` {:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x}} :data {:x x}}
(+ 1 2))) (+ 1 2)))
(tel/with-signal (tel/catch->error! (/ 1 0))) (t/with-signal (t/catch->error! (/ 1 0)))
(tel/with-signal (tel/catch->error! ::my-id (/ 1 0))) (t/with-signal (t/catch->error! ::my-id (/ 1 0)))
(tel/with-signal (t/with-signal
(tel/catch->error! (t/catch->error!
{:let [x "x"] ; Available to `:data` and `:msg` {:let [x "x"] ; Available to `:data` and `:msg`
:data {:x x} :data {:x x}
:msg ["My msg:" x] :msg ["My msg:" x]
@ -181,25 +177,25 @@
;;; Filter signals ;;; Filter signals
(tel/set-min-level! :info) ; Set global minimum level (t/set-min-level! :info) ; Set global minimum level
(tel/with-signal (tel/event! ::my-id1 :info)) ; => {:keys [inst id ...]} (t/with-signal (t/event! ::my-id1 :info)) ; => {:keys [inst id ...]}
(tel/with-signal (tel/event! ::my-id1 :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/event! ::my-id1 :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.*"})
;;; Configuring handlers ;;; Configuring handlers
;; 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) ; %>
@ -208,8 +204,8 @@
;; 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", ...}
@ -218,9 +214,9 @@
;; 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)})}))
@ -229,20 +225,19 @@
;; {"inst":"2024-04-11T10:54:57.202869Z","msg_":"My message","ns":"examples", ...} ;; {"inst":"2024-04-11T10:54:57.202869Z","msg_":"My message","ns":"examples", ...}
;; Deregister the default console handler ;; Deregister the default console handler
(tel/remove-handler! :defaultel/console) (t/remove-handler! :default/console)
;; Register our custom console handler ;; Register our custom console handler
(tel/add-handler! :my-handler my-handler (t/add-handler! :my-handler my-handler
;; Lots of options here for filtering, etc. ;; Lots of options here for filtering, etc.
;; See `help:handler-dispatch-options` docstring! ;; See `help:handler-dispatch-options` docstring!
{}) {})
;; NB make sure to always stop handlers at the end of your ;; NB make sure to always stop handlers at the end of your
;; `-main` or shutdown procedure ;; `-main` or shutdown procedure
(tel/call-on-shutdown! (t/call-on-shutdown! t/stop-handlers!)
(fn [] (tel/stop-handlers!)))
;; See `tel/help:handlers` docstring for more ;; See `t/help:handlers` docstring for more
;;; Writing handlers ;;; Writing handlers
@ -296,7 +291,7 @@
(with-meta handler-fn (with-meta handler-fn
{:dispatch-opts {:dispatch-opts
{:min-level :info {:min-level :info
:limit :rate-limit
[[1 1000] ; Max 1 signal per second [[1 1000] ; Max 1 signal per second
[10 60000] ; Max 10 signals per minute [10 60000] ; Max 10 signals per minute
]}})))) ]}}))))
@ -304,18 +299,18 @@
;;; Message building ;;; Message building
;; 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)]
@ -328,54 +323,54 @@
;; %> {: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! (enc/format "This message was built by `%s`" "format")) (t/log! (enc/format "This message was built by `%s`" "format"))
;; %> {:msg "This message was built by `format`"} ;; %> {:msg "This message was built by `format`"}
;;; App-level kvs ;;; App-level kvs
(tel/with-signal (t/with-signal
(tel/event! ::my-id (t/event! ::my-id
{:my-data-for-xfn "foo" {:my-middleware-data "foo"
:my-data-for-handler "bar"})) :my-handler-data "bar"}))
;; %> ;; %>
;; {;; App-level kvs included inline (assoc'd to signal root) ;; {;; App-level kvs included inline (assoc'd to signal root)
;; :my-data-for-xfn "foo" ;; :my-middleware-data "foo"
;; :my-data-for-handler "bar" ;; :my-handler-data "bar"
;; :kvs ; And also collected together under ":kvs" key ;; :kvs ; And also collected together under ":kvs" key
;; {:my-data-for-xfn "foo" ;; {:my-middleware-data "foo"
;; :my-data-for-handler "bar"} ;; :my-handler-data "bar"}
;; ... } ;; ... }
;;;; Misc extra examples ;;;; Misc extra examples
(tel/log! {:id ::my-id, :data {:x1 :x2}} ["My 2-part" "message"]) ; %> (t/log! {:id ::my-id, :data {:x1 :x2}} ["My 2-part" "message"]) ; %>
;; 2024-04-11T10:54:57.202869Z INFO LOG MyHost examples(56,1) ::my-id - My 2-part message ;; 2024-04-11T10:54:57.202869Z INFO LOG MyHost examples(56,1) ::my-id - My 2-part message
;; data: {:x1 :x2} ;; data: {:x1 :x2}
;; `:let` bindings are available to `:data` and message, but only paid ;; `:let` bindings are available to `:data` and message, but only paid
;; for when allowed by minimum level and other filtering criteria ;; for when allowed by minimum level and other filtering criteria
(tel/log! (t/log!
{:level :info {:level :info
:let [expensive (reduce * (range 1 12))] ; 12 factorial :let [expensive (reduce * (range 1 12))] ; 12 factorial
:data {:my-metric expensive}} :data {:my-metric expensive}}
["Message with metric:" expensive]) ["Message with metric:" expensive])
;; With sampling 50% and 1/sec rate limiting ;; With sampling 50% and 1/sec rate limiting
(tel/log! (t/log!
{:sample 0.5 {:sample-rate 0.5
:limit {"1 per sec" [1 1000]}} :rate-limit {"1 per sec" [1 1000]}}
"This signal will be sampled and rate limited") "This signal will be sampled and rate limited")
;; Several signal creators are available for convenience. ;; Several signal creators are available for convenience.
;; All offer the same options, but each has an API optimized ;; All offer the same options, but each has an API optimized
;; for a particular use case: ;; for a particular use case:
(tel/log! {:level :info, :id ::my-id} "Hi!") ; [msg] or [level-or-opts msg] (t/log! {:level :info, :id ::my-id} "Hi!") ; [msg] or [level-or-opts msg]
(tel/event! ::my-id {:level :info, :msg "Hi!"}) ; [id] or [id level-or-opts] (t/event! ::my-id {:level :info, :msg "Hi!"}) ; [id] or [id level-or-opts]
(tel/signal! {:level :info, :id ::my-id, :msg "Hi!"}) ; [opts] (t/signal! {:level :info, :id ::my-id, :msg "Hi!"}) ; [opts]
) )

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 340 KiB

After

Width:  |  Height:  |  Size: 333 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-RC4"
: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.137.5"]]
: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.11.132"]
[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"]]}
@ -46,19 +46,19 @@
*unchecked-math* false #_:warn-on-boxed} *unchecked-math* false #_:warn-on-boxed}
:dependencies :dependencies
[[org.clojure/core.async "1.8.741"] [[org.clojure/core.async "1.7.701"]
[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-RC4"]
#_[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.47.0"]
[io.opentelemetry/opentelemetry-sdk-extension-autoconfigure "1.57.0"] [io.opentelemetry/opentelemetry-sdk-extension-autoconfigure "1.47.0"]
[io.opentelemetry/opentelemetry-exporter-otlp "1.57.0"] [io.opentelemetry/opentelemetry-exporter-otlp "1.47.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

@ -1,17 +1,14 @@
Telemere signals are maps with {:keys [inst id ns level data msg_ ...]}, Signals are maps with {:keys [inst id ns level data msg_ ...]}, though they
though they can be modified by call and/or handler transform (xfns). can be modified by signal and/or handler middleware.
Default signal keys: Default signal keys:
`:schema` ------ Int version of signal schema (current: 1) `:schema` ------ Int version of signal schema (current: 1)
`:inst` -------- Platform instant [1] when signal was created, monotonicity depends on system clock `:inst` -------- Platform instant [1] when signal was created, monotonicity depends on system clock
`:ns` ---------- ?str namespace of signal callsite
`:coords` ------ ?[line column] of signal callsite
`:kind` -------- Signal ?kind ∈ #{nil :event :error :log :trace :spy :slf4j :tools-logging <app-val> ...}
`:level` ------- Signal level ∈ #{<int> :trace :debug :info :warn :error :fatal :report ...} `:level` ------- Signal level ∈ #{<int> :trace :debug :info :warn :error :fatal :report ...}
`:id` ---------- Signal callsite ?id (usu. keyword) (common to all signals created at callsite, contrast with `:uid`) `:kind` -------- Signal ?kind ∈ #{nil :event :error :log :trace :spy :slf4j :tools-logging <app-val> ...}
`:uid` --------- Signal instance ?id (usu. string) (unique to each signal created at callsite when tracing, contrast with `:id`) `:id` ---------- ?id of signal (common to all signals created at callsite, contrast with `:uid`)
`:uid` --------- ?id of signal instance (unique to each signal created at callsite when tracing, contrast with `:id`)
`:msg_` -------- Arb app-level message ?str given to signal creator - may be a delay, always use `force` to unwrap! `:msg_` -------- Arb app-level message ?str given to signal creator - may be a delay, always use `force` to unwrap!
`:data` -------- Arb app-level data ?val (usu. a map) given to signal creator `:data` -------- Arb app-level data ?val (usu. a map) given to signal creator
@ -26,14 +23,17 @@ Default signal keys:
`:root` -------- ?{:keys [id uid]} of root signal, present in nested signals when tracing `:root` -------- ?{:keys [id uid]} of root signal, present in nested signals when tracing
`:ctx` --------- ?val of `*ctx*` (arb app-level state) when signal was created `:ctx` --------- ?val of `*ctx*` (arb app-level state) when signal was created
`:ns` ---------- ?str namespace of signal callsite
`:coords` ------ ?[line column] of signal callsite
`:host` -------- (Clj only) {:keys [name ip]} info for network host `:host` -------- (Clj only) {:keys [name ip]} info for network host
`:thread` ------ (Clj only) {:keys [name id group]} info for thread that created signal `:thread` ------ (Clj only) {:keys [name id group]} info for thread that created signal
`:sample` ------ Sample ?rate ∈ℝ[0,1] for combined call AND handler sampling (0.75 => allow 75% of signals, nil => allow all) `:sample-rate` - ?rate ∈ℝ[0,1] for combined signal AND handler sampling (0.75 => allow 75% of signals, nil => allow all)
<kvs> ---------- Other arb app-level ?kvs given to signal creator. Typically NOT included <kvs> ---------- Other arb app-level ?kvs given to signal creator. Typically NOT included
in handler output, so a great way to provide custom data/opts for use in handler output, so a great way to provide custom data/opts for use
(only) by custom transforms/handlers. (only) by custom middleware/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!

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
@ -24,26 +24,20 @@ All options are available for all signal creator calls:
`:ns` ---------- Custom ?str namespace to override auto signal callsite info `:ns` ---------- Custom ?str namespace to override auto signal callsite info
`: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) `:sample-rate` - ?rate ∈ℝ[0,1] for signal sampling (0.75 => allow 75% of signals, nil => allow all)
`: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)
`:when` -------- Arb ?form; when present, form must return truthy to allow signal `:when` -------- Arb ?form; when present, form must return truthy to allow signal
`:limit` ------- Rate limit ?spec given to `taoensso.telemere/rate-limiter`, see its docstring for details `:rate-limit` -- ?spec as given to `taoensso.telemere/rate-limiter`, see its docstring for details
`:limit-by` ---- When present, rate limits will be enforced independently for each value (any Clojure value!) `:rate-limit-by` When present, rate limits will be enforced independently for each id (any Clojure value!)
`:xfn` --------- Optional transform (fn [signal]) => ?modified-signal to apply when signal is created, as per `with-xfn` `:middleware` -- Optional (fn [signal]) => ?modified-signal to apply when signal is created, as per `with-middleware`
`:xfn+` -------- Optional extra transform (fn [signal]) => ?modified-signal to apply when signal is created, as per `with-xfn+` `:middleware+` - Optional (fn [signal]) => ?modified-signal to apply when signal is created, as per `with-middleware+`
`:trace?` ------ Should tracing be enabled for `:run` form?
<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 middleware/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

@ -25,60 +25,39 @@
;; Via `sigs/def-api` ;; Via `sigs/def-api`
without-filters with-kind-filter with-ns-filter with-id-filter without-filters with-kind-filter with-ns-filter with-id-filter
with-min-level with-handler with-handler+ with-min-level with-handler with-handler+
with-ctx with-ctx+ with-xfn with-xfn+]]))) with-ctx with-ctx+ with-middleware with-middleware+]])))
(comment (comment
(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 137 5])
;;;; Shared signal API ;;;; Shared signal API
(declare ; Needed to avoid `clj-kondo` "Unresolved var" warnings
level-aliases
help:filters help:handlers help:handler-dispatch-options
get-filters get-min-levels get-handlers get-handlers-stats
#?(:clj without-filters)
set-kind-filter! #?(:clj with-kind-filter)
set-ns-filter! #?(:clj with-ns-filter)
set-id-filter! #?(:clj with-id-filter)
set-min-level! #?(:clj with-min-level)
#?(:clj with-handler) #?(:clj with-handler+)
add-handler! remove-handler! stop-handlers!
with-signal with-signals
^:dynamic *ctx* set-ctx! #?(:clj with-ctx) #?(:clj with-ctx+)
^:dynamic *xfn* set-xfn! #?(:clj with-xfn) #?(:clj with-xfn+))
(def default-handler-dispatch-opts
"See `help:handler-dispatch-opts` for details."
(dissoc sigs/default-handler-dispatch-opts
:convey-bindings? ; We use `enc/bound-delay`
))
(sigs/def-api (sigs/def-api
{:sf-arity 4 {:sf-arity 4
:ct-call-filter impl/ct-call-filter :ct-call-filter impl/ct-call-filter
:*rt-call-filter* impl/*rt-call-filter* :*rt-call-filter* impl/*rt-call-filter*
:*sig-handlers* impl/*sig-handlers* :*sig-handlers* impl/*sig-handlers*
:lib-dispatch-opts default-handler-dispatch-opts}) :lib-dispatch-opts
(assoc sigs/default-handler-dispatch-opts
:convey-bindings? false ; Handled manually
)})
;;;; Aliases ;;;; Aliases
(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 enc/comp-middleware
sigs/default-handler-dispatch-opts
#?(:clj truss/keep-callsite) #?(:clj truss/keep-callsite)
;; Impl ;; Impl
@ -232,7 +211,7 @@
(defn- args->opts [args] (defn- args->opts [args]
(case (count args) (case (count args)
0 {} 0 {}
1 (first args) 1 (impl/valid-opts! (first args))
(apply hash-map args)))) (apply hash-map args))))
#?(:clj #?(:clj
@ -242,26 +221,24 @@
Allows you to use Telemere's rich filtering system for conditionally Allows you to use Telemere's rich filtering system for conditionally
executing arbitrary code. Also handy for batching multiple signals executing arbitrary code. Also handy for batching multiple signals
under a single set of conditions (incl. sampling, rate limiting, etc.): under a single set of conditions (incl. rate-limiting, sampling, etc.):
;; Logs exactly 2 or 0 messages (never 1): ;; Logs exactly 2 or 0 messages (never 1):
(when (signal-allowed? {:level :info, :sample 0.5}) (when (signal-allowed? {:level :info, :sample-rate 0.5})
(log! {:allow? true} \"Message 1\") (log! {:allow? true} \"Message 1\")
(log! {:allow? true} \"Message 2\"))" (log! {:allow? true} \"Message 2\"))"
;; Used also for interop (tools.logging, SLF4J), etc. ;; Used also for interop (tools.logging, SLF4J), etc.
{:arglists (impl/arglists :signal-allowed?)} {:arglists (impl/signal-arglists :signal-allowed?)}
[& args] [& args] `(impl/signal-allowed? ~(args->opts args))))
(truss/keep-callsite
`(impl/signal-allowed? ~(args->opts args)))))
(comment (macroexpand '(signal-allowed? {:ns "my-ns"}))) (comment (macroexpand '(signal-allowed? {:ns "my-ns"})))
#?(:clj #?(:clj
(defmacro signal! (defmacro signal!
"opts => allowed? / run result (value or throw)." "opts => allowed? / run result (value or throw)."
{:doc (impl/docstring :signal!) {:doc (impl/signal-docstring :signal!)
:arglists (impl/arglists :signal!)} :arglists (impl/signal-arglists :signal!)}
[& args] [& args]
(truss/keep-callsite (truss/keep-callsite
`(impl/signal! ~(args->opts args))))) `(impl/signal! ~(args->opts args)))))
@ -279,8 +256,8 @@
(let [base-opts {:kind :log, :level :info}] (let [base-opts {:kind :log, :level :info}]
(defmacro log!? (defmacro log!?
"?level + msg => allowed?" "?level + msg => allowed?"
{:doc (impl/docstring :log!) {:doc (impl/signal-docstring :log!)
:arglists (impl/arglists :log!)} :arglists (impl/signal-arglists :log!)}
([opts-or-msg ] `(impl/signal! ~(merge-or-assoc-opts base-opts &form :msg opts-or-msg))) ([opts-or-msg ] `(impl/signal! ~(merge-or-assoc-opts base-opts &form :msg opts-or-msg)))
([opts-or-level msg] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :level opts-or-level) :msg msg)))))) ([opts-or-level msg] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :level opts-or-level) :msg msg))))))
@ -289,8 +266,8 @@
#?(:clj #?(:clj
(defmacro log! (defmacro log!
"Like `log!?` but always returns nil." "Like `log!?` but always returns nil."
{:doc (impl/docstring :log!) {:doc (impl/signal-docstring :log!)
:arglists (impl/arglists :log!)} :arglists (impl/signal-arglists :log!)}
[& args] `(do ~(truss/keep-callsite `(log!? ~@args)) nil))) [& args] `(do ~(truss/keep-callsite `(log!? ~@args)) nil)))
(comment (:coords (with-signal (log! :info "My msg")))) (comment (:coords (with-signal (log! :info "My msg"))))
@ -299,8 +276,8 @@
(let [base-opts {:kind :event, :level :info}] (let [base-opts {:kind :event, :level :info}]
(defmacro event!? (defmacro event!?
"id + ?level => allowed? Note unique arg order: [x opts] rather than [opts x]!" "id + ?level => allowed? Note unique arg order: [x opts] rather than [opts x]!"
{:doc (impl/docstring :event!) {:doc (impl/signal-docstring :event!)
:arglists (impl/arglists :event!)} :arglists (impl/signal-arglists :event!)}
([ opts-or-id] `(impl/signal! ~(merge-or-assoc-opts base-opts &form :id opts-or-id))) ([ opts-or-id] `(impl/signal! ~(merge-or-assoc-opts base-opts &form :id opts-or-id)))
([id opts-or-level] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :level opts-or-level) :id id)))))) ([id opts-or-level] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :level opts-or-level) :id id))))))
@ -309,8 +286,8 @@
#?(:clj #?(:clj
(defmacro event! (defmacro event!
"Like `event!?` but always returns nil." "Like `event!?` but always returns nil."
{:doc (impl/docstring :event!) {:doc (impl/signal-docstring :event!)
:arglists (impl/arglists :event!)} :arglists (impl/signal-arglists :event!)}
[& args] `(do ~(truss/keep-callsite `(event!? ~@args)) nil))) [& args] `(do ~(truss/keep-callsite `(event!? ~@args)) nil)))
(comment (:coords (with-signal (event! ::my-id :info)))) (comment (:coords (with-signal (event! ::my-id :info))))
@ -319,8 +296,8 @@
(let [base-opts {:kind :trace, :level :info, :msg `impl/default-trace-msg}] (let [base-opts {:kind :trace, :level :info, :msg `impl/default-trace-msg}]
(defmacro trace! (defmacro trace!
"?id + run => run result (value or throw)." "?id + run => run result (value or throw)."
{:doc (impl/docstring :trace!) {:doc (impl/signal-docstring :trace!)
:arglists (impl/arglists :trace!)} :arglists (impl/signal-arglists :trace!)}
([opts-or-run] `(impl/signal! ~(merge-or-assoc-opts base-opts &form :run opts-or-run))) ([opts-or-run] `(impl/signal! ~(merge-or-assoc-opts base-opts &form :run opts-or-run)))
([opts-or-id run] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :id opts-or-id) :run run)))))) ([opts-or-id run] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :id opts-or-id) :run run))))))
@ -330,8 +307,8 @@
(let [base-opts {:kind :spy, :level :info, :msg `impl/default-trace-msg}] (let [base-opts {:kind :spy, :level :info, :msg `impl/default-trace-msg}]
(defmacro spy! (defmacro spy!
"?level + run => run result (value or throw)." "?level + run => run result (value or throw)."
{:doc (impl/docstring :spy!) {:doc (impl/signal-docstring :spy!)
:arglists (impl/arglists :spy!)} :arglists (impl/signal-arglists :spy!)}
([opts-or-run] `(impl/signal! ~(merge-or-assoc-opts base-opts &form :run opts-or-run))) ([opts-or-run] `(impl/signal! ~(merge-or-assoc-opts base-opts &form :run opts-or-run)))
([opts-or-level run] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :level opts-or-level) :run run)))))) ([opts-or-level run] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form :level opts-or-level) :run run))))))
@ -341,8 +318,8 @@
(let [base-opts {:kind :error, :level :error}] (let [base-opts {:kind :error, :level :error}]
(defmacro error! (defmacro error!
"?id + error => given error." "?id + error => given error."
{:doc (impl/docstring :error!) {:doc (impl/signal-docstring :error!)
:arglists (impl/arglists :error!)} :arglists (impl/signal-arglists :error!)}
([opts-or-id error] `(error! ~(assoc (merge-or-assoc-opts base-opts &form :id opts-or-id) :error error))) ([opts-or-id error] `(error! ~(assoc (merge-or-assoc-opts base-opts &form :id opts-or-id) :error error)))
([opts-or-error] ([opts-or-error]
(let [opts (merge-or-assoc-opts base-opts &form :error opts-or-error) (let [opts (merge-or-assoc-opts base-opts &form :error opts-or-error)
@ -358,8 +335,8 @@
(let [base-opts {:kind :error, :level :error}] (let [base-opts {:kind :error, :level :error}]
(defmacro catch->error! (defmacro catch->error!
"?id + run => run value or ?catch-val." "?id + run => run value or ?catch-val."
{:doc (impl/docstring :catch->error!) {:doc (impl/signal-docstring :catch->error!)
:arglists (impl/arglists :catch->error!)} :arglists (impl/signal-arglists :catch->error!)}
([opts-or-id run] `(catch->error! ~(assoc (merge-or-assoc-opts base-opts &form :id opts-or-id) :run run))) ([opts-or-id run] `(catch->error! ~(assoc (merge-or-assoc-opts base-opts &form :id opts-or-id) :run run)))
([opts-or-run] ([opts-or-run]
(let [opts (merge-or-assoc-opts base-opts &form :run opts-or-run) (let [opts (merge-or-assoc-opts base-opts &form :run opts-or-run)
@ -401,7 +378,7 @@
uncaught JVM errors. uncaught JVM errors.
See `uncaught->handler!` and `error!` for details." See `uncaught->handler!` and `error!` for details."
{:arglists (impl/arglists :uncaught->error!)} {:arglists (impl/signal-arglists :uncaught->error!)}
([ ] (truss/keep-callsite `(uncaught->error! {}))) ([ ] (truss/keep-callsite `(uncaught->error! {})))
([opts-or-id] ([opts-or-id]
(let [opts (merge-or-assoc-opts base-opts &form :id opts-or-id)] (let [opts (merge-or-assoc-opts base-opts &form :id opts-or-id)]

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]
@ -13,6 +14,7 @@
#?(:clj #?(:clj
(defn ^:public handler:console (defn ^:public handler:console
"Alpha, subject to change. "Alpha, subject to change.
Returns a signal handler that: Returns a signal handler that:
- Takes a Telemere signal (map). - Takes a Telemere signal (map).
- Writes the signal as a string to specified stream. - Writes the signal as a string to specified stream.
@ -22,11 +24,11 @@
Options: Options:
`:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn` `:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
`:stream` ---- `java.io.writer` `:stream` - `java.io.writer`
Defaults to `*err*` if `utils/error-signal?` is true, and `*out*` otherwise." Defaults to `*err*` if `utils/error-signal?` is true, and `*out*` otherwise."
([] (handler:console nil)) ([] (handler:console nil))
([{:keys [stream output-fn] ([{:keys [stream output-fn ]
:or :or
{stream :auto {stream :auto
output-fn (utils/format-signal-fn)}}] output-fn (utils/format-signal-fn)}}]
@ -50,6 +52,7 @@
:cljs :cljs
(defn ^:public handler:console (defn ^:public handler:console
"Alpha, subject to change. "Alpha, subject to change.
If `js/console` exists, returns a signal handler that: If `js/console` exists, returns a signal handler that:
- Takes a Telemere signal (map). - Takes a Telemere signal (map).
- Writes the signal as a string to JavaScript console. - Writes the signal as a string to JavaScript console.
@ -86,6 +89,7 @@
#?(:cljs #?(:cljs
(defn ^:public handler:console-raw (defn ^:public handler:console-raw
"Alpha, subject to change. "Alpha, subject to change.
If `js/console` exists, returns a signal handler that: If `js/console` exists, returns a signal handler that:
- Takes a Telemere signal (map). - Takes a Telemere signal (map).
- Writes the raw signal to JavaScript console. - Writes the raw signal to JavaScript console.
@ -94,7 +98,7 @@
Ref. <https://github.com/binaryage/cljs-devtools>. Ref. <https://github.com/binaryage/cljs-devtools>.
Options: Options:
`:preamble-fn` ----- (fn [signal]) => string, see [1]. `:preamble-fn` - (fn [signal]) => string, see [1].
`:format-nsecs-fn` - (fn [nanosecs]) => string. `:format-nsecs-fn` - (fn [nanosecs]) => string.
[1] `taoensso.telemere.utils/signal-preamble-fn`, etc." [1] `taoensso.telemere.utils/signal-preamble-fn`, etc."

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]
@ -285,18 +286,18 @@
Options: Options:
`:output-fn`- (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn` `:output-fn`- (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
`:path` ----- Path string of the target output file (default `logs/telemere.log`) `:path` - Path string of the target output file (default `logs/telemere.log`)
`:interval` - #{nil :daily :weekly :monthly} (default `:monthly`) `:interval` - #{nil :daily :weekly :monthly} (default `:monthly`)
When non-nil, causes interval-based archives to be maintained. When non-nil, causes interval-based archives to be maintained.
`:max-file-size` - #{nil <pos-int>} (default 4MB) `:max-file-size` #{nil <pos-int>} (default 4MB)
When `path` file size > ~this many bytes, rotates old content to numbered archives. When `path` file size > ~this many bytes, rotates old content to numbered archives.
`:max-num-parts` - #{nil <pos-int>} (default 8) `:max-num-parts` #{nil <pos-int>} (default 8)
Maximum number of numbered archives to retain for any particular interval. Maximum number of numbered archives to retain for any particular interval.
`:max-num-intervals` - #{nil <pos-int>} (default 6) `:max-num-intervals` #{nil <pos-int>} (default 6)
Maximum number of intervals (days/weeks/months) to retain." Maximum number of intervals (days/weeks/months) to retain."
([] (handler:file nil)) ([] (handler:file nil))

View file

@ -18,7 +18,7 @@
#?(:clj #?(:clj
(enc/declare-remote (enc/declare-remote
^:dynamic taoensso.telemere/*ctx* ^:dynamic taoensso.telemere/*ctx*
^:dynamic taoensso.telemere/*xfn* ^:dynamic taoensso.telemere/*middleware*
^:dynamic taoensso.telemere/*uid-fn* ^:dynamic taoensso.telemere/*uid-fn*
^:dynamic taoensso.telemere/*otel-tracer*)) ^:dynamic taoensso.telemere/*otel-tracer*))
@ -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)
@ -253,22 +250,31 @@
;; Telemere's main public data type, we avoid nesting and duplication ;; Telemere's main public data type, we avoid nesting and duplication
[schema inst uid, ns coords, [schema inst uid, ns coords,
#?@(:clj [host thread _otel-context]), #?@(:clj [host thread _otel-context]),
sample, kind id level, ctx parent root, data kvs msg_, sample-rate, kind id level, ctx parent root, data kvs msg_,
error run-form run-val end-inst run-nsecs] error run-form run-val end-inst run-nsecs]
Object (toString [sig] (str "taoensso.telemere.Signal" (enc/pr-edn* (into {} sig))))) Object (toString [sig] (str "taoensso.telemere.Signal" (into {} sig))))
;; Verbose constructors for readability + to support extra keys ;; NB intentionally verbose constructors for readability, to support extra keys
(do (enc/def-print-impl [sig Signal] (str "#taoensso.telemere.Signal" (enc/pr-edn* (into {} sig))))) (do (enc/def-print-impl [sig Signal] (str "#taoensso.telemere.Signal" (pr-str (into {} sig)))))
#?(:clj (enc/def-print-dup [sig Signal] (str "#taoensso.telemere.impl.Signal" (enc/pr-edn* (into {} sig))))) #?(:clj (enc/def-print-dup [sig Signal] (str "#taoensso.telemere.impl.Signal" (pr-str (into {} sig)))))
(defn signal? #?(:cljs {:tag 'boolean}) [x] (instance? Signal x))
(def impl-signal-keys #{:_otel-context}) (def impl-signal-keys #{:_otel-context})
(def standard-signal-keys (def standard-signal-keys
(set/difference (set (keys (map->Signal {:schema 0}))) (set/difference (set (keys (map->Signal {:schema 0})))
impl-signal-keys)) impl-signal-keys))
(comment
(def s1 (with-signal (signal! {:level :info, :my-k1 :my-v1})))
(read-string (str (assoc s1 :my-k2 :my-v2)))
(read-string (pr-str (assoc s1 :my-k2 :my-v2)))
(read-string (binding [*print-dup* true] (pr-str (assoc s1 :my-k2 :my-v2))))
(defrecord MyRec [x])
(read-string ; Non-verbose will fail on any extra keys
(binding [*print-dup* true, *verbose-defrecords* false]
(pr-str (assoc (MyRec. :x) :y :y)))))
(deftype #_defrecord WrappedSignal (deftype #_defrecord WrappedSignal
[kind ns id level signal-value_] [kind ns id level signal-value_]
sigs/ISignalHandling sigs/ISignalHandling
@ -374,36 +380,38 @@
(sigs/call-handlers! *sig-handlers* signal) (sigs/call-handlers! *sig-handlers* signal)
:dispatched)) :dispatched))
;;;; API helpers ;;;; Signal API helpers
#?(:clj (defmacro docstring [ rname] (enc/slurp-resource (str "docs/" (name rname) ".txt")))) #?(:clj (defmacro signal-docstring [ rname] (enc/slurp-resource (str "signal-docstrings/" (name rname) ".txt"))))
#?(:clj (defmacro defhelp [sym rname] `(enc/def* ~sym {:doc ~(eval `(docstring ~rname))} "See docstring"))) #?(:clj (defmacro defhelp [sym rname] `(enc/def* ~sym {:doc ~(eval `(signal-docstring ~rname))} "See docstring")))
#?(:clj #?(:clj
(defn arglists [macro-id] (defn signal-arglists [macro-id]
;; + Undocumented [elide? allow? callsite-id host thread otel/context]
(case macro-id (case macro-id
:signal-allowed? ; opts => allowed? :signal-allowed? ; opts => allowed?
'( [& opts-kvs] '( [& opts-kvs]
[{:as opts-map :keys [{:as opts-map :keys
[elidable? coords #_inst #_uid #_xfn #_xfn+, [#_elide? #_allow? #_callsite-id, ; Undocumented
sample kind ns id level when limit limit-by, elidable? coords #_inst #_uid #_middleware #_middleware+,
sample-rate kind ns id level when rate-limit rate-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]}])
: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+, [#_elide? #_allow? #_callsite-id, ; Undocumented
sample kind ns id level when limit limit-by, elidable? coords inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-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]}])
:log! ; ?level + msg => nil / allowed? :log! ; ?level + msg => nil / allowed?
'([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+, [#_elide? #_allow? #_callsite-id,
sample kind ns id level when limit limit-by, elidable? coords inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-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,16 +420,18 @@
[id level] [id level]
[id [id
{:as opts-map :keys {:as opts-map :keys
[elidable? coords inst uid xfn xfn+ #_kvs+, [#_elide? #_allow? #_callsite-id,
sample kind ns id level when limit limit-by, elidable? coords inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-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]}])
:trace! ; ?id + run => run result (value or throw) :trace! ; ?id + run => run result (value or throw)
'([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+, [#_elide? #_allow? #_callsite-id,
sample kind ns id level when limit limit-by, elidable? coords inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-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,8 +439,9 @@
'([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+, [#_elide? #_allow? #_callsite-id,
sample kind ns id level when limit limit-by, elidable? coords inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-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,8 +449,9 @@
'([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+, [#_elide? #_allow? #_callsite-id,
sample kind ns id level when limit limit-by, elidable? coords inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-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])
@ -447,9 +459,9 @@
'([opts-or-run] '([opts-or-run]
[id run] [id run]
[{:as opts-map :keys [{:as opts-map :keys
[catch-val, [#_elide? #_allow? #_callsite-id, catch-val,
elidable? coords inst uid xfn xfn+ #_kvs+, elidable? coords inst uid middleware middleware+,
sample kind ns id level when limit limit-by, sample-rate kind ns id level when rate-limit rate-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,8 +469,9 @@
'([] '([]
[opts-or-id] [opts-or-id]
[{:as opts-map :keys [{:as opts-map :keys
[elidable? coords inst uid xfn xfn+ #_kvs+, [#_elide? #_allow? #_callsite-id,
sample kind ns id level when limit limit-by, elidable? coords inst uid middleware middleware+,
sample-rate kind ns id level when rate-limit rate-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]}])
(truss/unexpected-arg! macro-id)))) (truss/unexpected-arg! macro-id))))
@ -486,12 +499,12 @@
(comment (enc/qb 1e6 (inst+nsecs (enc/now-inst) 1e9))) (comment (enc/qb 1e6 (inst+nsecs (enc/now-inst) 1e9)))
#?(:clj #?(:clj
(defn- valid-opts! [macro-form macro-env caller opts] (defn valid-opts! [x]
(if (map? opts) (if (map? x)
(do opts) (do x)
(truss/ex-info! ;; We require const map keys, but vals may require eval
(str "`" caller "` needs compile-time map opts at " (truss/ex-info! "Telemere signal opts must be a map with const (compile-time) keys."
(sigs/format-callsite (enc/get-source macro-form macro-env))))))) {:opts (truss/typed-val x)}))))
#?(:clj (defn- auto-> [form auto-form] (if (= form :auto) auto-form form))) #?(:clj (defn- auto-> [form auto-form] (if (= form :auto) auto-form form)))
#?(:clj #?(:clj
@ -500,8 +513,8 @@
Wrapped for public API." Wrapped for public API."
([ opts] (truss/keep-callsite `(signal-allowed? nil ~opts))) ([ opts] (truss/keep-callsite `(signal-allowed? nil ~opts)))
([base-opts opts] ([base-opts opts]
(valid-opts! &form &env 'telemere/signal-allowed? (or base-opts {})) (valid-opts! (or base-opts {}))
(valid-opts! &form &env 'telemere/signal-allowed? (or opts {})) (valid-opts! (or opts {}))
(let [opts (merge {:kind :generic, :level :info} base-opts opts) (let [opts (merge {:kind :generic, :level :info} base-opts opts)
{:keys [#_callsite-id elide? allow?]} {:keys [#_callsite-id elide? allow?]}
(sigs/filter-call (sigs/filter-call
@ -521,22 +534,20 @@
"Generic low-level signal creator. Wrapped for public API." "Generic low-level signal creator. Wrapped for public API."
([ opts] (truss/keep-callsite `(signal! nil ~opts))) ([ opts] (truss/keep-callsite `(signal! nil ~opts)))
([base-opts opts] ([base-opts opts]
(valid-opts! &form &env 'telemere/signal! (or base-opts {})) (valid-opts! (or base-opts {}))
(valid-opts! &form &env 'telemere/signal! (or opts {})) (valid-opts! (or opts {}))
(let [cljs? (boolean (:ns &env)) (let [cljs? (boolean (:ns &env))
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)
@ -562,23 +573,29 @@
(if elide? (if elide?
run-form run-form
(let [coords (get opts :coords (when (= ns-form* :auto) (truss/callsite-coords &form))) (let [coords
(get opts :coords
(when (= ns-form* :auto)
;; Auto coords iff auto ns
(truss/callsite-coords &form)))
{inst-form :inst {inst-form :inst
level-form :level
kind-form :kind kind-form :kind
id-form :id id-form :id} 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/unexpected-arg! trace?
(str "Signal needs compile-time `:trace?` value at " {:param 'trace?
(sigs/format-callsite ns-form coords)))) :context `signal!
:msg "Expected constant (compile-time) `:trace?` boolean"}))
host-form (auto-> (get opts :host :auto) (when (and clj? enabled:incl-host-info?) `(enc/host-info))) thread-form (when clj? `(enc/thread-info))
thread-form (auto-> (get opts :thread :auto) (when (and clj? enabled:incl-thread-info?) `(enc/thread-info)))
inst-form (auto-> (get opts :inst :auto) `(enc/now-inst*)) inst-form (get opts :inst :auto)
inst-form (auto-> inst-form `(enc/now-inst*))
parent-form (get opts :parent `*trace-parent*) parent-form (get opts :parent `*trace-parent*)
root-form0 (get opts :root `*trace-root*) root-form0 (get opts :root `*trace-root*)
@ -591,7 +608,7 @@
msg-form :msg msg-form :msg
data-form :data data-form :data
error-form :error error-form :error
sample-form :sample} opts sample-rate-form :sample-rate} opts
let-form (or let-form '[]) let-form (or let-form '[])
msg-form (parse-msg-form msg-form) msg-form (parse-msg-form msg-form)
@ -601,53 +618,45 @@
`(taoensso.encore.signals/update-ctx taoensso.telemere/*ctx* ~ctx+) `(taoensso.encore.signals/update-ctx taoensso.telemere/*ctx* ~ctx+)
(get opts :ctx `taoensso.telemere/*ctx*)) (get opts :ctx `taoensso.telemere/*ctx*))
xfn-form middleware-form
(if-let [xfn+ (get opts :xfn+)] (if-let [middleware+ (get opts :middleware+)]
`(taoensso.encore.signals/comp-xfn taoensso.telemere/*xfn* ~xfn+) `(taoensso.encore/comp-middleware taoensso.telemere/*middleware* ~middleware+)
(get opts :xfn `taoensso.telemere/*xfn*)) (get opts :middleware `taoensso.telemere/*middleware*))
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 :middleware :middleware+,
:sample :ns :kind :id :level :filter :when #_:limit #_:limit-by, :sample-rate :ns :kind :id :level :filter :when #_:rate-limit #_:rate-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 :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! "Signals cannot have both `:run` and `:error` opts at the same time"
(str "Signal cannot have both `:run` and `:error` opts at " {:run-form run-form
(sigs/format-callsite ns-form coords)))) :error-form error-form
:ns ns-form
:coords coords
:other-opts (dissoc opts :run :error)}))
(when-let [e (find opts :msg_)] ; Common typo/confusion (when-let [e (find opts :msg_)] ; Common typo/confusion
(truss/ex-info! (truss/ex-info! "Signals cannot have `:msg_` opt (did you mean `:msg`?))"
(str "Signal cannot have `:msg_` opt (did you mean `:msg`?) at " {:msg_ (truss/typed-val (val e))})))
(sigs/format-callsite ns-form coords)))))
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 (enc/host-info) ~'__thread ~'__otel-context1, ~sample-rate-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-rate-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 (enc/host-info) ~'__thread ~'__otel-context1, ~sample-rate-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-rate-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/unexpected-arg! clause {:context :signal-constructor-args})))
(str "Unexpected signal constructor args at "
(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)
@ -668,42 +677,28 @@
`(enc/bound-delay `(enc/bound-delay
;; Delay (cache) shared by all handlers, incl. `:let` eval, ;; Delay (cache) shared by all handlers, incl. `:let` eval,
;; signal construction, transform (xfn), etc. Throws caught by handler. ;; signal construction, middleware, etc. Throws caught by handler.
~do-form ~do-form
(let [~@let-form ; Allow to throw, eval BEFORE data, msg, etc. (let [~@let-form ; Allow to throw, eval BEFORE data, msg, etc.
signal# ~signal-form] signal# ~signal-form]
;; Final unwrapped signal value visible to users/handler-fns, allow to throw ;; Final unwrapped signal value visible to users/handler-fns, allow to throw
(if-let [xfn# ~xfn-form] (if-let [sig-middleware# ~middleware-form]
(xfn# signal#) (sig-middleware# signal#) ; Apply signal middleware, can throw
(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 +706,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,26 +720,29 @@
;; 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#)))
(catch :all t# (RunResult. nil t# (- (enc/now-nano*) t0#))) (catch :all t# (RunResult. nil t# (- (enc/now-nano*) t0#)))
(finally (.close otel-scope#))))))])] (finally (.close otel-scope#))))))])
`((fn [] ; iife for better IoC compatibility final-form
;; Unless otherwise specified, allow errors to throw on call ;; Unless otherwise specified, allow errors to throw on call
(let [~'__run-fn-form ~run-fn-form `(let [~'__run-fn-form ~run-fn-form
~'__kind ~kind-form ~'__kind ~kind-form
~'__ns ~ns-form ~'__ns ~ns-form
~'__id ~id-form ~'__id ~id-form
@ -750,8 +750,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!
@ -762,7 +765,12 @@
(if ~'__run-result (if ~'__run-result
( ~'__run-result signal#) ( ~'__run-result signal#)
true)))))))))))) true))))]
(if-let [iife-wrap? true #_cljs?]
;; Small perf hit to improve compatibility within `go` and other IOC-style bodies
`((fn [] ~final-form))
(do final-form))))))))
(comment (comment
(with-signal (signal! {:level :warn :let [x :x] :msg ["Test" "message" x] :data {:a :A :x x} :run (+ 1 2)})) (with-signal (signal! {:level :warn :let [x :x] :msg ["Test" "message" x] :data {:a :A :x x} :run (+ 1 2)}))

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]
@ -185,7 +183,7 @@
(put-attr! ab "run.val" run-val) (put-attr! ab "run.val" run-val)
(put-attr! ab "run.nsecs" run-nsecs))) (put-attr! ab "run.nsecs" run-nsecs)))
(put-attr! ab "sample" (get signal :sample)) (put-attr! ab "sample" (get signal :sample-rate))
(when-let [{:keys [id uid]} (get signal :parent)] (when-let [{:keys [id uid]} (get signal :parent)]
(put-attr! ab "parent.id" id) (put-attr! ab "parent.id" id)
@ -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)))))))
@ -256,10 +254,10 @@
(see `telemere/otel-default-providers_` for default). (see `telemere/otel-default-providers_` for default).
Optional signal keys: Optional signal keys:
`:otel/attrs` ------- Attributes [1] to add to log records AND tracing spans/events `:otel/attrs` - Attributes [1] to add to log records AND tracing spans/events
`:otel/log-attrs` --- Attributes [1] to add to log records ONLY `:otel/log-attrs` - Attributes [1] to add to log records ONLY
`:otel/trace-attrs` - Attributes [1] to add to tracing spans/events ONLY `:otel/trace-attrs` - Attributes [1] to add to tracing spans/events ONLY
`:otel/span-kind` --- Span kind #{:internal (default) :client :server :consumer :producer} `:otel/span-kind` - Span kind #{:internal (default) :client :server :consumer :producer}
[1] `io.opentelemetry.api.common.Attributes` or Clojure map with str/kw keys and vals [1] `io.opentelemetry.api.common.Attributes` or Clojure map with str/kw keys and vals
#{nil boolean keyword string UUID long double string-vec long-vec double-vec boolean-vec}. #{nil boolean keyword string UUID long double string-vec long-vec double-vec boolean-vec}.
@ -300,8 +298,8 @@
(when-let [drained (enc/reset-in! span-buffer1_ #{})] (when-let [drained (enc/reset-in! span-buffer1_ #{})]
(when-not (empty? drained) (when-not (empty? drained)
(span-buffer2_ (fn [old] (set/union old drained))))))) (span-buffer2_ (fn [old] (set/union old drained)))))))
3000 3000)
t3s)) 3000 3000)))
stop-tracing! stop-tracing!
(fn stop-tracing! [] (fn stop-tracing! []
@ -384,9 +382,9 @@
(comment (comment
(do (do
(require '[taoensso.telemere :as tel]) (require '[taoensso.telemere :as t])
(def h1 (handler:open-telemetry)) (def h1 (handler:open-telemetry))
(let [{[s1 s2] :signals} (tel/with-signals (tel/trace! ::id1 (tel/trace! ::id2 "form2")))] (let [{[s1 s2] :signals} (t/with-signals (t/trace! ::id1 (t/trace! ::id2 "form2")))]
(def s1 s1) (def s1 s1)
(def s2 s2))) (def s2 s2)))

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]
@ -15,11 +15,12 @@
(def default-dispatch-opts (def default-dispatch-opts
{:min-level :info {:min-level :info
:limit :rate-limit
[[5 (enc/msecs :mins 1)] [[5 (enc/msecs :mins 1)]
[10 (enc/msecs :mins 15)] [10 (enc/msecs :mins 15)]
[15 (enc/msecs :hours 1)] [15 (enc/msecs :hours 1)]
[30 (enc/msecs :hours 6)]]}) [30 (enc/msecs :hours 6)]
]})
(defn handler:postal (defn handler:postal
"Alpha, subject to change. "Alpha, subject to change.
@ -34,7 +35,7 @@
Default handler dispatch options (override when calling `add-handler!`): Default handler dispatch options (override when calling `add-handler!`):
`:min-level` - `:info` `:min-level` - `:info`
`:limit` - `:rate-limit` -
[[5 (enc/msecs :mins 1)] ; Max 5 emails in 1 min [[5 (enc/msecs :mins 1)] ; Max 5 emails in 1 min
[10 (enc/msecs :mins 15)] ; Max 10 emails in 15 mins [10 (enc/msecs :mins 15)] ; Max 10 emails in 15 mins
[15 (enc/msecs :hours 1)] ; Max 15 emails in 1 hour [15 (enc/msecs :hours 1)] ; Max 15 emails in 1 hour
@ -58,7 +59,7 @@
:cc \"engineering@example.com\" :cc \"engineering@example.com\"
:X-MyHeader \"A custom header\"} :X-MyHeader \"A custom header\"}
`:subject-fn` ------ (fn [signal]) => email subject string `:subject-fn` - (fn [signal]) => email subject string
`:subject-max-len` - Truncate subjects beyond this length (default 90) `:subject-max-len` - Truncate subjects beyond this length (default 90)
`:body-fn` - (fn [signal]) => email body content string, `:body-fn` - (fn [signal]) => email body content string,

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]
@ -15,11 +15,12 @@
(def default-dispatch-opts (def default-dispatch-opts
{:min-level :info {:min-level :info
:limit :rate-limit
[[5 (enc/msecs :mins 1)] [[5 (enc/msecs :mins 1)]
[10 (enc/msecs :mins 15)] [10 (enc/msecs :mins 15)]
[15 (enc/msecs :hours 1)] [15 (enc/msecs :hours 1)]
[30 (enc/msecs :hours 6)]]}) [30 (enc/msecs :hours 6)]
]})
(defn handler:slack (defn handler:slack
"Alpha, subject to change. "Alpha, subject to change.
@ -34,7 +35,7 @@
Default handler dispatch options (override when calling `add-handler!`): Default handler dispatch options (override when calling `add-handler!`):
`:min-level` - `:info` `:min-level` - `:info`
`:limit` - `:rate-limit` -
[[5 (enc/msecs :mins 1)] ; Max 5 posts in 1 min [[5 (enc/msecs :mins 1)] ; Max 5 posts in 1 min
[10 (enc/msecs :mins 15)] ; Max 10 posts in 15 mins [10 (enc/msecs :mins 15)] ; Max 10 posts in 15 mins
[15 (enc/msecs :hours 1)] ; Max 15 posts in 1 hour [15 (enc/msecs :hours 1)] ; Max 15 posts in 1 hour

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]
@ -25,11 +25,11 @@
Can output signals as human or machine-readable (edn, JSON) strings. Can output signals as human or machine-readable (edn, JSON) strings.
Options: Options:
`:output-fn` --- (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn` `:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
`:socket-opts` - {:keys [host port ssl? connect-timeout-msecs]} `:socket-opts` - {:keys [host port ssl? connect-timeout-msecs]}
`:host` ------ Destination TCP socket hostname string `:host` - Destination TCP socket hostname string
`:port` ------ Destination TCP socket port int `:port` - Destination TCP socket port int
`:ssl?` ------ Use SSL/TLS (default false) `:ssl?` - Use SSL/TLS (default false)
`:connect-timeout-msecs` - Connection timeout (default 3000 msecs) `:connect-timeout-msecs` - Connection timeout (default 3000 msecs)
Limitations: Limitations:
@ -59,10 +59,10 @@
Can output signals as human or machine-readable (edn, JSON) strings. Can output signals as human or machine-readable (edn, JSON) strings.
Options: Options:
`:output-fn` ---------- (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn` `:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
`:socket-opts` -------- {:keys [host port max-packet-bytes]} `:socket-opts` - {:keys [host port max-packet-bytes]}
`:host` ------------- Destination UDP socket hostname string `:host` - Destination UDP socket hostname string
`:port` ------------- Destination UDP socket port int `:port` - Destination UDP socket port int
`:max-packet-bytes` - Max packet size (in bytes) before truncating output (default 512) `:max-packet-bytes` - Max packet size (in bytes) before truncating output (default 512)
`:truncation-warning-fn` `:truncation-warning-fn`

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

@ -1,5 +1,5 @@
(ns taoensso.telemere.utils (ns taoensso.telemere.utils
"Misc utils useful for Telemere handlers, transforms, etc." "Misc utils useful for Telemere handlers, middleware, etc."
(:refer-clojure :exclude [newline]) (:refer-clojure :exclude [newline])
(:require (:require
[clojure.string :as str] [clojure.string :as str]
@ -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
@ -220,30 +220,20 @@
^java.io.File [file] ^java.io.File [file]
(let [file (as-file file)] (let [file (as-file file)]
(when-not (.exists file) (when-not (.exists file)
(truss/catching (when-let [parent (.getParentFile (.getCanonicalFile file))] (.mkdirs parent))
(let [path (.toPath file) (.createNewFile file))
fa (into-array java.nio.file.attribute.FileAttribute [])]
(when-let [parent (.getParent path)]
(do (java.nio.file.Files/createDirectories parent fa)))
(do (java.nio.file.Files/createFile path fa)))))
(if (.canWrite file) (if (.canWrite file)
file file
(truss/ex-info! "Unable to prepare writable `java.io.File`" (truss/ex-info! "Unable to prepare writable `java.io.File`"
{:path (.getAbsolutePath file)}))))) {:path (.getAbsolutePath file)})))))
(comment
(let [f (writeable-file! "__test-file.txt")]
(enc/qb 1e4 ; [10.27 37.69]
(.exists f)
(.canWrite f))))
#?(:clj #?(:clj
(defn ^:no-doc writeable-file-stream! (defn ^:no-doc file-stream
"Private, don't use. "Private, don't use.
Returns new writeable `java.io.FileOutputStream` or throws." Returns new `java.io.FileOutputStream` for given `java.io.File`."
^java.io.FileOutputStream [file append?] ^java.io.FileOutputStream [file append?]
(java.io.FileOutputStream. (writeable-file! file) (boolean append?)))) (java.io.FileOutputStream. (as-file file) (boolean append?))))
#?(:clj #?(:clj
(defn file-writer (defn file-writer
@ -265,8 +255,8 @@
(when-not file (truss/ex-info! "Expected `:file` value" (truss/typed-val file))) (when-not file (truss/ex-info! "Expected `:file` value" (truss/typed-val file)))
(let [file (as-file file) (let [file (writeable-file! file)
stream_ (volatile! (writeable-file-stream! file append?)) stream_ (volatile! (file-stream file append?))
open?_ (enc/latom true) open?_ (enc/latom true)
close! close!
@ -280,7 +270,7 @@
reset! reset!
(fn [] (fn []
(close!) (close!)
(vreset! stream_ (writeable-file-stream! file append?)) (vreset! stream_ (file-stream file append?))
(reset! open?_ true) (reset! open?_ true)
true) true)
@ -291,11 +281,11 @@
(.flush stream) (.flush stream)
true)) true))
check-file! file-exists!
(let [rl (enc/rate-limiter-once-per 100)] (let [rl (enc/rate-limiter-once-per 100)]
(fn [] (fn []
(or (rl) #_(.exists file) (.canWrite file) (or (rl) (.exists file)
(throw (java.io.IOException. "File doesn't exist or isn't writeable"))))) (throw (java.io.IOException. "File doesn't exist")))))
lock (Object.)] lock (Object.)]
@ -311,7 +301,7 @@
ba (enc/str->utf8-ba (str content))] ba (enc/str->utf8-ba (str content))]
(locking lock (locking lock
(try (try
(check-file!) (file-exists!)
(write-ba! ba) (write-ba! ba)
(catch java.io.IOException _ ; Retry once (catch java.io.IOException _ ; Retry once
(reset!) (reset!)
@ -352,10 +342,10 @@
Useful for basic handlers that write to a TCP socket, etc. Useful for basic handlers that write to a TCP socket, etc.
Options: Options:
`:ssl?` ------------------ Use SSL/TLS? `:ssl?` - Use SSL/TLS?
`:connect-timeout-msecs` - Connection timeout (default 3000 msecs) `:connect-timeout-msecs` - Connection timeout (default 3000 msecs)
`:socket-fn` ------------- (fn [host port timeout]) => `java.net.Socket` `:socket-fn` - (fn [host port timeout]) => `java.net.Socket`
`:ssl-socket-fn` --------- (fn [socket host port]) => `java.net.Socket` `:ssl-socket-fn` - (fn [socket host port]) => `java.net.Socket`
Notes: Notes:
- Writer should be manually closed after use (with zero-arity call). - Writer should be manually closed after use (with zero-arity call).
@ -493,11 +483,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)
@ -522,8 +512,8 @@
Options: Options:
`:format-inst-fn` - (fn format [instant]) => string. `:format-inst-fn` - (fn format [instant]) => string.
`:format-id-fn` --- (fn format [ns id]) => string. `:format-id-fn` - (fn format [ns id]) => string.
`:format-msg-fn` -- (fn format [msg]) => string." `:format-msg-fn` - (fn format [msg]) => string."
([] (signal-preamble-fn nil)) ([] (signal-preamble-fn nil))
([{:keys [format-inst-fn format-id-fn format-msg-fn] ([{:keys [format-inst-fn format-id-fn format-msg-fn]
:or {format-inst-fn (format-inst-fn) :or {format-inst-fn (format-inst-fn)
@ -540,10 +530,17 @@
(when kind (s+spc (sigs/upper-qn kind))) (when kind (s+spc (sigs/upper-qn kind)))
#?(:clj #?(:clj
(when-let [hostname (enc/get-in* signal [:host :name])] (when-let [host (get signal :host)]
(s+spc hostname))) (when-let [hostname (get host :name)]
(s+spc hostname))))
(when ns
(enc/sb-append sb " " ns)
(when-let [[line column] (get signal :coords)]
(if column
(enc/sb-append sb "[" line "," column "]")
(enc/sb-append sb "[" line "]"))))
(when ns (s+spc (sigs/format-callsite ns (get signal :coords))))
(when id (when-let [ff format-id-fn] (s+spc (ff ns id)))) (when id (when-let [ff format-id-fn] (s+spc (ff ns id))))
(enc/when-let [ff format-msg-fn (enc/when-let [ff format-msg-fn
msg (force msg_)] msg (force msg_)]
@ -556,12 +553,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"})))
@ -572,8 +565,8 @@
- Returns a human-readable signal content ?string (incl. data, ctx, etc.). - Returns a human-readable signal content ?string (incl. data, ctx, etc.).
Options: Options:
`:raw-error?` ------ Retain unformatted error? (default false) `:raw-error?` - Retain unformatted error? (default false)
`:incl-keys` ------- Subset of signal keys to retain from those `:incl-keys` - Subset of signal keys to retain from those
otherwise excluded by default: #{:kvs :host :thread} otherwise excluded by default: #{:kvs :host :thread}
`:format-nsecs-fn` - (fn [nanosecs]) => string. `:format-nsecs-fn` - (fn [nanosecs]) => string.
`:format-error-fn` - (fn [error]) => string." `:format-error-fn` - (fn [error]) => string."
@ -605,8 +598,8 @@
(let [af append-fn (let [af append-fn
vf val-fn] vf val-fn]
(let [{:keys [ns uid parent root data kvs ctx #?@(:clj [host thread]) sample]} signal] (let [{:keys [ns uid parent root data kvs ctx #?@(:clj [host thread]) sample-rate]} signal]
(when sample (af " sample: " (vf sample))) (when sample-rate (af " sample: " (vf sample-rate)))
(when uid (af " uid: " (vf uid))) (when uid (af " uid: " (vf uid)))
(when (and parent (not= parent root)) (af " parent: " (vf (format-parent ns parent)))) ; {:keys [id uid]} (when (and parent (not= parent root)) (af " parent: " (vf (format-parent ns parent)))) ; {:keys [id uid]}
(when root (af " root: " (vf (format-parent ns root)))) ; {:keys [id uid]} (when root (af " root: " (vf (format-parent ns root)))) ; {:keys [id uid]}
@ -658,9 +651,9 @@
Options: Options:
`:incl-nils?` - Include signal's keys with nil values? (default false) `:incl-nils?` - Include signal's keys with nil values? (default false)
`:incl-kvs?` -- Include signal's app-level root kvs? (default false) `:incl-kvs?` - Include signal's app-level root kvs? (default false)
`:incl-keys` -- Subset of signal keys to retain from those otherwise `:incl-keys` - Subset of signal keys to retain from those otherwise
excluded by default: #{:schema :kvs :host :thread}" excluded by default: #{:kvs :host :thread}"
([] (clean-signal-fn nil)) ([] (clean-signal-fn nil))
([{:keys [incl-kvs? incl-nils? incl-keys] :as opts}] ([{:keys [incl-kvs? incl-nils? incl-keys] :as opts}]
(let [assoc!* (let [assoc!*
@ -668,7 +661,6 @@
(fn [m k v] (if (nil? v) m (assoc! m k v))) ; As `remove-signal-nils` (fn [m k v] (if (nil? v) m (assoc! m k v))) ; As `remove-signal-nils`
(do assoc!)) (do assoc!))
incl-schema? (contains? incl-keys :schema)
incl-kvs-key? (contains? incl-keys :kvs) incl-kvs-key? (contains? incl-keys :kvs)
incl-host? (contains? incl-keys :host) incl-host? (contains? incl-keys :host)
incl-thread? (contains? incl-keys :thread)] incl-thread? (contains? incl-keys :thread)]
@ -683,7 +675,7 @@
(clojure.core/into () (clojure.core/into ()
(clojure.core/disj (clojure.core/disj
taoensso.telemere.impl/standard-signal-keys taoensso.telemere.impl/standard-signal-keys
:msg_ :error :schema :kvs :host :thread)) :msg_ :error :kvs :host :thread))
(assoc!* m k v) (assoc!* m k v)
;; Main keys to include with modified val ;; Main keys to include with modified val
@ -695,7 +687,6 @@
taoensso.telemere.impl/impl-signal-keys) m ; noop taoensso.telemere.impl/impl-signal-keys) m ; noop
;;; Other keys to exclude by default ;;; Other keys to exclude by default
:schema (if incl-schema? (assoc!* m k v) m)
:kvs (if incl-kvs-key? (assoc!* m k v) m) :kvs (if incl-kvs-key? (assoc!* m k v) m)
:thread (if incl-thread? (assoc!* m k v) m) :thread (if incl-thread? (assoc!* m k v) m)
:host (if incl-host? (assoc!* m k v) m) :host (if incl-host? (assoc!* m k v) m)
@ -718,8 +709,8 @@
- Returns a machine-readable signal string. - Returns a machine-readable signal string.
Options: Options:
`:pr-fn` --------- ∈ #{<unary-fn> :edn (default) :json (Cljs only)} `:pr-fn` - ∈ #{<unary-fn> :edn (default) :json (Cljs only)}
`:clean-fn` ------ (fn [signal]) => clean signal map, see [1] `:clean-fn` - (fn [signal]) => clean signal map, see [1]
`:incl-newline?` - Include terminating system newline? (default true) `:incl-newline?` - Include terminating system newline? (default true)
Examples: Examples:
@ -784,8 +775,8 @@
Options: Options:
`:incl-newline?` - Include terminating system newline? (default true) `:incl-newline?` - Include terminating system newline? (default true)
`:preamble-fn` --- (fn [signal]) => signal preamble string, see [1] `:preamble-fn` - (fn [signal]) => signal preamble string, see [1]
`:content-fn` ---- (fn [signal]) => signal content string, see [2] `:content-fn` - (fn [signal]) => signal content string, see [2]
[1] `taoensso.telemere.utils/signal-preamble-fn`, etc. [1] `taoensso.telemere.utils/signal-preamble-fn`, etc.
[2] `taoensso.telemere.utils/signal-content-fn`, etc. [2] `taoensso.telemere.utils/signal-content-fn`, etc.

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)))
@ -250,83 +250,78 @@
(with-sig (sig! {:level :info, :ctx+ {:baz :qux}})))] (with-sig (sig! {:level :info, :ctx+ {:baz :qux}})))]
(is (sm? sv {:ctx {:foo :bar, :baz :qux}}) "`*ctx*` can be updated via call opt"))]) (is (sm? sv {:ctx {:foo :bar, :baz :qux}}) "`*ctx*` can be updated via call opt"))])
(testing "Transforms" (testing "Middleware"
[(testing "Dynamic transforms (`*xfn*`)" [(testing "Dynamic middleware (`*middleware*`)"
[(is (sm? (tel/with-xfn nil (with-sig (sig! {:level :info }))) {:level :info }) "nil xfn ~ identity") [(is (sm? (tel/with-middleware nil (with-sig (sig! {:level :info }))) {:level :info }) "nil middleware ~ identity")
(is (sm? (tel/with-xfn identity (with-sig (sig! {:level :info }))) {:level :info }) "nil xfn ~ identity") (is (sm? (tel/with-middleware identity (with-sig (sig! {:level :info }))) {:level :info }) "nil middleware ~ identity")
(is (sm? (tel/with-xfn #(assoc % :foo 1) (with-sig (sig! {:level :info }))) {:level :info, :foo 1 })) (is (sm? (tel/with-middleware #(assoc % :foo 1) (with-sig (sig! {:level :info }))) {:level :info, :foo 1 }))
(is (sm? (tel/with-xfn #(assoc % :foo 1) (with-sig (sig! {:level :info, :xfn #(assoc % :foo 2)}))) {:level :info, :foo 2 }) "call > dynamic") (is (sm? (tel/with-middleware #(assoc % :foo 1) (with-sig (sig! {:level :info, :middleware #(assoc % :foo 2)}))) {:level :info, :foo 2 }) "call > dynamic")
(is (sm? (tel/with-xfn #(assoc % :foo 1) (with-sig (sig! {:level :info, :xfn nil}))) {:level :info, :foo :submap/nx}) "call > dynamic") (is (sm? (tel/with-middleware #(assoc % :foo 1) (with-sig (sig! {:level :info, :middleware nil}))) {:level :info, :foo :submap/nx}) "call > dynamic")
(is (= (tel/with-xfn #(do nil) (with-sig (sig! {:level :info }))) nil) "return nil => suppress") (is (= (tel/with-middleware #(do nil) (with-sig (sig! {:level :info }))) nil) "return nil => suppress")
(is (sm? (tel/with-xfn #(do nil) (with-sig (sig! {:level :info, :xfn nil}))) {:level :info}) "call > dynamic")]) (is (sm? (tel/with-middleware #(do nil) (with-sig (sig! {:level :info, :middleware nil}))) {:level :info}) "call > dynamic")])
(testing "Call transforms" (testing "Call middleware"
(let [c (enc/counter) (let [c (enc/counter)
{rv1 :value, [sv1] :signals} (with-sigs :raw nil (sig! {:level :info, :run (c), :xfn (tel/comp-xfn #(assoc % :m1 (c)) #(assoc % :m2 (c)))})) {rv1 :value, [sv1] :signals} (with-sigs :raw nil (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c)))}))
{rv2 :value, [sv2] :signals} (with-sigs :raw nil (sig! {:level :info, :run (c), :xfn (tel/comp-xfn #(assoc % :m1 (c)) #(assoc % :m2 (c))), :allow? false})) {rv2 :value, [sv2] :signals} (with-sigs :raw nil (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c))), :allow? false}))
{rv3 :value, [sv3] :signals} (with-sigs :raw nil (sig! {:level :info, :run (c), :xfn (tel/comp-xfn #(assoc % :m1 (c)) #(assoc % :m2 (c)))})) {rv3 :value, [sv3] :signals} (with-sigs :raw nil (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c)))}))
{rv4 :value, [sv4] :signals} (with-sigs :raw nil (sig! {:level :info, :xfn (fn [_] "signal-value")})) {rv4 :value, [sv4] :signals} (with-sigs :raw nil (sig! {:level :info, :middleware (fn [_] "signal-value")}))
{rv5 :value, [sv5] :signals} (with-sigs :raw nil (sig! {:level :info, :xfn (fn [_] nil)}))] {rv5 :value, [sv5] :signals} (with-sigs :raw nil (sig! {:level :info, :middleware (fn [_] nil)}))]
[(is (= rv1 0)) (is (sm? sv1 {:m1 1 :m2 2})) [(is (= rv1 0)) (is (sm? sv1 {:m1 1 :m2 2}))
(is (= rv2 3)) (is (nil? sv2)) (is (= rv2 3)) (is (nil? sv2))
(is (= rv3 4)) (is (sm? sv3 {:m1 5 :m2 6})) (is (= rv3 4)) (is (sm? sv3 {:m1 5 :m2 6}))
(is (= rv4 true)) (is (= sv4 "signal-value")) (is (= rv4 true)) (is (= sv4 "signal-value"))
(is (= rv5 true)) (is (nil? sv5)) (is (= rv5 true)) (is (nil? sv5))
(is (= @c 7) "3x run + 4x xfn")])) (is (= @c 7) "3x run + 4x middleware")]))
(testing "Mixed transforms" (testing "Mixed middleware"
[(let [sv [(let [sv
(binding [tel/*xfn* #(assoc % :foo true)] (binding [tel/*middleware* #(assoc % :foo true)]
(with-sig (sig! {:level :info, :xfn+ #(assoc % :bar true)})))] (with-sig (sig! {:level :info, :middleware+ #(assoc % :bar true)})))]
(is (sm? sv {:foo true, :bar true})))])]) (is (sm? sv {:foo true, :bar true})))])])
(testing "Printing"
[#?(:clj (is (impl/signal? (read-string (binding [*print-dup* true] (pr-str (impl/map->Signal {})))))))
#?(:clj (is (impl/signal? (read-string (binding [*print-dup* true] (pr-str (assoc (impl/map->Signal {}) :k :v)))))))
(is (enc/str-starts-with? (binding [*print-dup* true] (str (assoc (impl/map->Signal {}) :k :v))) "taoensso.telemere.Signal{"))
(is (enc/str-starts-with? (pr-str (assoc (impl/map->Signal {}) :k :v)) "#taoensso.telemere.Signal{"))
(is (enc/str-starts-with? (str (assoc (impl/map->Signal {}) :k :v)) "taoensso.telemere.Signal{"))
#?(:clj #?(:clj
(testing "Printing"
(let [sv1 (dissoc (with-sig (sig! {:level :info, :run (+ 1 2), :my-k1 :my-v1})) :_otel-context) (let [sv1 (dissoc (with-sig (sig! {:level :info, :run (+ 1 2), :my-k1 :my-v1})) :_otel-context)
sv1 ; Ensure instants are printable sv1 ; Ensure instants are printable
(-> sv1 (-> sv1
(update-in [:inst] enc/inst->udt) (update-in [:inst] enc/inst->udt)
(update-in [:end-inst] enc/inst->udt))] (update-in [:end-inst] enc/inst->udt))]
(is (= sv1 (read-string (pr-str sv1))) "Equality holds")))])])
[(is (= sv1 (read-string (pr-str sv1))))])))])
(deftest _handlers (deftest _handlers
;; Basic handler tests are in Encore ;; Basic handler tests are in Encore
[(testing "Handler transforms" [(testing "Handler middleware"
(let [c (enc/counter) (let [c (enc/counter)
sv-h1_ (atom nil) sv-h1_ (atom nil)
sv-h2_ (atom nil) sv-h2_ (atom nil)
wh1 (sigs/wrap-handler :hid1 (fn [sv] (reset! sv-h1_ sv)) nil {:async nil, :xfn (tel/comp-xfn #(assoc % :hm1 (c)) #(assoc % :hm2 (c)))}) wh1 (sigs/wrap-handler :hid1 (fn [sv] (reset! sv-h1_ sv)) nil {:async nil, :middleware (tel/comp-middleware #(assoc % :hm1 (c)) #(assoc % :hm2 (c)))})
wh2 (sigs/wrap-handler :hid2 (fn [sv] (reset! sv-h2_ sv)) nil {:async nil, :xfn (tel/comp-xfn #(assoc % :hm1 (c)) #(assoc % :hm2 (c)))})] wh2 (sigs/wrap-handler :hid2 (fn [sv] (reset! sv-h2_ sv)) nil {:async nil, :middleware (tel/comp-middleware #(assoc % :hm1 (c)) #(assoc % :hm2 (c)))})]
;; Note that call xfn output is cached and shared across all handlers ;; Note that call middleware output is cached and shared across all handlers
(binding [impl/*sig-handlers* [wh1 wh2]] (binding [impl/*sig-handlers* [wh1 wh2]]
(let [;; 1x run + 4x handler xfn + 2x call xfn = 7x (let [;; 1x run + 4x handler middleware + 2x call middleware = 7x
rv1 (sig! {:level :info, :run (c), :xfn (tel/comp-xfn #(assoc % :m1 (c)) #(assoc % :m2 (c)))}) rv1 (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c)))})
sv1-h1 @sv-h1_ sv1-h1 @sv-h1_
sv1-h2 @sv-h2_ sv1-h2 @sv-h2_
c1 @c c1 @c
;; 1x run ;; 1x run
rv2 (sig! {:level :info, :run (c), :xfn (tel/comp-xfn #(assoc % :m1 (c)) #(assoc % :m2 (c))), :allow? false}) rv2 (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c))), :allow? false})
sv2-h1 @sv-h1_ sv2-h1 @sv-h1_
sv2-h2 @sv-h2_ sv2-h2 @sv-h2_
c2 @c ; 8 c2 @c ; 8
;; 1x run + 4x handler xfn + 2x call xfn = 7x ;; 1x run + 4x handler middleware + 2x call middleware = 7x
rv3 (sig! {:level :info, :run (c), :xfn (tel/comp-xfn #(assoc % :m1 (c)) #(assoc % :m2 (c)))}) rv3 (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c)))})
sv3-h1 @sv-h1_ sv3-h1 @sv-h1_
sv3-h2 @sv-h2_ sv3-h2 @sv-h2_
c3 @c ; 15 c3 @c ; 15
;; 4x handler xfn ;; 4x handler middleware
rv4 (sig! {:level :info, :xfn (fn [_] {:my-sig-val? true})}) rv4 (sig! {:level :info, :middleware (fn [_] {:my-sig-val? true})})
sv4-h1 @sv-h1_ sv4-h1 @sv-h1_
sv4-h2 @sv-h2_ sv4-h2 @sv-h2_
c4 @c] c4 @c]
@ -335,10 +330,10 @@
(is (= rv2 7)) (is (sm? sv2-h1 {:m1 1, :m2 2, :hm1 3, :hm2 4})) (is (sm? sv2-h2 {:m1 1, :m2 2, :hm1 5, :hm2 6})) (is (= rv2 7)) (is (sm? sv2-h1 {:m1 1, :m2 2, :hm1 3, :hm2 4})) (is (sm? sv2-h2 {:m1 1, :m2 2, :hm1 5, :hm2 6}))
(is (= rv3 8)) (is (sm? sv3-h1 {:m1 9, :m2 10, :hm1 11, :hm2 12})) (is (sm? sv3-h2 {:m1 9, :m2 10, :hm1 13, :hm2 14})) (is (= rv3 8)) (is (sm? sv3-h1 {:m1 9, :m2 10, :hm1 11, :hm2 12})) (is (sm? sv3-h2 {:m1 9, :m2 10, :hm1 13, :hm2 14}))
(is (= rv4 true)) (is (sm? sv4-h1 {:my-sig-val? true, :hm1 15, :hm2 16})) (is (sm? sv4-h2 {:my-sig-val? true, :hm1 17, :hm2 18})) (is (= rv4 true)) (is (sm? sv4-h1 {:my-sig-val? true, :hm1 15, :hm2 16})) (is (sm? sv4-h2 {:my-sig-val? true, :hm1 17, :hm2 18}))
(is (= c1 7) "1x run + 4x handler xfn + 2x call xfn") (is (= c1 7) "1x run + 4x handler middleware + 2x call middleware")
(is (= c2 8) "2x run + 4x handler xfn + 2x call xfn") (is (= c2 8) "2x run + 4x handler middleware + 2x call middleware")
(is (= c3 15) "3x run + 8x handler xfn + 4x call xfn") (is (= c3 15) "3x run + 8x handler middleware + 4x call middleware")
(is (= c4 19) "3x run + 12x handler xfn + 4x call xfn")])))) (is (= c4 19) "3x run + 12x handler middleware + 4x call middleware")]))))
(testing "Handler binding conveyance" (testing "Handler binding conveyance"
(let [a (atom nil) (let [a (atom nil)
@ -384,7 +379,7 @@
(test1 64 {:async {:mode :dropping, :buffer-size 64}}) (test1 64 {:async {:mode :dropping, :buffer-size 64}})
(test1 64 {:async {:mode :sliding, :buffer-size 64}})]))))))]) (test1 64 {:async {:mode :sliding, :buffer-size 64}})]))))))])
(def ^:dynamic *throwing-handler-xfn?* false) (def ^:dynamic *throwing-handler-middleware?* false)
(deftest _throwing (deftest _throwing
(let [sv_ (atom :nx) (let [sv_ (atom :nx)
@ -398,7 +393,7 @@
(tel/with-handler :hid1 (tel/with-handler :hid1
(fn [sv] (force (:data sv)) (reset! sv_ sv)) (fn [sv] (force (:data sv)) (reset! sv_ sv))
{:async nil, :error-fn (fn [x] (reset! error_ x)), :rl-error nil, {:async nil, :error-fn (fn [x] (reset! error_ x)), :rl-error nil,
:xfn (fn [sv] (if *throwing-handler-xfn?* (ex1!) sv))} :middleware (fn [sv] (if *throwing-handler-middleware?* (ex1!) sv))}
[(is (->> (sig! {:level :info, :when (ex1!)}) (throws? :ex-info "Ex1")) "`~filterable-expansion/allow` throws at call") [(is (->> (sig! {:level :info, :when (ex1!)}) (throws? :ex-info "Ex1")) "`~filterable-expansion/allow` throws at call")
(is (->> (sig! {:level :info, :inst (ex1!)}) (throws? :ex-info "Ex1")) "`~inst-form` throws at call") (is (->> (sig! {:level :info, :inst (ex1!)}) (throws? :ex-info "Ex1")) "`~inst-form` throws at call")
@ -414,15 +409,15 @@
(is (= @sv_ :nx)) (is (= @sv_ :nx))
(is (sm? @error_ {:handler-id :hid1, :error ex1}))]) (is (sm? @error_ {:handler-id :hid1, :error ex1}))])
(testing "Throwing call transform" (testing "Throwing call middleware"
(reset-state!) (reset-state!)
[(is (true? (sig! {:level :info, :xfn (fn [_] (ex1!))}))) [(is (true? (sig! {:level :info, :middleware (fn [_] (ex1!))})))
(is (= @sv_ :nx)) (is (= @sv_ :nx))
(is (sm? @error_ {:handler-id :hid1, :error ex1}))]) (is (sm? @error_ {:handler-id :hid1, :error ex1}))])
(testing "Throwing handler transform" (testing "Throwing handler middleware"
(reset-state!) (reset-state!)
(binding [*throwing-handler-xfn?* true] (binding [*throwing-handler-middleware?* true]
[(is (true? (sig! {:level :info}))) [(is (true? (sig! {:level :info})))
(is (= @sv_ :nx)) (is (= @sv_ :nx))
(is (sm? @error_ {:handler-id :hid1, :error ex1}))])) (is (sm? @error_ {:handler-id :hid1, :error ex1}))]))
@ -554,11 +549,11 @@
(let [c (enc/counter) (let [c (enc/counter)
sr_ (atom nil)] sr_ (atom nil)]
(tel/with-handler "h1" (tel/with-handler "h1"
(fn h1 [x] (c) (compare-and-set! sr_ nil (:sample x))) (fn h1 [x] (c) (compare-and-set! sr_ nil (:sample-rate x)))
{:async nil, :sample handler-sample-rate} {:async nil, :sample-rate handler-sample-rate}
(do (do
;; Repeat to ensure >=1 gets through sampling ;; Repeat to ensure >=1 gets through sampling
(dotimes [_ 1000] (sig! {:level :info, :sample call-sample-rate})) (dotimes [_ 1000] (sig! {:level :info, :sample-rate call-sample-rate}))
[@sr_ @c]))))] [@sr_ @c]))))]
[(is (= (test1 nil nil) [nil 1000]) "[none none] = none") [(is (= (test1 nil nil) [nil 1000]) "[none none] = none")
@ -594,8 +589,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 +721,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 +732,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 +748,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 +831,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")]))
@ -861,7 +855,7 @@
[(is (= sig*1 sig*2) "Default :pr-fn is :edn") [(is (= sig*1 sig*2) "Default :pr-fn is :edn")
(is (is
(sm? sig*1 (sm? sig*1
{:kind :event, :id ::ev-id, :level :info, {:schema 1, :kind :event, :id ::ev-id, :level :info,
:ns "taoensso.telemere-tests" :ns "taoensso.telemere-tests"
:msg_ "a b" :msg_ "a b"
:inst udt1 :inst udt1
@ -872,7 +866,7 @@
(let [sig* (enc/read-json ((tel/pr-signal-fn {:pr-fn :json}) sig))] (let [sig* (enc/read-json ((tel/pr-signal-fn {:pr-fn :json}) sig))]
(is (is
(sm? sig* (sm? sig*
{"kind" "event", "id" "taoensso.telemere-tests/ev-id", {"schema" 1, "kind" "event", "id" "taoensso.telemere-tests/ev-id",
"level" "info", "ns" "taoensso.telemere-tests" "level" "info", "ns" "taoensso.telemere-tests"
"msg_" "a b" "msg_" "a b"
"inst" t1s "inst" t1s
@ -1032,7 +1026,7 @@
:run-form '(+ 3 2) :run-form '(+ 3 2)
:run-val 5 :run-val 5
:run-nsecs 100 :run-nsecs 100
:sample 0.5 :sample-rate 0.5
:data :data
{:key-kw :val-kw {:key-kw :val-kw

View file

@ -1,4 +1,4 @@
(defproject com.taoensso/telemere-slf4j "1.2.1" (defproject com.taoensso/telemere-slf4j "1.0.0-RC4"
: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-RC4"]]}
: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`)
@ -123,7 +123,7 @@
:inst inst :inst inst
:error error :error error
:ctx+ :ctx
(when-let [hmap (org.slf4j.MDC/getCopyOfContextMap)] (when-let [hmap (org.slf4j.MDC/getCopyOfContextMap)]
(clojure.lang.PersistentHashMap/create hmap)) (clojure.lang.PersistentHashMap/create hmap))
@ -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
@ -179,25 +175,23 @@ A signal will be provided to a handler iff **ALL** of the following are true:
- a. Compile time: not applicable - a. Compile time: not applicable
- b. Runtime: sample rate, kind, ns, id, level, when fn, rate limit - b. Runtime: sample rate, kind, ns, id, level, when fn, rate limit
- 3. **Call transform** `(fn [signal]) => ?modified-signal` returns non-nil - 3. **Call middleware** `(fn [signal]) => ?modified-signal` returns non-nil
- 4. **Handler transform** `(fn [signal]) => ?modified-signal` returns non-nil - 4. **Handler middleware** `(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). > Middleware provides a flexible way to modify and/or filter signals by arbitrary signal data/content conditions (return nil to skip).
> 👉 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).
@ -212,7 +206,7 @@ Telemere includes extensive internal help docstrings:
| :---------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------- | | :---------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------- |
| [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) | Creating signals | | [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) | Creating signals |
| [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Options when creating signals | | [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Options when creating signals |
| [`help:signal-content`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) | Signal content (map given to transforms/handlers) | | [`help:signal-content`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) | Signal content (map given to middleware/handlers) |
| [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) | Signal filtering and transformation | | [`help:filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:filters) | Signal filtering and transformation |
| [`help:handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handlers) | Signal handler management | | [`help:handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handlers) | Signal handler management |
| [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) | Signal handler dispatch options | | [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) | Signal handler dispatch options |

View file

@ -16,4 +16,4 @@ This flow is visualized below:
<img src="https://raw.githubusercontent.com/taoensso/telemere/master/imgs/signal-flow.svg" alt="Telemere signal flowchart" width="640"/> <img src="https://raw.githubusercontent.com/taoensso/telemere/master/imgs/signal-flow.svg" alt="Telemere signal flowchart" width="640"/>
- `A/sync queue` semantics are specified via [handler dispatch options](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options). - `A/sync queue` semantics are specified via [handler dispatch options](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options).
- The shared **call transform** cache is super useful when doing signal transformations that are expensive and/or involve side effects (like syncing with another service/db to get a unique tx id, etc.). - The shared **call middleware cache** is super useful when doing signal transformations that are expensive and/or involve side effects (like syncing with another service/db to get a unique tx id, etc.).

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
@ -12,27 +12,13 @@ A signal will be provided to a handler iff **ALL** of the following are true:
- a. Compile time: not applicable - a. Compile time: not applicable
- b. Runtime: sample rate, kind, ns, id, level, when fn, rate limit - b. Runtime: sample rate, kind, ns, id, level, when fn, rate limit
- 3. **Call transform** `(fn [signal]) => ?modified-signal` returns non-nil - 3. **Call middleware** `(fn [signal]) => ?modified-signal` returns non-nil
- 4. **Handler transform** `(fn [signal]) => ?modified-signal` returns non-nil - 4. **Handler middleware** `(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). > Middleware provides a flexible way to modify and/or filter signals by arbitrary signal data/content conditions (return nil to skip).
> 👉 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

@ -48,11 +48,11 @@ There's two kinds of config relevant to all signal handlers:
## Dispatch opts ## Dispatch opts
Handler dispatch opts includes dispatch priority (determines order in which handlers are called), handler filtering, handler transform, a/sync queue semantics, back-pressure opts, etc. Handler dispatch opts includes dispatch priority (determines order in which handlers are called), handler filtering, handler middleware, a/sync queue semantics, back-pressure opts, etc.
See [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) for full info, and [`default-handler-dispatch-opts`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#default-handler-dispatch-opts) for defaults. See [`help:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) for full info, and [`default-handler-dispatch-opts`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#default-handler-dispatch-opts) for defaults.
Note that the handler transform is an easily overlooked but powerful feature, allowing you to arbitrarily modify and/or filter every [signal map](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) before it is given to each handler. Note that handler middleware in particular is an easily overlooked but powerful feature, allowing you to arbitrarily transform and/or filter every [signal map](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) before it is given to each handler.
## Handler-specific opts ## Handler-specific opts
@ -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)})}))
@ -120,28 +120,27 @@ Note that when writing JSON with Clojure, you *must* provide an appropriate `pr-
### Handler-specific per-signal kvs ### Handler-specific per-signal kvs
Telemere includes a handy mechanism for including arbitrary app-level data/opts in individual signals for use by custom transforms and/or handlers. Telemere includes a handy mechanism for including arbitrary app-level data/opts in individual signals for use by custom middleware and/or handlers.
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-middleware-data "foo"
:my-data-for-xfn "foo" :my-handler-data "bar"}))
:my-data-for-handler "bar"}))
;; %> ;; %>
;; {;; App-level kvs included inline (assoc'd to signal root) ;; {;; App-level kvs included inline (assoc'd to signal root)
;; :my-data-for-xfn "foo" ;; :my-middleware-data "foo"
;; :my-data-for-handler "bar" ;; :my-handler-data "bar"
;; :kvs ; And also collected together under ":kvs" key ;; :kvs ; And also collected together under ":kvs" key
;; {:my-data-for-xfn "foo" ;; {:my-middleware-data "foo"
;; :my-data-for-handler "bar"} ;; :my-handler-data "bar"}
;; ... } ;; ... }
``` ```
These app-level data/opts are typically NOT included by default in handler output, making them a great way to convey data/opts to custom transforms/handlers. These app-level data/opts are typically NOT included by default in handler output, making them a great way to convey data/opts to custom middleware/handlers.
# Managing handlers # Managing handlers
@ -236,7 +235,7 @@ If you're making a customizable handler for use by others, it's often handy to d
(with-meta handler-fn (with-meta handler-fn
{:dispatch-opts {:dispatch-opts
{:min-level :info {:min-level :info
:limit :rate-limit
[[1 1000] ; Max 1 signal per second [[1 1000] ; Max 1 signal per second
[10 60000] ; Max 10 signals per minute [10 60000] ; Max 10 signals per minute
]}})))) ]}}))))
@ -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

@ -80,9 +80,9 @@ Consider the [differences](https://www.youtube.com/watch?v=oyLBGkS5ICk) between
This way you can see all your ids in one place, and precise info on when ids were added/removed/changed. This way you can see all your ids in one place, and precise info on when ids were added/removed/changed.
- Use **signal call transforms** to your advantage. - Use **signal call middleware** to your advantage.
The result of call-side signal transforms is cached and *shared between all handlers* making it an efficient place to modify signals going to >1 handler. The result of call middleware is cached and *shared between all handlers* making it an efficient place to transform signals. For this reason - prefer signal middleware to handler middleware when possible/convenient.
- Signal and handler **sampling is multiplicative**. - Signal and handler **sampling is multiplicative**.
@ -90,14 +90,16 @@ Consider the [differences](https://www.youtube.com/watch?v=oyLBGkS5ICk) between
If a signal is created with *20%* sampling and a handler handles *50%* of received signals, then *10%* of possible signals will be handled (50% of 20%). If a signal is created with *20%* sampling and a handler handles *50%* of received signals, then *10%* of possible signals will be handled (50% of 20%).
When sampling is active, the final (combined multiplicative) rate is helpfully reflected in each signal's `:sample` rate value ∈ℝ[0,1]. This makes it possible to estimate _unsampled_ cardinalities: for `n` randomly sampled signals matching some criteria, you'd have seen an estimated `Σ(1.0/sample-rate_i)` such signals _without_ sampling, etc. This multiplicative rate is helpfully reflected in each signal's final `:sample-rate` value, making it possible to estimate *unsampled* cardinalities in relevant cases.
- Transforms can technically return any type, but it's best to return only `nil` or a map. This ensures maximum compatibility with community transforms, handlers, and tools. So for `n` randomly sampled signals matching some criteria, you'd have seen an estimated `Σ(1.0/sample-rate_i)` such signals _without_ sampling, etc.
- Transforms can be used to **filter signals** by returning `nil`. - Middleware can return any type, but it's best to return only `nil` or a map. This ensures maximum compatibility with community middleware, handlers, and tools.
- Transforms can be used to **split signals**:
Your transforms can *call signal creators* like any other code. Return `nil` after to filter the source signal. Just be aware that new signals will re-enter your handler queue/s as would any other signal - and so may be subject to handling delay and normal handler queue back-pressure. - Middleware can be used to **filter signals** by returning `nil`.
- Middleware can be used to **split signals**:
Your middleware can *call signal creators* like any other code. Return `nil` after to filter the source signal. Just be aware that new signals will re-enter your handler queue/s as would any other signal - and so may be subject to handling delay and normal handler queue back-pressure.
See also the [`dispatch-signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#dispatch-signal!) util. See also the [`dispatch-signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#dispatch-signal!) util.
@ -114,13 +116,13 @@ 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", ...}, ...}
``` ```
Note that all app-level kvs will *also* be available *together* under the signal's `:kvs` key. Note that all app-level kvs will *also* be available *together* under the signal's `:kvs` key.
App-level kvs are typically *not* included in handler output, so are a great way of providing custom data/opts for use (only) by custom transforms or handlers. App-level kvs are typically *not* included in handler output, so are a great way of providing custom data/opts for use (only) by custom middleware or handlers.
- Signal `kind` can be useful in advanced cases. - Signal `kind` can be useful in advanced cases.

View file

@ -1,12 +1,13 @@
My plan for Telemere is to offer a **stable core of limited scope**, then to focus on making it as easy for the **community** to write additional stuff like handlers, transforms, and utils. My plan for Telemere is to offer a **stable core of limited scope**, then to focus on making it as easy for the **community** to write additional stuff like handlers, middleware, and utils.
[PRs](../wiki#contributions-welcome) **very welcome** to add links to this page! **PRs very welcome** to add links to this page!
If you spot issues with any linked resources, please **contact the relevant authors** to let them know! Thank you! 🙏 - [Peter](https://www.taoensso.com) If you spot issues with any linked resources, please **contact the relevant authors** to let them know! Thank you! 🙏 - [Peter](https://www.taoensso.com)
# Learning # Learning
Includes videos, tutorials, demo projects, etc. Includes videos, tutorials, demo projects, etc.
[PRs](../wiki#contributions-welcome) welcome for additions!
| Type | Description | | Type | Description |
| ------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
@ -19,9 +20,9 @@ Includes videos, tutorials, demo projects, etc.
# Handlers and tools # Handlers and tools
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)), middleware, handler utils (e.g. formatters), tools for analyzing signals, etc. [PRs](../wiki#contributions-welcome) welcome for additions!
| 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.