mirror of
https://github.com/taoensso/telemere.git
synced 2025-12-18 10:11:10 +00:00
Compare commits
97 commits
v1.0.0-RC2
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
798e0fddc4 | ||
|
|
6030c472ae | ||
|
|
1d6bdaf7e5 | ||
|
|
6cc54527f2 | ||
|
|
56e35f3f58 | ||
|
|
a6fc4adf6a | ||
|
|
6155713fde | ||
|
|
a883df3c41 | ||
|
|
e6ce33dd4e | ||
|
|
cc680b06f5 | ||
|
|
47af80319d | ||
|
|
b56e1c4529 | ||
|
|
8a3ae14f45 | ||
|
|
917b1b408e | ||
|
|
125e006753 | ||
|
|
f7006f31fe | ||
|
|
c6a71652d7 | ||
|
|
4cc4f45e7c | ||
|
|
ff9e3f4007 | ||
|
|
eb28d365a8 | ||
|
|
d9ad1ba379 | ||
|
|
b2a8b66cc0 | ||
|
|
1bcd46adf3 | ||
|
|
f6ec872f7c | ||
|
|
b7b3a25a82 | ||
|
|
070fe88abb | ||
|
|
dcfeba5b91 | ||
|
|
9d655bb9ce | ||
|
|
75a90c6b6d | ||
|
|
269c58d8fe | ||
|
|
7603ae2fcf | ||
|
|
6fb18bd3b9 | ||
|
|
d6264afe7c | ||
|
|
f08b60bce4 | ||
|
|
3746de8039 | ||
|
|
1bdb667b6c | ||
|
|
2e0a2938b7 | ||
|
|
9d040d70cd | ||
|
|
475e5ba6c2 | ||
|
|
51e8a1062f | ||
|
|
31a4fc26d2 | ||
|
|
94fec57c9e | ||
|
|
345b125f6b | ||
|
|
248e91f982 | ||
|
|
32e8909e42 | ||
|
|
e8f02ac13e | ||
|
|
254cd6471b | ||
|
|
c2e7d0c2d6 | ||
|
|
0608d43d44 | ||
|
|
d67fc4a76d | ||
|
|
f37f54e1da | ||
|
|
1f4b49a21a | ||
|
|
7cccf672f5 | ||
|
|
c78eb07385 | ||
|
|
82f4c31651 | ||
|
|
af45ffc396 | ||
|
|
79173a68cc | ||
|
|
c60f33edeb | ||
|
|
bb3d351be8 | ||
|
|
2510c5dbb9 | ||
|
|
9ba4bd986d | ||
|
|
b03b06de6a | ||
|
|
6b0e0b9fff | ||
|
|
bfea51570f | ||
|
|
ac5feb4723 | ||
|
|
e32ed8deb5 | ||
|
|
46e82f0816 | ||
|
|
ea20f6836b | ||
|
|
634cc53405 | ||
|
|
410ed8914c | ||
|
|
78ed4d7f14 | ||
|
|
4fdc55e9b8 | ||
|
|
a60f5b8d7c | ||
|
|
dd9f4b2a33 | ||
|
|
b7d2b4a1ed | ||
|
|
4a6771a907 | ||
|
|
824f8e3d53 | ||
|
|
fda22ce80c | ||
|
|
1f99f7186b | ||
|
|
bb715fb206 | ||
|
|
2c5599c234 | ||
|
|
97efef3d40 | ||
|
|
2795a6cd52 | ||
|
|
94f13e44f9 | ||
|
|
fc7e748ac8 | ||
|
|
feb2f64f92 | ||
|
|
af14494637 | ||
|
|
35606d971d | ||
|
|
7d4aed60d8 | ||
|
|
f984cdd213 | ||
|
|
db26a5d683 | ||
|
|
413cce87c3 | ||
|
|
db0498b22c | ||
|
|
0e642ba21f | ||
|
|
1517f30abf | ||
|
|
3a9ffc6206 | ||
|
|
8c7caf45fa |
66 changed files with 2322 additions and 2079 deletions
|
|
@ -1,4 +1,4 @@
|
|||
name: Main tests
|
||||
name: Clj tests
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
|
|
@ -7,7 +7,6 @@ jobs:
|
|||
matrix:
|
||||
java: ['17', '19', '21']
|
||||
os: [ubuntu-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -15,17 +14,14 @@ jobs:
|
|||
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('projects/main/project.clj') }}
|
||||
key: deps-${{ hashFiles('main/project.clj') }}
|
||||
restore-keys: deps-
|
||||
|
||||
- run: lein test-all
|
||||
working-directory: projects/main
|
||||
- run: lein test-clj
|
||||
working-directory: main
|
||||
27
.github/workflows/cljs-tests.yml
vendored
Normal file
27
.github/workflows/cljs-tests.yml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
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
|
||||
13
.github/workflows/graal-tests.yml
vendored
13
.github/workflows/graal-tests.yml
vendored
|
|
@ -2,7 +2,7 @@ name: Graal tests
|
|||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
tests:
|
||||
strategy:
|
||||
matrix:
|
||||
java: ['17']
|
||||
|
|
@ -26,8 +26,13 @@ jobs:
|
|||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: deps-${{ hashFiles('projects/main/project.clj') }}
|
||||
key: deps-${{ hashFiles('main/project.clj') }}
|
||||
restore-keys: deps-
|
||||
|
||||
- run: bb graal-tests
|
||||
working-directory: projects/main
|
||||
- name: Run Graal tests
|
||||
run: bb graal-tests
|
||||
working-directory: main
|
||||
|
||||
# - name: Run Babashka tests
|
||||
# run: bb bb-tests
|
||||
# working-directory: main
|
||||
|
|
|
|||
325
CHANGELOG.md
325
CHANGELOG.md
|
|
@ -2,174 +2,209 @@ This project uses [**Break Versioning**](https://www.taoensso.com/break-versioni
|
|||
|
||||
---
|
||||
|
||||
# `v1.0.0-RC2` (2024-12-24)
|
||||
# `v1.2.1` (2025-12-16)
|
||||
|
||||
## 📦 Dependencies
|
||||
|
||||
Available on Clojars:
|
||||
|
||||
1. [Telemere](https://clojars.org/com.taoensso/telemere/versions/1.0.0-RC1) - main dependency.
|
||||
2. [SLF4J provider](https://clojars.org/com.taoensso/telemere-slf4j/versions/1.0.0-RC1) - additional dependency for users that want their Java logging [to go to](https://github.com/taoensso/telemere/wiki/3-Config#java-logging) Telemere.
|
||||
1. [Telemere](https://clojars.org/com.taoensso/telemere/versions/1.2.1) - main dep
|
||||
2. [SLF4J provider](https://clojars.org/com.taoensso/telemere-slf4j/versions/1.2.1) - extra dep to [send Java logging](https://github.com/taoensso/telemere/wiki/3-Config#java-logging) to Telemere
|
||||
|
||||
This project uses [Break Versioning](https://www.taoensso.com/break-versioning).
|
||||
|
||||
## Release notes
|
||||
|
||||
RC2 includes many usability improvements and several minor fixes.
|
||||
This is a **hotfix release** to fix a regression in v1.2.0 that prevented errors from correctly appearing via the Timbre->Telemere appender.
|
||||
|
||||
It also includes **3x small 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 more unexpected issues come up, RC2 will become **v1 stable** in Jan 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)
|
||||
|
||||
Happy holidays everyone! 🎄🫶
|
||||
## 📦 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)
|
||||
|
||||
## Recent CHANGES
|
||||
## Changes since `v1 RC1`
|
||||
|
||||
### Since `v1.0.0-RC1` (2024-10-29)
|
||||
> See linked commits for more info:
|
||||
|
||||
#### ➤ Changes to API
|
||||
In **v1 stable** (2025-04-30):
|
||||
- \[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]
|
||||
|
||||
Please read these carefully in case you might be affected:
|
||||
In **v1 RC5** (2025-03-10):
|
||||
* \[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)
|
||||
|
||||
* ➤ **\[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]** Remove rarely-used advanced options from [`catch->error!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#catch-%3Eerror!) \[0de5c09] (RC2)
|
||||
In **v1 RC4** (2025-03-03):
|
||||
* \[mod] `log!`, `event!` now always return nil \[ac5feb4] (RC4)
|
||||
* \[mod] [#51] Make default console handler sync by default \[78ed4d7] (RC4)
|
||||
* \[mod] [#52] `signal-preamble-fn` now ignores nil `:kind` (@marksto) \[634cc53] (RC4)
|
||||
* \[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)
|
||||
|
||||
#### Changes to default output
|
||||
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)
|
||||
|
||||
* **\[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]** 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): 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)
|
||||
|
||||
### Earlier
|
||||
|
||||
* \[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 [`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]** 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]** Give [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) a default kind and level \[7532c2e] (RC2)
|
||||
* **\[new]** Better error message when [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) given non-map arg \[d563ac1] (RC2)
|
||||
* **\[new]** Improve error info on worst-case handler errors \[484b3df] (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]** Alias low-level formatters in utils ns \[9dc9a46] (RC2)
|
||||
|
||||
#### Doc improvements
|
||||
|
||||
* **\[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]** [#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]** Document that `:msg` may be a delay \[13d9dbf] (RC2)
|
||||
|
||||
### Earlier
|
||||
|
||||
* \[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]** 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
|
||||
|
||||
* \[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)
|
||||
In **v1 RC2** (2024-12-24):
|
||||
* \[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] 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 "- " 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] 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): 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)
|
||||
* \[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)
|
||||
* \[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] 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] Give [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) a default kind and level \[7532c2e] (RC2)
|
||||
* \[new] Better error message when [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) given non-map arg \[d563ac1] (RC2)
|
||||
* \[new] Improve error info on worst-case handler errors \[484b3df] (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] Alias low-level formatters in utils ns \[9dc9a46] (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] [#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] Document that `:msg` may be a delay \[13d9dbf] (RC2)
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
253
README.md
253
README.md
|
|
@ -1,83 +1,79 @@
|
|||
<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 docs] | [**Wiki**][GitHub wiki] | [Latest releases](#latest-releases) | [Slack channel][]
|
||||
[**API**][cljdoc] | [**Wiki**][GitHub wiki] | [Slack][] | Latest release: [v1.2.1](../../releases/tag/v1.2.1) (2025-12-16)
|
||||
|
||||
[![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"/>
|
||||
|
||||
### Structured telemetry library for Clojure/Script
|
||||
### Structured logs and telemetry for Clojure/Script
|
||||
|
||||
**Telemere** is a **pure Clojure/Script library** that offers an elegant and simple **unified API** to cover:
|
||||
**Telemere** is the next-gen version of [Timbre](https://www.taoensso.com/timbre). It offers **one API** to cover:
|
||||
|
||||
- **Traditional logging** (string messages)
|
||||
- **Structured logging** (rich Clojure data types and structures)
|
||||
- **Events** (named thing happened, with optional data)
|
||||
- **Tracing** (nested flow tracking, with optional data)
|
||||
- Basic **performance monitoring** (nested form runtimes)
|
||||
- Any combination of the above
|
||||
|
||||
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
|
||||
|
||||
- `2024-12-24` `v1.0.0-RC2`: [release info](../../releases/tag/v1.0.0-RC2)
|
||||
|
||||
[![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/>
|
||||
It's pure Clj/s, small, **easy to use**, super fast, and **seriously flexible**:
|
||||
|
||||
```clojure
|
||||
(require '[taoensso.telemere :as t])
|
||||
(tel/log! {:level :info, :id ::login, :data {:user-id 1234}, :msg "User logged in!"})
|
||||
```
|
||||
|
||||
;; (Just works / no config necessary for typical use cases)
|
||||
Works great with:
|
||||
|
||||
;; Without structured data
|
||||
(t/log! :info "Hello world!") ; %> Basic log signal (has message)
|
||||
(t/event! ::my-id :debug) ; %> Basic event signal (just id)
|
||||
- [Trove](https://www.taoensso.com/trove) for logging by **library authors**
|
||||
- [Tufte](https://www.taoensso.com/tufte) for rich **performance monitoring**
|
||||
- [Truss](https://www.taoensso.com/truss) for **assertions** and error handling
|
||||
|
||||
;; With structured data
|
||||
(t/log! {:level :info, :data {...}} "Hello again!")
|
||||
(t/event! ::my-id {:level :debug, :data {...}})
|
||||
## Why structured logging?
|
||||
|
||||
- Traditional logging outputs **strings** (messages).
|
||||
- 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**. It’s 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)
|
||||
;; Tracks form runtime, return value, and (nested) parent tree
|
||||
(t/trace! {:id ::my-id :data {...}}
|
||||
(tel/trace! {:id ::my-id :data {...}}
|
||||
(do-some-work))
|
||||
|
||||
;; Check resulting signal content for debug/tests
|
||||
(t/with-signal (t/event! ::my-id)) ; => {:keys [ns level id data msg_ ...]}
|
||||
(tel/with-signal (tel/log! {...})) ; => {:keys [ns level id data msg_ ...]}
|
||||
|
||||
;; Getting fancy (all costs are conditional!)
|
||||
(t/log!
|
||||
{:level :debug
|
||||
:sample-rate 0.75 ; 75% sampling (noop 25% of the time)
|
||||
:when (my-conditional)
|
||||
:rate-limit {"1 per sec" [1 1000]
|
||||
"5 per min" [5 60000]}
|
||||
:rate-limit-by my-user-ip-address ; Optional rate-limit scope
|
||||
(tel/log!
|
||||
{:level :debug
|
||||
:sample 0.75 ; 75% sampling (noop 25% of the time)
|
||||
:when (my-conditional)
|
||||
:limit {"1 per sec" [1 1000]
|
||||
"5 per min" [5 60000]} ; Rate limit
|
||||
:limit-by my-user-ip-address ; Rate limit scope
|
||||
|
||||
:do (inc-my-metric!)
|
||||
:let
|
||||
|
|
@ -99,30 +95,33 @@ It enables you to write code that is **information-verbose by default**.
|
|||
|
||||
```clojure
|
||||
;; Set minimum level
|
||||
(t/set-min-level! :warn) ; For all signals
|
||||
(t/set-min-level! :log :debug) ; For `log!` signals only
|
||||
(tel/set-min-level! :warn) ; For all signals
|
||||
(tel/set-min-level! :log :debug) ; For `log!` signals specifically
|
||||
|
||||
;; Set namespace and id filters
|
||||
(t/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"})
|
||||
(t/set-id-filter! {:allow #{::my-particular-id "my-app/*"}})
|
||||
;; Set id and namespace filters
|
||||
(tel/set-id-filter! {:allow #{::my-particular-id "my-app/*"}})
|
||||
(tel/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"})
|
||||
|
||||
;; Set minimum level for `event!` signals for particular ns pattern
|
||||
(t/set-min-level! :event "taoensso.sente.*" :warn)
|
||||
;; SLF4J signals will have their `:ns` key set to the logger's name
|
||||
;; (typically a source class)
|
||||
(tel/set-ns-filter! {:disallow "com.noisy.java.package.*"})
|
||||
|
||||
;; Use middleware to:
|
||||
;; - Transform signals
|
||||
;; - Filter signals by arb conditions (incl. data/content)
|
||||
;; Set minimum level for `log!` signals for particular ns pattern
|
||||
(tel/set-min-level! :log "taoensso.sente.*" :warn)
|
||||
|
||||
(t/set-middleware!
|
||||
;; Use transforms (xfns) to filter and/or arbitrarily modify signals
|
||||
;; by signal data/content/etc.
|
||||
|
||||
(tel/set-xfn!
|
||||
(fn [signal]
|
||||
(if (-> signal :data :skip-me?)
|
||||
nil ; Filter signal (don't handle)
|
||||
(assoc signal :passed-through-middleware? true))))
|
||||
(assoc signal :transformed? true))))
|
||||
|
||||
(t/with-signal (t/event! ::my-id {:data {:skip-me? true}})) ; => nil
|
||||
(t/with-signal (t/event! ::my-id {:data {:skip-me? false}})) ; => {...}
|
||||
(tel/with-signal (tel/log! {... :data {:skip-me? true}})) ; => nil
|
||||
(tel/with-signal (tel/log! {... :data {:skip-me? false}})) ; => {...}
|
||||
|
||||
;; See `t/help:filters` docstring for more filtering options
|
||||
;; See `tel/help:filters` docstring for more filtering options
|
||||
```
|
||||
|
||||
</details>
|
||||
|
|
@ -131,44 +130,44 @@ It enables you to write code that is **information-verbose by default**.
|
|||
|
||||
```clojure
|
||||
;; Add your own signal handler
|
||||
(t/add-handler! :my-handler
|
||||
(tel/add-handler! :my-handler
|
||||
(fn
|
||||
([signal] (println signal))
|
||||
([] (println "Handler has shut down"))))
|
||||
|
||||
;; Use `add-handler!` to set handler-level filtering and back-pressure
|
||||
(t/add-handler! :my-handler
|
||||
(tel/add-handler! :my-handler
|
||||
(fn
|
||||
([signal] (println signal))
|
||||
([] (println "Handler has shut down")))
|
||||
|
||||
{:async {:mode :dropping, :buffer-size 1024, :n-threads 1}
|
||||
:priority 100
|
||||
:sample-rate 0.5
|
||||
:min-level :info
|
||||
:ns-filter {:disallow "taoensso.*"}
|
||||
:rate-limit {"1 per sec" [1 1000]}
|
||||
;; See `t/help:handler-dispatch-options` for more
|
||||
:priority 100
|
||||
:sample 0.5
|
||||
:min-level :info
|
||||
:ns-filter {:disallow "taoensso.*"}
|
||||
:limit {"1 per sec" [1 1000]}
|
||||
;; See `tel/help:handler-dispatch-options` for more
|
||||
})
|
||||
|
||||
;; See current handlers
|
||||
(t/get-handlers) ; => {<handler-id> {:keys [handler-fn handler-stats_ dispatch-opts]}}
|
||||
(tel/get-handlers) ; => {<handler-id> {:keys [handler-fn handler-stats_ dispatch-opts]}}
|
||||
|
||||
;; Add console handler to print signals as human-readable text
|
||||
(t/add-handler! :my-handler
|
||||
(t/handler:console
|
||||
{:output-fn (t/format-signal-fn {})}))
|
||||
(tel/add-handler! :my-handler
|
||||
(tel/handler:console
|
||||
{:output-fn (tel/format-signal-fn {})}))
|
||||
|
||||
;; Add console handler to print signals as edn
|
||||
(t/add-handler! :my-handler
|
||||
(t/handler:console
|
||||
{:output-fn (t/pr-signal-fn {:pr-fn :edn})}))
|
||||
(tel/add-handler! :my-handler
|
||||
(tel/handler:console
|
||||
{:output-fn (tel/pr-signal-fn {:pr-fn :edn})}))
|
||||
|
||||
;; Add console handler to print signals as JSON
|
||||
;; Ref. <https://github.com/metosin/jsonista> (or any alt JSON lib)
|
||||
#?(:clj (require '[jsonista.core :as jsonista]))
|
||||
(t/add-handler! :my-handler
|
||||
(t/handler:console
|
||||
(tel/add-handler! :my-handler
|
||||
(tel/handler:console
|
||||
{:output-fn
|
||||
#?(:cljs :json ; Use js/JSON.stringify
|
||||
:clj jsonista/write-value-as-string)}))
|
||||
|
|
@ -180,34 +179,28 @@ It enables you to write code that is **information-verbose by default**.
|
|||
|
||||
### Ergonomics
|
||||
|
||||
- Elegant, lightweight API that's **easy to use**, **easy to configure**, and **deeply flexible**.
|
||||
- **Sensible defaults** to make getting started **fast and easy**.
|
||||
- Extensive **beginner-oriented** [documentation][GitHub wiki], [docstrings](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere), and error messages.
|
||||
- Elegant unified API that's **easy to use** and **deeply flexible**.
|
||||
- Pure **Clojure vals and fns** for easy config, composition, and REPL debugging.
|
||||
- **Sensible defaults** to get started fast.
|
||||
- **Beginner-oriented** [documentation][GitHub wiki], [docstrings](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere), and error messages.
|
||||
|
||||
### Interop
|
||||
|
||||
- 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).
|
||||
- Included [shim](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.timbre) for easy/gradual [migration from Timbre](../../wiki/5-Migrating).
|
||||
- **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).
|
||||
- [Timbre 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.
|
||||
|
||||
### Scaling
|
||||
|
||||
- Hyper-optimized and **blazing fast**, see [performance](#performance).
|
||||
- An API that **scales comfortably** from the smallest disposable code, to the most massive and complex real-world production environments.
|
||||
- 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.
|
||||
|
||||
### 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.
|
||||
- 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.
|
||||
- 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).
|
||||
- Turn-key **sampling**, **rate limiting**, and **back-pressure monitoring**.
|
||||
- Highly optimized and [blazing fast](#performance)!
|
||||
|
||||
## Comparisons
|
||||
|
||||
- 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-mulog) to [Mulog](https://github.com/BrunoBonacci/mulog) (Structured micro-logging library)
|
||||
- Telemere [compared](../../wiki/6-FAQ#how-does-telemere-compare-to-%CE%BClog) to [μ/log](https://github.com/BrunoBonacci/mulog) (structured micro-logging library)
|
||||
|
||||
## Videos
|
||||
|
||||
|
|
@ -225,19 +218,21 @@ It enables you to write code that is **information-verbose by default**.
|
|||
|
||||
## API overview
|
||||
|
||||
See relevant docstrings (links below) for usage info-
|
||||
|
||||
### Creating signals
|
||||
|
||||
| Name | Kind | Args | Returns |
|
||||
| :---------------------------------------------------------------------------------------------------------- | :--------- | :--------------- | :--------------------------- |
|
||||
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `:generic` | `opts` | Depends on opts |
|
||||
| [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `:event` | `id` + `?level` | Signal allowed? |
|
||||
| [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `:log` | `?level` + `msg` | Signal allowed? |
|
||||
| [`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 |
|
||||
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).
|
||||
|
||||
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:
|
||||
|
||||
| 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 |
|
||||
|
||||
### Internal help
|
||||
|
||||
|
|
@ -247,7 +242,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-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 middleware/handlers) |
|
||||
| [`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: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:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) | Signal handler dispatch options |
|
||||
|
|
@ -276,7 +271,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.
|
||||
|
||||
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.
|
||||
|
||||
|
|
@ -310,25 +305,25 @@ You can also easily [write your own handlers](../../wiki/4-Handlers#writing-hand
|
|||
|
||||
## 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, middleware, 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, transforms, and utils.
|
||||
|
||||
See [here](../../wiki/8-Community) for community resources.
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Wiki][GitHub wiki] (getting started, usage, etc.)
|
||||
- API reference via [cljdoc][cljdoc docs] or [Codox][Codox docs]
|
||||
- API reference via [cljdoc][cljdoc]
|
||||
- Extensive [internal help](#internal-help) (no need to leave your IDE)
|
||||
- Support via [Slack channel][] or [GitHub issues][]
|
||||
- Support via [Slack][] 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)
|
||||
|
||||
## Funding
|
||||
|
||||
You can [help support][sponsor] continued work on this project, thank you!! 🙏
|
||||
You can [help support][sponsor] continued work on this project and [others][my work], thank you!! 🙏
|
||||
|
||||
## License
|
||||
|
||||
Copyright © 2023-2024 [Peter Taoussanis][].
|
||||
Copyright © 2023-2025 [Peter Taoussanis][].
|
||||
Licensed under [EPL 1.0](LICENSE.txt) (same as Clojure).
|
||||
|
||||
<!-- Common -->
|
||||
|
|
@ -336,20 +331,22 @@ Licensed under [EPL 1.0](LICENSE.txt) (same as Clojure).
|
|||
[GitHub releases]: ../../releases
|
||||
[GitHub issues]: ../../issues
|
||||
[GitHub wiki]: ../../wiki
|
||||
[Slack channel]: https://www.taoensso.com/telemere/slack
|
||||
[Slack]: https://www.taoensso.com/telemere/slack
|
||||
|
||||
[Peter Taoussanis]: https://www.taoensso.com
|
||||
[sponsor]: https://www.taoensso.com/sponsor
|
||||
[my work]: https://www.taoensso.com/clojure-libraries
|
||||
|
||||
<!-- Project -->
|
||||
|
||||
[Codox docs]: https://taoensso.github.io/telemere/
|
||||
[cljdoc docs]: https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere
|
||||
[cljdoc]: https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere
|
||||
|
||||
[Clojars SVG]: https://img.shields.io/clojars/v/com.taoensso/telemere.svg
|
||||
[Clojars URL]: https://clojars.org/com.taoensso/telemere
|
||||
|
||||
[Main tests SVG]: https://github.com/taoensso/telemere/actions/workflows/main-tests.yml/badge.svg
|
||||
[Main tests URL]: https://github.com/taoensso/telemere/actions/workflows/main-tests.yml
|
||||
[Clj tests SVG]: https://github.com/taoensso/telemere/actions/workflows/clj-tests.yml/badge.svg
|
||||
[Clj tests URL]: https://github.com/taoensso/telemere/actions/workflows/clj-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 URL]: https://github.com/taoensso/telemere/actions/workflows/graal-tests.yml
|
||||
|
|
|
|||
|
|
@ -10,4 +10,4 @@ Please report possible security vulnerabilities [via GitHub](https://github.com/
|
|||
|
||||
Thank you!
|
||||
|
||||
\- [Peter Taoussanis](https://www.taoensso.com)
|
||||
\- [Peter Taoussanis](https://www.taoensso.com)
|
||||
249
examples.cljc
249
examples.cljc
|
|
@ -1,39 +1,41 @@
|
|||
(ns examples
|
||||
"Basic Telemere usage examples that appear in the Wiki or docstrings."
|
||||
(:require [taoensso.telemere :as t]))
|
||||
(:require [taoensso.telemere :as tel]))
|
||||
|
||||
(comment
|
||||
|
||||
;;;; README "Quick examples"
|
||||
|
||||
(require '[taoensso.telemere :as t])
|
||||
(require '[taoensso.telemere :as tel])
|
||||
|
||||
;; (Just works / no config necessary for typical use cases)
|
||||
;; No config needed for typical use cases!!
|
||||
;; Signals print to console by default for both Clj and Cljs
|
||||
|
||||
;; Without structured data
|
||||
(t/log! :info "Hello world!") ; %> Basic log signal (has message)
|
||||
(t/event! ::my-id :debug) ; %> Basic event signal (just id)
|
||||
;; Traditional style logging (data formatted into message string):
|
||||
(tel/log! {:level :info, :msg (str "User " 1234 " logged in!")})
|
||||
|
||||
;; With structured data
|
||||
(t/log! {:level :info, :data {}} "Hello again!")
|
||||
(t/event! ::my-id {:level :debug, :data {}})
|
||||
;; Modern/structured style logging (explicit id and data)
|
||||
(tel/log! {:level :info, :id :auth/login, :data {:user-id 1234}})
|
||||
|
||||
;; Trace (auto interops with OpenTelemetry)
|
||||
;; 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)
|
||||
;; Tracks form runtime, return value, and (nested) parent tree
|
||||
(t/trace! {:id ::my-id :data {}}
|
||||
(tel/trace! {:id ::my-id :data {...}}
|
||||
(do-some-work))
|
||||
|
||||
;; Check resulting signal content for debug/tests
|
||||
(t/with-signal (t/event! ::my-id)) ; => {:keys [ns level id data msg_ ...]}
|
||||
(tel/with-signal (tel/log! {...})) ; => {:keys [ns level id data msg_ ...]}
|
||||
|
||||
;; Getting fancy (all costs are conditional!)
|
||||
(t/log!
|
||||
{:level :debug
|
||||
:sample-rate 0.75 ; 75% sampling (noop 25% of the time)
|
||||
:when (my-conditional)
|
||||
:rate-limit {"1 per sec" [1 1000]
|
||||
"5 per min" [5 60000]}
|
||||
:rate-limit-by my-user-ip-address ; Optional rate-limit scope
|
||||
(tel/log!
|
||||
{:level :debug
|
||||
:sample 0.75 ; 75% sampling (noop 25% of the time)
|
||||
:when (my-conditional)
|
||||
:limit {"1 per sec" [1 1000]
|
||||
"5 per min" [5 60000]} ; Rate limit
|
||||
:limit-by my-user-ip-address ; Rate limit scope
|
||||
|
||||
:do (inc-my-metric!)
|
||||
:let
|
||||
|
|
@ -47,126 +49,128 @@
|
|||
|
||||
;; Message string or vector to join as string
|
||||
["Something interesting happened!" formatted])
|
||||
)
|
||||
|
||||
;; Set minimum level
|
||||
(t/set-min-level! :warn) ; For all signals
|
||||
(t/set-min-level! :log :debug) ; For `log!` signals only
|
||||
(tel/set-min-level! :warn) ; For all signals
|
||||
(tel/set-min-level! :log :debug) ; For `log!` signals specifically
|
||||
|
||||
;; Set namespace and id filters
|
||||
(t/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"})
|
||||
(t/set-id-filter! {:allow #{::my-particular-id "my-app/*"}})
|
||||
;; Set id and namespace filters
|
||||
(tel/set-id-filter! {:allow #{::my-particular-id "my-app/*"}})
|
||||
(tel/set-ns-filter! {:disallow "taoensso.*" :allow "taoensso.sente.*"})
|
||||
|
||||
;; Set minimum level for `event!` signals for particular ns pattern
|
||||
(t/set-min-level! :event "taoensso.sente.*" :warn)
|
||||
;; SLF4J signals will have their `:ns` key set to the logger's name
|
||||
;; (typically a source class)
|
||||
(tel/set-ns-filter! {:disallow "com.noisy.java.package.*"})
|
||||
|
||||
;; Use middleware to:
|
||||
;; - Transform signals
|
||||
;; - Filter signals by arb conditions (incl. data/content)
|
||||
;; Set minimum level for `log!` signals for particular ns pattern
|
||||
(tel/set-min-level! :log "taoensso.sente.*" :warn)
|
||||
|
||||
(t/set-middleware!
|
||||
;; Use transforms (xfns) to filter and/or arbitrarily modify signals
|
||||
;; by signal data/content/etc.
|
||||
|
||||
(tel/set-xfn!
|
||||
(fn [signal]
|
||||
(if (-> signal :data :skip-me?)
|
||||
nil ; Filter signal (don't handle)
|
||||
(assoc signal :passed-through-middleware? true))))
|
||||
(assoc signal :transformed? true))))
|
||||
|
||||
(t/with-signal (t/event! ::my-id {:data {:skip-me? true}})) ; => nil
|
||||
(t/with-signal (t/event! ::my-id {:data {:skip-me? false}})) ; => {...}
|
||||
(tel/with-signal (tel/log! {... :data {:skip-me? true}})) ; => nil
|
||||
(tel/with-signal (tel/log! {... :data {:skip-me? false}})) ; => {...}
|
||||
|
||||
;; See `t/help:filters` docstring for more filtering options
|
||||
|
||||
;;;; README "More examples"
|
||||
;; See `tel/help:filters` docstring for more filtering options
|
||||
|
||||
;; Add your own signal handler
|
||||
(t/add-handler! :my-handler
|
||||
(tel/add-handler! :my-handler
|
||||
(fn
|
||||
([signal] (println signal))
|
||||
([] (println "Handler has shut down"))))
|
||||
|
||||
;; Use `add-handler!` to set handler-level filtering and back-pressure
|
||||
(t/add-handler! :my-handler
|
||||
(tel/add-handler! :my-handler
|
||||
(fn
|
||||
([signal] (println signal))
|
||||
([] (println "Handler has shut down")))
|
||||
|
||||
{:async {:mode :dropping, :buffer-size 1024, :n-threads 1}
|
||||
:priority 100
|
||||
:sample-rate 0.5
|
||||
:min-level :info
|
||||
:ns-filter {:disallow "taoensso.*"}
|
||||
:rate-limit {"1 per sec" [1 1000]}
|
||||
;; See `t/help:handler-dispatch-options` for more
|
||||
:priority 100
|
||||
:sample 0.5
|
||||
:min-level :info
|
||||
:ns-filter {:disallow "taoensso.*"}
|
||||
:limit {"1 per sec" [1 1000]}
|
||||
;; See `tel/help:handler-dispatch-options` for more
|
||||
})
|
||||
|
||||
;; See current handlers
|
||||
(t/get-handlers) ; => {<handler-id> {:keys [handler-fn handler-stats_ dispatch-opts]}}
|
||||
(tel/get-handlers) ; => {<handler-id> {:keys [handler-fn handler-stats_ dispatch-opts]}}
|
||||
|
||||
;; Add console handler to print signals as human-readable text
|
||||
(t/add-handler! :my-handler
|
||||
(t/handler:console
|
||||
{:output-fn (t/format-signal-fn {})}))
|
||||
(tel/add-handler! :my-handler
|
||||
(tel/handler:console
|
||||
{:output-fn (tel/format-signal-fn {})}))
|
||||
|
||||
;; Add console handler to print signals as edn
|
||||
(t/add-handler! :my-handler
|
||||
(t/handler:console
|
||||
{:output-fn (t/pr-signal-fn {:pr-fn :edn})}))
|
||||
(tel/add-handler! :my-handler
|
||||
(tel/handler:console
|
||||
{:output-fn (tel/pr-signal-fn {:pr-fn :edn})}))
|
||||
|
||||
;; Add console handler to print signals as JSON
|
||||
;; Ref. <https://github.com/metosin/jsonista> (or any alt JSON lib)
|
||||
#?(:clj (require '[jsonista.core :as jsonista]))
|
||||
(t/add-handler! :my-handler
|
||||
(t/handler:console
|
||||
(tel/add-handler! :my-handler
|
||||
(tel/handler:console
|
||||
{:output-fn
|
||||
#?(:cljs :json ; Use js/JSON.stringify
|
||||
:clj jsonista/write-value-as-string)}))
|
||||
|
||||
;;;; Docstring examples
|
||||
|
||||
(t/with-signal (t/event! ::my-id))
|
||||
(t/with-signal (t/event! ::my-id :warn))
|
||||
(t/with-signal
|
||||
(t/event! ::my-id
|
||||
(tel/with-signal (tel/event! ::my-id))
|
||||
(tel/with-signal (tel/event! ::my-id :warn))
|
||||
(tel/with-signal
|
||||
(tel/event! ::my-id
|
||||
{:let [x "x"] ; Available to `:data` and `:msg`
|
||||
:data {:x x}
|
||||
:msg ["My msg:" x]}))
|
||||
|
||||
(t/with-signal (t/log! "My msg"))
|
||||
(t/with-signal (t/log! :warn "My msg"))
|
||||
(t/with-signal
|
||||
(t/log!
|
||||
(tel/with-signal (tel/log! "My msg"))
|
||||
(tel/with-signal (tel/log! :warn "My msg"))
|
||||
(tel/with-signal
|
||||
(tel/log!
|
||||
{:let [x "x"] ; Available to `:data` and `:msg`
|
||||
:data {:x x}}
|
||||
["My msg:" x]))
|
||||
|
||||
(t/with-signal (throw (t/error! (ex-info "MyEx" {}))))
|
||||
(t/with-signal (throw (t/error! ::my-id (ex-info "MyEx" {}))))
|
||||
(t/with-signal
|
||||
(tel/with-signal (throw (tel/error! (ex-info "MyEx" {}))))
|
||||
(tel/with-signal (throw (tel/error! ::my-id (ex-info "MyEx" {}))))
|
||||
(tel/with-signal
|
||||
(throw
|
||||
(t/error!
|
||||
(tel/error!
|
||||
{:let [x "x"] ; Available to `:data` and `:msg`
|
||||
:data {:x x}
|
||||
:msg ["My msg:" x]}
|
||||
(ex-info "MyEx" {}))))
|
||||
|
||||
(t/with-signal (t/trace! (+ 1 2)))
|
||||
(t/with-signal (t/trace! ::my-id (+ 1 2)))
|
||||
(t/with-signal
|
||||
(t/trace!
|
||||
(tel/with-signal (tel/trace! (+ 1 2)))
|
||||
(tel/with-signal (tel/trace! ::my-id (+ 1 2)))
|
||||
(tel/with-signal
|
||||
(tel/trace!
|
||||
{:let [x "x"] ; Available to `:data` and `:msg`
|
||||
:data {:x x}}
|
||||
(+ 1 2)))
|
||||
|
||||
(t/with-signal (t/spy! (+ 1 2)))
|
||||
(t/with-signal (t/spy! :debug (+ 1 2)))
|
||||
(t/with-signal
|
||||
(t/spy!
|
||||
(tel/with-signal (tel/spy! (+ 1 2)))
|
||||
(tel/with-signal (tel/spy! :debug (+ 1 2)))
|
||||
(tel/with-signal
|
||||
(tel/spy!
|
||||
{:let [x "x"] ; Available to `:data` and `:msg`
|
||||
:data {:x x}}
|
||||
(+ 1 2)))
|
||||
|
||||
(t/with-signal (t/catch->error! (/ 1 0)))
|
||||
(t/with-signal (t/catch->error! ::my-id (/ 1 0)))
|
||||
(t/with-signal
|
||||
(t/catch->error!
|
||||
(tel/with-signal (tel/catch->error! (/ 1 0)))
|
||||
(tel/with-signal (tel/catch->error! ::my-id (/ 1 0)))
|
||||
(tel/with-signal
|
||||
(tel/catch->error!
|
||||
{:let [x "x"] ; Available to `:data` and `:msg`
|
||||
:data {:x x}
|
||||
:msg ["My msg:" x]
|
||||
|
|
@ -177,25 +181,25 @@
|
|||
|
||||
;;; Filter signals
|
||||
|
||||
(t/set-min-level! :info) ; Set global minimum level
|
||||
(t/with-signal (t/event! ::my-id1 :info)) ; => {:keys [inst id ...]}
|
||||
(t/with-signal (t/event! ::my-id1 :debug)) ; => nil (signal not allowed)
|
||||
(tel/set-min-level! :info) ; Set global minimum level
|
||||
(tel/with-signal (tel/event! ::my-id1 :info)) ; => {:keys [inst id ...]}
|
||||
(tel/with-signal (tel/event! ::my-id1 :debug)) ; => nil (signal not allowed)
|
||||
|
||||
(t/with-min-level :trace ; Override global minimum level
|
||||
(t/with-signal (t/event! ::my-id1 :debug))) ; => {:keys [inst id ...]}
|
||||
(tel/with-min-level :trace ; Override global minimum level
|
||||
(tel/with-signal (tel/event! ::my-id1 :debug))) ; => {:keys [inst id ...]}
|
||||
|
||||
;; Disallow all signals in matching namespaces
|
||||
(t/set-ns-filter! {:disallow "some.nosy.namespace.*"})
|
||||
(tel/set-ns-filter! {:disallow "some.nosy.namespace.*"})
|
||||
|
||||
;;; Configuring handlers
|
||||
|
||||
;; Create a test signal
|
||||
(def my-signal
|
||||
(t/with-signal
|
||||
(t/log! {:id ::my-id, :data {:x1 :x2}} "My message")))
|
||||
(tel/with-signal
|
||||
(tel/log! {:id ::my-id, :data {:x1 :x2}} "My message")))
|
||||
|
||||
;; Create console handler with default opts (writes formatted string)
|
||||
(def my-handler (t/handler:console {}))
|
||||
(def my-handler (tel/handler:console {}))
|
||||
|
||||
;; Test handler, remember it's just a (fn [signal])
|
||||
(my-handler my-signal) ; %>
|
||||
|
|
@ -204,8 +208,8 @@
|
|||
|
||||
;; Create console handler which writes signals as edn
|
||||
(def my-handler
|
||||
(t/handler:console
|
||||
{:output-fn (t/pr-signal-fn {:pr-fn :edn})}))
|
||||
(tel/handler:console
|
||||
{:output-fn (tel/pr-signal-fn {:pr-fn :edn})}))
|
||||
|
||||
(my-handler my-signal) ; %>
|
||||
;; {:inst #inst "2024-04-11T10:54:57.202869Z", :msg_ "My message", :ns "examples", ...}
|
||||
|
|
@ -214,9 +218,9 @@
|
|||
;; Ref. <https://github.com/metosin/jsonista> (or any alt JSON lib)
|
||||
#?(:clj (require '[jsonista.core :as jsonista]))
|
||||
(def my-handler
|
||||
(t/handler:console
|
||||
(tel/handler:console
|
||||
{:output-fn
|
||||
(t/pr-signal-fn
|
||||
(tel/pr-signal-fn
|
||||
{:pr-fn
|
||||
#?(:cljs :json ; Use js/JSON.stringify
|
||||
:clj jsonista/write-value-as-string)})}))
|
||||
|
|
@ -225,19 +229,20 @@
|
|||
;; {"inst":"2024-04-11T10:54:57.202869Z","msg_":"My message","ns":"examples", ...}
|
||||
|
||||
;; Deregister the default console handler
|
||||
(t/remove-handler! :default/console)
|
||||
(tel/remove-handler! :defaultel/console)
|
||||
|
||||
;; Register our custom console handler
|
||||
(t/add-handler! :my-handler my-handler
|
||||
(tel/add-handler! :my-handler my-handler
|
||||
;; Lots of options here for filtering, etc.
|
||||
;; See `help:handler-dispatch-options` docstring!
|
||||
{})
|
||||
|
||||
;; NB make sure to always stop handlers at the end of your
|
||||
;; `-main` or shutdown procedure
|
||||
(t/call-on-shutdown! t/stop-handlers!)
|
||||
(tel/call-on-shutdown!
|
||||
(fn [] (tel/stop-handlers!)))
|
||||
|
||||
;; See `t/help:handlers` docstring for more
|
||||
;; See `tel/help:handlers` docstring for more
|
||||
|
||||
;;; Writing handlers
|
||||
|
||||
|
|
@ -272,17 +277,17 @@
|
|||
;; Do option validation and other prep here, i.e. try to keep
|
||||
;; expensive work outside handler function when possible!
|
||||
|
||||
(let [handler-fn ; Fn of exactly 2 arities
|
||||
(let [handler-fn ; Fn of exactly 2 arities (1 and 0)
|
||||
(fn a-handler:my-fancy-handler ; Note fn naming convention
|
||||
|
||||
([] ; Arity-0 called when stopping the handler
|
||||
;; Flush buffers, close files, etc. May just noop.
|
||||
;; Return value is ignored.
|
||||
)
|
||||
|
||||
([signal] ; Arity-1 called when handling a signal
|
||||
;; Do something useful with the given signal (write to
|
||||
;; console/file/queue/db, etc.). Return value is ignored.
|
||||
)
|
||||
|
||||
([] ; Arity-0 called when stopping the handler
|
||||
;; Flush buffers, close files, etc. May just noop.
|
||||
;; Return value is ignored.
|
||||
))]
|
||||
|
||||
;; (Advanced, optional) You can use metadata to provide default
|
||||
|
|
@ -291,7 +296,7 @@
|
|||
(with-meta handler-fn
|
||||
{:dispatch-opts
|
||||
{:min-level :info
|
||||
:rate-limit
|
||||
:limit
|
||||
[[1 1000] ; Max 1 signal per second
|
||||
[10 60000] ; Max 10 signals per minute
|
||||
]}}))))
|
||||
|
|
@ -299,18 +304,18 @@
|
|||
;;; Message building
|
||||
|
||||
;; A fixed message (string arg)
|
||||
(t/log! "A fixed message") ; %> {:msg "A fixed message"}
|
||||
(tel/log! "A fixed message") ; %> {:msg "A fixed message"}
|
||||
|
||||
;; A joined message (vector arg)
|
||||
(let [user-arg "Bob"]
|
||||
(t/log! ["User" (str "`" user-arg "`") "just logged in!"]))
|
||||
(tel/log! ["User" (str "`" user-arg "`") "just logged in!"]))
|
||||
;; %> {:msg_ "User `Bob` just logged in!` ...}
|
||||
|
||||
;; With arg prep
|
||||
(let [user-arg "Bob"
|
||||
usd-balance-str "22.4821"]
|
||||
|
||||
(t/log!
|
||||
(tel/log!
|
||||
{:let
|
||||
[username (clojure.string/upper-case user-arg)
|
||||
usd-balance (parse-double usd-balance-str)]
|
||||
|
|
@ -323,54 +328,54 @@
|
|||
|
||||
;; %> {:msg "User BOB has balance: $22" ...}
|
||||
|
||||
(t/log! (str "This message " "was built " "by `str`"))
|
||||
(tel/log! (str "This message " "was built " "by `str`"))
|
||||
;; %> {:msg "This message was built by `str`"}
|
||||
|
||||
(t/log! (enc/format "This message was built by `%s`" "format"))
|
||||
(tel/log! (enc/format "This message was built by `%s`" "format"))
|
||||
;; %> {:msg "This message was built by `format`"}
|
||||
|
||||
;;; App-level kvs
|
||||
|
||||
(t/with-signal
|
||||
(t/event! ::my-id
|
||||
{:my-middleware-data "foo"
|
||||
:my-handler-data "bar"}))
|
||||
(tel/with-signal
|
||||
(tel/event! ::my-id
|
||||
{:my-data-for-xfn "foo"
|
||||
:my-data-for-handler "bar"}))
|
||||
|
||||
;; %>
|
||||
;; {;; App-level kvs included inline (assoc'd to signal root)
|
||||
;; :my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"
|
||||
;; :my-data-for-xfn "foo"
|
||||
;; :my-data-for-handler "bar"
|
||||
;; :kvs ; And also collected together under ":kvs" key
|
||||
;; {:my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"}
|
||||
;; {:my-data-for-xfn "foo"
|
||||
;; :my-data-for-handler "bar"}
|
||||
;; ... }
|
||||
|
||||
;;;; Misc extra examples
|
||||
|
||||
(t/log! {:id ::my-id, :data {:x1 :x2}} ["My 2-part" "message"]) ; %>
|
||||
(tel/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
|
||||
;; data: {:x1 :x2}
|
||||
|
||||
;; `:let` bindings are available to `:data` and message, but only paid
|
||||
;; for when allowed by minimum level and other filtering criteria
|
||||
(t/log!
|
||||
(tel/log!
|
||||
{:level :info
|
||||
:let [expensive (reduce * (range 1 12))] ; 12 factorial
|
||||
:data {:my-metric expensive}}
|
||||
["Message with metric:" expensive])
|
||||
|
||||
;; With sampling 50% and 1/sec rate limiting
|
||||
(t/log!
|
||||
{:sample-rate 0.5
|
||||
:rate-limit {"1 per sec" [1 1000]}}
|
||||
(tel/log!
|
||||
{:sample 0.5
|
||||
:limit {"1 per sec" [1 1000]}}
|
||||
"This signal will be sampled and rate limited")
|
||||
|
||||
;; Several signal creators are available for convenience.
|
||||
;; All offer the same options, but each has an API optimized
|
||||
;; for a particular use case:
|
||||
|
||||
(t/log! {:level :info, :id ::my-id} "Hi!") ; [msg] or [level-or-opts msg]
|
||||
(t/event! ::my-id {:level :info, :msg "Hi!"}) ; [id] or [id level-or-opts]
|
||||
(t/signal! {:level :info, :id ::my-id, :msg "Hi!"}) ; [opts]
|
||||
(tel/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]
|
||||
(tel/signal! {:level :info, :id ::my-id, :msg "Hi!"}) ; [opts]
|
||||
|
||||
)
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 339 KiB After Width: | Height: | Size: 340 KiB |
Binary file not shown.
|
|
@ -1,6 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd projects/main; lein install; cd ../..;
|
||||
cd projects/api; lein install; cd ../..;
|
||||
cd projects/slf4j; lein install; cd ../..;
|
||||
cd main; lein install; cd ..;
|
||||
cd slf4j; lein install; cd ..;
|
||||
|
||||
|
|
|
|||
0
projects/main/.gitignore → main/.gitignore
vendored
0
projects/main/.gitignore → main/.gitignore
vendored
|
|
@ -1,6 +1,6 @@
|
|||
(defproject com.taoensso/telemere "1.0.0-RC2"
|
||||
(defproject com.taoensso/telemere "1.2.1"
|
||||
:author "Peter Taoussanis <https://www.taoensso.com>"
|
||||
:description "Structured telemetry library for Clojure/Script"
|
||||
:description "Structured logs and telemetry for Clojure/Script"
|
||||
:url "https://www.taoensso.com/telemere"
|
||||
|
||||
:license
|
||||
|
|
@ -10,15 +10,15 @@
|
|||
:scm {:name "git" :url "https://github.com/taoensso/telemere"}
|
||||
|
||||
:dependencies
|
||||
[[com.taoensso/encore "3.132.0"]]
|
||||
[[com.taoensso/encore "3.159.0"]]
|
||||
|
||||
:test-paths ["test" #_"src"]
|
||||
|
||||
:profiles
|
||||
{;; :default [:base :system :user :provided :dev]
|
||||
:provided {:dependencies [[org.clojure/clojurescript "1.11.132"]
|
||||
:provided {:dependencies [[org.clojure/clojurescript "1.12.134"]
|
||||
[org.clojure/clojure "1.11.4"]]}
|
||||
:c1.12 {:dependencies [[org.clojure/clojure "1.12.0"]]}
|
||||
:c1.12 {:dependencies [[org.clojure/clojure "1.12.3"]]}
|
||||
:c1.11 {:dependencies [[org.clojure/clojure "1.11.4"]]}
|
||||
:c1.10 {:dependencies [[org.clojure/clojure "1.10.3"]]}
|
||||
|
||||
|
|
@ -46,33 +46,28 @@
|
|||
*unchecked-math* false #_:warn-on-boxed}
|
||||
|
||||
:dependencies
|
||||
[[org.clojure/core.async "1.7.701"]
|
||||
[org.clojure/test.check "1.1.1"]
|
||||
[[org.clojure/core.async "1.8.741"]
|
||||
[org.clojure/test.check "1.1.2"]
|
||||
[org.clojure/tools.logging "1.3.0"]
|
||||
[org.slf4j/slf4j-api "2.0.16"]
|
||||
[com.taoensso/telemere-slf4j "1.0.0-RC2"]
|
||||
[org.slf4j/slf4j-api "2.0.17"]
|
||||
[com.taoensso/telemere-slf4j "1.2.1"]
|
||||
#_[org.slf4j/slf4j-simple "2.0.16"]
|
||||
#_[org.slf4j/slf4j-nop "2.0.16"]
|
||||
#_[io.github.paintparty/bling "0.4.2"]
|
||||
|
||||
;;; For optional handlers
|
||||
[io.opentelemetry/opentelemetry-api "1.45.0"]
|
||||
[io.opentelemetry/opentelemetry-sdk-extension-autoconfigure "1.45.0"]
|
||||
[io.opentelemetry/opentelemetry-exporter-otlp "1.45.0"]
|
||||
[io.opentelemetry/opentelemetry-api "1.57.0"]
|
||||
[io.opentelemetry/opentelemetry-sdk-extension-autoconfigure "1.57.0"]
|
||||
[io.opentelemetry/opentelemetry-exporter-otlp "1.57.0"]
|
||||
#_[io.opentelemetry/opentelemetry-exporters-jaeger "0.9.1"]
|
||||
[metosin/jsonista "0.3.13"]
|
||||
[com.draines/postal "2.0.5"]
|
||||
[org.julienxx/clj-slack "0.8.3"]]
|
||||
|
||||
:plugins
|
||||
[[lein-pprint "1.3.2"]
|
||||
[lein-ancient "0.7.0"]
|
||||
[lein-cljsbuild "1.1.8"]
|
||||
[com.taoensso.forks/lein-codox "0.10.11"]]
|
||||
|
||||
:codox
|
||||
{:language #{:clojure :clojurescript}
|
||||
:base-language :clojure}}}
|
||||
[[lein-pprint "1.3.2"]
|
||||
[lein-ancient "0.7.0"]
|
||||
[lein-cljsbuild "1.1.8"]]}}
|
||||
|
||||
:cljsbuild
|
||||
{:test-commands {"node" ["node" "target/test.js"]}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
ALWAYS (unconditionally) executes given `run` form and:
|
||||
If `run` form succeeds: returns the form's result.
|
||||
If `run` form throws ANYTHING:
|
||||
Calls `error!` with the thrown error and given signal options [2], then
|
||||
either returns given (:catch-val opts), or rethrows.
|
||||
|
||||
Default kind: `:error`
|
||||
Default level: `:error`
|
||||
Returns:
|
||||
- If given `run` form succeeds: returns the form's result.
|
||||
- If given `run` form throws ANYTHING:
|
||||
Calls `error!` with the thrown error and given signal options [2], then
|
||||
either returns given (:catch-val opts), or rethrows.
|
||||
|
||||
Just a convenience util. For more flexibility use your own `try/catch`.
|
||||
See `taoensso.encore/try*` for easily catching cross-platform errors.
|
||||
60
main/resources/docs/environmental-config.txt
Normal file
60
main/resources/docs/environmental-config.txt
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
Telemere supports extensive environmental config via JVM properties,
|
||||
environment variables, or classpath resources.
|
||||
|
||||
Environmental filter config includes:
|
||||
|
||||
1. Minimum level (see signal `:level`):
|
||||
a. JVM property: `taoensso.telemere.rt-min-level`
|
||||
b. Env variable: `TAOENSSO_TELEMERE_RT_MIN_LEVEL`
|
||||
c. Classpath resource: `taoensso.telemere.rt-min-level`
|
||||
|
||||
2. Namespace filter (see signal `:ns`):
|
||||
a. JVM property: `taoensso.telemere.rt-ns-filter`
|
||||
b. Env variable: `TAOENSSO_TELEMERE_RT_NS_FILTER`
|
||||
c. Classpath resource: `taoensso.telemere.rt-ns-filter`
|
||||
|
||||
3. Id filter (see signal `:id`):
|
||||
a. JVM property: `taoensso.telemere.rt-id-filter`
|
||||
b. Env variable: `TAOENSSO_TELEMERE_RT_ID_FILTER`
|
||||
c. Classpath resource: `taoensso.telemere.rt-id-filter`
|
||||
|
||||
4. Kind filter (signal `:kind`):
|
||||
a. JVM property: `taoensso.telemere.rt-kind-filter`
|
||||
b. Env variable: `TAOENSSO_TELEMERE_RT_KIND_FILTER`
|
||||
c. Classpath resource: `taoensso.telemere.rt-kind-filter`
|
||||
|
||||
Config values are parsed as edn, examples:
|
||||
|
||||
`taoensso.telemere.rt-min-level` => ":info"
|
||||
`TAOENSSO_TELEMERE_RT_NS_FILTER` => "{:disallow \"taoensso.*\"}"
|
||||
`taoensso.telemere.rt-id-filter.cljs` => "#{:my-id1 :my-id2}"
|
||||
`TAOENSSO_TELEMERE_RT_KIND_FILTER_CLJ` => "nil"
|
||||
|
||||
Runtime vs compile-time filters
|
||||
|
||||
The above filters (1..4) all apply at RUNTIME ("rt").
|
||||
This is typically what you want, since it allows you to freely adjust filtering
|
||||
(making it less OR MORE permissive) through later API calls like `set-min-level!`.
|
||||
|
||||
As an advanced option, you can instead/additionally ELIDE (entirely omit) filtered
|
||||
callsites at COMPILE-TIME ("ct") by replacing "rt"->"ct" / "RT"->"CT" in the config
|
||||
ids above. Compile-time filters CANNOT be made MORE permissive at runtime.
|
||||
|
||||
Tips:
|
||||
|
||||
- The above config ids will affect both Clj AND Cljs.
|
||||
For platform-specific filters, use
|
||||
".clj" / "_CLJ" or
|
||||
".cljs" / "_CLJS" suffixes instead.
|
||||
e.g. "taoensso.telemere.rt-min-level.cljs".
|
||||
|
||||
- To get the right edn syntax, first set your runtime filters using the
|
||||
standard utils (`set-min-level!`, etc.). Then call `get-filters` and
|
||||
serialize the relevant parts to edn with `pr-str`.
|
||||
|
||||
- All environmental config uses `get-env` underneath.
|
||||
See the `get-env` docstring for more/advanced details.
|
||||
|
||||
- Classpath resources are files accessible on your project's
|
||||
classpath. This usually includes files in your project's
|
||||
`resources/` dir.
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
"Error" signal creator, emphasizing (optional id) + error (Exception, etc.).
|
||||
ALWAYS (unconditionally) returns the given error, so can conveniently be wrapped
|
||||
by `throw`: (throw (error! (ex-info ...)), etc.
|
||||
|
||||
Default kind: `:error`
|
||||
Default level: `:error`
|
||||
Returns:
|
||||
ALWAYS (unconditionally) returns the given error, so can conveniently be
|
||||
wrapped by `throw`: (throw (error! (ex-info ...)), etc.
|
||||
|
||||
Examples:
|
||||
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
"Event" signal creator, emphasizing id + (optional level).
|
||||
Returns true iff signal was created (allowed by filtering).
|
||||
|
||||
Default kind: `:event`
|
||||
Default level: `:info`
|
||||
Returns:
|
||||
- For `event!` variant: nil, unconditionally.
|
||||
- For `event!?` variant: true iff signal was created (allowed by filtering).
|
||||
|
||||
When filtering conditions are met [4], creates a Telemere signal [3] and
|
||||
dispatches it to registered handlers for processing (e.g. writing to
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
"Log" signal creator, emphasizing (optional level) + message.
|
||||
Returns true iff signal was created (allowed by filtering).
|
||||
|
||||
Default kind: `:log`
|
||||
Default level: `:info`
|
||||
Returns:
|
||||
- For `log!` variant: nil, unconditionally.
|
||||
- For `log!?` variant: true iff signal was created (allowed by filtering).
|
||||
|
||||
When filtering conditions are met [4], creates a Telemere signal [3] and
|
||||
dispatches it to registered handlers for processing (e.g. writing to
|
||||
|
|
@ -1,21 +1,22 @@
|
|||
Low-level "generic" signal creator for creating signals of any "kind".
|
||||
Takes a single map of options [2] with compile-time keys.
|
||||
|
||||
Return value depends on options:
|
||||
- If given `:run` form: unconditionally returns run value, or rethrows run error.
|
||||
- Otherwise: returns true iff signal was created (allowed by filtering).
|
||||
|
||||
Default kind: `:generic` (feel free to change!)
|
||||
Default level: `:info`
|
||||
Returns:
|
||||
- If given `:run` form: unconditionally returns run value, or rethrows run error.
|
||||
- Otherwise: returns true iff signal was created (allowed by filtering).
|
||||
|
||||
When filtering conditions are met [4], creates a Telemere signal [3] and
|
||||
dispatches it to registered handlers for processing (e.g. writing to
|
||||
console/file/queue/db, etc.).
|
||||
|
||||
Generic signals are fairly low-level and useful mostly for library authors or
|
||||
advanced users writing their own wrapper macros. Regular users will typically
|
||||
prefer one of the higher-level signal creators optimized for ease-of-use in
|
||||
common cases [1].
|
||||
advanced users writing their own wrapper macros. NB see `keep-callsite` for
|
||||
preserving callsite coords when wrapping Telemere macros like `signal!`.
|
||||
|
||||
Regular users will typically prefer one of the higher-level signal creators
|
||||
optimized for ease-of-use in common cases [1].
|
||||
|
||||
Tips:
|
||||
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
Signals are maps with {:keys [inst id ns level data msg_ ...]}, though they
|
||||
can be modified by signal and/or handler middleware.
|
||||
Telemere signals are maps with {:keys [inst id ns level data msg_ ...]},
|
||||
though they can be modified by call and/or handler transform (xfns).
|
||||
|
||||
Default signal keys:
|
||||
|
||||
`:schema` ------ Int version of signal schema (current: 1)
|
||||
`:inst` -------- Platform instant [1] when signal was created
|
||||
`:level` ------- Signal level ∈ #{<int> :trace :debug :info :warn :error :fatal :report ...}
|
||||
`: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> ...}
|
||||
`: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`)
|
||||
`: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`)
|
||||
`:uid` --------- Signal instance ?id (usu. string) (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!
|
||||
`:data` -------- Arb app-level data ?val (usu. a map) given to signal creator
|
||||
|
|
@ -19,24 +22,18 @@ Default signal keys:
|
|||
`:run-nsecs` --- ?int nanosecs runtime of `:run` ?form
|
||||
`:end-inst` ---- Platform ?instant [1] when `:run` ?form completed
|
||||
|
||||
`:ctx` --------- ?val of `*ctx*` (arb app-level state) when signal was created
|
||||
`:parent` ------ ?{:keys [id uid]} of parent signal, present in nested signals when tracing
|
||||
`:root` -------- ?{:keys [id uid]} of root signal, present in nested signals when tracing
|
||||
|
||||
`:location` ---- ?{:keys [ns file line column]} signal creator callsite
|
||||
`:ns` ---------- ?str namespace of signal creator callsite, same as (:ns location)
|
||||
`:line` -------- ?int line of signal creator callsite, same as (:line location)
|
||||
`:column` ------ ?int column of signal creator callsite, same as (:column location)
|
||||
`:file` -------- ?str filename of signal creator callsite, same as (:file location)
|
||||
`:ctx` --------- ?val of `*ctx*` (arb app-level state) when signal was created
|
||||
|
||||
`:host` -------- (Clj only) {:keys [name ip]} info for network host
|
||||
`:thread` ------ (Clj only) {:keys [name id group]} info for thread that created signal
|
||||
|
||||
`:sample-rate` - ?rate ∈ℝ[0,1] for combined signal AND handler sampling (0.75 => allow 75% of signals, nil => allow all)
|
||||
`:sample` ------ Sample ?rate ∈ℝ[0,1] for combined call 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
|
||||
in handler output, so a great way to provide custom data/opts for use
|
||||
(only) by custom middleware/handlers.
|
||||
(only) by custom transforms/handlers.
|
||||
|
||||
If anything is unclear, please ping me (@ptaoussanis) so that I can improve these docs!
|
||||
|
||||
|
|
@ -11,16 +11,17 @@ various keys:
|
|||
- All signal creators offer the same options [2], and
|
||||
- All signal kinds can contain the same content [3]
|
||||
|
||||
Creators vary only in in their default options and call APIs (expected args
|
||||
and return values), making them more/less convenient for certain use cases:
|
||||
Creators vary only in in their default `:kind` value and call APIs (expected
|
||||
args and return values), making them more/less convenient for certain use cases:
|
||||
|
||||
`signal!` ------- opts => allowed? / unconditional run result (value or throw)
|
||||
`event!` -------- id + ?level => allowed?
|
||||
`log!` ---------- ?level + msg => allowed?
|
||||
`trace!` -------- ?id + run => unconditional run result (value or throw)
|
||||
`spy!` ---------- ?level + run => unconditional run result (value or throw)
|
||||
`error!` -------- ?id + error => unconditional given error
|
||||
`catch->error!` - ?id + run => unconditional run value or ?catch-val
|
||||
`log!` ------------- ?level + msg => nil
|
||||
`event!` ----------- id + ?level => nil
|
||||
`trace!` ----------- ?id + run => run result (value or throw)
|
||||
`spy!` ------------- ?level + run => run result (value or throw)
|
||||
`error!` ----------- ?id + error => given error
|
||||
`catch->error!` ---- ?id + run => run value or ?catch-val
|
||||
`uncaught->error!` - ?id => nil
|
||||
`signal!` ---------- opts => allowed? / run result (value or throw)
|
||||
|
||||
- `log!` and `event!` are both good default/general-purpose signal creators.
|
||||
- `log!` emphasizes messages, while `event!` emphasizes ids.
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
Signal options are provided as a map with COMPILE-TIME keys.
|
||||
All options are available for all signal creators:
|
||||
All options are available for all signal creator calls:
|
||||
|
||||
`:inst` -------- Platform instant [1] when signal was created, ∈ #{nil :auto <[1]>}
|
||||
`:level` ------- Signal level ∈ #{<int> :trace :debug :info :warn :error :fatal :report ...}
|
||||
|
|
@ -9,33 +9,41 @@ All options are available for all signal creators:
|
|||
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
|
||||
`:data` -------- Arb app-level ?data to incl. in signal: usu. a map
|
||||
`:data` -------- Arb app-level ?data to incl. in signal: usu. a map, LAZY! [3]
|
||||
`:error` ------- Arb app-level ?error to incl. in signal: platform error [2]
|
||||
|
||||
`:run` --------- ?form to execute UNCONDITIONALLY; will incl. `:run-val` in signal
|
||||
`:do` ---------- ?form to execute conditionally (iff signal allowed), before establishing `:let` ?binding
|
||||
`:let` --------- ?bindings to establish conditionally (iff signal allowed), BEFORE evaluating `:data` and `:msg` (useful!)
|
||||
`:do` ---------- ?form to execute conditionally (iff signal allowed) and LAZILY [3], before establishing `:let` ?binding
|
||||
`:let` --------- ?bindings to establish conditionally (iff signal allowed) and LAZILY [3], BEFORE evaluating `:data` and `:msg` (useful!)
|
||||
|
||||
`: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
|
||||
`:location` ---- Custom ?{:keys [ns line column file]} to override auto signal creator callsite location
|
||||
`:ctx` --------- Custom ?val to override auto (dynamic `*ctx*`) in signal, as per `with-ctx`
|
||||
`:ctx+` -------- Custom ?val to update auto (dynamic `*ctx*`) in signal, as per `with-ctx+`
|
||||
|
||||
`:elidable?` --- Should signal be subject to compile-time elision? (Default: true)
|
||||
`:sample-rate` - ?rate ∈ℝ[0,1] for signal sampling (0.75 => allow 75% of signals, nil => allow all)
|
||||
`:when` -------- Arb ?form; when present, form must return truthy to allow signal
|
||||
`:rate-limit` -- ?spec as given to `taoensso.telemere/rate-limiter`, see its docstring for details
|
||||
`:rate-limit-by` When present, rate limits will be enforced independently for each id (any Clojure value!)
|
||||
`:middleware` -- Optional (fn [signal]) => ?modified-signal to apply when signal is created, as per `with-middleware`
|
||||
`:middleware+` - Optional (fn [signal]) => ?modified-signal to apply when signal is created, as per `with-middleware+`
|
||||
`:ns` ---------- Custom ?str namespace 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)
|
||||
`:allow?` ------ Custom override for usual runtime filtering (true => ALWAYS create signal)
|
||||
`: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
|
||||
`:limit` ------- Rate limit ?spec 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!)
|
||||
`:xfn` --------- Optional transform (fn [signal]) => ?modified-signal to apply when signal is created, as per `with-xfn`
|
||||
`:xfn+` -------- Optional extra transform (fn [signal]) => ?modified-signal to apply when signal is created, as per `with-xfn+`
|
||||
|
||||
<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
|
||||
(only) by custom middleware/handlers.
|
||||
(only) by custom transforms/handlers. LAZY! [3]
|
||||
|
||||
If anything is unclear, please ping me (@ptaoussanis) so that I can improve these docs!
|
||||
|
||||
[1] `java.time.Instant` or `js/Date`
|
||||
[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!
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
"Spy" signal creator, emphasizing (optional level) + form to run.
|
||||
ALWAYS (unconditionally) returns run value, or rethrows run error.
|
||||
|
||||
Default kind: `:spy`
|
||||
Default level: `:info`
|
||||
Returns: ALWAYS (unconditionally) returns run value, or rethrows run error.
|
||||
|
||||
When filtering conditions are met [4], creates a Telemere signal [3] and
|
||||
dispatches it to registered handlers for processing (e.g. writing to
|
||||
|
|
@ -31,10 +31,10 @@ Limitations:
|
|||
|
||||
Examples:
|
||||
|
||||
(spy! (+ 1 2)) ; %> {:kind :trace, :level :info, :run-form '(+ 1 2),
|
||||
; :run-val 3, :run-nsecs <int>, :parent {:keys [id uid]}
|
||||
; :msg "(+ 1 2) => 3" ...}
|
||||
(spy! ::my-id (+ 1 2)) ; %> {... :id ::my-id ...}
|
||||
(spy! (+ 1 2)) ; %> {:kind :trace, :level :info, :run-form '(+ 1 2),
|
||||
; :run-val 3, :run-nsecs <int>, :parent {:keys [id uid]}
|
||||
; :msg "(+ 1 2) => 3" ...}
|
||||
(spy! :debug (+ 1 2)) ; %> {... :level :debug ...}
|
||||
(spy!
|
||||
{:let [x "x"] ; Available to `:data` and `:msg`
|
||||
:data {:x x}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
"Trace" signal creator, emphasizing (optional id) + form to run.
|
||||
ALWAYS (unconditionally) returns run value, or rethrows run error.
|
||||
|
||||
Default kind: `:trace`
|
||||
Default level: `:info` (intentionally NOT `:trace`!)
|
||||
Returns: ALWAYS (unconditionally) returns run value, or rethrows run error.
|
||||
|
||||
When filtering conditions are met [4], creates a Telemere signal [3] and
|
||||
dispatches it to registered handlers for processing (e.g. writing to
|
||||
|
|
@ -5,9 +5,10 @@
|
|||
<https://www.taoensso.com/telemere>"
|
||||
|
||||
{:author "Peter Taoussanis (@ptaoussanis)"}
|
||||
(:refer-clojure :exclude [binding newline])
|
||||
(:refer-clojure :exclude [newline])
|
||||
(:require
|
||||
[taoensso.encore :as enc :refer [binding have have?]]
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.encore.signals :as sigs]
|
||||
[taoensso.telemere.impl :as impl]
|
||||
[taoensso.telemere.utils :as utils]
|
||||
|
|
@ -18,53 +19,73 @@
|
|||
#?(:cljs
|
||||
(:require-macros
|
||||
[taoensso.telemere :refer
|
||||
[with-signal with-signals
|
||||
[with-signal with-signals signal-allowed?
|
||||
signal! event! log! trace! spy! catch->error!
|
||||
|
||||
;; Via `sigs/def-api`
|
||||
without-filters with-kind-filter with-ns-filter with-id-filter
|
||||
with-min-level with-handler with-handler+
|
||||
with-ctx with-ctx+ with-middleware with-middleware+]])))
|
||||
with-ctx with-ctx+ with-xfn with-xfn+]])))
|
||||
|
||||
(comment
|
||||
(remove-ns (symbol (str *ns*)))
|
||||
(:api (enc/interns-overview)))
|
||||
|
||||
(enc/assert-min-encore-version [3 132 0])
|
||||
(enc/assert-min-encore-version [3 159 0])
|
||||
|
||||
;;;; 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
|
||||
{:sf-arity 4
|
||||
:ct-sig-filter impl/ct-sig-filter
|
||||
:*rt-sig-filter* impl/*rt-sig-filter*
|
||||
:*sig-handlers* impl/*sig-handlers*
|
||||
:lib-dispatch-opts
|
||||
(assoc sigs/default-handler-dispatch-opts
|
||||
:convey-bindings? false ; Handled manually
|
||||
)})
|
||||
:ct-call-filter impl/ct-call-filter
|
||||
:*rt-call-filter* impl/*rt-call-filter*
|
||||
:*sig-handlers* impl/*sig-handlers*
|
||||
:lib-dispatch-opts default-handler-dispatch-opts})
|
||||
|
||||
;;;; Aliases
|
||||
|
||||
(enc/defaliases
|
||||
;; Encore
|
||||
#?(:clj enc/set-var-root!)
|
||||
#?(:clj enc/update-var-root!)
|
||||
#?(:clj ^:no-doc enc/set-var-root!)
|
||||
#?(:clj ^:no-doc enc/update-var-root!)
|
||||
#?(:clj enc/get-env)
|
||||
#?(:clj enc/call-on-shutdown!)
|
||||
enc/chance
|
||||
^:no-doc enc/chance
|
||||
enc/rate-limiter
|
||||
enc/newline
|
||||
enc/comp-middleware
|
||||
sigs/default-handler-dispatch-opts
|
||||
^:no-doc enc/newline
|
||||
sigs/comp-xfn
|
||||
#?(:clj truss/keep-callsite)
|
||||
|
||||
;; Impl
|
||||
impl/msg-splice
|
||||
impl/msg-skip
|
||||
#?(:clj impl/with-signal)
|
||||
#?(:clj impl/with-signals)
|
||||
#?(:clj impl/signal!)
|
||||
#?(:clj impl/signal-allowed?)
|
||||
|
||||
;; Utils
|
||||
utils/clean-signal-fn
|
||||
|
|
@ -95,9 +116,9 @@
|
|||
more entropy and so are usually longer (e.g. 32 vs 16 hex chars).
|
||||
|
||||
Override default by setting one of the following:
|
||||
JVM property: `taoensso.telemere/uid-fn`
|
||||
Env variable: `TAOENSSO_TELEMERE_UID_FN`
|
||||
Classpath resource: `taoensso.telemere/uid-fn`
|
||||
1. JVM property: `taoensso.telemere.uid-kind`
|
||||
2. Env variable: `TAOENSSO_TELEMERE_UID_KIND`
|
||||
3. Classpath resource: `taoensso.telemere.uid-kind`
|
||||
|
||||
Possible (compile-time) values include:
|
||||
`:uuid` - UUID string (Cljs) or `java.util.UUID` (Clj)
|
||||
|
|
@ -133,9 +154,9 @@
|
|||
2. Telemere and OpenTelemetry WILL recognize each other's spans.
|
||||
|
||||
Override default by setting one of the following to \"true\" or \"false\":
|
||||
JVM property: `taoensso.telemere.otel-tracing`
|
||||
Env variable: `TAOENSSO_TELEMERE_otel-tracing`
|
||||
Classpath resource: `taoensso.telemere.otel-tracing`
|
||||
1. JVM property: `taoensso.telemere.otel-tracing`
|
||||
2. Env variable: `TAOENSSO_TELEMERE_OTEL_TRACING`
|
||||
3. Classpath resource: `taoensso.telemere.otel-tracing`
|
||||
|
||||
See also: `otel-default-providers_`, `*otel-tracer*`,
|
||||
`taoensso.telemere.open-telemere/handler:open-telemetry`.
|
||||
|
|
@ -166,7 +187,7 @@
|
|||
;; Via SDK autoconfiguration extension (when available)
|
||||
(enc/compile-when
|
||||
io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk
|
||||
(enc/catching :common
|
||||
(truss/catching :common
|
||||
(let [builder (io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk/builder)
|
||||
sdk (.getOpenTelemetrySdk (.build builder))]
|
||||
{:logger-provider (.getLogsBridge sdk)
|
||||
|
|
@ -198,101 +219,162 @@
|
|||
(comment (enc/qb 1e6 (force *otel-tracer*))) ; 51.23
|
||||
|
||||
;;;; Signal creators
|
||||
;; - signal! ---------- opts => allowed? / unconditional run result (value or throw)
|
||||
;; - event! ----------- id + ?level => allowed?
|
||||
;; - log! ------------- ?level + msg => allowed?
|
||||
;; - trace! ----------- ?id + run => unconditional run result (value or throw)
|
||||
;; - spy! ------------- ?level + run => unconditional run result (value or throw)
|
||||
;; - error! ----------- ?id + error => unconditional given error
|
||||
;; - catch->error! ---- ?id + run => unconditional run value or ?catch-val
|
||||
;; - log! ------------- ?level + msg => nil
|
||||
;; - event! ----------- id + ?level => nil
|
||||
;; - trace! ----------- ?id + run => run result (value or throw)
|
||||
;; - spy! ------------- ?level + run => run result (value or throw)
|
||||
;; - error! ----------- ?id + error => given error
|
||||
;; - catch->error! ---- ?id + run => run value or ?catch-val
|
||||
;; - uncaught->error! - ?id => nil
|
||||
;; - signal! ---------- opts => allowed? / run result (value or throw)
|
||||
|
||||
#?(:clj
|
||||
(defn- merge-or-assoc-opts [m &form &env k v]
|
||||
(let [m (assoc m :location* (enc/get-source &form &env))]
|
||||
(defn- args->opts [args]
|
||||
(case (count args)
|
||||
0 {}
|
||||
1 (first args)
|
||||
(apply hash-map args))))
|
||||
|
||||
#?(:clj
|
||||
(defmacro signal-allowed?
|
||||
"Returns true iff signal with given opts would meet filtering conditions:
|
||||
(when (signal-allowed? {:level :warn, <...>}) (my-custom-code))
|
||||
|
||||
Allows you to use Telemere's rich filtering system for conditionally
|
||||
executing arbitrary code. Also handy for batching multiple signals
|
||||
under a single set of conditions (incl. sampling, rate limiting, etc.):
|
||||
|
||||
;; Logs exactly 2 or 0 messages (never 1):
|
||||
(when (signal-allowed? {:level :info, :sample 0.5})
|
||||
(log! {:allow? true} \"Message 1\")
|
||||
(log! {:allow? true} \"Message 2\"))"
|
||||
|
||||
;; Used also for interop (tools.logging, SLF4J), etc.
|
||||
{:arglists (impl/arglists :signal-allowed?)}
|
||||
[& args]
|
||||
(truss/keep-callsite
|
||||
`(impl/signal-allowed? ~(args->opts args)))))
|
||||
|
||||
(comment (macroexpand '(signal-allowed? {:ns "my-ns"})))
|
||||
|
||||
#?(:clj
|
||||
(defmacro signal!
|
||||
"opts => allowed? / run result (value or throw)."
|
||||
{:doc (impl/docstring :signal!)
|
||||
:arglists (impl/arglists :signal!)}
|
||||
[& args]
|
||||
(truss/keep-callsite
|
||||
`(impl/signal! ~(args->opts args)))))
|
||||
|
||||
(comment (:coords (macroexpand '(with-signal (signal!)))))
|
||||
|
||||
#?(:clj
|
||||
(defn- merge-or-assoc-opts [m macro-form k v]
|
||||
(let [m (assoc m :coords (truss/callsite-coords macro-form))]
|
||||
(if (map? v)
|
||||
(merge m v)
|
||||
(assoc m k v)))))
|
||||
|
||||
#?(:clj
|
||||
(let [base-opts {:kind :event, :level :info}]
|
||||
(defmacro event!
|
||||
"id + ?level => allowed? Note unique arg order: [x opts] rather than [opts x]!"
|
||||
{:doc (impl/signal-docstring :event!)
|
||||
:arglists (impl/signal-arglists :event!)}
|
||||
([ opts-or-id] `(impl/signal! ~(merge-or-assoc-opts base-opts &form &env :id opts-or-id)))
|
||||
([id opts-or-level] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form &env :level opts-or-level) :id id))))))
|
||||
(let [base-opts {:kind :log, :level :info}]
|
||||
(defmacro log!?
|
||||
"?level + msg => allowed?"
|
||||
{:doc (impl/docstring :log!)
|
||||
:arglists (impl/arglists :log!)}
|
||||
([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))))))
|
||||
|
||||
(comment (with-signal (event! ::my-id :info)))
|
||||
(comment (:coords (with-signal (log!? :info "My msg"))))
|
||||
|
||||
#?(:clj
|
||||
(let [base-opts {:kind :log, :level :info}]
|
||||
(defmacro log!
|
||||
"?level + msg => allowed?"
|
||||
{:doc (impl/signal-docstring :log!)
|
||||
:arglists (impl/signal-arglists :log!)}
|
||||
([opts-or-msg ] `(impl/signal! ~(merge-or-assoc-opts base-opts &form &env :msg opts-or-msg)))
|
||||
([opts-or-level msg] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form &env :level opts-or-level) :msg msg))))))
|
||||
(defmacro log!
|
||||
"Like `log!?` but always returns nil."
|
||||
{:doc (impl/docstring :log!)
|
||||
:arglists (impl/arglists :log!)}
|
||||
[& args] `(do ~(truss/keep-callsite `(log!? ~@args)) nil)))
|
||||
|
||||
(comment (with-signal (log! :info "My msg")))
|
||||
(comment (:coords (with-signal (log! :info "My msg"))))
|
||||
|
||||
#?(:clj
|
||||
(let [base-opts {:kind :event, :level :info}]
|
||||
(defmacro event!?
|
||||
"id + ?level => allowed? Note unique arg order: [x opts] rather than [opts x]!"
|
||||
{:doc (impl/docstring :event!)
|
||||
:arglists (impl/arglists :event!)}
|
||||
([ 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))))))
|
||||
|
||||
(comment (:coords (with-signal (event!? ::my-id :info))))
|
||||
|
||||
#?(:clj
|
||||
(defmacro event!
|
||||
"Like `event!?` but always returns nil."
|
||||
{:doc (impl/docstring :event!)
|
||||
:arglists (impl/arglists :event!)}
|
||||
[& args] `(do ~(truss/keep-callsite `(event!? ~@args)) nil)))
|
||||
|
||||
(comment (:coords (with-signal (event! ::my-id :info))))
|
||||
|
||||
#?(:clj
|
||||
(let [base-opts {:kind :trace, :level :info, :msg `impl/default-trace-msg}]
|
||||
(defmacro trace!
|
||||
"?id + run => unconditional run result (value or throw)."
|
||||
{:doc (impl/signal-docstring :trace!)
|
||||
:arglists (impl/signal-arglists :trace!)}
|
||||
([opts-or-run] `(impl/signal! ~(merge-or-assoc-opts base-opts &form &env :run opts-or-run)))
|
||||
([opts-or-id run] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form &env :id opts-or-id) :run run))))))
|
||||
"?id + run => run result (value or throw)."
|
||||
{:doc (impl/docstring :trace!)
|
||||
:arglists (impl/arglists :trace!)}
|
||||
([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))))))
|
||||
|
||||
(comment (with-signal (trace! ::my-id (+ 1 2))))
|
||||
(comment (:coords (with-signal (trace! ::my-id (+ 1 2)))))
|
||||
|
||||
#?(:clj
|
||||
(let [base-opts {:kind :spy, :level :info, :msg `impl/default-trace-msg}]
|
||||
(defmacro spy!
|
||||
"?level + run => unconditional run result (value or throw)."
|
||||
{:doc (impl/signal-docstring :spy!)
|
||||
:arglists (impl/signal-arglists :spy!)}
|
||||
([opts-or-run] `(impl/signal! ~(merge-or-assoc-opts base-opts &form &env :run opts-or-run)))
|
||||
([opts-or-level run] `(impl/signal! ~(assoc (merge-or-assoc-opts base-opts &form &env :level opts-or-level) :run run))))))
|
||||
"?level + run => run result (value or throw)."
|
||||
{:doc (impl/docstring :spy!)
|
||||
:arglists (impl/arglists :spy!)}
|
||||
([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))))))
|
||||
|
||||
(comment (with-signals (spy! :info (+ 1 2))))
|
||||
|
||||
#?(:clj
|
||||
(let [base-opts {:kind :error, :level :error}]
|
||||
(defmacro error!
|
||||
"?id + error => unconditional given error."
|
||||
{:doc (impl/signal-docstring :error!)
|
||||
:arglists (impl/signal-arglists :error!)}
|
||||
([opts-or-id error] `(error! ~(assoc (merge-or-assoc-opts base-opts &form &env :id opts-or-id) :error error)))
|
||||
"?id + error => given error."
|
||||
{:doc (impl/docstring :error!)
|
||||
:arglists (impl/arglists :error!)}
|
||||
([opts-or-id error] `(error! ~(assoc (merge-or-assoc-opts base-opts &form :id opts-or-id) :error error)))
|
||||
([opts-or-error]
|
||||
(let [opts (merge-or-assoc-opts base-opts &form &env :error opts-or-error)]
|
||||
`(let [~'__error ~(get opts :error)]
|
||||
(impl/signal! ~(assoc opts :error '__error))
|
||||
(do ~'__error)))))))
|
||||
(let [opts (merge-or-assoc-opts base-opts &form :error opts-or-error)
|
||||
gs-error (gensym "error")]
|
||||
|
||||
(comment (with-signal (throw (error! ::my-id (ex-info "MyEx" {})))))
|
||||
`(let [~gs-error ~(get opts :error)]
|
||||
(impl/signal! ~(assoc opts :error gs-error))
|
||||
~gs-error))))))
|
||||
|
||||
(comment (:coords (with-signal (throw (error! ::my-id (truss/ex-info "MyEx" {}))))))
|
||||
|
||||
#?(:clj
|
||||
(let [base-opts {:kind :error, :level :error}]
|
||||
(defmacro catch->error!
|
||||
"?id + run => unconditional run value or ?catch-val."
|
||||
{:doc (impl/signal-docstring :catch->error!)
|
||||
:arglists (impl/signal-arglists :catch->error!)}
|
||||
([opts-or-id run] `(catch->error! ~(assoc (merge-or-assoc-opts base-opts &form &env :id opts-or-id) :run run)))
|
||||
"?id + run => run value or ?catch-val."
|
||||
{:doc (impl/docstring :catch->error!)
|
||||
:arglists (impl/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-run]
|
||||
(let [opts (merge-or-assoc-opts base-opts &form &env :run opts-or-run)
|
||||
(let [opts (merge-or-assoc-opts base-opts &form :run opts-or-run)
|
||||
rethrow? (not (contains? opts :catch-val))
|
||||
catch-val (get opts :catch-val)
|
||||
run-form (get opts :run)
|
||||
opts (dissoc opts :run :catch-val)]
|
||||
opts (dissoc opts :run :catch-val)
|
||||
gs-caught (gensym "caught")]
|
||||
|
||||
`(enc/try* ~run-form
|
||||
(catch :all ~'__caught
|
||||
(impl/signal! ~(assoc opts :error '__caught))
|
||||
(if ~rethrow? (throw ~'__caught) ~catch-val))))))))
|
||||
`(truss/try* ~run-form
|
||||
(catch :all ~gs-caught
|
||||
(impl/signal! ~(assoc opts :error gs-caught))
|
||||
(if ~rethrow? (throw ~gs-caught) ~catch-val))))))))
|
||||
|
||||
(comment (with-signal (catch->error! ::my-id (/ 1 0))))
|
||||
(comment (:coords (with-signal (catch->error! ::my-id (/ 1 0)))))
|
||||
|
||||
#?(:clj
|
||||
(defn uncaught->handler!
|
||||
|
|
@ -319,10 +401,10 @@
|
|||
uncaught JVM errors.
|
||||
|
||||
See `uncaught->handler!` and `error!` for details."
|
||||
{:arglists (impl/signal-arglists :uncaught->error!)}
|
||||
([ ] (enc/keep-callsite `(uncaught->error! {})))
|
||||
{:arglists (impl/arglists :uncaught->error!)}
|
||||
([ ] (truss/keep-callsite `(uncaught->error! {})))
|
||||
([opts-or-id]
|
||||
(let [opts (merge-or-assoc-opts base-opts &form &env :id opts-or-id)]
|
||||
(let [opts (merge-or-assoc-opts base-opts &form :id opts-or-id)]
|
||||
`(uncaught->handler!
|
||||
(fn [~'__thread-arg ~'__throwable-arg]
|
||||
(impl/signal! ~opts))))))))
|
||||
|
|
@ -378,29 +460,29 @@
|
|||
(enc/set-var-root! sigs/*default-handler-error-fn*
|
||||
(fn [{:keys [error] :as m}]
|
||||
(impl/signal!
|
||||
{:kind :error
|
||||
:level :error
|
||||
:error error
|
||||
:location {:ns "taoensso.encore.signals"}
|
||||
:id :taoensso.encore.signals/handler-error
|
||||
:msg "Error executing wrapped handler fn"
|
||||
:data (dissoc m :error)})))
|
||||
{:kind :error
|
||||
:level :error
|
||||
:error error
|
||||
:ns "taoensso.encore.signals"
|
||||
:id :taoensso.encore.signals/handler-error
|
||||
:msg "Error executing wrapped handler fn"
|
||||
:data (dissoc m :error)})))
|
||||
|
||||
(enc/set-var-root! sigs/*default-handler-backp-fn*
|
||||
(fn [data]
|
||||
(impl/signal!
|
||||
{:kind :event
|
||||
:level :warn
|
||||
:location {:ns "taoensso.encore.signals"}
|
||||
:id :taoensso.encore.signals/handler-back-pressure
|
||||
:msg "Back pressure on wrapped handler fn"
|
||||
:data data})))
|
||||
{:kind :event
|
||||
:level :warn
|
||||
:ns "taoensso.encore.signals"
|
||||
:id :taoensso.encore.signals/handler-back-pressure
|
||||
:msg "Back pressure on wrapped handler fn"
|
||||
:data data})))
|
||||
|
||||
(add-handler! :default/console (handler:console))
|
||||
(add-handler! :default/console (handler:console) {:async nil})
|
||||
|
||||
#?(:clj (enc/catching (require '[taoensso.telemere.tools-logging]))) ; TL->Telemere
|
||||
#?(:clj (enc/catching (require '[taoensso.telemere.slf4j]))) ; SLF4J->Telemere
|
||||
#?(:clj (enc/catching (require '[taoensso.telemere.open-telemetry]))) ; Telemere->OTel
|
||||
#?(:clj (truss/catching (require '[taoensso.telemere.tools-logging]))) ; TL->Telemere
|
||||
#?(:clj (truss/catching (require '[taoensso.telemere.slf4j]))) ; SLF4J->Telemere
|
||||
#?(:clj (truss/catching (require '[taoensso.telemere.open-telemetry]))) ; Telemere->OTel
|
||||
)
|
||||
|
||||
;;;; Flow benchmarks
|
||||
|
|
@ -465,8 +547,8 @@
|
|||
(event! ::ev-id
|
||||
{:data {:a :A :b :b}
|
||||
:error
|
||||
(ex-info "Ex2" {:b :B}
|
||||
(ex-info "Ex1" {:a :A}))}))]
|
||||
(truss/ex-info "Ex2" {:b :B}
|
||||
(truss/ex-info "Ex1" {:a :A}))}))]
|
||||
|
||||
(do (let [hf (handler:file)] (hf sig) (hf)))
|
||||
(do (let [hf (handler:console)] (hf sig) (hf)))
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
(ns ^:no-doc taoensso.telemere.consoles
|
||||
"Private ns, implementation detail.
|
||||
Core console handlers, aliased in main Telemere ns."
|
||||
"Telemere -> console handlers."
|
||||
(:require
|
||||
[taoensso.encore :as enc :refer [have have?]]
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.telemere.utils :as utils]))
|
||||
|
||||
(comment
|
||||
|
|
@ -12,8 +12,7 @@
|
|||
|
||||
#?(:clj
|
||||
(defn ^:public handler:console
|
||||
"Experimental, subject to change.
|
||||
|
||||
"Alpha, subject to change.
|
||||
Returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Writes the signal as a string to specified stream.
|
||||
|
|
@ -23,11 +22,11 @@
|
|||
|
||||
Options:
|
||||
`: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."
|
||||
|
||||
([] (handler:console nil))
|
||||
([{:keys [stream output-fn ]
|
||||
([{:keys [stream output-fn]
|
||||
:or
|
||||
{stream :auto
|
||||
output-fn (utils/format-signal-fn)}}]
|
||||
|
|
@ -50,8 +49,7 @@
|
|||
|
||||
:cljs
|
||||
(defn ^:public handler:console
|
||||
"Experimental, subject to change.
|
||||
|
||||
"Alpha, subject to change.
|
||||
If `js/console` exists, returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Writes the signal as a string to JavaScript console.
|
||||
|
|
@ -87,8 +85,7 @@
|
|||
|
||||
#?(:cljs
|
||||
(defn ^:public handler:console-raw
|
||||
"Experimental, subject to change.
|
||||
|
||||
"Alpha, subject to change.
|
||||
If `js/console` exists, returns a signal handler that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Writes the raw signal to JavaScript console.
|
||||
|
|
@ -97,7 +94,7 @@
|
|||
Ref. <https://github.com/binaryage/cljs-devtools>.
|
||||
|
||||
Options:
|
||||
`:preamble-fn` - (fn [signal]) => string, see [1].
|
||||
`:preamble-fn` ----- (fn [signal]) => string, see [1].
|
||||
`:format-nsecs-fn` - (fn [nanosecs]) => string.
|
||||
|
||||
[1] `taoensso.telemere.utils/signal-preamble-fn`, etc."
|
||||
|
|
@ -126,7 +123,7 @@
|
|||
(.group js/console (preamble-fn signal))
|
||||
(content-fn signal (logger-fn logger) identity)
|
||||
|
||||
(when-let [stack (and error (.-stack (enc/ex-root error)))]
|
||||
(when-let [stack (and error (.-stack (truss/ex-root error)))]
|
||||
(.call logger logger stack))
|
||||
|
||||
(.groupEnd js/console)))))))))
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
(ns ^:no-doc taoensso.telemere.files
|
||||
"Private ns, implementation detail.
|
||||
Core file handler, aliased in main Telemere ns."
|
||||
"Telemere -> file handler."
|
||||
(:require
|
||||
[taoensso.encore :as enc :refer [have have?]]
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.telemere.utils :as utils]))
|
||||
|
||||
(comment
|
||||
|
|
@ -71,9 +71,9 @@
|
|||
:daily (str (.format dtf (java.time.LocalDate/ofEpochDay edy)) "d")
|
||||
:weekly (str (.format dtf (java.time.LocalDate/ofEpochDay (edy-week edy))) "w")
|
||||
:monthly (str (.format dtf (java.time.LocalDate/ofEpochDay (edy-month edy))) "m")
|
||||
(enc/unexpected-arg! interval
|
||||
{:context `file-timestamp
|
||||
:param 'interval
|
||||
(truss/unexpected-arg! interval
|
||||
{:param 'interval
|
||||
:context `file-timestamp
|
||||
:expected #{:daily :weekly :monthly}}))))
|
||||
|
||||
(comment (file-timestamp->edy (format-file-timestamp :weekly (udt->edy (enc/now-udt*)))))
|
||||
|
|
@ -81,7 +81,7 @@
|
|||
(defn manage-test-files!
|
||||
"Describes/creates/deletes files used for tests/debugging, etc."
|
||||
[action]
|
||||
(have? [:el #{:return :println :create :delete}] action)
|
||||
(truss/have? [:el #{:return :println :create :delete}] action)
|
||||
(let [fnames_ (volatile! [])
|
||||
action!
|
||||
(fn [app timestamp part gz? timestamp main?]
|
||||
|
|
@ -136,7 +136,7 @@
|
|||
- Have the same `interval` type ∈ #{:daily :weekly :monthly nil} (=> ?timestamped).
|
||||
- Have the given timestamp (e.g. \"2020-01-01d\", or nil for NO timestamp)."
|
||||
[main-path interval timestamp sort?]
|
||||
(have? [:el #{:daily :weekly :monthly nil}] interval)
|
||||
(truss/have? [:el #{:daily :weekly :monthly nil}] interval)
|
||||
(let [main-file (utils/as-file main-path) ; `logs/app.log`
|
||||
main-dir (.getParentFile (.getAbsoluteFile main-file)) ; `.../logs`
|
||||
|
||||
|
|
@ -168,9 +168,8 @@
|
|||
(let [actual (.getAbsolutePath file-in)
|
||||
expected file-name]
|
||||
(when-not (.endsWith actual expected)
|
||||
(throw
|
||||
(ex-info "Unexpected file name"
|
||||
{:actual actual, :expected expected}))))
|
||||
(truss/ex-info! "Unexpected file name"
|
||||
{:actual actual, :expected expected})))
|
||||
|
||||
(conj acc
|
||||
{:file file-in
|
||||
|
|
@ -234,8 +233,8 @@
|
|||
arch-file+gz (utils/as-file arch-file-name+gz) ; `logs/app.log.1.gz` or `logs/app.log-2020-01-01d.1.gz`
|
||||
]
|
||||
|
||||
(have? false? (.exists arch-file+gz)) ; No pre-existing `.1.gz`
|
||||
(.renameTo main-file arch-file-gz)
|
||||
(truss/have? false? (.exists arch-file+gz)) ; No pre-existing `.1.gz`
|
||||
(.renameTo main-file arch-file-gz)
|
||||
(.createNewFile main-file)
|
||||
|
||||
(when gz?
|
||||
|
|
@ -272,32 +271,32 @@
|
|||
- Takes a Telemere signal (map).
|
||||
- Writes (appends) the signal as a string to file specified by `path`.
|
||||
|
||||
Depending on options, archives may be maintained:
|
||||
Can output signals as human or machine-readable (edn, JSON) strings.
|
||||
|
||||
Depending on options, archive file/s may also be maintained:
|
||||
- `logs/app.log.n.gz` (for nil `:interval`, non-nil `:max-file-size`)
|
||||
- `logs/app.log-YYYY-MM-DDd.n.gz` (for non-nil `:interval`) ; d=daily/w=weekly/m=monthly
|
||||
|
||||
Can output signals as human or machine-readable (edn, JSON) strings.
|
||||
|
||||
Example files with default options:
|
||||
`/logs/telemere.log` ; Current file
|
||||
`/logs/telemere.log` ; Current file (newest entries)
|
||||
`/logs/telemere.log-2020-01-01m.1.gz` ; Archive for Jan 2020, part 1 (newest entries)
|
||||
...
|
||||
`/logs/telemere.log-2020-01-01m.8.gz` ; Archive for Jan 2020, part 8 (oldest entries)
|
||||
|
||||
Options:
|
||||
`: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`)
|
||||
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.
|
||||
|
||||
`: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.
|
||||
|
||||
`: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."
|
||||
|
||||
([] (handler:file nil))
|
||||
810
main/src/taoensso/telemere/impl.cljc
Normal file
810
main/src/taoensso/telemere/impl.cljc
Normal file
|
|
@ -0,0 +1,810 @@
|
|||
(ns ^:no-doc taoensso.telemere.impl
|
||||
"Private ns, implementation detail.
|
||||
Signal design shared by: Telemere, Tufte, Timbre."
|
||||
(:require
|
||||
[clojure.set :as set]
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.encore.signals :as sigs])
|
||||
|
||||
#?(:cljs
|
||||
(:require-macros
|
||||
[taoensso.telemere.impl :refer [with-signal]])))
|
||||
|
||||
(comment
|
||||
(remove-ns (symbol (str *ns*)))
|
||||
(:api (enc/interns-overview)))
|
||||
|
||||
#?(:clj
|
||||
(enc/declare-remote
|
||||
^:dynamic taoensso.telemere/*ctx*
|
||||
^:dynamic taoensso.telemere/*xfn*
|
||||
^:dynamic taoensso.telemere/*uid-fn*
|
||||
^:dynamic taoensso.telemere/*otel-tracer*))
|
||||
|
||||
;;;; Config
|
||||
|
||||
#?(:clj
|
||||
(do
|
||||
(def present:tools-logging? (enc/have-resource? "clojure/tools/logging.clj"))
|
||||
(def present:slf4j? (enc/compile-if org.slf4j.Logger true false))
|
||||
(def present:telemere-slf4j? (enc/compile-if com.taoensso.telemere.slf4j.TelemereLogger true false))
|
||||
(def present:otel? (enc/compile-if io.opentelemetry.context.Context true false))
|
||||
|
||||
(def enabled:tools-logging?
|
||||
"Documented at `taoensso.telemere.tools-logging/tools-logging->telemere!`."
|
||||
(enc/get-env {:as :bool, :default false} :clojure.tools.logging/to-telemere))
|
||||
|
||||
(def enabled:otel-tracing?
|
||||
"Documented at `taoensso.telemere/otel-tracing?`."
|
||||
(enc/get-env {:as :bool, :default present:otel?}
|
||||
: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
|
||||
"Documented at `taoensso.telemere/*uid-fn*`."
|
||||
(enc/get-env {:as :edn, :default :default}
|
||||
:taoensso.telemere/uid-kind<.platform><.edn>))
|
||||
|
||||
#?(:clj
|
||||
(let [base (enc/get-env {:as :edn} :taoensso.telemere/ct-filters<.platform><.edn>)
|
||||
kind-filter (enc/get-env {:as :edn} :taoensso.telemere/ct-kind-filter<.platform><.edn>)
|
||||
ns-filter (enc/get-env {:as :edn} :taoensso.telemere/ct-ns-filter<.platform><.edn>)
|
||||
id-filter (enc/get-env {:as :edn} :taoensso.telemere/ct-id-filter<.platform><.edn>)
|
||||
min-level (enc/get-env {:as :edn} :taoensso.telemere/ct-min-level<.platform><.edn>)]
|
||||
|
||||
(enc/defonce ct-call-filter
|
||||
"`SpecFilter` used for compile-time elision, or nil."
|
||||
(sigs/spec-filter
|
||||
{:kind-filter (or kind-filter (get base :kind-filter))
|
||||
:ns-filter (or ns-filter (get base :ns-filter))
|
||||
:id-filter (or id-filter (get base :id-filter))
|
||||
:min-level (or min-level (get base :min-level))}))))
|
||||
|
||||
(let [base (enc/get-env {:as :edn} :taoensso.telemere/rt-filters<.platform><.edn>)
|
||||
kind-filter (enc/get-env {:as :edn} :taoensso.telemere/rt-kind-filter<.platform><.edn>)
|
||||
ns-filter (enc/get-env {:as :edn} :taoensso.telemere/rt-ns-filter<.platform><.edn>)
|
||||
id-filter (enc/get-env {:as :edn} :taoensso.telemere/rt-id-filter<.platform><.edn>)
|
||||
min-level (enc/get-env {:as :edn, :default :info} :taoensso.telemere/rt-min-level<.platform><.edn>)]
|
||||
|
||||
(enc/defonce ^:dynamic *rt-call-filter*
|
||||
"`SpecFilter` used for runtime filtering, or nil."
|
||||
(sigs/spec-filter
|
||||
{:kind-filter (or kind-filter (get base :kind-filter))
|
||||
:ns-filter (or ns-filter (get base :ns-filter))
|
||||
:id-filter (or id-filter (get base :id-filter))
|
||||
:min-level (or min-level (get base :min-level))})))
|
||||
|
||||
(comment (enc/get-env {:as :edn, :return :explain} :taoensso.telemere/rt-filters<.platform><.edn>))
|
||||
|
||||
;;;; Utils
|
||||
|
||||
#?(:clj
|
||||
(defmacro on-init [& body]
|
||||
(let [sym (with-meta '__on-init {:private true})
|
||||
compiling? (if (:ns &env) false `*compile-files*)]
|
||||
`(defonce ~sym (when-not ~compiling? ~@body nil)))))
|
||||
|
||||
(comment (macroexpand-1 '(on-init (println "foo"))))
|
||||
|
||||
;;;; Messages
|
||||
|
||||
(deftype MsgSkip [])
|
||||
(deftype MsgSplice [args])
|
||||
|
||||
(def ^:public msg-skip
|
||||
"For use within signal message vectors.
|
||||
Special value that will be ignored (noop) when creating message.
|
||||
Useful for conditionally skipping parts of message content, etc.:
|
||||
|
||||
(signal! {:msg [\"Hello\" (if <cond> <then> msg-skip) \"world\"] <...>}) or
|
||||
(log! [\"Hello\" (if <cond> <then> msg-skip) \"world\"]), etc.
|
||||
|
||||
%> {:msg_ \"Hello world\" <...>}"
|
||||
|
||||
(MsgSkip.))
|
||||
|
||||
(defn ^:public msg-splice
|
||||
"For use within signal message vectors.
|
||||
Wraps given arguments so that they're spliced when creating message.
|
||||
Useful for conditionally splicing in extra message content, etc.:
|
||||
|
||||
(signal! {:msg [(when <cond> (msg-splice [\"Username:\" \"Steve\"])) <...>]}) or
|
||||
(log! [(when <cond> (msg-splice [\"Username:\" \"Steve\"]))])
|
||||
|
||||
%> {:msg_ \"Username: Steve\"}"
|
||||
|
||||
[args] (MsgSplice. args))
|
||||
|
||||
(let [;; xform (map #(if (nil? %) "nil" %))
|
||||
xform
|
||||
(fn [rf]
|
||||
(let [;; Protocol-based impln (extensible but ~20% slower)
|
||||
;; rf* (fn rf* [acc in] (reduce-msg-arg in acc rf))
|
||||
rf*
|
||||
(fn rf* [acc in]
|
||||
(enc/cond
|
||||
(instance? MsgSplice in) (reduce rf* acc (.-args ^MsgSplice in))
|
||||
(instance? MsgSkip in) acc
|
||||
(nil? in) (rf acc "nil")
|
||||
:else (rf acc in)))]
|
||||
(fn
|
||||
([ ] (rf))
|
||||
([acc ] (rf acc))
|
||||
([acc in] (rf* acc in)))))]
|
||||
|
||||
(defn signal-msg
|
||||
"Returns string formed by joining all args with \" \" separator,
|
||||
rendering nils as \"nil\". Supports `msg-skip`, `msg-splice`.
|
||||
|
||||
API intended to be usefully different to `str`:
|
||||
- `str`: no spacers, skip nils, no splicing
|
||||
- `signal-msg`: auto spacers, show nils, opt-in splicing"
|
||||
|
||||
{:tag #?(:clj 'String :cljs 'string)}
|
||||
[args] (enc/str-join " " xform args)))
|
||||
|
||||
(comment
|
||||
(enc/qb 2e6 ; [305.61 625.35]
|
||||
(str "a" "b" "c" nil :kw) ; "abc:kw"
|
||||
(signal-msg ["a" "b" "c" nil :kw (msg-splice ["d" "e"])]) ; "a b c nil :kw d e"
|
||||
))
|
||||
|
||||
#?(:clj
|
||||
(defn- parse-msg-form [msg-form]
|
||||
(when msg-form
|
||||
(enc/cond
|
||||
(string? msg-form) msg-form
|
||||
(vector? msg-form)
|
||||
(enc/cond
|
||||
(empty? msg-form) nil
|
||||
:let [[m1 & more] msg-form]
|
||||
(and (string? m1) (nil? more)) m1
|
||||
:else `(delay (signal-msg ~msg-form)))
|
||||
|
||||
;; Auto delay-wrap (user should never delay-wrap!)
|
||||
;; :else `(delay ~msg-form)
|
||||
|
||||
;; Leave user to delay-wrap when appropriate (document)
|
||||
:else msg-form))))
|
||||
|
||||
(defn default-trace-msg
|
||||
[form value error nsecs]
|
||||
(if error
|
||||
(str (if (nil? form) "nil" form) " !> " (truss/ex-type error))
|
||||
(str (if (nil? form) "nil" form) " => " (if (nil? value) "nil" value))))
|
||||
|
||||
(comment
|
||||
(default-trace-msg "(+ 1 2)" 3 nil 12345)
|
||||
(default-trace-msg "(+ 1 2)" nil (Exception. "Ex") 12345))
|
||||
|
||||
;;;; Tracing
|
||||
|
||||
(enc/def* ^:dynamic *trace-root* "?{:keys [id uid]}" nil) ; Fixed once bound
|
||||
(enc/def* ^:dynamic *trace-parent* "?{:keys [id uid]}" nil) ; Changes each nesting level
|
||||
|
||||
;; Root Telemere ids: {:parent nil, :id id1, :uid uid1 :root {:id id1, :uid uid1}}
|
||||
;; Root OTel ids: {:parent nil, :id id1, :uid span1,:root {:id id1, :uid trace1}}
|
||||
|
||||
;;;; OpenTelemetry
|
||||
|
||||
#?(:clj
|
||||
(enc/compile-when present:otel?
|
||||
(do
|
||||
(enc/def* ^:dynamic *otel-context* "`?Context`" nil)
|
||||
(defmacro otel-context [] `(or *otel-context* (io.opentelemetry.context.Context/current)))
|
||||
|
||||
(defn otel-trace-id
|
||||
"Returns valid `traceId` or nil."
|
||||
[^io.opentelemetry.context.Context context]
|
||||
(let [sc (.getSpanContext (io.opentelemetry.api.trace.Span/fromContext context))]
|
||||
(when (.isValid sc) (.getTraceId sc))))
|
||||
|
||||
(defn otel-span-id
|
||||
"Returns valid `spanId` or nil."
|
||||
[^io.opentelemetry.context.Context context]
|
||||
(let [sc (.getSpanContext (io.opentelemetry.api.trace.Span/fromContext context))]
|
||||
(when (.isValid sc) (.getSpanId sc))))
|
||||
|
||||
(defn viable-tracer
|
||||
"Returns viable `Tracer`, or nil."
|
||||
[tracer]
|
||||
(when-let [tracer ^io.opentelemetry.api.trace.Tracer tracer]
|
||||
(let [sb (.spanBuilder tracer "test-span")
|
||||
span (.startSpan sb)]
|
||||
(when (.isValid (.getSpanContext span))
|
||||
tracer))))
|
||||
|
||||
(def ^String otel-name (enc/fmemoize (fn [id] (if id (enc/as-qname id) "telemere/no-id"))))
|
||||
(defn otel-context+span
|
||||
"Returns new `Context` that includes minimal `Span` in given parent `Context`.
|
||||
We leave the (expensive) population of attributes, etc. for signal handler.
|
||||
Interop needs only the basics (t0, traceId, spanId, spanName) right away."
|
||||
^io.opentelemetry.context.Context
|
||||
[id inst ?parent-context ?span-kind]
|
||||
(let [parent-context (or ?parent-context (otel-context))]
|
||||
(enc/if-not [tracer (force taoensso.telemere/*otel-tracer*)]
|
||||
parent-context ; Can't add Span without Tracer
|
||||
(let [sb (.spanBuilder ^io.opentelemetry.api.trace.Tracer tracer (otel-name id))]
|
||||
(.setStartTimestamp sb ^java.time.Instant inst)
|
||||
(.setSpanKind sb
|
||||
(case ?span-kind
|
||||
(nil :internal) io.opentelemetry.api.trace.SpanKind/INTERNAL
|
||||
:client io.opentelemetry.api.trace.SpanKind/CLIENT
|
||||
:server io.opentelemetry.api.trace.SpanKind/SERVER
|
||||
:consumer io.opentelemetry.api.trace.SpanKind/CONSUMER
|
||||
:producer io.opentelemetry.api.trace.SpanKind/PRODUCER
|
||||
(truss/unexpected-arg! ?span-kind
|
||||
{:expected #{nil :internal :client :server :consumer :producer}})))
|
||||
|
||||
(.with ^io.opentelemetry.context.Context parent-context
|
||||
(.startSpan sb)))))))))
|
||||
|
||||
(comment
|
||||
(enc/qb 1e6 (otel-context) (otel-context+span ::id1 (enc/now-inst) nil nil)) ; [46.42 186.89]
|
||||
(viable-tracer (force taoensso.telemere/*otel-tracer*))
|
||||
(otel-trace-id (otel-context)))
|
||||
|
||||
;;;; Main types
|
||||
|
||||
(defrecord Signal
|
||||
;; Telemere's main public data type, we avoid nesting and duplication
|
||||
[schema inst uid, ns coords,
|
||||
#?@(:clj [host thread _otel-context]),
|
||||
sample, kind id level, ctx parent root, data kvs msg_,
|
||||
error run-form run-val end-inst run-nsecs]
|
||||
|
||||
Object (toString [sig] (str "taoensso.telemere.Signal" (enc/pr-edn* (into {} sig)))))
|
||||
|
||||
;; Verbose constructors for readability + to support extra keys
|
||||
(do (enc/def-print-impl [sig Signal] (str "#taoensso.telemere.Signal" (enc/pr-edn* (into {} sig)))))
|
||||
#?(:clj (enc/def-print-dup [sig Signal] (str "#taoensso.telemere.impl.Signal" (enc/pr-edn* (into {} sig)))))
|
||||
|
||||
(defn signal? #?(:cljs {:tag 'boolean}) [x] (instance? Signal x))
|
||||
|
||||
(def impl-signal-keys #{:_otel-context})
|
||||
(def standard-signal-keys
|
||||
(set/difference (set (keys (map->Signal {:schema 0})))
|
||||
impl-signal-keys))
|
||||
|
||||
(deftype #_defrecord WrappedSignal
|
||||
[kind ns id level signal-value_]
|
||||
sigs/ISignalHandling
|
||||
(allow-signal? [_ spec-filter] (spec-filter kind ns id level))
|
||||
(signal-debug [_] {:kind kind, :ns ns, :id id, :level level})
|
||||
(signal-value [_ handler-sample-rate]
|
||||
(sigs/signal-with-combined-sample-rate handler-sample-rate
|
||||
(force signal-value_))))
|
||||
|
||||
(defn wrap-signal
|
||||
"Used by `taoensso.telemere/dispatch-signal!`."
|
||||
[signal]
|
||||
(when (map? signal)
|
||||
(let [{:keys [kind ns id level]} signal]
|
||||
(WrappedSignal. kind ns id level signal))))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(enc/defonce ^:dynamic *sig-handlers* "?[<wrapped-handler-fn>]" nil)
|
||||
|
||||
(defrecord SpyOpts [vol_ last-only? trap?])
|
||||
(def ^:dynamic *sig-spy* "?SpyOpts" nil)
|
||||
|
||||
(defn force-msg-in-sig [sig]
|
||||
(if-not (map? sig)
|
||||
sig
|
||||
(if-let [e (find sig :msg_)]
|
||||
(assoc sig :msg_ (force (val e)))
|
||||
(do sig))))
|
||||
|
||||
#?(:clj
|
||||
(defmacro ^:public with-signal
|
||||
"Executes given form, trapping errors. Returns the LAST signal created by form.
|
||||
Useful for tests/debugging.
|
||||
|
||||
Options:
|
||||
`trap-signals?` (default false)
|
||||
Should ALL signals created by form be trapped to prevent normal dispatch
|
||||
to registered handlers?
|
||||
|
||||
`raw-msg?` (default false)
|
||||
Should delayed `:msg_` in returned signal be retained as-is?
|
||||
Delay is otherwise replaced by realized string.
|
||||
|
||||
See also `with-signals` for more advanced options."
|
||||
([ form] `(with-signal false false ~form))
|
||||
([ trap-signals? form] `(with-signal false ~trap-signals? ~form))
|
||||
([raw-msg? trap-signals? form]
|
||||
`(let [sig_# (volatile! nil)]
|
||||
(binding [*sig-spy* (SpyOpts. sig_# true ~trap-signals?)]
|
||||
(truss/try* ~form (catch :all _#)))
|
||||
|
||||
(if ~raw-msg?
|
||||
(do @sig_#)
|
||||
(force-msg-in-sig @sig_#))))))
|
||||
|
||||
#?(:clj
|
||||
(defmacro ^:public with-signals
|
||||
"Like `with-signal` but returns {:keys [value error signals]}.
|
||||
Useful for more advanced tests/debugging.
|
||||
|
||||
Destructuring example:
|
||||
(let [{:keys [value error] [sig1 sig2] :signals} (with-signals ...)]
|
||||
...)"
|
||||
([ form] `(with-signals false false ~form))
|
||||
([ trap-signals? form] `(with-signals false ~trap-signals? ~form))
|
||||
([raw-msgs? trap-signals? form]
|
||||
`(let [sigs_# (volatile! nil)
|
||||
base-map#
|
||||
(binding [*sig-spy* (SpyOpts. sigs_# false ~trap-signals?)]
|
||||
(truss/try*
|
||||
(do {:value ~form})
|
||||
(catch :all t# {:error t#})))
|
||||
|
||||
sigs#
|
||||
(not-empty
|
||||
(if ~raw-msgs?
|
||||
(do @sigs_#)
|
||||
(mapv force-msg-in-sig @sigs_#)))]
|
||||
|
||||
(if sigs#
|
||||
(assoc base-map# :signals sigs#)
|
||||
(do base-map#))))))
|
||||
|
||||
#?(:clj (def ^:dynamic *sig-spy-off-thread?* false))
|
||||
(defn dispatch-signal!
|
||||
"Dispatches given signal to registered handlers, supports `with-signal/s`."
|
||||
[signal]
|
||||
(or
|
||||
(when-let [{:keys [vol_ last-only? trap?]} *sig-spy*]
|
||||
(let [sv
|
||||
#?(:cljs (sigs/signal-value signal nil)
|
||||
:clj
|
||||
(if *sig-spy-off-thread?* ; Simulate async handler
|
||||
(deref (enc/promised :user (sigs/signal-value signal nil)))
|
||||
(do (sigs/signal-value signal nil))))]
|
||||
|
||||
(if last-only?
|
||||
(vreset! vol_ sv)
|
||||
(vswap! vol_ #(conj (or % []) sv))))
|
||||
(when trap? :trapped))
|
||||
|
||||
(sigs/call-handlers! *sig-handlers* signal)
|
||||
:dispatched))
|
||||
|
||||
;;;; API helpers
|
||||
|
||||
#?(:clj (defmacro docstring [ rname] (enc/slurp-resource (str "docs/" (name rname) ".txt"))))
|
||||
#?(:clj (defmacro defhelp [sym rname] `(enc/def* ~sym {:doc ~(eval `(docstring ~rname))} "See docstring")))
|
||||
|
||||
#?(:clj
|
||||
(defn arglists [macro-id]
|
||||
;; + Undocumented [elide? allow? callsite-id host thread otel/context]
|
||||
(case macro-id
|
||||
|
||||
:signal-allowed? ; opts => allowed?
|
||||
'( [& opts-kvs]
|
||||
[{:as opts-map :keys
|
||||
[elidable? coords #_inst #_uid #_xfn #_xfn+,
|
||||
sample kind ns id level when limit limit-by,
|
||||
#_ctx #_ctx+ #_parent #_root #_trace?, #_do #_let #_data #_msg #_error #_run #_& #_kvs]}])
|
||||
|
||||
:signal! ; opts => allowed? / run result (value or throw)
|
||||
'( [& opts-kvs]
|
||||
[{:as opts-map :keys
|
||||
[elidable? coords inst uid xfn xfn+ #_kvs+,
|
||||
sample kind ns id level when limit limit-by,
|
||||
ctx ctx+ parent root trace?, do let data msg error run & kvs]}])
|
||||
|
||||
:log! ; ?level + msg => nil / allowed?
|
||||
'([opts-or-msg]
|
||||
[level msg]
|
||||
[{:as opts-map :keys
|
||||
[elidable? coords inst uid xfn xfn+ #_kvs+,
|
||||
sample kind ns id level when limit limit-by,
|
||||
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}
|
||||
msg])
|
||||
|
||||
:event! ; id + ?level => nil / allowed?
|
||||
'([opts-or-id]
|
||||
[id level]
|
||||
[id
|
||||
{:as opts-map :keys
|
||||
[elidable? coords inst uid xfn xfn+ #_kvs+,
|
||||
sample kind ns id level when limit limit-by,
|
||||
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}])
|
||||
|
||||
:trace! ; ?id + run => run result (value or throw)
|
||||
'([opts-or-run]
|
||||
[id run]
|
||||
[{:as opts-map :keys
|
||||
[elidable? coords inst uid xfn xfn+ #_kvs+,
|
||||
sample kind ns id level when limit limit-by,
|
||||
ctx ctx+ parent root trace?, do let data msg error run & kvs]}
|
||||
run])
|
||||
|
||||
:spy! ; ?level + run => run result (value or throw)
|
||||
'([opts-or-run]
|
||||
[level run]
|
||||
[{:as opts-map :keys
|
||||
[elidable? coords inst uid xfn xfn+ #_kvs+,
|
||||
sample kind ns id level when limit limit-by,
|
||||
ctx ctx+ parent root trace?, do let data msg error run & kvs]}
|
||||
run])
|
||||
|
||||
:error! ; ?id + error => given error
|
||||
'([opts-or-error]
|
||||
[id error]
|
||||
[{:as opts-map :keys
|
||||
[elidable? coords inst uid xfn xfn+ #_kvs+,
|
||||
sample kind ns id level when limit limit-by,
|
||||
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}
|
||||
error])
|
||||
|
||||
:catch->error! ; ?id + run => run value or ?catch-val
|
||||
'([opts-or-run]
|
||||
[id run]
|
||||
[{:as opts-map :keys
|
||||
[catch-val,
|
||||
elidable? coords inst uid xfn xfn+ #_kvs+,
|
||||
sample kind ns id level when limit limit-by,
|
||||
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}
|
||||
run])
|
||||
|
||||
:uncaught->error! ; ?id => nil
|
||||
'([]
|
||||
[opts-or-id]
|
||||
[{:as opts-map :keys
|
||||
[elidable? coords inst uid xfn xfn+ #_kvs+,
|
||||
sample kind ns id level when limit limit-by,
|
||||
ctx ctx+ parent root trace?, do let data msg error #_run & kvs]}])
|
||||
|
||||
(truss/unexpected-arg! macro-id))))
|
||||
|
||||
;;;; Signal macro
|
||||
|
||||
(deftype RunResult [value error ^long run-nsecs]
|
||||
#?(:clj clojure.lang.IFn :cljs IFn)
|
||||
(#?(:clj invoke :cljs -invoke) [_] (if error (throw error) value))
|
||||
(#?(:clj invoke :cljs -invoke) [_ signal_]
|
||||
(if error
|
||||
(truss/ex-info! "Signal `:run` form error"
|
||||
(truss/try*
|
||||
(do {:taoensso.telemere/signal (force signal_)})
|
||||
(catch :all t {:taoensso.telemere/signal-error t}))
|
||||
error)
|
||||
value)))
|
||||
|
||||
(defn inst+nsecs
|
||||
"Returns given platform instant plus given number of nanosecs."
|
||||
[inst run-nsecs]
|
||||
#?(:clj (.plusNanos ^java.time.Instant inst run-nsecs)
|
||||
:cljs (js/Date. (+ (.getTime inst) (/ run-nsecs 1e6)))))
|
||||
|
||||
(comment (enc/qb 1e6 (inst+nsecs (enc/now-inst) 1e9)))
|
||||
|
||||
#?(:clj
|
||||
(defn- valid-opts! [macro-form macro-env caller opts]
|
||||
(if (map? opts)
|
||||
(do opts)
|
||||
(truss/ex-info!
|
||||
(str "`" caller "` needs compile-time map opts at "
|
||||
(sigs/format-callsite (enc/get-source macro-form macro-env)))))))
|
||||
|
||||
#?(:clj (defn- auto-> [form auto-form] (if (= form :auto) auto-form form)))
|
||||
#?(:clj
|
||||
(defmacro signal-allowed?
|
||||
"Returns true iff signal with given opts would meet filtering conditions.
|
||||
Wrapped for public API."
|
||||
([ opts] (truss/keep-callsite `(signal-allowed? nil ~opts)))
|
||||
([base-opts opts]
|
||||
(valid-opts! &form &env 'telemere/signal-allowed? (or base-opts {}))
|
||||
(valid-opts! &form &env 'telemere/signal-allowed? (or opts {}))
|
||||
(let [opts (merge {:kind :generic, :level :info} base-opts opts)
|
||||
{:keys [#_callsite-id elide? allow?]}
|
||||
(sigs/filter-call
|
||||
{:cljs? (boolean (:ns &env))
|
||||
:sf-arity 4
|
||||
:ct-call-filter ct-call-filter
|
||||
:*rt-call-filter* `*rt-call-filter*}
|
||||
(assoc opts
|
||||
:ns (auto-> (get opts :ns :auto) (str *ns*))))]
|
||||
|
||||
(if elide? false `(if ~allow? true false))))))
|
||||
|
||||
(comment (macroexpand '(signal-allowed? {:level :info})))
|
||||
|
||||
#?(:clj
|
||||
(defmacro signal!
|
||||
"Generic low-level signal creator. Wrapped for public API."
|
||||
([ opts] (truss/keep-callsite `(signal! nil ~opts)))
|
||||
([base-opts opts]
|
||||
(valid-opts! &form &env 'telemere/signal! (or base-opts {}))
|
||||
(valid-opts! &form &env 'telemere/signal! (or opts {}))
|
||||
(let [cljs? (boolean (:ns &env))
|
||||
clj? (not cljs?)
|
||||
|
||||
opts (merge {:kind :generic, :level :info} base-opts opts)
|
||||
|
||||
run-form? (contains? opts :run)
|
||||
run-form (get opts :run)
|
||||
|
||||
ns-form* (get opts :ns :auto)
|
||||
ns-form (auto-> ns-form* (str *ns*))
|
||||
|
||||
show-run-val (get opts :run-val '_run-val)
|
||||
show-run-form
|
||||
(when run-form?
|
||||
(get opts :run-form
|
||||
(if (and
|
||||
(enc/list-form? run-form)
|
||||
(> (count run-form) 1)
|
||||
(> (count (str run-form)) 32))
|
||||
(list (first run-form) '...)
|
||||
(do run-form))))
|
||||
|
||||
{:keys [#_callsite-id elide? allow?]}
|
||||
(sigs/filter-call
|
||||
{:cljs? cljs?
|
||||
:sf-arity 4
|
||||
:ct-call-filter ct-call-filter
|
||||
:*rt-call-filter* `*rt-call-filter*}
|
||||
|
||||
(assoc opts
|
||||
:ns ns-form
|
||||
:local-forms
|
||||
{:kind '__kind
|
||||
:ns '__ns
|
||||
:id '__id
|
||||
:level '__level}))]
|
||||
|
||||
(if elide?
|
||||
run-form
|
||||
(let [coords (get opts :coords (when (= ns-form* :auto) (truss/callsite-coords &form)))
|
||||
|
||||
{inst-form :inst
|
||||
kind-form :kind
|
||||
id-form :id
|
||||
level-form :level} opts
|
||||
|
||||
trace? (get opts :trace? run-form?)
|
||||
_
|
||||
(when-not (contains? #{true false nil} trace?)
|
||||
(truss/ex-info!
|
||||
(str "Signal needs compile-time `:trace?` value at "
|
||||
(sigs/format-callsite ns-form coords))))
|
||||
|
||||
host-form (auto-> (get opts :host :auto) (when (and clj? enabled:incl-host-info?) `(enc/host-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*))
|
||||
|
||||
parent-form (get opts :parent `*trace-parent*)
|
||||
root-form0 (get opts :root `*trace-root*)
|
||||
|
||||
uid-form (get opts :uid (when trace? :auto))
|
||||
|
||||
signal-delay-form
|
||||
(let [{do-form :do
|
||||
let-form :let
|
||||
msg-form :msg
|
||||
data-form :data
|
||||
error-form :error
|
||||
sample-form :sample} opts
|
||||
|
||||
let-form (or let-form '[])
|
||||
msg-form (parse-msg-form msg-form)
|
||||
|
||||
ctx-form
|
||||
(if-let [ctx+ (get opts :ctx+)]
|
||||
`(taoensso.encore.signals/update-ctx taoensso.telemere/*ctx* ~ctx+)
|
||||
(get opts :ctx `taoensso.telemere/*ctx*))
|
||||
|
||||
xfn-form
|
||||
(if-let [xfn+ (get opts :xfn+)]
|
||||
`(taoensso.encore.signals/comp-xfn taoensso.telemere/*xfn* ~xfn+)
|
||||
(get opts :xfn `taoensso.telemere/*xfn*))
|
||||
|
||||
kvs-form
|
||||
(let [base
|
||||
(not-empty
|
||||
(dissoc opts
|
||||
:elidable? :coords :inst :uid :xfn :xfn+ :kvs+,
|
||||
:sample :ns :kind :id :level :filter :when #_:limit #_:limit-by,
|
||||
:ctx :ctx+ :parent :trace?, :do :let :data :msg :error,
|
||||
:run :run-form :run-val, :elide? :allow? #_:callsite-id,
|
||||
: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
|
||||
(do
|
||||
(when (and run-form? error-form) ; Ambiguous source of error
|
||||
(truss/ex-info!
|
||||
(str "Signal cannot have both `:run` and `:error` opts at "
|
||||
(sigs/format-callsite ns-form coords))))
|
||||
|
||||
(when-let [e (find opts :msg_)] ; Common typo/confusion
|
||||
(truss/ex-info!
|
||||
(str "Signal cannot have `:msg_` opt (did you mean `:msg`?) at "
|
||||
(sigs/format-callsite ns-form coords)))))
|
||||
|
||||
signal-form
|
||||
(let [record-form
|
||||
(let [clause [(if run-form? :run :no-run) (if clj? :clj :cljs)]]
|
||||
(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 :cljs] `(Signal. 1 ~'__inst ~'__uid, ~'__ns ~coords ~sample-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~'_msg_, ~'_run-err '~show-run-form ~show-run-val ~'_end-inst ~'_run-nsecs)
|
||||
[:no-run :clj ] `(Signal. 1 ~'__inst ~'__uid, ~'__ns ~coords ~host-form ~'__thread ~'__otel-context, ~sample-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~msg-form, ~error-form nil nil nil nil)
|
||||
[:no-run :cljs] `(Signal. 1 ~'__inst ~'__uid, ~'__ns ~coords ~sample-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~msg-form, ~error-form nil nil nil nil)
|
||||
(truss/ex-info!
|
||||
(str "Unexpected signal constructor args at "
|
||||
(sigs/format-callsite ns-form coords)))))
|
||||
|
||||
record-form
|
||||
(if-not run-form?
|
||||
record-form
|
||||
`(let [~(with-meta '_run-result {:tag `RunResult}) ~'__run-result
|
||||
~'_run-nsecs (.-run-nsecs ~'_run-result)
|
||||
~'_run-val (.-value ~'_run-result)
|
||||
~'_run-err (.-error ~'_run-result)
|
||||
~'_end-inst (inst+nsecs ~'__inst ~'_run-nsecs)
|
||||
~'_msg_
|
||||
(let [mf# ~msg-form]
|
||||
(if (fn? mf#) ; Undocumented, handy for `trace!`/`spy!`, etc.
|
||||
(delay (mf# '~show-run-form ~show-run-val ~'_run-err ~'_run-nsecs))
|
||||
mf#))]
|
||||
~record-form))]
|
||||
|
||||
(if-not kvs-form
|
||||
record-form
|
||||
`(let [signal# ~record-form]
|
||||
(reduce-kv assoc signal# (.-kvs signal#)))))]
|
||||
|
||||
`(enc/bound-delay
|
||||
;; Delay (cache) shared by all handlers, incl. `:let` eval,
|
||||
;; signal construction, transform (xfn), etc. Throws caught by handler.
|
||||
~do-form
|
||||
(let [~@let-form ; Allow to throw, eval BEFORE data, msg, etc.
|
||||
signal# ~signal-form]
|
||||
|
||||
;; Final unwrapped signal value visible to users/handler-fns, allow to throw
|
||||
(if-let [xfn# ~xfn-form]
|
||||
(xfn# signal#)
|
||||
(do signal#)))))
|
||||
|
||||
;; Trade-off: avoid double `run-form` expansion
|
||||
run-fn-form (when run-form? `(fn [] ~run-form))
|
||||
run-form* (when run-form? `(~'__run-fn-form))
|
||||
|
||||
binds-form-base
|
||||
`[~'__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!
|
||||
(not trace?) ; Non-tracing signal
|
||||
`[~'__root1 ~'__root0 ; Retain, but don't establish
|
||||
~'__run-result
|
||||
~(when run-form?
|
||||
`(let [t0# (enc/now-nano*)]
|
||||
(truss/try*
|
||||
(do (RunResult. ~run-form* nil (- (enc/now-nano*) t0#)))
|
||||
(catch :all t# (RunResult. nil t# (- (enc/now-nano*) t0#))))))]
|
||||
|
||||
;; Trace without OpenTelemetry
|
||||
(or cljs? (not enabled:otel-tracing?))
|
||||
`[~'__root1 (or ~'__root0 ~(when trace? `{:id ~'__id, :uid ~'__uid}))
|
||||
~'__run-result
|
||||
~(when run-form?
|
||||
`(binding [*trace-root* ~'__root1
|
||||
*trace-parent* {:id ~'__id, :uid ~'__uid}]
|
||||
(let [t0# (enc/now-nano*)]
|
||||
(truss/try*
|
||||
(do (RunResult. ~run-form* nil (- (enc/now-nano*) t0#)))
|
||||
(catch :all t# (RunResult. nil t# (- (enc/now-nano*) t0#)))))))]
|
||||
|
||||
;; Trace with OpenTelemetry
|
||||
(and clj? enabled:otel-tracing?)
|
||||
`[~'__root1
|
||||
(or ~'__root0
|
||||
~(when trace?
|
||||
`{:id ~'__id, :uid (or (otel-trace-id ~'__otel-context) (com.taoensso.encore.Ids/genHexId32))}))
|
||||
|
||||
~'__run-result
|
||||
~(when run-form?
|
||||
`(binding [*otel-context* ~'__otel-context
|
||||
*trace-root* ~'__root1
|
||||
*trace-parent* {:id ~'__id, :uid ~'__uid}]
|
||||
(let [otel-scope# (.makeCurrent ~'__otel-context)
|
||||
t0# (enc/now-nano*)]
|
||||
(truss/try*
|
||||
(do (RunResult. ~run-form* nil (- (enc/now-nano*) t0#)))
|
||||
(catch :all t# (RunResult. nil t# (- (enc/now-nano*) t0#)))
|
||||
(finally (.close otel-scope#))))))])]
|
||||
|
||||
`((fn [] ; iife for better IoC compatibility
|
||||
;; Unless otherwise specified, allow errors to throw on call
|
||||
(let [~'__run-fn-form ~run-fn-form
|
||||
~'__kind ~kind-form
|
||||
~'__ns ~ns-form
|
||||
~'__id ~id-form
|
||||
~'__level ~level-form]
|
||||
|
||||
(enc/if-not ~allow?
|
||||
~run-form*
|
||||
(let [~@binds-form-base
|
||||
~@binds-form-more
|
||||
signal# ~signal-delay-form]
|
||||
|
||||
(dispatch-signal!
|
||||
;; Unconditionally send same wrapped signal to all handlers.
|
||||
;; Each handler will use wrapper for handler filtering,
|
||||
;; unwrapping (realizing) only allowed signals.
|
||||
(WrappedSignal. ~'__kind ~'__ns ~'__id ~'__level signal#))
|
||||
|
||||
(if ~'__run-result
|
||||
( ~'__run-result signal#)
|
||||
true))))))))))))
|
||||
|
||||
(comment
|
||||
(with-signal (signal! {:level :warn :let [x :x] :msg ["Test" "message" x] :data {:a :A :x x} :run (+ 1 2)}))
|
||||
(macroexpand '(signal! {:level :warn :let [x :x] :msg ["Test" "message" x] :data {:a :A :x x} :run (+ 1 2)}))
|
||||
(macroexpand '(signal! {:level :info}))
|
||||
|
||||
(do
|
||||
(println "---")
|
||||
(sigs/with-handler *sig-handlers* "hf1" (fn hf1 [x] (println x)) {}
|
||||
(signal! {:level :info, :run "run"}))))
|
||||
|
||||
;;;; Interop
|
||||
|
||||
#?(:clj
|
||||
(do
|
||||
(enc/defonce ^:private interop-checks_
|
||||
"{<source-id> (fn check [])}"
|
||||
(atom
|
||||
{:tools-logging (fn [] {:present? present:tools-logging?, :enabled-by-env? enabled:tools-logging?})
|
||||
:slf4j (fn [] {:present? present:slf4j?, :telemere-provider-present? present:telemere-slf4j?})
|
||||
:open-telemetry (fn [] {:present? present:otel?, :use-tracer? enabled:otel-tracing?})}))
|
||||
|
||||
(defn add-interop-check! [source-id check-fn] (swap! interop-checks_ assoc source-id check-fn))
|
||||
|
||||
(defn ^:public check-interop
|
||||
"Runs Telemere's registered interop checks and returns info useful
|
||||
for tests/debugging, e.g.:
|
||||
|
||||
{:open-telemetry {:present? false}
|
||||
:tools-logging {:present? false}
|
||||
:slf4j {:present? true
|
||||
:sending->telemere? true
|
||||
:telemere-receiving? true}
|
||||
...}"
|
||||
[]
|
||||
(enc/map-vals (fn [check-fn] (check-fn))
|
||||
@interop-checks_))
|
||||
|
||||
(defn test-interop! [msg test-fn]
|
||||
(let [msg (str "Interop test: " msg " (" (enc/uuid-str) ")")
|
||||
signal
|
||||
(binding [*rt-call-filter* nil] ; Without runtime filters
|
||||
(with-signal :raw :trap (test-fn msg)))]
|
||||
|
||||
(= (force (get signal :msg_)) msg)))))
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
(ns taoensso.telemere.open-telemetry
|
||||
"OpenTelemetry handler using `opentelemetry-java`,
|
||||
Ref. <https://github.com/open-telemetry/opentelemetry-java>,
|
||||
<https://javadoc.io/doc/io.opentelemetry/opentelemetry-api/latest/index.html>"
|
||||
"Telemere -> OpenTelemetry handler using `opentelemetry-java`,
|
||||
Ref. <https://github.com/open-telemetry/opentelemetry-java>,
|
||||
<https://javadoc.io/doc/io.opentelemetry/opentelemetry-api/latest/index.html>
|
||||
|
||||
Telemere will attempt to load this ns automatically when possible."
|
||||
(:require
|
||||
[clojure.string :as str]
|
||||
[clojure.set :as set]
|
||||
[taoensso.encore :as enc :refer [have have?]]
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.telemere.utils :as utils]
|
||||
[taoensso.telemere.impl :as impl]
|
||||
[taoensso.telemere :as tel])
|
||||
|
|
@ -61,24 +64,24 @@
|
|||
clojure.lang.IPersistentCollection
|
||||
(-put-attr! [v ^String k ^AttributesBuilder ab]
|
||||
(if (map? v)
|
||||
(when-let [^String s (enc/catching :common (enc/pr-edn* v))]
|
||||
(when-let [^String s (truss/catching :common (enc/pr-edn* v))]
|
||||
(.put ab k s))
|
||||
|
||||
(when-some [v1 (if (indexed? v) (nth v 0 nil) (first v))]
|
||||
(or
|
||||
(cond
|
||||
(string? v1) (enc/catching :common (.put ab k ^"[Ljava.lang.String;" (into-array String v)))
|
||||
(int? v1) (enc/catching :common (.put ab k (long-array v)))
|
||||
(float? v1) (enc/catching :common (.put ab k (double-array v)))
|
||||
(boolean? v1) (enc/catching :common (.put ab k (boolean-array v))))
|
||||
(string? v1) (truss/catching :common (.put ab k ^"[Ljava.lang.String;" (into-array String v)))
|
||||
(int? v1) (truss/catching :common (.put ab k (long-array v)))
|
||||
(float? v1) (truss/catching :common (.put ab k (double-array v)))
|
||||
(boolean? v1) (truss/catching :common (.put ab k (boolean-array v))))
|
||||
|
||||
(when-let [^String s (enc/catching :common (enc/pr-edn* v))]
|
||||
(when-let [^String s (truss/catching :common (enc/pr-edn* v))]
|
||||
(.put ab k s)))))
|
||||
ab)
|
||||
|
||||
Object
|
||||
(-put-attr! [v ^String k ^AttributesBuilder ab]
|
||||
(when-let [^String s (enc/catching :common (enc/pr-edn* v))]
|
||||
(when-let [^String s (truss/catching :common (enc/pr-edn* v))]
|
||||
(.put ab k s))))
|
||||
|
||||
(defmacro ^:private put-attr! [attrs-builder attr-name attr-val]
|
||||
|
|
@ -90,8 +93,9 @@
|
|||
(map? attrs) (enc/run-kv! (fn [k v] (put-attr! attrs-builder (attr-name k) v)) attrs) ; Unprefixed
|
||||
(instance? Attributes attrs) (.putAll attrs-builder ^Attributes attrs) ; Unprefixed
|
||||
:else
|
||||
(enc/unexpected-arg! attrs
|
||||
{:context `put-attrs!
|
||||
(truss/unexpected-arg! attrs
|
||||
{:param 'attrs
|
||||
:context `put-attrs!
|
||||
:expected #{nil map io.opentelemetry.api.common.Attributes}})))
|
||||
|
||||
(defn- merge-attrs!
|
||||
|
|
@ -131,34 +135,45 @@
|
|||
|
||||
(defn- signal->attrs
|
||||
"Returns `Attributes` for given signal.
|
||||
Ref. <https://opentelemetry.io/docs/specs/otel/logs/data-model/>."
|
||||
Ref. <https://opentelemetry.io/docs/specs/otel/logs/data-model/>,
|
||||
<https://opentelemetry.io/docs/specs/semconv/attributes-registry/>."
|
||||
^Attributes [signal]
|
||||
(let [ab (Attributes/builder)]
|
||||
(put-attr! ab "error" (utils/error-signal? signal)) ; Standard
|
||||
;; (put-attr! ab "host.name" (utils/hostname)) ; Standard
|
||||
|
||||
(when-let [{:keys [name ip]} (get signal :host)]
|
||||
(put-attr! ab "host.name" name) ; Standard
|
||||
;; Both standard
|
||||
(put-attr! ab "host.name" name)
|
||||
(put-attr! ab "host.ip" ip))
|
||||
|
||||
(when-let [level (get signal :level)]
|
||||
(put-attr! ab "level" ; Standard
|
||||
(level->string level)))
|
||||
(when-let [{:keys [name id]} (get signal :thread)]
|
||||
;; Both standard
|
||||
(put-attr! ab "thread.name" name)
|
||||
(put-attr! ab "thread.id" id))
|
||||
|
||||
(when-let [{:keys [type msg trace data]} (enc/ex-map (get signal :error))]
|
||||
(put-attr! ab "exception.type" type) ; Standard
|
||||
(put-attr! ab "exception.message" msg) ; Standard
|
||||
(when-let [level (get signal :level)]
|
||||
(put-attr! ab "level" (level->string level)))
|
||||
|
||||
(when-let [{:keys [type msg trace data]} (truss/ex-map (get signal :error))]
|
||||
;; Standard
|
||||
(put-attr! ab "exception.type" type)
|
||||
(put-attr! ab "exception.message" msg)
|
||||
(when trace
|
||||
(put-attr! ab "exception.stacktrace" ; Standard
|
||||
(put-attr! ab "exception.stacktrace"
|
||||
(#'utils/format-clj-stacktrace trace)))
|
||||
|
||||
(when data (merge-attrs! ab "exception.data" data)))
|
||||
(when data ; Non-standard
|
||||
(merge-attrs! ab "exception.data" data)))
|
||||
|
||||
(let [{:keys [ns line file, kind id uid]} signal]
|
||||
(put-attr! ab "ns" ns)
|
||||
(put-attr! ab "line" line)
|
||||
(put-attr! ab "file" file)
|
||||
(let [ns (get signal :ns)]
|
||||
;; All standard
|
||||
(put-attr! ab "code.namespace" ns)
|
||||
(when-let [[line column] (get signal :coords)]
|
||||
(when line (put-attr! ab "code.line.number" line))
|
||||
(when column (put-attr! ab "code.column.number" column))))
|
||||
|
||||
(let [{:keys [kind id uid]} signal]
|
||||
(put-attr! ab "kind" kind)
|
||||
(put-attr! ab "id" id)
|
||||
(put-attr! ab "uid" uid))
|
||||
|
|
@ -170,7 +185,7 @@
|
|||
(put-attr! ab "run.val" run-val)
|
||||
(put-attr! ab "run.nsecs" run-nsecs)))
|
||||
|
||||
(put-attr! ab "sample" (get signal :sample-rate))
|
||||
(put-attr! ab "sample" (get signal :sample))
|
||||
|
||||
(when-let [{:keys [id uid]} (get signal :parent)]
|
||||
(put-attr! ab "parent.id" id)
|
||||
|
|
@ -204,15 +219,15 @@
|
|||
|
||||
(if (or common-attrs trace-attrs)
|
||||
(let [ab (Attributes/builder)]
|
||||
(when-let [ns (get signal :ns)] (.put ab "ns" (str ns)))
|
||||
(when-let [line (get signal :line)] (.put ab "line" (long line)))
|
||||
(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 [attrs common-attrs] (put-attrs! ab attrs))
|
||||
(when-let [attrs trace-attrs] (put-attrs! ab attrs))
|
||||
(.build ab))
|
||||
|
||||
;; Common case
|
||||
(when-let [ns (get signal :ns)]
|
||||
(if-let [line (get signal :line)]
|
||||
(when-let [ns (get signal :ns)]
|
||||
(if-let [line (enc/get-in* signal [:coords 0])]
|
||||
(Attributes/of ak-ns ns, ak-line (long line))
|
||||
(Attributes/of ak-ns ns)))))))
|
||||
|
||||
|
|
@ -241,9 +256,10 @@
|
|||
(see `telemere/otel-default-providers_` for default).
|
||||
|
||||
Optional signal keys:
|
||||
`: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/attrs` ------- Attributes [1] to add to log records AND tracing spans/events
|
||||
`:otel/log-attrs` --- Attributes [1] to add to log records ONLY
|
||||
`:otel/trace-attrs` - Attributes [1] to add to tracing spans/events ONLY
|
||||
`: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 ∈
|
||||
#{nil boolean keyword string UUID long double string-vec long-vec double-vec boolean-vec}.
|
||||
|
|
@ -284,8 +300,8 @@
|
|||
(when-let [drained (enc/reset-in! span-buffer1_ #{})]
|
||||
(when-not (empty? drained)
|
||||
(span-buffer2_ (fn [old] (set/union old drained)))))))
|
||||
|
||||
3000 3000)))
|
||||
3000 3000)
|
||||
t3s))
|
||||
|
||||
stop-tracing!
|
||||
(fn stop-tracing! []
|
||||
|
|
@ -355,7 +371,7 @@
|
|||
(force msg_)
|
||||
(when-let [error (get signal :error)]
|
||||
(when (instance? Throwable error)
|
||||
(str (enc/ex-type error) ": " (enc/ex-message error)))))]
|
||||
(str (truss/ex-type error) ": " (ex-message error)))))]
|
||||
(.setBody lrb body))
|
||||
|
||||
;; Emit to `LogRecordExporter`
|
||||
|
|
@ -368,9 +384,9 @@
|
|||
|
||||
(comment
|
||||
(do
|
||||
(require '[taoensso.telemere :as t])
|
||||
(require '[taoensso.telemere :as tel])
|
||||
(def h1 (handler:open-telemetry))
|
||||
(let [{[s1 s2] :signals} (t/with-signals (t/trace! ::id1 (t/trace! ::id2 "form2")))]
|
||||
(let [{[s1 s2] :signals} (tel/with-signals (tel/trace! ::id1 (tel/trace! ::id2 "form2")))]
|
||||
(def s1 s1)
|
||||
(def s2 s2)))
|
||||
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
(ns taoensso.telemere.postal
|
||||
"Email handler using `postal`,
|
||||
Ref. <https://github.com/drewr/postal>."
|
||||
"Telemere -> email handler using `postal`,
|
||||
Ref. <https://github.com/drewr/postal>."
|
||||
(:require
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.encore.signals :as sigs]
|
||||
[taoensso.telemere.utils :as utils]
|
||||
|
|
@ -14,15 +15,14 @@
|
|||
|
||||
(def default-dispatch-opts
|
||||
{:min-level :info
|
||||
:rate-limit
|
||||
:limit
|
||||
[[5 (enc/msecs :mins 1)]
|
||||
[10 (enc/msecs :mins 15)]
|
||||
[15 (enc/msecs :hours 1)]
|
||||
[30 (enc/msecs :hours 6)]
|
||||
]})
|
||||
[30 (enc/msecs :hours 6)]]})
|
||||
|
||||
(defn handler:postal
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
|
||||
Needs `postal`, Ref. <https://github.com/drewr/postal>.
|
||||
|
||||
|
|
@ -33,8 +33,8 @@
|
|||
Useful for emailing important alerts to admins, etc.
|
||||
|
||||
Default handler dispatch options (override when calling `add-handler!`):
|
||||
`:min-level` - `:info`
|
||||
`:rate-limit` -
|
||||
`:min-level` - `:info`
|
||||
`:limit` -
|
||||
[[5 (enc/msecs :mins 1)] ; Max 5 emails in 1 min
|
||||
[10 (enc/msecs :mins 15)] ; Max 10 emails in 15 mins
|
||||
[15 (enc/msecs :hours 1)] ; Max 15 emails in 1 hour
|
||||
|
|
@ -58,7 +58,7 @@
|
|||
:cc \"engineering@example.com\"
|
||||
: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)
|
||||
|
||||
`:body-fn` - (fn [signal]) => email body content string,
|
||||
|
|
@ -76,8 +76,8 @@
|
|||
subject-fn (utils/signal-preamble-fn {:format-inst-fn nil})
|
||||
subject-max-len 128}}]
|
||||
|
||||
(when-not (map? conn-opts) (throw (ex-info "Expected `:conn-opts` map" (enc/typed-val conn-opts))))
|
||||
(when-not (map? msg-opts) (throw (ex-info "Expected `:msg-opts` map" (enc/typed-val msg-opts))))
|
||||
(when-not (map? conn-opts) (truss/ex-info! "Expected `:conn-opts` map" (truss/typed-val conn-opts)))
|
||||
(when-not (map? msg-opts) (truss/ex-info! "Expected `:msg-opts` map" (truss/typed-val msg-opts)))
|
||||
|
||||
(let [subject-fn
|
||||
(if-let [n subject-max-len]
|
||||
|
|
@ -108,7 +108,7 @@
|
|||
success? (= (get result :code) 0)]
|
||||
|
||||
(when-not success?
|
||||
(throw (ex-info "Failed to send email" result ex)))))))]
|
||||
(truss/ex-info! "Failed to send email" result ex))))))]
|
||||
|
||||
(with-meta handler-fn
|
||||
{:dispatch-opts default-dispatch-opts}))))
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
(ns taoensso.telemere.slack
|
||||
"Slack handler using `clj-slack`,
|
||||
Ref. <https://github.com/julienXX/clj-slack>"
|
||||
"Telemere -> Slack handler using `clj-slack`,
|
||||
Ref. <https://github.com/julienXX/clj-slack>"
|
||||
(:require
|
||||
[taoensso.encore :as enc :refer [have have?]]
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.telemere.utils :as utils]
|
||||
[clj-slack.core :as slack]
|
||||
[clj-slack.chat :as slack.chat]))
|
||||
|
|
@ -14,15 +15,14 @@
|
|||
|
||||
(def default-dispatch-opts
|
||||
{:min-level :info
|
||||
:rate-limit
|
||||
:limit
|
||||
[[5 (enc/msecs :mins 1)]
|
||||
[10 (enc/msecs :mins 15)]
|
||||
[15 (enc/msecs :hours 1)]
|
||||
[30 (enc/msecs :hours 6)]
|
||||
]})
|
||||
[30 (enc/msecs :hours 6)]]})
|
||||
|
||||
(defn handler:slack
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
|
||||
Needs `clj-slack`, Ref. <https://github.com/julienXX/clj-slack>.
|
||||
|
||||
|
|
@ -33,8 +33,8 @@
|
|||
Can output signals as human or machine-readable (edn, JSON) strings.
|
||||
|
||||
Default handler dispatch options (override when calling `add-handler!`):
|
||||
`:min-level` - `:info`
|
||||
`:rate-limit` -
|
||||
`:min-level` - `:info`
|
||||
`:limit` -
|
||||
[[5 (enc/msecs :mins 1)] ; Max 5 posts in 1 min
|
||||
[10 (enc/msecs :mins 15)] ; Max 10 posts in 15 mins
|
||||
[15 (enc/msecs :hours 1)] ; Max 15 posts in 1 hour
|
||||
|
|
@ -68,8 +68,8 @@
|
|||
{:keys [channel-id]} post-opts
|
||||
post-opts (dissoc post-opts :channel-id)
|
||||
|
||||
_ (when-not (string? token) (throw (ex-info "Expected `:conn-opts/token` string" (enc/typed-val token))))
|
||||
_ (when-not (string? channel-id) (throw (ex-info "Expected `:post-opts/channel-id` string" (enc/typed-val channel-id))))
|
||||
_ (when-not (string? token) (truss/ex-info! "Expected `:conn-opts/token` string" (truss/typed-val token)))
|
||||
_ (when-not (string? channel-id) (truss/ex-info! "Expected `:post-opts/channel-id` string" (truss/typed-val channel-id)))
|
||||
|
||||
handler-fn
|
||||
(fn a-handler:slack
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
(ns taoensso.telemere.sockets
|
||||
"Basic TCP/UDP socket handlers."
|
||||
"Telemere -> TCP/UDP socket handlers."
|
||||
(:require
|
||||
[taoensso.encore :as enc :refer [have have?]]
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.telemere.utils :as utils])
|
||||
|
||||
(:import
|
||||
|
|
@ -24,11 +25,11 @@
|
|||
Can output signals as human or machine-readable (edn, JSON) strings.
|
||||
|
||||
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]}
|
||||
`:host` - Destination TCP socket hostname string
|
||||
`:port` - Destination TCP socket port int
|
||||
`:ssl?` - Use SSL/TLS (default false)
|
||||
`:host` ------ Destination TCP socket hostname string
|
||||
`:port` ------ Destination TCP socket port int
|
||||
`:ssl?` ------ Use SSL/TLS (default false)
|
||||
`:connect-timeout-msecs` - Connection timeout (default 3000 msecs)
|
||||
|
||||
Limitations:
|
||||
|
|
@ -48,7 +49,7 @@
|
|||
(sw output)))))))
|
||||
|
||||
(defn handler:udp-socket
|
||||
"Highly experimental, subject to change.
|
||||
"Highly experimental, subject to change!
|
||||
Feedback very welcome!
|
||||
|
||||
Returns a signal handler that:
|
||||
|
|
@ -58,10 +59,10 @@
|
|||
Can output signals as human or machine-readable (edn, JSON) strings.
|
||||
|
||||
Options:
|
||||
`:output-fn` - (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
|
||||
`:socket-opts` - {:keys [host port max-packet-bytes]}
|
||||
`:host` - Destination UDP socket hostname string
|
||||
`:port` - Destination UDP socket port int
|
||||
`:output-fn` ---------- (fn [signal]) => string, see `format-signal-fn` or `pr-signal-fn`
|
||||
`:socket-opts` -------- {:keys [host port max-packet-bytes]}
|
||||
`:host` ------------- Destination UDP socket hostname string
|
||||
`:port` ------------- Destination UDP socket port int
|
||||
`:max-packet-bytes` - Max packet size (in bytes) before truncating output (default 512)
|
||||
|
||||
`:truncation-warning-fn`
|
||||
|
|
@ -90,8 +91,8 @@
|
|||
socket (DatagramSocket.) ; No need to change socket once created
|
||||
lock (Object.)]
|
||||
|
||||
(when-not (string? host) (throw (ex-info "Expected `:host` string" (enc/typed-val host))))
|
||||
(when-not (int? port) (throw (ex-info "Expected `:port` int" (enc/typed-val port))))
|
||||
(when-not (string? host) (truss/ex-info! "Expected `:host` string" (truss/typed-val host)))
|
||||
(when-not (int? port) (truss/ex-info! "Expected `:port` int" (truss/typed-val port)))
|
||||
|
||||
(.connect socket (InetSocketAddress. (str host) (int port)))
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
(ns taoensso.telemere.streams
|
||||
"Interop support for standard stream/s -> Telemere."
|
||||
(:refer-clojure :exclude [binding])
|
||||
"Standard streams -> Telemere interop."
|
||||
(:require
|
||||
[taoensso.encore :as enc :refer [binding have have?]]
|
||||
[taoensso.encore :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.telemere.impl :as impl]))
|
||||
|
||||
(enc/defonce ^:private orig-*out* "Original `*out*` on ns load" *out*)
|
||||
|
|
@ -35,12 +35,11 @@
|
|||
*err* (or prev-*err* orig-*err*)]
|
||||
|
||||
(impl/signal!
|
||||
{:location nil
|
||||
:ns nil
|
||||
:kind kind
|
||||
:level level
|
||||
:id id
|
||||
:msg msg})))))))]
|
||||
{:ns nil
|
||||
:kind kind
|
||||
:level level
|
||||
:id id
|
||||
:msg msg})))))))]
|
||||
|
||||
(java.io.PrintStream. baos true ; Auto flush
|
||||
java.nio.charset.StandardCharsets/UTF_8)))
|
||||
|
|
@ -3,7 +3,8 @@
|
|||
Intended to help ease migration from Timbre to Telemere."
|
||||
(:require
|
||||
[clojure.string :as str]
|
||||
[taoensso.encore :as enc :refer [have have?]]
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.telemere.impl :as impl]
|
||||
[taoensso.telemere :as tel]))
|
||||
|
||||
|
|
@ -21,10 +22,10 @@
|
|||
(defn ^:no-doc parse-vargs
|
||||
"Private, don't use. Adapted from Timbre."
|
||||
[format-msg? vargs]
|
||||
(let [[v0] vargs]
|
||||
(let [[v0] vargs]
|
||||
|
||||
(if (enc/error? v0)
|
||||
(let [error v0
|
||||
(if (truss/error? v0)
|
||||
(let [error v0
|
||||
vargs (enc/vrest vargs)
|
||||
pattern (if format-msg? (let [[v0] vargs] v0) nil)
|
||||
vargs (if format-msg? (enc/vrest vargs) vargs)
|
||||
|
|
@ -34,21 +35,21 @@
|
|||
(enc/format* pattern vargs)
|
||||
(enc/str-join " " (map arg-str) vargs)))]
|
||||
|
||||
[error msg {:vargs vargs}])
|
||||
[error msg vargs])
|
||||
|
||||
(let [md (if (and (map? v0) (get (meta v0) :meta)) v0 nil)
|
||||
error (get md :err)
|
||||
error (get md :err)
|
||||
md (dissoc md :err)
|
||||
vargs (if md (enc/vrest vargs) vargs)
|
||||
pattern (if format-msg? (let [[v0] vargs] v0) nil)
|
||||
vargs (if format-msg? (enc/vrest vargs) vargs)
|
||||
vargs (if md (enc/vrest vargs) vargs)
|
||||
pattern (if format-msg? (let [[v0] vargs] v0) nil)
|
||||
vargs (if format-msg? (enc/vrest vargs) vargs)
|
||||
msg
|
||||
(delay
|
||||
(if format-msg?
|
||||
(enc/format* pattern vargs)
|
||||
(enc/str-join " " (map arg-str) vargs)))]
|
||||
|
||||
[error msg {:vargs vargs}])))))
|
||||
[error msg (when-not (empty? vargs) vargs)])))))
|
||||
|
||||
(comment
|
||||
(parse-vargs true [ "hello %s" "stu"])
|
||||
|
|
@ -60,15 +61,15 @@
|
|||
(defmacro ^:no-doc log!
|
||||
"Private, don't use."
|
||||
[level format-msg? vargs]
|
||||
(enc/keep-callsite
|
||||
(truss/keep-callsite
|
||||
`(when (impl/signal-allowed? {:kind :log, :level ~level, :id shim-id})
|
||||
(let [[error# msg# data#] (parse-vargs ~format-msg? ~vargs)]
|
||||
(let [[error# msg# vargs#] (parse-vargs ~format-msg? ~vargs)]
|
||||
(tel/log!
|
||||
{:allow? true
|
||||
:level ~level
|
||||
:id shim-id
|
||||
:error error#
|
||||
:data data#}
|
||||
{:allow? true
|
||||
:level ~level
|
||||
:id shim-id
|
||||
:error error#
|
||||
:timbre/vargs vargs#}
|
||||
msg#)
|
||||
nil)))))
|
||||
|
||||
|
|
@ -79,55 +80,72 @@
|
|||
|
||||
#?(:clj
|
||||
(do
|
||||
(defmacro log "Prefer `telemere/log!`, etc." [level & args] (enc/keep-callsite `(log! ~level false [~@args])))
|
||||
(defmacro trace "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :trace false [~@args])))
|
||||
(defmacro debug "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :debug false [~@args])))
|
||||
(defmacro info "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :info false [~@args])))
|
||||
(defmacro warn "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :warn false [~@args])))
|
||||
(defmacro error "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :error false [~@args])))
|
||||
(defmacro fatal "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :fatal false [~@args])))
|
||||
(defmacro report "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :report false [~@args])))
|
||||
(defmacro log "Prefer `telemere/log!`, etc." [level & args] (truss/keep-callsite `(log! ~level false [~@args])))
|
||||
(defmacro trace "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :trace false [~@args])))
|
||||
(defmacro debug "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :debug false [~@args])))
|
||||
(defmacro info "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :info false [~@args])))
|
||||
(defmacro warn "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :warn false [~@args])))
|
||||
(defmacro error "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :error false [~@args])))
|
||||
(defmacro fatal "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :fatal false [~@args])))
|
||||
(defmacro report "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :report false [~@args])))
|
||||
|
||||
(defmacro logf "Prefer `telemere/log!`, etc." [level & args] (enc/keep-callsite `(log! ~level true [~@args])))
|
||||
(defmacro tracef "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :trace true [~@args])))
|
||||
(defmacro debugf "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :debug true [~@args])))
|
||||
(defmacro infof "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :info true [~@args])))
|
||||
(defmacro warnf "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :warn true [~@args])))
|
||||
(defmacro errorf "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :error true [~@args])))
|
||||
(defmacro fatalf "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :fatal true [~@args])))
|
||||
(defmacro reportf "Prefer `telemere/log!`, etc." [& args] (enc/keep-callsite `(log! :report true [~@args])))))
|
||||
(defmacro logf "Prefer `telemere/log!`, etc." [level & args] (truss/keep-callsite `(log! ~level true [~@args])))
|
||||
(defmacro tracef "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :trace true [~@args])))
|
||||
(defmacro debugf "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :debug true [~@args])))
|
||||
(defmacro infof "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :info true [~@args])))
|
||||
(defmacro warnf "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :warn true [~@args])))
|
||||
(defmacro errorf "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :error true [~@args])))
|
||||
(defmacro fatalf "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :fatal true [~@args])))
|
||||
(defmacro reportf "Prefer `telemere/log!`, etc." [& args] (truss/keep-callsite `(log! :report true [~@args])))))
|
||||
|
||||
#?(:clj
|
||||
(defmacro spy!
|
||||
"Prefer `telemere/spy!`."
|
||||
([ form] (enc/keep-callsite `(spy! :debug nil ~form)))
|
||||
([level form] (enc/keep-callsite `(spy! ~level nil ~form)))
|
||||
(defmacro spy
|
||||
"Prefer `telemere/spy!`.
|
||||
|
||||
Note that for extra flexibility and improved interop with Open Telemetry,
|
||||
this shim intentionally handles errors (forms that throw) slightly differently
|
||||
to Timbre's original `spy`:
|
||||
|
||||
When the given `form` throws, this shim may create an ADDITIONAL signal of
|
||||
`:error` kind and level. The behaviour is equivalent to:
|
||||
|
||||
(telemere/spy! level ; Creates 0/1 `:spy` signals with given/default (`:debug`) level
|
||||
(telemere/catch->error! form)) ; Creates 0/1 `:error` signals with `:error` level
|
||||
|
||||
The additional signal helps to separate the success and error cases for
|
||||
individual filtering and/or handling."
|
||||
|
||||
([ form] (truss/keep-callsite `(spy :debug nil ~form)))
|
||||
([level form] (truss/keep-callsite `(spy ~level nil ~form)))
|
||||
([level form-name form]
|
||||
(let [location* (enc/get-source &form &env)
|
||||
(let [ns (str *ns*)
|
||||
coords (truss/callsite-coords &form)
|
||||
msg
|
||||
(if form-name
|
||||
`(fn [_form# value# error# nsecs#] (impl/default-trace-msg ~form-name value# error# nsecs#))
|
||||
`(fn [_form# value# error# nsecs#] (impl/default-trace-msg '~form value# error# nsecs#)))]
|
||||
|
||||
`(tel/spy!
|
||||
{:location* ~location*
|
||||
:id shim-id
|
||||
:level ~level
|
||||
:msg ~msg}
|
||||
{:ns ~ns
|
||||
:coords ~coords
|
||||
:id shim-id
|
||||
:level ~level
|
||||
:msg ~msg}
|
||||
|
||||
(tel/catch->error!
|
||||
{:location* ~location*
|
||||
:id shim-id}
|
||||
{:ns ~ns
|
||||
:coords ~coords
|
||||
:id shim-id}
|
||||
~form))))))
|
||||
|
||||
(comment
|
||||
(:level (tel/with-signal (spy! (/ 1 0))))
|
||||
(select-keys (tel/with-signal (spy! :info #_"my-form-name" (+ 1 2))) [:level :msg_])
|
||||
(select-keys (tel/with-signal (spy! :info #_"my-form-name" (throw (Exception. "Ex")))) [:level :msg_]))
|
||||
(:level (tel/with-signal (spy (/ 1 0))))
|
||||
(select-keys (tel/with-signal (spy :info #_"my-form-name" (+ 1 2))) [:level :msg_])
|
||||
(select-keys (tel/with-signal (spy :info #_"my-form-name" (throw (Exception. "Ex")))) [:level :msg_]))
|
||||
|
||||
#?(:clj (defmacro log-errors "Prefer `telemere/catch->error!`." [& body] (enc/keep-callsite `(tel/catch->error! {:id shim-id, :catch-val nil} (do ~@body)))))
|
||||
#?(:clj (defmacro log-and-rethrow-errors "Prefer `telemere/catch->error!`." [& body] (enc/keep-callsite `(tel/catch->error! {:id shim-id} (do ~@body)))))
|
||||
#?(:clj (defmacro logged-future "Prefer `telemere/catch->error!`." [& body] (enc/keep-callsite `(future (tel/catch->error! {:id shim-id} (do ~@body))))))
|
||||
#?(:clj (defmacro log-errors "Prefer `telemere/catch->error!`." [& body] (truss/keep-callsite `(tel/catch->error! {:id shim-id, :catch-val nil} (do ~@body)))))
|
||||
#?(:clj (defmacro log-and-rethrow-errors "Prefer `telemere/catch->error!`." [& body] (truss/keep-callsite `(tel/catch->error! {:id shim-id} (do ~@body)))))
|
||||
#?(:clj (defmacro logged-future "Prefer `telemere/catch->error!`." [& body] (truss/keep-callsite `(future (tel/catch->error! {:id shim-id} (do ~@body))))))
|
||||
|
||||
#?(:clj
|
||||
(defmacro refer-timbre
|
||||
|
|
@ -172,8 +190,11 @@
|
|||
:min-level nil
|
||||
:fn
|
||||
(fn [data]
|
||||
(let [{:keys [instant level context ?err output_
|
||||
?ns-str ?file ?line ?column]} data]
|
||||
(let [{:keys [instant level context ?err msg-type vargs
|
||||
?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!
|
||||
{:kind :timbre
|
||||
|
|
@ -182,9 +203,10 @@
|
|||
:ctx+ context
|
||||
|
||||
:ns ?ns-str
|
||||
:file ?file
|
||||
:line ?line
|
||||
:column ?column
|
||||
:coords (when ?line [?line ?column])
|
||||
:file ?file ; Non-standard, goes to kvs
|
||||
|
||||
:error ?err
|
||||
:msg (force output_)})))})
|
||||
:msg (when msg-type msg)
|
||||
|
||||
:timbre/vargs vargs})))})
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
(ns taoensso.telemere.tools-logging
|
||||
"Interop support for tools.logging -> Telemere.
|
||||
"tools.logging -> Telemere interop.
|
||||
Telemere will attempt to load this ns automatically when possible.
|
||||
|
||||
Naming conventions:
|
||||
|
|
@ -8,7 +8,8 @@
|
|||
`clojure.tools.logging` - For env config to match library's conventions."
|
||||
|
||||
(:require
|
||||
[taoensso.encore :as enc :refer [have have?]]
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.telemere.impl :as impl]
|
||||
[clojure.tools.logging :as ctl]))
|
||||
|
||||
|
|
@ -20,19 +21,19 @@
|
|||
(enabled? [_ level]
|
||||
(when-debug (println [:tools-logging/enabled? level logger-name]))
|
||||
(impl/signal-allowed?
|
||||
{:location {:ns logger-name}
|
||||
:kind :tools-logging
|
||||
:level level}))
|
||||
{:ns logger-name
|
||||
:kind :tools-logging
|
||||
:level level}))
|
||||
|
||||
(write! [_ level throwable message]
|
||||
(when-debug (println [:tools-logging/write! level logger-name]))
|
||||
(impl/signal!
|
||||
{:allow? true ; Pre-filtered by `enabled?` call
|
||||
:location {:ns logger-name}
|
||||
:kind :tools-logging
|
||||
:level level
|
||||
:error throwable
|
||||
:msg message})
|
||||
{:allow? true ; Pre-filtered by `enabled?` call
|
||||
:ns logger-name
|
||||
:kind :tools-logging
|
||||
:level level
|
||||
:error throwable
|
||||
:msg message})
|
||||
nil))
|
||||
|
||||
(deftype TelemereLoggerFactory []
|
||||
|
|
@ -45,9 +46,9 @@
|
|||
implementation (backend).
|
||||
|
||||
Called automatically if one of the following is \"true\":
|
||||
JVM property: `clojure.tools.logging.to-telemere`
|
||||
Env variable: `CLOJURE_TOOLS_LOGGING_TO_TELEMERE`
|
||||
Classpath resource: `clojure.tools.logging.to-telemere`"
|
||||
1. JVM property: `clojure.tools.logging.to-telemere`
|
||||
2. Env variable: `CLOJURE_TOOLS_LOGGING_TO_TELEMERE`
|
||||
3. Classpath resource: `clojure.tools.logging.to-telemere`"
|
||||
[]
|
||||
(impl/signal!
|
||||
{:kind :event
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
(ns taoensso.telemere.utils
|
||||
"Misc utils useful for Telemere handlers, middleware, etc."
|
||||
"Misc utils useful for Telemere handlers, transforms, etc."
|
||||
(:refer-clojure :exclude [newline])
|
||||
(:require
|
||||
[clojure.string :as str]
|
||||
#?(:clj [clojure.java.io :as jio])
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.encore.signals :as sigs]
|
||||
[taoensso.telemere.impl :as impl]))
|
||||
|
|
@ -15,12 +16,12 @@
|
|||
|
||||
;;;;
|
||||
|
||||
(enc/defaliases #_sigs/upper-qn sigs/format-level sigs/format-id sigs/format-location)
|
||||
(enc/defaliases #_sigs/upper-qn sigs/format-level sigs/format-id)
|
||||
|
||||
;;;; Unique IDs (UIDs)
|
||||
|
||||
(defn nano-uid-fn
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
Returns a (fn nano-uid [root?]) that returns a random nano-style uid string like:
|
||||
\"r76-B8LoIPs5lBG1_Uhdy\" - 126 bit (21 char) root uid
|
||||
\"tMEYoZH0K-\" - 60 bit (10 char) non-root (child) uid"
|
||||
|
|
@ -51,7 +52,7 @@
|
|||
(comment ((nano-uid-fn) true))
|
||||
|
||||
(defn hex-uid-fn
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
Returns a (fn hex-uid [root?]) that returns a random hex-style uid string like:
|
||||
\"05039666eb9dc3206475f44ab9f3d843\" - 128 bit (32 char) root uid
|
||||
\"721fcef639a51513\" - 64 bit (16 char) non-root (child) uid"
|
||||
|
|
@ -118,8 +119,9 @@
|
|||
:hex/secure (hex-uid-fn {:secure? true, :root-len root-len, :child-len child-len})
|
||||
nil)))
|
||||
|
||||
(enc/unexpected-arg! kind
|
||||
{:context `uid-fn
|
||||
(truss/unexpected-arg! kind
|
||||
{:param 'kind
|
||||
:context `uid-fn
|
||||
:expected
|
||||
'#{:uuid :uuid-str :default,
|
||||
:nano/secure [:nano/secure <root-len> <child-len>]
|
||||
|
|
@ -140,14 +142,14 @@
|
|||
#?(:cljs
|
||||
(defn js-console-logger
|
||||
"Returns JavaScript console logger to match given signal level:
|
||||
`:trace` -> `js/console.trace`,
|
||||
`:debug` -> `js/console.debug`,
|
||||
`:error` -> `js/console.error`, etc.
|
||||
|
||||
Defaults to `js.console.log` for unmatched signal levels.
|
||||
NB: assumes that `js/console` exists, handler constructors should check first!"
|
||||
[level]
|
||||
(case level
|
||||
:trace js/console.trace
|
||||
:trace js/console.debug
|
||||
:debug js/console.debug
|
||||
:info js/console.info
|
||||
:warn js/console.warn
|
||||
|
|
@ -159,8 +161,7 @@
|
|||
(comment (js-console-logger))
|
||||
|
||||
(defn error-signal?
|
||||
"Experimental, subject to change.
|
||||
Returns true iff given signal has an `:error` value, or a `:kind` or `:level`
|
||||
"Returns true iff given signal has an `:error` value, or a `:kind` or `:level`
|
||||
that indicates that it's an error."
|
||||
#?(:cljs {:tag 'boolean})
|
||||
[signal]
|
||||
|
|
@ -205,10 +206,10 @@
|
|||
[{:keys [type msg data]} ...] cause chain."
|
||||
[signal]
|
||||
(enc/if-let [error (get signal :error)
|
||||
chain (enc/ex-chain :as-map error)]
|
||||
chain (truss/ex-chain :as-map error)]
|
||||
(assoc signal :error chain)
|
||||
(do signal)))
|
||||
|
||||
|
||||
;;;; Files
|
||||
|
||||
#?(:clj (defn ^:no-doc as-file ^java.io.File [file] (jio/as-file file)))
|
||||
|
|
@ -219,25 +220,34 @@
|
|||
^java.io.File [file]
|
||||
(let [file (as-file file)]
|
||||
(when-not (.exists file)
|
||||
(when-let [parent (.getParentFile (.getCanonicalFile file))] (.mkdirs parent))
|
||||
(.createNewFile file))
|
||||
(truss/catching
|
||||
(let [path (.toPath 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)
|
||||
file
|
||||
(throw
|
||||
(ex-info "Unable to prepare writable `java.io.File`"
|
||||
{:path (.getAbsolutePath file)}))))))
|
||||
(truss/ex-info! "Unable to prepare writable `java.io.File`"
|
||||
{:path (.getAbsolutePath file)})))))
|
||||
|
||||
(comment
|
||||
(let [f (writeable-file! "__test-file.txt")]
|
||||
(enc/qb 1e4 ; [10.27 37.69]
|
||||
(.exists f)
|
||||
(.canWrite f))))
|
||||
|
||||
#?(:clj
|
||||
(defn ^:no-doc file-stream
|
||||
(defn ^:no-doc writeable-file-stream!
|
||||
"Private, don't use.
|
||||
Returns new `java.io.FileOutputStream` for given `java.io.File`."
|
||||
Returns new writeable `java.io.FileOutputStream` or throws."
|
||||
^java.io.FileOutputStream [file append?]
|
||||
(java.io.FileOutputStream. (as-file file) (boolean append?))))
|
||||
(java.io.FileOutputStream. (writeable-file! file) (boolean append?))))
|
||||
|
||||
#?(:clj
|
||||
(defn file-writer
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
Opens the specified file and returns a stateful fn of 2 arities:
|
||||
[content] => Writes given content to file, or noops if closed.
|
||||
[] => Closes the writer.
|
||||
|
|
@ -253,10 +263,10 @@
|
|||
[{:keys [file append?]
|
||||
:or {append? true}}]
|
||||
|
||||
(when-not file (throw (ex-info "Expected `:file` value" (enc/typed-val file))))
|
||||
(when-not file (truss/ex-info! "Expected `:file` value" (truss/typed-val file)))
|
||||
|
||||
(let [file (writeable-file! file)
|
||||
stream_ (volatile! (file-stream file append?))
|
||||
(let [file (as-file file)
|
||||
stream_ (volatile! (writeable-file-stream! file append?))
|
||||
open?_ (enc/latom true)
|
||||
|
||||
close!
|
||||
|
|
@ -270,7 +280,7 @@
|
|||
reset!
|
||||
(fn []
|
||||
(close!)
|
||||
(vreset! stream_ (file-stream file append?))
|
||||
(vreset! stream_ (writeable-file-stream! file append?))
|
||||
(reset! open?_ true)
|
||||
true)
|
||||
|
||||
|
|
@ -281,11 +291,11 @@
|
|||
(.flush stream)
|
||||
true))
|
||||
|
||||
file-exists!
|
||||
check-file!
|
||||
(let [rl (enc/rate-limiter-once-per 100)]
|
||||
(fn []
|
||||
(or (rl) (.exists file)
|
||||
(throw (java.io.IOException. "File doesn't exist")))))
|
||||
(or (rl) #_(.exists file) (.canWrite file)
|
||||
(throw (java.io.IOException. "File doesn't exist or isn't writeable")))))
|
||||
|
||||
lock (Object.)]
|
||||
|
||||
|
|
@ -301,7 +311,7 @@
|
|||
ba (enc/str->utf8-ba (str content))]
|
||||
(locking lock
|
||||
(try
|
||||
(file-exists!)
|
||||
(check-file!)
|
||||
(write-ba! ba)
|
||||
(catch java.io.IOException _ ; Retry once
|
||||
(reset!)
|
||||
|
|
@ -342,10 +352,10 @@
|
|||
Useful for basic handlers that write to a TCP socket, etc.
|
||||
|
||||
Options:
|
||||
`:ssl?` - Use SSL/TLS?
|
||||
`:ssl?` ------------------ Use SSL/TLS?
|
||||
`:connect-timeout-msecs` - Connection timeout (default 3000 msecs)
|
||||
`:socket-fn` - (fn [host port timeout]) => `java.net.Socket`
|
||||
`:ssl-socket-fn` - (fn [socket host port]) => `java.net.Socket`
|
||||
`:socket-fn` ------------- (fn [host port timeout]) => `java.net.Socket`
|
||||
`:ssl-socket-fn` --------- (fn [socket host port]) => `java.net.Socket`
|
||||
|
||||
Notes:
|
||||
- Writer should be manually closed after use (with zero-arity call).
|
||||
|
|
@ -364,8 +374,8 @@
|
|||
socket-fn default-socket-fn
|
||||
ssl-socket-fn default-ssl-socket-fn}}]
|
||||
|
||||
(when-not (string? host) (throw (ex-info "Expected `:host` string" (enc/typed-val host))))
|
||||
(when-not (int? port) (throw (ex-info "Expected `:port` int" (enc/typed-val port))))
|
||||
(when-not (string? host) (truss/ex-info! "Expected `:host` string" (truss/typed-val host)))
|
||||
(when-not (int? port) (truss/ex-info! "Expected `:port` int" (truss/typed-val port)))
|
||||
|
||||
(let [new-conn! ; => [<java.net.Socket> <java.io.OutputStream>], or throws
|
||||
(fn []
|
||||
|
|
@ -379,7 +389,7 @@
|
|||
[socket (.getOutputStream socket)])
|
||||
|
||||
(catch Exception ex
|
||||
(throw (ex-info "Failed to create connection" opts ex)))))
|
||||
(truss/ex-info! "Failed to create connection" opts ex))))
|
||||
|
||||
conn_ (volatile! (new-conn!))
|
||||
open?_ (enc/latom true)
|
||||
|
|
@ -440,7 +450,7 @@
|
|||
;;;; Formatters
|
||||
|
||||
(defn format-nsecs-fn
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
Returns a (fn format [nanosecs]) that:
|
||||
- Takes a long nanoseconds (e.g. runtime).
|
||||
- Returns a human-readable string like:
|
||||
|
|
@ -464,10 +474,10 @@
|
|||
(s+nl " " class "/" method " at " file ":" line)))
|
||||
(str sb))))
|
||||
|
||||
(comment (println (format-clj-stacktrace (:trace (enc/ex-map (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"})))))))
|
||||
(comment (println (format-clj-stacktrace (:trace (truss/ex-map (truss/ex-info "Ex2" {:k2 "v2"} (truss/ex-info "Ex1" {:k1 "v1"})))))))
|
||||
|
||||
(defn format-error-fn
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
Returns a (fn format [error]) that:
|
||||
- Takes a platform error (`Throwable` or `js/Error`).
|
||||
- Returns a human-readable error string."
|
||||
|
|
@ -477,17 +487,17 @@
|
|||
nls enc/newlines]
|
||||
|
||||
(fn format-error [error]
|
||||
(when-let [em (enc/ex-map error)]
|
||||
(when-let [em (truss/ex-map error)]
|
||||
(let [sb (enc/str-builder)
|
||||
s+ (partial enc/sb-append sb)
|
||||
{:keys [chain trace]} em]
|
||||
|
||||
(let [s+cause (enc/sb-appender sb (str nls "Caused: "))]
|
||||
(s+ " Root: ")
|
||||
(s+ "Root: ")
|
||||
(doseq [{:keys [type msg data]} (rseq chain)]
|
||||
(s+cause type " - " msg)
|
||||
(when data
|
||||
(s+ nl " data: " (enc/pr-edn* data)))))
|
||||
(s+ nl "data: " (enc/pr-edn* data)))))
|
||||
|
||||
(when trace
|
||||
(s+ nl nl "Root stack trace:" nl)
|
||||
|
|
@ -497,23 +507,23 @@
|
|||
(str sb)))))))
|
||||
|
||||
(comment
|
||||
(do (throw (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"}))))
|
||||
(do (enc/ex-map (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"}))))
|
||||
(println (str "--\n" ((format-error-fn) (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"}))))))
|
||||
(do (throw (truss/ex-info "Ex2" {:k2 "v2"} (truss/ex-info "Ex1" {:k1 "v1"}))))
|
||||
(do (truss/ex-map (truss/ex-info "Ex2" {:k2 "v2"} (truss/ex-info "Ex1" {:k1 "v1"}))))
|
||||
(println (str "--\n" ((format-error-fn) (truss/ex-info "Ex2" {:k2 "v2"} (truss/ex-info "Ex1" {:k1 "v1"}))))))
|
||||
|
||||
;;;;
|
||||
|
||||
(defn signal-preamble-fn
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
Returns a (fn preamble [signal]) that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Returns a signal preamble ?string like:
|
||||
\"2024-03-26T11:14:51.806Z INFO EVENT Hostname taoensso.telemere(2,21) ::ev-id msg\"
|
||||
\"2024-03-26T11:14:51.806Z INFO EVENT Hostname taoensso.telemere[2,21] ::ev-id msg\"
|
||||
|
||||
Options:
|
||||
`:format-inst-fn` - (fn format [instant]) => string.
|
||||
`:format-id-fn` - (fn format [ns id]) => string.
|
||||
`:format-msg-fn` - (fn format [msg]) => string."
|
||||
`:format-id-fn` --- (fn format [ns id]) => string.
|
||||
`:format-msg-fn` -- (fn format [msg]) => string."
|
||||
([] (signal-preamble-fn nil))
|
||||
([{:keys [format-inst-fn format-id-fn format-msg-fn]
|
||||
:or {format-inst-fn (format-inst-fn)
|
||||
|
|
@ -527,19 +537,13 @@
|
|||
|
||||
(when inst (when-let [ff format-inst-fn] (s+spc (ff inst))))
|
||||
(when level (s+spc (format-level level)))
|
||||
(when kind (s+spc (sigs/upper-qn kind)))
|
||||
|
||||
(if kind (s+spc (sigs/upper-qn kind)) (s+spc "DEFAULT"))
|
||||
#?(:clj (s+spc (hostname)))
|
||||
|
||||
;; As `format-location`
|
||||
(when-let [base (or ns (get signal :file))]
|
||||
(let [s+ (partial enc/sb-append sb)] ; Without separator
|
||||
(s+ " " base)
|
||||
(when-let [l (get signal :line)]
|
||||
(s+ "(" l)
|
||||
(when-let [c (get signal :column)] (s+ "," c))
|
||||
(s+ ")"))))
|
||||
#?(:clj
|
||||
(when-let [hostname (enc/get-in* signal [:host :name])]
|
||||
(s+spc hostname)))
|
||||
|
||||
(when ns (s+spc (sigs/format-callsite ns (get signal :coords))))
|
||||
(when id (when-let [ff format-id-fn] (s+spc (ff ns id))))
|
||||
(enc/when-let [ff format-msg-fn
|
||||
msg (force msg_)]
|
||||
|
|
@ -552,20 +556,24 @@
|
|||
|
||||
(defn- format-parent [ns {:keys [id uid]}]
|
||||
(if id
|
||||
{:id (symbol (format-id ns id)), :uid uid}
|
||||
{:id (symbol (format-id ns id))}))
|
||||
(if uid
|
||||
{:id (symbol (format-id ns id)), :uid uid}
|
||||
{:id (symbol (format-id ns id))})
|
||||
(if uid
|
||||
{:uid uid}
|
||||
nil)))
|
||||
|
||||
(comment (str (format-parent (str *ns*) {:id ::id1 :uid "uid1"})))
|
||||
|
||||
(defn signal-content-fn
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
Returns a (fn content [signal]) that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Returns a human-readable signal content ?string (incl. data, ctx, etc.).
|
||||
|
||||
Options:
|
||||
`:raw-error?` - Retain unformatted error? (default false)
|
||||
`:incl-keys` - Subset of signal keys to retain from those
|
||||
`:raw-error?` ------ Retain unformatted error? (default false)
|
||||
`:incl-keys` ------- Subset of signal keys to retain from those
|
||||
otherwise excluded by default: #{:kvs :host :thread}
|
||||
`:format-nsecs-fn` - (fn [nanosecs]) => string.
|
||||
`:format-error-fn` - (fn [error]) => string."
|
||||
|
|
@ -597,17 +605,17 @@
|
|||
(let [af append-fn
|
||||
vf val-fn]
|
||||
|
||||
(let [{:keys [ns uid parent root data kvs ctx #?@(:clj [host thread]) sample-rate]} signal]
|
||||
(when sample-rate (af " sample: " (vf sample-rate)))
|
||||
(let [{:keys [ns uid parent root data kvs ctx #?@(:clj [host thread]) sample]} signal]
|
||||
(when sample (af " sample: " (vf sample)))
|
||||
(when uid (af " uid: " (vf 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]}
|
||||
|
||||
#?(:clj (when (enc/and* host incl-host?) (af " host: " (vf host)))) ; {:keys [ name ip]}
|
||||
#?(:clj (when (enc/and* thread incl-thread?) (af " thread: " (vf thread)))) ; {:keys [group name id]}
|
||||
#?(:clj (when (enc/and? host incl-host?) (af " host: " (vf host)))) ; {:keys [ name ip]}
|
||||
#?(:clj (when (enc/and? thread incl-thread?) (af " thread: " (vf thread)))) ; {:keys [group name id]}
|
||||
(when (enc/not-empty-coll data) (af " data: " (vf data)))
|
||||
(when (enc/not-empty-coll ctx) (af " ctx: " (vf ctx)))
|
||||
(when (enc/and* kvs incl-kvs?) (af " kvs: " (vf kvs))))
|
||||
(when (enc/and? kvs incl-kvs?) (af " kvs: " (vf kvs))))
|
||||
|
||||
(let [{:keys [run-form error]} signal]
|
||||
(when run-form
|
||||
|
|
@ -637,7 +645,7 @@
|
|||
((signal-content-fn) (tel/with-signal (tel/event! ::ev-id {:data {:k1 "v1"}}))))
|
||||
|
||||
(defn clean-signal-fn
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
Returns a (fn clean [signal]) that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Returns a minimal signal (map) ready for printing, etc.
|
||||
|
|
@ -650,9 +658,9 @@
|
|||
|
||||
Options:
|
||||
`:incl-nils?` - Include signal's keys with nil values? (default false)
|
||||
`:incl-kvs?` - Include signal's app-level root kvs? (default false)
|
||||
`:incl-keys` - Subset of signal keys to retain from those otherwise
|
||||
excluded by default: #{:location :kvs :file :host :thread}"
|
||||
`:incl-kvs?` -- Include signal's app-level root kvs? (default false)
|
||||
`:incl-keys` -- Subset of signal keys to retain from those otherwise
|
||||
excluded by default: #{:schema :kvs :host :thread}"
|
||||
([] (clean-signal-fn nil))
|
||||
([{:keys [incl-kvs? incl-nils? incl-keys] :as opts}]
|
||||
(let [assoc!*
|
||||
|
|
@ -660,11 +668,10 @@
|
|||
(fn [m k v] (if (nil? v) m (assoc! m k v))) ; As `remove-signal-nils`
|
||||
(do assoc!))
|
||||
|
||||
incl-location? (contains? incl-keys :location)
|
||||
incl-kvs-key? (contains? incl-keys :kvs)
|
||||
incl-file? (contains? incl-keys :file)
|
||||
incl-host? (contains? incl-keys :host)
|
||||
incl-thread? (contains? incl-keys :thread)]
|
||||
incl-schema? (contains? incl-keys :schema)
|
||||
incl-kvs-key? (contains? incl-keys :kvs)
|
||||
incl-host? (contains? incl-keys :host)
|
||||
incl-thread? (contains? incl-keys :thread)]
|
||||
|
||||
(fn clean-signal [signal]
|
||||
(when (map? signal)
|
||||
|
|
@ -676,23 +683,22 @@
|
|||
(clojure.core/into ()
|
||||
(clojure.core/disj
|
||||
taoensso.telemere.impl/standard-signal-keys
|
||||
:msg_ :error :location :kvs :file :host :thread))
|
||||
:msg_ :error :schema :kvs :host :thread))
|
||||
(assoc!* m k v)
|
||||
|
||||
;; Main keys to include with modified val
|
||||
:error (if-let [chain (enc/ex-chain :as-map v)] (assoc! m k chain) m) ; As `expand-signal-error`
|
||||
:msg_ (assoc!* m k (force v)) ; As `force-signal-msg`
|
||||
:error (if-let [chain (truss/ex-chain :as-map v)] (assoc! m k chain) m) ; As `expand-signal-error`
|
||||
:msg_ (assoc!* m k (force v)) ; As `force-signal-msg`
|
||||
|
||||
;; Implementation keys to always exclude
|
||||
(clojure.core/into ()
|
||||
taoensso.telemere.impl/impl-signal-keys) m ; noop
|
||||
|
||||
;;; Other keys to exclude by default
|
||||
:location (if incl-location? (assoc!* m k v) m)
|
||||
:kvs (if incl-kvs-key? (assoc!* m k v) m)
|
||||
:file (if incl-file? (assoc!* m k v) m)
|
||||
:thread (if incl-thread? (assoc!* m k v) m)
|
||||
:host (if incl-host? (assoc!* m k v) m)
|
||||
:schema (if incl-schema? (assoc!* m k v) m)
|
||||
:kvs (if incl-kvs-key? (assoc!* m k v) m)
|
||||
:thread (if incl-thread? (assoc!* m k v) m)
|
||||
:host (if incl-host? (assoc!* m k v) m)
|
||||
|
||||
;; Other (app-level) keys
|
||||
(enc/cond
|
||||
|
|
@ -706,14 +712,14 @@
|
|||
(comment ((clean-signal-fn {:incl-keys #{:a}}) {:level :info, :id nil, :a "a", :b "b", :msg_ (delay "hi")}))
|
||||
|
||||
(defn pr-signal-fn
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
Returns a (fn pr [signal]) that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Returns a machine-readable signal string.
|
||||
|
||||
Options:
|
||||
`:pr-fn` - ∈ #{<unary-fn> :edn (default) :json (Cljs only)}
|
||||
`:clean-fn` - (fn [signal]) => clean signal map, see [1]
|
||||
`:pr-fn` --------- ∈ #{<unary-fn> :edn (default) :json (Cljs only)}
|
||||
`:clean-fn` ------ (fn [signal]) => clean signal map, see [1]
|
||||
`:incl-newline?` - Include terminating system newline? (default true)
|
||||
|
||||
Examples:
|
||||
|
|
@ -748,15 +754,14 @@
|
|||
:json
|
||||
#?(:cljs pr-json
|
||||
:clj
|
||||
(throw
|
||||
(ex-info "`:json` pr-fn only supported in Cljs. To output JSON in Clj, please provide an appropriate unary fn instead (e.g. jsonista/write-value-as-string)."
|
||||
{})))
|
||||
(truss/ex-info! "`:json` pr-fn only supported in Cljs. To output JSON in Clj, please provide an appropriate unary fn instead (e.g. jsonista/write-value-as-string)."
|
||||
{}))
|
||||
|
||||
(if (fn? pr-fn)
|
||||
(do pr-fn)
|
||||
(enc/unexpected-arg! pr-fn
|
||||
{:context `pr-signal-fn
|
||||
:param 'pr-fn
|
||||
(truss/unexpected-arg! pr-fn
|
||||
{:param 'pr-fn
|
||||
:context `pr-signal-fn
|
||||
:expected
|
||||
#?(:clj '#{:edn unary-fn}
|
||||
:cljs '#{:edn :json unary-fn})}))))]
|
||||
|
|
@ -772,15 +777,15 @@
|
|||
(tel/with-signal (tel/event! ::ev-id {:kvs {:k1 "v1"}}))))
|
||||
|
||||
(defn format-signal-fn
|
||||
"Experimental, subject to change.
|
||||
"Alpha, subject to change.
|
||||
Returns a (fn format [signal]) that:
|
||||
- Takes a Telemere signal (map).
|
||||
- Returns a human-readable signal string.
|
||||
|
||||
Options:
|
||||
`:incl-newline?` - Include terminating system newline? (default true)
|
||||
`:preamble-fn` - (fn [signal]) => signal preamble string, see [1]
|
||||
`:content-fn` - (fn [signal]) => signal content string, see [2]
|
||||
`:preamble-fn` --- (fn [signal]) => signal preamble string, see [1]
|
||||
`:content-fn` ---- (fn [signal]) => signal content string, see [2]
|
||||
|
||||
[1] `taoensso.telemere.utils/signal-preamble-fn`, etc.
|
||||
[2] `taoensso.telemere.utils/signal-content-fn`, etc.
|
||||
|
|
@ -813,5 +818,5 @@
|
|||
{:my-k1 #{:a :b :c}
|
||||
:msg "hi"
|
||||
:data {:a :A}
|
||||
;; :error (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"}))
|
||||
;; :error (truss/ex-info "Ex2" {:k2 "v2"} (truss/ex-info "Ex1" {:k1 "v1"}))
|
||||
:run (/ 1 0)}))))))
|
||||
|
|
@ -2,7 +2,8 @@
|
|||
(:require
|
||||
[clojure.test :as test :refer [deftest testing is]]
|
||||
[clojure.core.async :as async]
|
||||
[taoensso.encore :as enc :refer [throws? submap?] :rename {submap? sm?}]
|
||||
[taoensso.truss :as truss :refer [throws? submap?] :rename {submap? sm?}]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.encore.signals :as sigs]
|
||||
[taoensso.telemere :as tel]
|
||||
[taoensso.telemere.impl :as impl
|
||||
|
|
@ -32,33 +33,29 @@
|
|||
(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)))
|
||||
|
||||
(def ex-info-type (#'enc/ex-type (ex-info "" {})))
|
||||
(def ex1 (ex-info "Ex1" {}))
|
||||
(def ex2 (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"})))
|
||||
(def ex2-chain (enc/ex-chain :as-map ex2))
|
||||
(def ex-info-type (truss/ex-type (ex-info "" {})))
|
||||
(def ex1 (ex-info "Ex1" {}))
|
||||
(def ex2 (ex-info "Ex2" {:k2 "v2"} (ex-info "Ex1" {:k1 "v1"})))
|
||||
(def ex2-chain (truss/ex-chain :as-map ex2))
|
||||
(defn ex1! [] (throw ex1))
|
||||
(defn ex1? [x] (= (truss/ex-root x) ex1)))
|
||||
|
||||
(defn ex1? [x] (= (enc/ex-root x) ex1))
|
||||
(def pstr? (enc/pred string?))
|
||||
(def pnat-int? (enc/pred enc/nat-int?))
|
||||
(def pinst? (enc/pred enc/inst?)))
|
||||
|
||||
(let [rt-sig-filter_ (atom nil)
|
||||
sig-handlers_ (atom nil)]
|
||||
(let [rt-call-filter_ (atom nil)
|
||||
sig-handlers_ (atom nil)]
|
||||
|
||||
(test/use-fixtures :once
|
||||
(enc/test-fixtures
|
||||
{:before
|
||||
(fn []
|
||||
(reset! rt-sig-filter_ impl/*rt-sig-filter*)
|
||||
(reset! sig-handlers_ impl/*sig-handlers*)
|
||||
(enc/set-var-root! impl/*sig-handlers* nil)
|
||||
(enc/set-var-root! impl/*rt-sig-filter* nil))
|
||||
(reset! rt-call-filter_ impl/*rt-call-filter*)
|
||||
(reset! sig-handlers_ impl/*sig-handlers*)
|
||||
(enc/set-var-root! impl/*sig-handlers* nil)
|
||||
(enc/set-var-root! impl/*rt-call-filter* nil))
|
||||
|
||||
:after
|
||||
(fn []
|
||||
(enc/set-var-root! impl/*rt-sig-filter* @rt-sig-filter_)
|
||||
(enc/set-var-root! impl/*sig-handlers* @sig-handlers_))})))
|
||||
(enc/set-var-root! impl/*rt-call-filter* @rt-call-filter_)
|
||||
(enc/set-var-root! impl/*sig-handlers* @sig-handlers_))})))
|
||||
|
||||
;;;;
|
||||
|
||||
|
|
@ -79,6 +76,8 @@
|
|||
|
||||
"x y nil [\"z1\" nil \"z2\" \"z3\"] s1 nil s2 s3 s4 :kw"))])
|
||||
|
||||
(defn coords? [x] (and (vector? x) (let [[line column] x] (and (enc/nat-int? line) (enc/nat-int? column)))))
|
||||
|
||||
(deftest _signal-macro
|
||||
[(is (= (with-sigs (sig! {:level :info, :elide? true })) {:value nil}) "With compile-time elision")
|
||||
(is (= (with-sigs (sig! {:level :info, :elide? true, :run (+ 1 2)})) {:value 3}) "With compile-time elision, run-form")
|
||||
|
|
@ -92,7 +91,7 @@
|
|||
{rv2 :value, [sv2] :signals} (with-sigs (sig! {:level :info, :run (+ 1 2)}))]
|
||||
|
||||
[(is (= rv1 true)) (is (sm? sv1 {:ns "taoensso.telemere-tests", :level :info, :run-form nil, :run-val nil, :run-nsecs nil}))
|
||||
(is (= rv2 3)) (is (sm? sv2 {:ns "taoensso.telemere-tests", :level :info, :run-form '(+ 1 2), :run-val 3, :run-nsecs pnat-int?}))])
|
||||
(is (= rv2 3)) (is (sm? sv2 {:ns "taoensso.telemere-tests", :level :info, :run-form '(+ 1 2), :run-val 3, :run-nsecs enc/nat-int?}))])
|
||||
|
||||
(testing "Nested signals"
|
||||
(let [{outer-rv :value, [outer-sv] :signals} (with-sigs (sig! {:level :info, :run (with-sigs (sig! {:level :warn, :run "inner-run"}))}))
|
||||
|
|
@ -126,6 +125,12 @@
|
|||
(is (> (inst-ms end) (inst-ms start)) "End instant is start + run-nsecs")
|
||||
(is (< (inst-ms end) 1e6) "End instant is start + run-nsecs")])]))
|
||||
|
||||
(testing "Callsite overrides"
|
||||
[(is (sm? (with-sig (sig! { })) {:ns "taoensso.telemere-tests", :coords coords?}))
|
||||
(is (sm? (with-sig (sig! {:ns "custom-ns" })) {:ns "custom-ns", :coords nil}) "Custom ns clears coords")
|
||||
(is (sm? (with-sig (sig! { :coords [1 2]})) {:ns "taoensso.telemere-tests", :coords [1 2]}) "Custom coords")
|
||||
(is (sm? (with-sig (sig! {:ns "custom-ns", :coords [1 2]})) {:ns "custom-ns", :coords [1 2]}) "Custom ns + coords")])
|
||||
|
||||
(testing "Support arb extra user kvs"
|
||||
(let [sv (with-sig (sig! {:level :info, :my-k1 "v1", :my-k2 "v2"}))]
|
||||
(is (sm? sv {:level :info, :my-k1 "v1", :my-k2 "v2"
|
||||
|
|
@ -245,78 +250,83 @@
|
|||
(with-sig (sig! {:level :info, :ctx+ {:baz :qux}})))]
|
||||
(is (sm? sv {:ctx {:foo :bar, :baz :qux}}) "`*ctx*` can be updated via call opt"))])
|
||||
|
||||
(testing "Middleware"
|
||||
[(testing "Dynamic middleware (`*middleware*`)"
|
||||
[(is (sm? (tel/with-middleware nil (with-sig (sig! {:level :info }))) {:level :info }) "nil middleware ~ identity")
|
||||
(is (sm? (tel/with-middleware identity (with-sig (sig! {:level :info }))) {:level :info }) "nil middleware ~ identity")
|
||||
(is (sm? (tel/with-middleware #(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, :middleware #(assoc % :foo 2)}))) {:level :info, :foo 2 }) "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-middleware #(do nil) (with-sig (sig! {:level :info }))) nil) "return nil => suppress")
|
||||
(is (sm? (tel/with-middleware #(do nil) (with-sig (sig! {:level :info, :middleware nil}))) {:level :info}) "call > dynamic")])
|
||||
(testing "Transforms"
|
||||
[(testing "Dynamic transforms (`*xfn*`)"
|
||||
[(is (sm? (tel/with-xfn nil (with-sig (sig! {:level :info }))) {:level :info }) "nil xfn ~ identity")
|
||||
(is (sm? (tel/with-xfn identity (with-sig (sig! {:level :info }))) {:level :info }) "nil xfn ~ identity")
|
||||
(is (sm? (tel/with-xfn #(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-xfn #(assoc % :foo 1) (with-sig (sig! {:level :info, :xfn nil}))) {:level :info, :foo :submap/nx}) "call > dynamic")
|
||||
(is (= (tel/with-xfn #(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")])
|
||||
|
||||
(testing "Call middleware"
|
||||
(testing "Call transforms"
|
||||
(let [c (enc/counter)
|
||||
{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), :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), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c)))}))
|
||||
{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, :middleware (fn [_] nil)}))]
|
||||
{rv1 :value, [sv1] :signals} (with-sigs :raw nil (sig! {:level :info, :run (c), :xfn (tel/comp-xfn #(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}))
|
||||
{rv3 :value, [sv3] :signals} (with-sigs :raw nil (sig! {:level :info, :run (c), :xfn (tel/comp-xfn #(assoc % :m1 (c)) #(assoc % :m2 (c)))}))
|
||||
{rv4 :value, [sv4] :signals} (with-sigs :raw nil (sig! {:level :info, :xfn (fn [_] "signal-value")}))
|
||||
{rv5 :value, [sv5] :signals} (with-sigs :raw nil (sig! {:level :info, :xfn (fn [_] nil)}))]
|
||||
|
||||
[(is (= rv1 0)) (is (sm? sv1 {:m1 1 :m2 2}))
|
||||
(is (= rv2 3)) (is (nil? sv2))
|
||||
(is (= rv3 4)) (is (sm? sv3 {:m1 5 :m2 6}))
|
||||
(is (= rv4 true)) (is (= sv4 "signal-value"))
|
||||
(is (= rv5 true)) (is (nil? sv5))
|
||||
(is (= @c 7) "3x run + 4x middleware")]))
|
||||
(is (= @c 7) "3x run + 4x xfn")]))
|
||||
|
||||
(testing "Mixed middleware"
|
||||
(testing "Mixed transforms"
|
||||
[(let [sv
|
||||
(binding [tel/*middleware* #(assoc % :foo true)]
|
||||
(with-sig (sig! {:level :info, :middleware+ #(assoc % :bar true)})))]
|
||||
(binding [tel/*xfn* #(assoc % :foo true)]
|
||||
(with-sig (sig! {:level :info, :xfn+ #(assoc % :bar true)})))]
|
||||
(is (sm? sv {:foo true, :bar true})))])])
|
||||
|
||||
#?(:clj
|
||||
(testing "Printing"
|
||||
(let [sv1 (dissoc (with-sig (sig! {:level :info, :run (+ 1 2), :my-k1 :my-v1})) :_otel-context)
|
||||
sv1 ; Ensure instants are printable
|
||||
(-> sv1
|
||||
(update-in [:inst] enc/inst->udt)
|
||||
(update-in [:end-inst] enc/inst->udt))]
|
||||
(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{"))
|
||||
|
||||
[(is (= sv1 (read-string (pr-str sv1))))])))])
|
||||
#?(:clj
|
||||
(let [sv1 (dissoc (with-sig (sig! {:level :info, :run (+ 1 2), :my-k1 :my-v1})) :_otel-context)
|
||||
sv1 ; Ensure instants are printable
|
||||
(-> sv1
|
||||
(update-in [:inst] enc/inst->udt)
|
||||
(update-in [:end-inst] enc/inst->udt))]
|
||||
(is (= sv1 (read-string (pr-str sv1))) "Equality holds")))])])
|
||||
|
||||
(deftest _handlers
|
||||
;; Basic handler tests are in Encore
|
||||
[(testing "Handler middleware"
|
||||
[(testing "Handler transforms"
|
||||
(let [c (enc/counter)
|
||||
sv-h1_ (atom nil)
|
||||
sv-h2_ (atom nil)
|
||||
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, :middleware (tel/comp-middleware #(assoc % :hm1 (c)) #(assoc % :hm2 (c)))})]
|
||||
wh1 (sigs/wrap-handler :hid1 (fn [sv] (reset! sv-h1_ 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, :xfn (tel/comp-xfn #(assoc % :hm1 (c)) #(assoc % :hm2 (c)))})]
|
||||
|
||||
;; Note that call middleware output is cached and shared across all handlers
|
||||
;; Note that call xfn output is cached and shared across all handlers
|
||||
(binding [impl/*sig-handlers* [wh1 wh2]]
|
||||
(let [;; 1x run + 4x handler middleware + 2x call middleware = 7x
|
||||
rv1 (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c)))})
|
||||
(let [;; 1x run + 4x handler xfn + 2x call xfn = 7x
|
||||
rv1 (sig! {:level :info, :run (c), :xfn (tel/comp-xfn #(assoc % :m1 (c)) #(assoc % :m2 (c)))})
|
||||
sv1-h1 @sv-h1_
|
||||
sv1-h2 @sv-h2_
|
||||
c1 @c
|
||||
|
||||
;; 1x run
|
||||
rv2 (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c))), :allow? false})
|
||||
rv2 (sig! {:level :info, :run (c), :xfn (tel/comp-xfn #(assoc % :m1 (c)) #(assoc % :m2 (c))), :allow? false})
|
||||
sv2-h1 @sv-h1_
|
||||
sv2-h2 @sv-h2_
|
||||
c2 @c ; 8
|
||||
|
||||
;; 1x run + 4x handler middleware + 2x call middleware = 7x
|
||||
rv3 (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c)))})
|
||||
;; 1x run + 4x handler xfn + 2x call xfn = 7x
|
||||
rv3 (sig! {:level :info, :run (c), :xfn (tel/comp-xfn #(assoc % :m1 (c)) #(assoc % :m2 (c)))})
|
||||
sv3-h1 @sv-h1_
|
||||
sv3-h2 @sv-h2_
|
||||
c3 @c ; 15
|
||||
|
||||
;; 4x handler middleware
|
||||
rv4 (sig! {:level :info, :middleware (fn [_] {:my-sig-val? true})})
|
||||
;; 4x handler xfn
|
||||
rv4 (sig! {:level :info, :xfn (fn [_] {:my-sig-val? true})})
|
||||
sv4-h1 @sv-h1_
|
||||
sv4-h2 @sv-h2_
|
||||
c4 @c]
|
||||
|
|
@ -325,10 +335,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 (= 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 (= c1 7) "1x run + 4x handler middleware + 2x call middleware")
|
||||
(is (= c2 8) "2x run + 4x handler middleware + 2x call middleware")
|
||||
(is (= c3 15) "3x run + 8x handler middleware + 4x call middleware")
|
||||
(is (= c4 19) "3x run + 12x handler middleware + 4x call middleware")]))))
|
||||
(is (= c1 7) "1x run + 4x handler xfn + 2x call xfn")
|
||||
(is (= c2 8) "2x run + 4x handler xfn + 2x call xfn")
|
||||
(is (= c3 15) "3x run + 8x handler xfn + 4x call xfn")
|
||||
(is (= c4 19) "3x run + 12x handler xfn + 4x call xfn")]))))
|
||||
|
||||
(testing "Handler binding conveyance"
|
||||
(let [a (atom nil)
|
||||
|
|
@ -374,7 +384,7 @@
|
|||
(test1 64 {:async {:mode :dropping, :buffer-size 64}})
|
||||
(test1 64 {:async {:mode :sliding, :buffer-size 64}})]))))))])
|
||||
|
||||
(def ^:dynamic *throwing-handler-middleware?* false)
|
||||
(def ^:dynamic *throwing-handler-xfn?* false)
|
||||
|
||||
(deftest _throwing
|
||||
(let [sv_ (atom :nx)
|
||||
|
|
@ -388,7 +398,7 @@
|
|||
(tel/with-handler :hid1
|
||||
(fn [sv] (force (:data sv)) (reset! sv_ sv))
|
||||
{:async nil, :error-fn (fn [x] (reset! error_ x)), :rl-error nil,
|
||||
:middleware (fn [sv] (if *throwing-handler-middleware?* (ex1!) sv))}
|
||||
:xfn (fn [sv] (if *throwing-handler-xfn?* (ex1!) sv))}
|
||||
|
||||
[(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")
|
||||
|
|
@ -404,15 +414,15 @@
|
|||
(is (= @sv_ :nx))
|
||||
(is (sm? @error_ {:handler-id :hid1, :error ex1}))])
|
||||
|
||||
(testing "Throwing call middleware"
|
||||
(testing "Throwing call transform"
|
||||
(reset-state!)
|
||||
[(is (true? (sig! {:level :info, :middleware (fn [_] (ex1!))})))
|
||||
[(is (true? (sig! {:level :info, :xfn (fn [_] (ex1!))})))
|
||||
(is (= @sv_ :nx))
|
||||
(is (sm? @error_ {:handler-id :hid1, :error ex1}))])
|
||||
|
||||
(testing "Throwing handler middleware"
|
||||
(testing "Throwing handler transform"
|
||||
(reset-state!)
|
||||
(binding [*throwing-handler-middleware?* true]
|
||||
(binding [*throwing-handler-xfn?* true]
|
||||
[(is (true? (sig! {:level :info})))
|
||||
(is (= @sv_ :nx))
|
||||
(is (sm? @error_ {:handler-id :hid1, :error ex1}))]))
|
||||
|
|
@ -438,7 +448,7 @@
|
|||
(let [sv (with-sig (sig! {:level :info, :root {:id :id1, :foo :bar}}))] (is (sm? sv {:root {:id :id1 :uid :submap/nx, :foo :bar}}) "Manual `:root/id`"))
|
||||
(let [sv (with-sig (sig! {:level :info, :root {:uid :uid1, :foo :bar}}))] (is (sm? sv {:root {:id :submap/nx :uid :uid1, :foo :bar}}) "Manual `:root/uid`"))
|
||||
(let [sv (with-sig (sig! {:level :info})) ] (is (sm? sv {:uid nil})))
|
||||
(let [sv (with-sig (sig! {:level :info, :uid :auto})) ] (is (sm? sv {:uid (enc/pred string?)})))
|
||||
(let [sv (with-sig (sig! {:level :info, :uid :auto})) ] (is (sm? sv {:uid string?})))
|
||||
(let [sv (binding [tel/*uid-fn* (fn [_] "my-uid")]
|
||||
(with-sig (sig! {:level :info, :uid :auto}))) ] (is (sm? sv {:uid "my-uid"})))])
|
||||
|
||||
|
|
@ -544,11 +554,11 @@
|
|||
(let [c (enc/counter)
|
||||
sr_ (atom nil)]
|
||||
(tel/with-handler "h1"
|
||||
(fn h1 [x] (c) (compare-and-set! sr_ nil (:sample-rate x)))
|
||||
{:async nil, :sample-rate handler-sample-rate}
|
||||
(fn h1 [x] (c) (compare-and-set! sr_ nil (:sample x)))
|
||||
{:async nil, :sample handler-sample-rate}
|
||||
(do
|
||||
;; Repeat to ensure >=1 gets through sampling
|
||||
(dotimes [_ 1000] (sig! {:level :info, :sample-rate call-sample-rate}))
|
||||
(dotimes [_ 1000] (sig! {:level :info, :sample call-sample-rate}))
|
||||
[@sr_ @c]))))]
|
||||
|
||||
[(is (= (test1 nil nil) [nil 1000]) "[none none] = none")
|
||||
|
|
@ -569,27 +579,28 @@
|
|||
;;;;
|
||||
|
||||
(deftest _common-signals
|
||||
[(testing "event!" ; id + ?level => allowed?
|
||||
[(let [{rv :value, [sv] :signals} (with-sigs (tel/event! :id1 ))] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/some, :level :info, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/event! :id1 :warn)) ] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/some, :level :warn, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/event! :id1 {:level :warn}))] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/some, :level :warn, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/event! {:id :id1, :level :warn}))] [(is (= rv true)) (is (sm? sv {:kind :event, :line :submap/some, :level :warn, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/event! :id1 {:allow? false})) ] [(is (= rv nil)) (is (nil? sv))])])
|
||||
[(testing "event!?" ; id + ?level => allowed?
|
||||
[(let [{rv :value, [sv] :signals} (with-sigs (tel/event!? :id1 ))] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :info, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/event!? :id1 :warn)) ] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :warn, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/event!? :id1 {:level :warn}))] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :warn, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/event!? {:id :id1, :level :warn}))] [(is (= rv true)) (is (sm? sv {:kind :event, :coords coords?, :level :warn, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/event!? :id1 {:allow? false})) ] [(is (= rv nil)) (is (nil? sv))])])
|
||||
|
||||
(testing "log!" ; ?level + msg => allowed?
|
||||
[(let [{rv :value, [sv] :signals} (with-sigs (tel/log! "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/some, :msg_ "msg", :level :info}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/log! :warn "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/some, :msg_ "msg", :level :warn}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/log! {:level :warn} "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/some, :msg_ "msg", :level :warn}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/log! {:level :warn, :msg "msg"}))] [(is (= rv true)) (is (sm? sv {:kind :log, :line :submap/some, :msg_ "msg", :level :warn}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/log! {:allow? false} "msg")) ] [(is (= rv nil)) (is (nil? sv))])])
|
||||
(testing "log!?" ; ?level + msg => allowed?
|
||||
[(let [{rv :value, [sv] :signals} (with-sigs (tel/log!? "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :info}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/log!? :warn "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :warn}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/log!? {:level :warn} "msg")) ] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :warn}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/log!? {:level :warn, :msg "msg"}))] [(is (= rv true)) (is (sm? sv {:kind :log, :coords coords?, :msg_ "msg", :level :warn}))])
|
||||
(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)
|
||||
[(let [{rv :value, [sv] :signals} (with-sigs (tel/trace! (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/some, :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, :line :submap/some, :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, :line :submap/some, :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, :line :submap/some, :level :info, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/trace! {:id :id1, :run (+ 1 2)}))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id :id1}))])
|
||||
(let [{re :error, [sv] :signals} (with-sigs (tel/trace! :id1 (ex1!))) ] [(is (ex1? re)) (is (sm? sv {:kind :trace, :line :submap/some, :level :info, :id :id1, :error ex1,
|
||||
[(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! {: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! {: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, :run (+ 1 2)}))] [(is (= rv 3)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id :id1}))])
|
||||
(let [{re :error, [sv] :signals} (with-sigs (tel/trace! :id1 (ex1!))) ] [(is (ex1? re)) (is (sm? sv {:kind :trace, :coords coords?, :level :info, :id :id1, :error ex1,
|
||||
:msg_ #?(:clj "(ex1!) !> clojure.lang.ExceptionInfo"
|
||||
:cljs "(ex1!) !> cljs.core/ExceptionInfo")}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/trace! {:allow? false} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
|
||||
|
|
@ -597,8 +608,8 @@
|
|||
(testing "Use with `catch->error!`"
|
||||
(let [{re :error, [sv1 sv2] :signals} (with-sigs (tel/trace! ::id1 (tel/catch->error! ::id2 (ex1!))))]
|
||||
[(is (ex1? re))
|
||||
(is (sm? sv1 {:kind :error, :line :submap/some, :level :error, :id ::id2, :error ex1, :parent {:id ::id1}}))
|
||||
(is (sm? sv2 {:kind :trace, :line :submap/some, :level :info, :id ::id1, :error ex1, :root {:id ::id1}}))]))
|
||||
(is (sm? sv1 {:kind :error, :coords coords?, :level :error, :id ::id2, :error ex1, :parent {:id ::id1}}))
|
||||
(is (sm? sv2 {:kind :trace, :coords coords?, :level :info, :id ::id1, :error ex1, :root {:id ::id1}}))]))
|
||||
|
||||
(testing ":run-form" ; Undocumented, experimental
|
||||
[(is (sm? (with-sig (tel/trace! :non-list)) {:run-form :non-list}))
|
||||
|
|
@ -611,12 +622,12 @@
|
|||
(is (sm? (with-sig (tel/trace! {:run-val "custom"} (+ 2 2))) {:run-val "custom", :msg_ "(+ 2 2) => custom", :kvs nil}))])])
|
||||
|
||||
(testing "spy" ; ?level + run => unconditional run result (value or throw)
|
||||
[(let [{rv :value, [sv] :signals} (with-sigs (tel/spy! (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :info, :msg_ "(+ 1 2) => 3"}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/spy! {:msg nil} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :info, :msg_ nil}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/spy! :warn (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :warn}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/spy! {:level :warn} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :warn}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/spy! {:level :warn, :run (+ 1 2)}))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :line :submap/some, :level :warn}))])
|
||||
(let [{re :error, [sv] :signals} (with-sigs (tel/spy! :warn (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :spy, :line :submap/some, :level :warn, :error ex1,
|
||||
[(let [{rv :value, [sv] :signals} (with-sigs (tel/spy! (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :coords coords?, :level :info, :msg_ "(+ 1 2) => 3"}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/spy! {:msg nil} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :coords coords?, :level :info, :msg_ nil}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/spy! :warn (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :coords coords?, :level :warn}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/spy! {:level :warn} (+ 1 2))) ] [(is (= rv 3)) (is (sm? sv {:kind :spy, :coords coords?, :level :warn}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/spy! {:level :warn, :run (+ 1 2)}))] [(is (= rv 3)) (is (sm? sv {:kind :spy, :coords coords?, :level :warn}))])
|
||||
(let [{re :error, [sv] :signals} (with-sigs (tel/spy! :warn (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :spy, :coords coords?, :level :warn, :error ex1,
|
||||
:msg_ #?(:clj "(ex1!) !> clojure.lang.ExceptionInfo"
|
||||
:cljs "(ex1!) !> cljs.core/ExceptionInfo")}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/spy! {:allow? false} (+ 1 2))) ] [(is (= rv 3)) (is (nil? sv))])
|
||||
|
|
@ -624,23 +635,23 @@
|
|||
(testing "Use with `catch->error!`"
|
||||
(let [{re :error, [sv1 sv2] :signals} (with-sigs (tel/spy! :warn (tel/catch->error! :error (ex1!))))]
|
||||
[(is (ex1? re))
|
||||
(is (sm? sv1 {:kind :error, :line :submap/some, :level :error, :error ex1, :parent {}}))
|
||||
(is (sm? sv2 {:kind :spy, :line :submap/some, :level :warn, :error ex1, :root {}}))]))])
|
||||
(is (sm? sv1 {:kind :error, :coords coords?, :level :error, :error ex1, :parent {}}))
|
||||
(is (sm? sv2 {:kind :spy, :coords coords?, :level :warn, :error ex1, :root {}}))]))])
|
||||
|
||||
(testing "error!" ; ?id + error => unconditional given error
|
||||
[(let [{rv :value, [sv] :signals} (with-sigs (tel/error! ex1)) ] [(is (ex1? rv)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error ex1, :id nil}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/error! :id1 ex1)) ] [(is (ex1? rv)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error ex1, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/error! {:id :id1} ex1)) ] [(is (ex1? rv)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error ex1, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/error! {:id :id1, :error ex1}))] [(is (ex1? rv)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error ex1, :id :id1}))])
|
||||
[(let [{rv :value, [sv] :signals} (with-sigs (tel/error! ex1)) ] [(is (ex1? rv)) (is (sm? sv {:kind :error, :coords coords?, :level :error, :error ex1, :id nil}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/error! :id1 ex1)) ] [(is (ex1? rv)) (is (sm? sv {:kind :error, :coords coords?, :level :error, :error ex1, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/error! {:id :id1} ex1)) ] [(is (ex1? rv)) (is (sm? sv {:kind :error, :coords coords?, :level :error, :error ex1, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/error! {:id :id1, :error ex1}))] [(is (ex1? rv)) (is (sm? sv {:kind :error, :coords coords?, :level :error, :error ex1, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/error! {:allow? false} ex1)) ] [(is (ex1? rv)) (is (nil? sv))])
|
||||
(let [c (enc/counter)] (tel/error! (do (c) ex1)) (is (= @c 1) "Error form evaluated exactly once"))])
|
||||
|
||||
(testing "catch->error!" ; ?id + run => unconditional run value or ?return
|
||||
[(let [{rv :value, [sv] :signals} (with-sigs (tel/catch->error! (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])
|
||||
(let [{re :error, [sv] :signals} (with-sigs (tel/catch->error! (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error ex1, :id nil}))])
|
||||
(let [{re :error, [sv] :signals} (with-sigs (tel/catch->error! :id1 (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error ex1, :id :id1}))])
|
||||
(let [{re :error, [sv] :signals} (with-sigs (tel/catch->error! {:id :id1} (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error ex1, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/catch->error! {:catch-val :foo} (ex1!)))] [(is (= rv :foo)) (is (sm? sv {:kind :error, :line :submap/some, :level :error, :error ex1, :id nil}))])
|
||||
(let [{re :error, [sv] :signals} (with-sigs (tel/catch->error! (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :error, :coords coords?, :level :error, :error ex1, :id nil}))])
|
||||
(let [{re :error, [sv] :signals} (with-sigs (tel/catch->error! :id1 (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :error, :coords coords?, :level :error, :error ex1, :id :id1}))])
|
||||
(let [{re :error, [sv] :signals} (with-sigs (tel/catch->error! {:id :id1} (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :error, :coords coords?, :level :error, :error ex1, :id :id1}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/catch->error! {:catch-val :foo} (ex1!)))] [(is (= rv :foo)) (is (sm? sv {:kind :error, :coords coords?, :level :error, :error ex1, :id nil}))])
|
||||
(let [{rv :value, [sv] :signals} (with-sigs (tel/catch->error! {:catch-val :foo} (+ 1 2)))] [(is (= rv 3)) (is (nil? sv))])])
|
||||
|
||||
#?(:clj
|
||||
|
|
@ -649,13 +660,13 @@
|
|||
[(do (enc/set-var-root! impl/*sig-handlers* [(sigs/wrap-handler "h1" (fn h1 [x] (reset! sv_ x)) nil {:async nil})]) :set-handler)
|
||||
;;
|
||||
(is (nil? (tel/uncaught->error!)))
|
||||
(is (do (.join (enc/threaded :user (ex1!))) (sm? @sv_ {:kind :error, :line :submap/some, :level :error, :error ex1, :id nil})))
|
||||
(is (do (.join (enc/threaded :user (ex1!))) (sm? @sv_ {:kind :error, :coords coords?, :level :error, :error ex1, :id nil})))
|
||||
;;
|
||||
(is (nil? (tel/uncaught->error! :id1)))
|
||||
(is (do (.join (enc/threaded :user (ex1!))) (sm? @sv_ {:kind :error, :line :submap/some, :level :error, :error ex1, :id :id1})))
|
||||
(is (do (.join (enc/threaded :user (ex1!))) (sm? @sv_ {:kind :error, :coords coords?, :level :error, :error ex1, :id :id1})))
|
||||
;;
|
||||
(is (nil? (tel/uncaught->error! {:id :id1})))
|
||||
(is (do (.join (enc/threaded :user (ex1!))) (sm? @sv_ {:kind :error, :line :submap/some, :level :error, :error ex1, :id :id1})))
|
||||
(is (do (.join (enc/threaded :user (ex1!))) (sm? @sv_ {:kind :error, :coords coords?, :level :error, :error ex1, :id :id1})))
|
||||
;;
|
||||
(do (enc/set-var-root! impl/*sig-handlers* nil) :unset-handler)])))])
|
||||
|
||||
|
|
@ -667,7 +678,7 @@
|
|||
|
||||
(deftest _dispatch-signal!
|
||||
[(sm? (tel/with-signal (tel/dispatch-signal! (assoc (tel/with-signal :trap (tel/log! "hello")) :level :warn)))
|
||||
{:kind :log, :level :warn, :line :submap/some})])
|
||||
{:kind :log, :level :warn, :coords coords?})])
|
||||
|
||||
#?(:clj
|
||||
(deftest _uncaught->handler!
|
||||
|
|
@ -687,9 +698,9 @@
|
|||
(deftest _interop
|
||||
[(testing "tools.logging -> Telemere"
|
||||
[(is (sm? (tel/check-interop) {:tools-logging {:present? true, :sending->telemere? true, :telemere-receiving? true}}))
|
||||
(is (sm? (with-sig (ctl/info "Hello" "x" "y")) {:level :info, :ns "taoensso.telemere-tests", :kind :tools-logging, :msg_ "Hello x y", :inst pinst?}))
|
||||
(is (sm? (with-sig (ctl/warn "Hello" "x" "y")) {:level :warn, :ns "taoensso.telemere-tests", :kind :tools-logging, :msg_ "Hello x y", :inst pinst?}))
|
||||
(is (sm? (with-sig (ctl/error ex1 "An error")) {:level :error, :error ex1, :inst pinst?}) "Errors")])
|
||||
(is (sm? (with-sig (ctl/info "Hello" "x" "y")) {:level :info, :ns "taoensso.telemere-tests", :kind :tools-logging, :msg_ "Hello x y", :inst enc/inst?}))
|
||||
(is (sm? (with-sig (ctl/warn "Hello" "x" "y")) {:level :warn, :ns "taoensso.telemere-tests", :kind :tools-logging, :msg_ "Hello x y", :inst enc/inst?}))
|
||||
(is (sm? (with-sig (ctl/error ex1 "An error")) {:level :error, :error ex1, :inst enc/inst?}) "Errors")])
|
||||
|
||||
(testing "Standard out/err streams -> Telemere"
|
||||
[(is (sm? (tel/check-interop) {:system/out {:sending->telemere? false, :telemere-receiving? false},
|
||||
|
|
@ -704,19 +715,19 @@
|
|||
:system/err {:sending->telemere? false, :telemere-receiving? false}}))
|
||||
|
||||
(is (sm? (with-sig (tel/with-out->telemere (println "Hello" "x" "y")))
|
||||
{:level :info, :location nil, :ns nil, :kind :system/out, :msg_ "Hello x y"}))])
|
||||
{:level :info, :coords nil, :ns nil, :kind :system/out, :msg_ "Hello x y"}))])
|
||||
|
||||
(testing "SLF4J -> Telemere"
|
||||
[(is (sm? (tel/check-interop) {:slf4j {:present? true, :sending->telemere? true, :telemere-receiving? true}}))
|
||||
(let [^org.slf4j.Logger sl (org.slf4j.LoggerFactory/getLogger "my.class")]
|
||||
[(testing "Basics"
|
||||
[(is (sm? (with-sig (.info sl "Hello")) {:level :info, :ns "my.class", :kind :slf4j, :msg_ "Hello", :inst pinst?}) "Legacy API: info basics")
|
||||
(is (sm? (with-sig (.warn sl "Hello")) {:level :warn, :ns "my.class", :kind :slf4j, :msg_ "Hello", :inst pinst?}) "Legacy API: warn basics")
|
||||
(is (sm? (with-sig (-> (.atInfo sl) (.log "Hello"))) {:level :info, :ns "my.class", :kind :slf4j, :msg_ "Hello", :inst pinst?}) "Fluent API: info basics")
|
||||
(is (sm? (with-sig (-> (.atWarn sl) (.log "Hello"))) {:level :warn, :ns "my.class", :kind :slf4j, :msg_ "Hello", :inst pinst?}) "Fluent API: warn basics")])
|
||||
[(is (sm? (with-sig (.info sl "Hello")) {:level :info, :ns "my.class", :kind :slf4j, :msg_ "Hello", :inst enc/inst?}) "Legacy API: info basics")
|
||||
(is (sm? (with-sig (.warn sl "Hello")) {:level :warn, :ns "my.class", :kind :slf4j, :msg_ "Hello", :inst enc/inst?}) "Legacy API: warn basics")
|
||||
(is (sm? (with-sig (-> (.atInfo sl) (.log "Hello"))) {:level :info, :ns "my.class", :kind :slf4j, :msg_ "Hello", :inst enc/inst?}) "Fluent API: info 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"
|
||||
(let [msgp "x={},y={}", expected {:msg_ "x=1,y=2", :data {:slf4j/args ["1" "2"]}}]
|
||||
(let [msgp "x={},y={}", expected {:msg_ "x=1,y=2", :slf4j/args (fn [objs] (= (vec objs) ["1" "2"]))}]
|
||||
[(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")]))
|
||||
|
||||
|
|
@ -727,8 +738,8 @@
|
|||
m2 (#'slf4j/est-marker! "M2")
|
||||
cm (#'slf4j/est-marker! "Compound" "M1" "M2")]
|
||||
|
||||
[(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))) {:data {:slf4j/marker-names #{"Compound" "M1" "M2"}}}) "Fluent API: markers")]))
|
||||
[(is (sm? (with-sig (.info sl cm "Hello")) {:slf4j/markers #{"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")]))
|
||||
|
||||
(testing "Errors"
|
||||
[(is (sm? (with-sig (.warn sl "An error" ^Throwable ex1)) {:level :warn, :error ex1}) "Legacy API: errors")
|
||||
|
|
@ -743,22 +754,22 @@
|
|||
;;;; 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", :data {:vargs ["x1" nil "x2"]}, :ns pstr?}))
|
||||
(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 pstr?}))
|
||||
(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 pstr?}))
|
||||
[(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/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/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/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 pstr?}))
|
||||
(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 pstr?}))
|
||||
(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 pstr?}))
|
||||
(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/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/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/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/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/spy! :info "my-name" (+ 1 2))) {:kind :spy, :level :info, :id timbre/shim-id, :msg_ "my-name => 3", :ns pstr?}))
|
||||
(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 pstr?}))
|
||||
(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?}))
|
||||
|
||||
(let [{[sv1 sv2] :signals} (tel/with-min-level :debug (with-sigs (timbre/spy! (ex1!))))]
|
||||
[(is (sm? sv1 {:kind :error, :level :error, :id timbre/shim-id, :msg_ nil, :error ex1, :ns pstr?}))
|
||||
(is (sm? sv2 {:kind :spy, :level :debug, :id timbre/shim-id, :msg_ pstr? :error ex1, :ns pstr?}))])
|
||||
(let [{[sv1 sv2] :signals} (tel/with-min-level :debug (with-sigs (timbre/spy (ex1!))))]
|
||||
[(is (sm? sv1 {:kind :error, :level :error, :id timbre/shim-id, :msg_ nil, :error ex1, :ns string?}))
|
||||
(is (sm? sv2 {:kind :spy, :level :debug, :id timbre/shim-id, :msg_ string? :error ex1, :ns string?}))])
|
||||
|
||||
(let [{re :error, [sv] :signals} (with-sigs (timbre/log-errors (ex1!)))] [(is (nil? re)) (is (sm? sv {:kind :error, :level :error, :error ex1, :id timbre/shim-id}))])
|
||||
(let [{re :error, [sv] :signals} (with-sigs (timbre/log-and-rethrow-errors (ex1!)))] [(is (ex1? re)) (is (sm? sv {:kind :error, :level :error, :error ex1, :id timbre/shim-id}))])])
|
||||
|
|
@ -780,9 +791,7 @@
|
|||
:id nil
|
||||
:msg_ (delay "msg")
|
||||
:error ex2
|
||||
:location "loc"
|
||||
:kvs "kvs"
|
||||
:file "file"
|
||||
:thread "thread"
|
||||
:a "a"
|
||||
:b "b"}]
|
||||
|
|
@ -793,8 +802,7 @@
|
|||
(is (= ((utils/clean-signal-fn {:incl-keys #{:kvs}}) sig) {:level :info, :msg_ "msg", :error ex2-chain, :kvs "kvs"}))
|
||||
(is (= ((utils/clean-signal-fn {:incl-keys #{:a}}) sig) {:level :info, :msg_ "msg", :error ex2-chain, :a "a"}))
|
||||
(is (= ((utils/clean-signal-fn {:incl-keys
|
||||
#{:location :kvs :file :thread}}) sig) {:level :info, :msg_ "msg", :error ex2-chain,
|
||||
:location "loc", :kvs "kvs", :file "file", :thread "thread"}))]))
|
||||
#{:kvs :thread}}) sig) {:level :info, :msg_ "msg", :error ex2-chain, :kvs "kvs", :thread "thread"}))]))
|
||||
|
||||
(testing "Misc utils"
|
||||
[(is (= (utils/remove-signal-kvs {:a :A, :b :B, :kvs {:b :B}}) {:a :A}))
|
||||
|
|
@ -829,18 +837,18 @@
|
|||
(testing "format-error-fn"
|
||||
(let [ex2-str ((utils/format-error-fn) ex2)]
|
||||
[(is (enc/str-starts-with? ex2-str
|
||||
#?(: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\n data: {:k1 \"v1\"}\n\nCaused: cljs.core/ExceptionInfo - Ex2\n data: {:k2 \"v2\"}\n\nRoot stack trace:\n")))
|
||||
#?(:clj "Root: clojure.lang.ExceptionInfo - Ex1\ndata: {:k1 \"v1\"}\n\nCaused: clojure.lang.ExceptionInfo - Ex2\ndata: {: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")))
|
||||
|
||||
(is (enc/str-contains? ex2-str "Root stack trace:"))
|
||||
(is (enc/str-contains? ex2-str "invoke") "Root stack trace includes content")]))
|
||||
|
||||
(testing "signal-preamble-fn"
|
||||
(let [sig (with-sig :raw :trap (tel/event! ::ev-id {:inst t1, :msg ["a" "b"]}))
|
||||
preamble ((utils/signal-preamble-fn) sig)] ; "2024-06-09T21:15:20.170Z INFO EVENT taoensso.telemere-tests(592,35) ::ev-id"
|
||||
preamble ((utils/signal-preamble-fn) sig)] ; "2024-06-09T21:15:20.170Z INFO EVENT taoensso.telemere-tests[592,35] ::ev-id"
|
||||
[(is (enc/str-starts-with? preamble "2024-01-01T01:01:01.110Z INFO EVENT"))
|
||||
(is (enc/str-ends-with? preamble "::ev-id a b"))
|
||||
(is (string? (re-find #"taoensso.telemere-tests\(\d+,\d+\)" preamble)))]))
|
||||
(is (string? (re-find #"taoensso.telemere-tests\[\d+,\d+\]" preamble)))]))
|
||||
|
||||
(testing "pr-signal-fn"
|
||||
(let [sig (with-sig :raw :trap (tel/event! ::ev-id {:inst t1, :msg ["a" "b"]}))]
|
||||
|
|
@ -852,25 +860,23 @@
|
|||
|
||||
[(is (= sig*1 sig*2) "Default :pr-fn is :edn")
|
||||
(is
|
||||
(enc/submap? sig*1
|
||||
{:schema 1, :kind :event, :id ::ev-id, :level :info,
|
||||
(sm? sig*1
|
||||
{:kind :event, :id ::ev-id, :level :info,
|
||||
:ns "taoensso.telemere-tests"
|
||||
:msg_ "a b"
|
||||
:inst udt1
|
||||
:line pnat-int?
|
||||
:column pnat-int?}))]))
|
||||
:coords vector?}))]))
|
||||
|
||||
#?(:cljs
|
||||
(testing ":json pr-fn"
|
||||
(let [sig* (enc/read-json ((tel/pr-signal-fn {:pr-fn :json}) sig))]
|
||||
(is
|
||||
(enc/submap? sig*
|
||||
{"schema" 1, "kind" "event", "id" "taoensso.telemere-tests/ev-id",
|
||||
"level" "info", "ns" "taoensso.telemere-tests"
|
||||
(sm? sig*
|
||||
{"kind" "event", "id" "taoensso.telemere-tests/ev-id",
|
||||
"level" "info", "ns" "taoensso.telemere-tests"
|
||||
"msg_" "a b"
|
||||
"inst" t1s
|
||||
"line" pnat-int?
|
||||
"column" pnat-int?})))))
|
||||
"coords" vector?})))))
|
||||
|
||||
(testing "Custom pr-fn"
|
||||
(is (= ((tel/pr-signal-fn {:pr-fn (fn [_] "str")}) sig) (str "str" utils/newline))))]))
|
||||
|
|
@ -1015,19 +1021,18 @@
|
|||
{:kind :event
|
||||
:level :info
|
||||
|
||||
:ns "ns"
|
||||
:file "file"
|
||||
:line 100
|
||||
:ns "ns"
|
||||
:coords [10 20]
|
||||
|
||||
:id ::id1
|
||||
:uid #uuid "7e9c1df6-78e4-40ac-8c5c-e2353df9ab82"
|
||||
:parent {:id ::parent-id1 :uid #uuid "443154cf-b6cf-47bf-b86a-8b185afee256"}
|
||||
:root {:id ::root-id1 :uid #uuid "82a53b6a-b28a-4102-8025-9e735dee103c"}
|
||||
|
||||
:run-form '(+ 3 2)
|
||||
:run-val 5
|
||||
:run-nsecs 100
|
||||
:sample-rate 0.5
|
||||
:run-form '(+ 3 2)
|
||||
:run-val 5
|
||||
:run-nsecs 100
|
||||
:sample 0.5
|
||||
|
||||
:data
|
||||
{:key-kw :val-kw
|
||||
|
|
@ -1040,9 +1045,9 @@
|
|||
{"kind" ":event"
|
||||
"level" "INFO"
|
||||
|
||||
"ns" "ns"
|
||||
"file" "file"
|
||||
"line" 100
|
||||
"code.namespace" "ns"
|
||||
"code.line.number" 10
|
||||
"code.column.number" 20
|
||||
|
||||
"id" ":taoensso.telemere-tests/id1",
|
||||
"uid" "7e9c1df6-78e4-40ac-8c5c-e2353df9ab82",
|
||||
|
|
@ -1065,7 +1070,7 @@
|
|||
"exception.type" "clojure.lang.ExceptionInfo"
|
||||
"exception.message" "Ex1"
|
||||
"exception.data.k1" "v1"
|
||||
"exception.stacktrace" (enc/pred string?)
|
||||
"exception.stacktrace" string?
|
||||
|
||||
"a1" ":A1"}))])]))
|
||||
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
Telemere supports extensive environmental config via JVM properties,
|
||||
environment variables, or classpath resources.
|
||||
|
||||
Environmental filter config includes:
|
||||
|
||||
Kind filter:
|
||||
JVM property: `taoensso.telemere.rt-kind-filter`
|
||||
Env variable: `TAOENSSO_TELEMERE_RT_KIND_FILTER`
|
||||
Classpath resource: `taoensso.telemere.rt-kind-filter`
|
||||
|
||||
Namespace filter:
|
||||
JVM property: `taoensso.telemere.rt-ns-filter`
|
||||
Env variable: `TAOENSSO_TELEMERE_RT_NS_FILTER`
|
||||
Classpath resource: `taoensso.telemere.rt-ns-filter`
|
||||
|
||||
Id filter:
|
||||
JVM property: `taoensso.telemere.rt-id-filter`
|
||||
Env variable: `TAOENSSO_TELEMERE_RT_ID_FILTER`
|
||||
Classpath resource: `taoensso.telemere.rt-id-filter`
|
||||
|
||||
Minimum level:
|
||||
JVM property: `taoensso.telemere.rt-min-level`
|
||||
Env variable: `TAOENSSO_TELEMERE_RT_MIN_LEVEL`
|
||||
Classpath resource: `taoensso.telemere.rt-min-level`
|
||||
|
||||
Values are edn, examples:
|
||||
|
||||
`taoensso.telemere.rt-min-level` -> ":info"
|
||||
`TAOENSSO_TELEMERE_RT_NS_FILTER` -> "{:disallow \"taoensso.*\"}"
|
||||
`taoensso.telemere.rt-id-filter.cljs` -> "#{:my-id1 :my-id2}"
|
||||
`TAOENSSO_TELEMERE_RT_KIND_FILTER_CLJ` -> "nil"
|
||||
|
||||
For other (non-filter) environmental config, see the relevant docstrings.
|
||||
|
||||
Tips:
|
||||
|
||||
- The above ids are for runtime filters (the most common).
|
||||
For compile-time filters, change `rt`->`ct` / `RT`->`CT`.
|
||||
|
||||
- The above ids will affect both Clj AND Cljs.
|
||||
For platform-specific filters, use
|
||||
".clj.edn" / "_CLJ_EDN" or
|
||||
".cljs.edn" / "_CLJS_EDN" suffixes instead.
|
||||
|
||||
- Optional ".edn" / "_EDN" suffixes may be added for clarity.
|
||||
|
||||
- To get the right edn syntax, first set your runtime filters using the
|
||||
standard utils (`set-min-level!`, etc.). Then call `get-filters` and
|
||||
serialize the relevant parts to edn with `pr-str`.
|
||||
|
||||
- All environmental config uses `get-env` underneath.
|
||||
See the `get-env` docstring for more/advanced details.
|
||||
|
||||
- Classpath resources are files accessible on your project's
|
||||
classpath. This usually includes files in your project's
|
||||
`resources/` dir.
|
||||
|
|
@ -1,822 +0,0 @@
|
|||
(ns ^:no-doc taoensso.telemere.impl
|
||||
"Private ns, implementation detail.
|
||||
Signal design shared by: Telemere, Tufte, Timbre."
|
||||
(:refer-clojure :exclude [binding])
|
||||
(:require
|
||||
[clojure.set :as set]
|
||||
[taoensso.encore :as enc :refer [binding have have?]]
|
||||
[taoensso.encore.signals :as sigs])
|
||||
|
||||
#?(:cljs
|
||||
(:require-macros
|
||||
[taoensso.telemere.impl :refer [with-signal]])))
|
||||
|
||||
(comment
|
||||
(remove-ns (symbol (str *ns*)))
|
||||
(:api (enc/interns-overview)))
|
||||
|
||||
#?(:clj
|
||||
(enc/declare-remote
|
||||
^:dynamic taoensso.telemere/*ctx*
|
||||
^:dynamic taoensso.telemere/*middleware*
|
||||
^:dynamic taoensso.telemere/*uid-fn*
|
||||
^:dynamic taoensso.telemere/*otel-tracer*))
|
||||
|
||||
;;;; Config
|
||||
|
||||
#?(:clj
|
||||
(do
|
||||
(def present:tools-logging? (enc/have-resource? "clojure/tools/logging.clj"))
|
||||
(def present:slf4j? (enc/compile-if org.slf4j.Logger true false))
|
||||
(def present:telemere-slf4j? (enc/compile-if com.taoensso.telemere.slf4j.TelemereLogger true false))
|
||||
(def present:otel? (enc/compile-if io.opentelemetry.context.Context true false))
|
||||
|
||||
(def enabled:tools-logging?
|
||||
"Documented at `taoensso.telemere.tools-logging/tools-logging->telemere!`."
|
||||
(enc/get-env {:as :bool, :default false} :clojure.tools.logging/to-telemere))
|
||||
|
||||
(def enabled:otel-tracing?
|
||||
"Documented at `taoensso.telemere/otel-tracing?`."
|
||||
(enc/get-env {:as :bool, :default present:otel?}
|
||||
:taoensso.telemere/otel-tracing<.platform>))))
|
||||
|
||||
(def uid-kind
|
||||
"Documented at `taoensso.telemere/*uid-fn*`."
|
||||
(enc/get-env {:as :edn, :default :default}
|
||||
:taoensso.telemere/uid-kind<.platform><.edn>))
|
||||
|
||||
#?(:clj
|
||||
(let [base (enc/get-env {:as :edn} :taoensso.telemere/ct-filters<.platform><.edn>)
|
||||
kind-filter (enc/get-env {:as :edn} :taoensso.telemere/ct-kind-filter<.platform><.edn>)
|
||||
ns-filter (enc/get-env {:as :edn} :taoensso.telemere/ct-ns-filter<.platform><.edn>)
|
||||
id-filter (enc/get-env {:as :edn} :taoensso.telemere/ct-id-filter<.platform><.edn>)
|
||||
min-level (enc/get-env {:as :edn} :taoensso.telemere/ct-min-level<.platform><.edn>)]
|
||||
|
||||
(enc/defonce ct-sig-filter
|
||||
"`SigFilter` used for compile-time elision, or nil."
|
||||
(sigs/sig-filter
|
||||
{:kind-filter (or kind-filter (get base :kind-filter))
|
||||
:ns-filter (or ns-filter (get base :ns-filter))
|
||||
:id-filter (or id-filter (get base :id-filter))
|
||||
:min-level (or min-level (get base :min-level))}))))
|
||||
|
||||
(let [base (enc/get-env {:as :edn} :taoensso.telemere/rt-filters<.platform><.edn>)
|
||||
kind-filter (enc/get-env {:as :edn} :taoensso.telemere/rt-kind-filter<.platform><.edn>)
|
||||
ns-filter (enc/get-env {:as :edn} :taoensso.telemere/rt-ns-filter<.platform><.edn>)
|
||||
id-filter (enc/get-env {:as :edn} :taoensso.telemere/rt-id-filter<.platform><.edn>)
|
||||
min-level (enc/get-env {:as :edn, :default :info} :taoensso.telemere/rt-min-level<.platform><.edn>)]
|
||||
|
||||
(enc/defonce ^:dynamic *rt-sig-filter*
|
||||
"`SigFilter` used for runtime filtering, or nil."
|
||||
(sigs/sig-filter
|
||||
{:kind-filter (or kind-filter (get base :kind-filter))
|
||||
:ns-filter (or ns-filter (get base :ns-filter))
|
||||
:id-filter (or id-filter (get base :id-filter))
|
||||
:min-level (or min-level (get base :min-level))})))
|
||||
|
||||
(comment (enc/get-env {:as :edn, :return :explain} :taoensso.telemere/rt-filters<.platform><.edn>))
|
||||
|
||||
;;;; Utils
|
||||
|
||||
#?(:clj
|
||||
(defmacro on-init [& body]
|
||||
(let [sym (with-meta '__on-init {:private true})
|
||||
compiling? (if (:ns &env) false `*compile-files*)]
|
||||
`(defonce ~sym (when-not ~compiling? ~@body nil)))))
|
||||
|
||||
(comment (macroexpand-1 '(on-init (println "foo"))))
|
||||
|
||||
;;;; Messages
|
||||
|
||||
(deftype MsgSkip [])
|
||||
(deftype MsgSplice [args])
|
||||
|
||||
(def ^:public msg-skip
|
||||
"For use within signal message vectors.
|
||||
Special value that will be ignored (noop) when creating message.
|
||||
Useful for conditionally skipping parts of message content, etc.:
|
||||
|
||||
(signal! {:msg [\"Hello\" (if <cond> <then> msg-skip) \"world\"] <...>}) or
|
||||
(log! [\"Hello\" (if <cond> <then> msg-skip) \"world\"]), etc.
|
||||
|
||||
%> {:msg_ \"Hello world\" <...>}"
|
||||
|
||||
(MsgSkip.))
|
||||
|
||||
(defn ^:public msg-splice
|
||||
"For use within signal message vectors.
|
||||
Wraps given arguments so that they're spliced when creating message.
|
||||
Useful for conditionally splicing in extra message content, etc.:
|
||||
|
||||
(signal! {:msg [(when <cond> (msg-splice [\"Username:\" \"Steve\"])) <...>]}) or
|
||||
(log! [(when <cond> (msg-splice [\"Username:\" \"Steve\"]))])
|
||||
|
||||
%> {:msg_ \"Username: Steve\"}"
|
||||
|
||||
[args] (MsgSplice. args))
|
||||
|
||||
(let [;; xform (map #(if (nil? %) "nil" %))
|
||||
xform
|
||||
(fn [rf]
|
||||
(let [;; Protocol-based impln (extensible but ~20% slower)
|
||||
;; rf* (fn rf* [acc in] (reduce-msg-arg in acc rf))
|
||||
rf*
|
||||
(fn rf* [acc in]
|
||||
(enc/cond
|
||||
(instance? MsgSplice in) (reduce rf* acc (.-args ^MsgSplice in))
|
||||
(instance? MsgSkip in) acc
|
||||
(nil? in) (rf acc "nil")
|
||||
:else (rf acc in)))]
|
||||
(fn
|
||||
([ ] (rf))
|
||||
([acc ] (rf acc))
|
||||
([acc in] (rf* acc in)))))]
|
||||
|
||||
(defn signal-msg
|
||||
"Returns string formed by joining all args with \" \" separator,
|
||||
rendering nils as \"nil\". Supports `msg-skip`, `msg-splice`.
|
||||
|
||||
API intended to be usefully different to `str`:
|
||||
- `str`: no spacers, skip nils, no splicing
|
||||
- `signal-msg`: auto spacers, show nils, opt-in splicing"
|
||||
|
||||
{:tag #?(:clj 'String :cljs 'string)}
|
||||
[args] (enc/str-join " " xform args)))
|
||||
|
||||
(comment
|
||||
(enc/qb 2e6 ; [305.61 625.35]
|
||||
(str "a" "b" "c" nil :kw) ; "abc:kw"
|
||||
(signal-msg ["a" "b" "c" nil :kw (msg-splice ["d" "e"])]) ; "a b c nil :kw d e"
|
||||
))
|
||||
|
||||
#?(:clj
|
||||
(defn- parse-msg-form [msg-form]
|
||||
(when msg-form
|
||||
(enc/cond
|
||||
(string? msg-form) msg-form
|
||||
(vector? msg-form)
|
||||
(enc/cond
|
||||
(empty? msg-form) nil
|
||||
:let [[m1 & more] msg-form]
|
||||
(and (string? m1) (nil? more)) m1
|
||||
:else `(delay (signal-msg ~msg-form)))
|
||||
|
||||
;; Auto delay-wrap (user should never delay-wrap!)
|
||||
;; :else `(delay ~msg-form)
|
||||
|
||||
;; Leave user to delay-wrap when appropriate (document)
|
||||
:else msg-form))))
|
||||
|
||||
(defn default-trace-msg
|
||||
[form value error nsecs]
|
||||
(if error
|
||||
(str form " !> " (enc/ex-type error))
|
||||
(str form " => " value)))
|
||||
|
||||
(comment
|
||||
(default-trace-msg "(+ 1 2)" 3 nil 12345)
|
||||
(default-trace-msg "(+ 1 2)" nil (Exception. "Ex") 12345))
|
||||
|
||||
;;;; Tracing
|
||||
|
||||
(enc/def* ^:dynamic *trace-root* "?{:keys [id uid]}" nil) ; Fixed once bound
|
||||
(enc/def* ^:dynamic *trace-parent* "?{:keys [id uid]}" nil) ; Changes each nesting level
|
||||
|
||||
;; Root Telemere ids: {:parent nil, :id id1, :uid uid1 :root {:id id1, :uid uid1}}
|
||||
;; Root OTel ids: {:parent nil, :id id1, :uid span1,:root {:id id1, :uid trace1}}
|
||||
|
||||
;;;; OpenTelemetry
|
||||
|
||||
#?(:clj
|
||||
(enc/compile-when present:otel?
|
||||
(do
|
||||
(enc/def* ^:dynamic *otel-context* "`?Context`" nil)
|
||||
(defmacro otel-context [] `(or *otel-context* (io.opentelemetry.context.Context/current)))
|
||||
|
||||
(defn otel-trace-id
|
||||
"Returns valid `traceId` or nil."
|
||||
[^io.opentelemetry.context.Context context]
|
||||
(let [sc (.getSpanContext (io.opentelemetry.api.trace.Span/fromContext context))]
|
||||
(when (.isValid sc) (.getTraceId sc))))
|
||||
|
||||
(defn otel-span-id
|
||||
"Returns valid `spanId` or nil."
|
||||
[^io.opentelemetry.context.Context context]
|
||||
(let [sc (.getSpanContext (io.opentelemetry.api.trace.Span/fromContext context))]
|
||||
(when (.isValid sc) (.getSpanId sc))))
|
||||
|
||||
(defn viable-tracer
|
||||
"Returns viable `Tracer`, or nil."
|
||||
[tracer]
|
||||
(when-let [tracer ^io.opentelemetry.api.trace.Tracer tracer]
|
||||
(let [sb (.spanBuilder tracer "test-span")
|
||||
span (.startSpan sb)]
|
||||
(when (.isValid (.getSpanContext span))
|
||||
tracer))))
|
||||
|
||||
(def ^String otel-name (enc/fmemoize (fn [id] (if id (enc/as-qname id) "telemere/no-id"))))
|
||||
(defn otel-context+span
|
||||
"Returns new `Context` that includes minimal `Span` in given parent `Context`.
|
||||
We leave the (expensive) population of attributes, etc. for signal handler.
|
||||
Interop needs only the basics (t0, traceId, spanId, spanName) right away."
|
||||
^io.opentelemetry.context.Context
|
||||
[id inst ?parent-context]
|
||||
(let [parent-context (or ?parent-context (otel-context))]
|
||||
(enc/if-not [tracer (force taoensso.telemere/*otel-tracer*)]
|
||||
parent-context ; Can't add Span without Tracer
|
||||
(let [sb (.spanBuilder ^io.opentelemetry.api.trace.Tracer tracer (otel-name id))]
|
||||
(.setStartTimestamp sb ^java.time.Instant inst)
|
||||
(.with ^io.opentelemetry.context.Context parent-context
|
||||
(.startSpan sb)))))))))
|
||||
|
||||
(comment
|
||||
(enc/qb 1e6 (otel-context) (otel-context+span ::id1 (enc/now-inst) nil)) ; [46.42 186.89]
|
||||
(viable-tracer (force taoensso.telemere/*otel-tracer*))
|
||||
(otel-trace-id (otel-context)))
|
||||
|
||||
;;;; Main types
|
||||
|
||||
(defrecord Signal
|
||||
;; Telemere's main public data type, we avoid nesting and duplication
|
||||
[schema inst uid,
|
||||
location ns line column file, #?@(:clj [host thread _otel-context]),
|
||||
sample-rate, kind id level, ctx parent root, data kvs msg_,
|
||||
error run-form run-val end-inst run-nsecs]
|
||||
|
||||
Object (toString [sig] (str "taoensso.telemere.Signal" (into {} sig))))
|
||||
|
||||
;; NB intentionally verbose constructors for readability, to support extra keys
|
||||
(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" (pr-str (into {} sig)))))
|
||||
|
||||
(def impl-signal-keys #{:_otel-context})
|
||||
(def standard-signal-keys
|
||||
(set/difference (set (keys (map->Signal {:schema 0})))
|
||||
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
|
||||
;; Internal type to implement `sigs/IFilterableSignal`,
|
||||
;; incl. lazy + cached `signal-value_` field.
|
||||
[kind ns id level signal-value_]
|
||||
sigs/IFilterableSignal
|
||||
(allow-signal? [_ sig-filter] (sig-filter kind ns id level))
|
||||
(signal-debug [_] {:kind kind, :ns ns, :id id, :level level})
|
||||
(signal-value [_ handler-sample-rate]
|
||||
(sigs/signal-with-combined-sample-rate handler-sample-rate
|
||||
(force signal-value_))))
|
||||
|
||||
(defn wrap-signal
|
||||
"Used by `taoensso.telemere/dispatch-signal!`."
|
||||
[signal]
|
||||
(when (map? signal)
|
||||
(let [{:keys [kind ns id level]} signal]
|
||||
(WrappedSignal. kind ns id level signal))))
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(enc/defonce ^:dynamic *sig-handlers* "?[<wrapped-handler-fn>]" nil)
|
||||
|
||||
(defrecord SpyOpts [vol_ last-only? trap?])
|
||||
(def ^:dynamic *sig-spy* "?SpyOpts" nil)
|
||||
|
||||
(defn force-msg-in-sig [sig]
|
||||
(if-not (map? sig)
|
||||
sig
|
||||
(if-let [e (find sig :msg_)]
|
||||
(assoc sig :msg_ (force (val e)))
|
||||
(do sig))))
|
||||
|
||||
#?(:clj
|
||||
(defmacro ^:public with-signal
|
||||
"Experimental, subject to change.
|
||||
Executes given form, trapping errors. Returns the LAST signal created by form.
|
||||
Useful for tests/debugging.
|
||||
|
||||
Options:
|
||||
`trap-signals?` (default false)
|
||||
Should ALL signals created by form be trapped to prevent normal dispatch
|
||||
to registered handlers?
|
||||
|
||||
`raw-msg?` (default false)
|
||||
Should delayed `:msg_` in returned signal be retained as-is?
|
||||
Delay is otherwise replaced by realized string.
|
||||
|
||||
See also `with-signals` for more advanced options."
|
||||
([ form] `(with-signal false false ~form))
|
||||
([ trap-signals? form] `(with-signal false ~trap-signals? ~form))
|
||||
([raw-msg? trap-signals? form]
|
||||
`(let [sig_# (volatile! nil)]
|
||||
(binding [*sig-spy* (SpyOpts. sig_# true ~trap-signals?)]
|
||||
(enc/try* ~form (catch :all _#)))
|
||||
|
||||
(if ~raw-msg?
|
||||
(do @sig_#)
|
||||
(force-msg-in-sig @sig_#))))))
|
||||
|
||||
#?(:clj
|
||||
(defmacro ^:public with-signals
|
||||
"Experimental, subject to change.
|
||||
Like `with-signal` but returns {:keys [value error signals]}.
|
||||
Useful for more advanced tests/debugging.
|
||||
|
||||
Destructuring example:
|
||||
(let [{:keys [value error] [sig1 sig2] :signals} (with-signals ...)]
|
||||
...)"
|
||||
([ form] `(with-signals false false ~form))
|
||||
([ trap-signals? form] `(with-signals false ~trap-signals? ~form))
|
||||
([raw-msgs? trap-signals? form]
|
||||
`(let [sigs_# (volatile! nil)
|
||||
base-map#
|
||||
(binding [*sig-spy* (SpyOpts. sigs_# false ~trap-signals?)]
|
||||
(enc/try*
|
||||
(do {:value ~form})
|
||||
(catch :all t# {:error t#})))
|
||||
|
||||
sigs#
|
||||
(not-empty
|
||||
(if ~raw-msgs?
|
||||
(do @sigs_#)
|
||||
(mapv force-msg-in-sig @sigs_#)))]
|
||||
|
||||
(if sigs#
|
||||
(assoc base-map# :signals sigs#)
|
||||
(do base-map#))))))
|
||||
|
||||
#?(:clj (def ^:dynamic *sig-spy-off-thread?* false))
|
||||
(defn dispatch-signal!
|
||||
"Dispatches given signal to registered handlers, supports `with-signal/s`."
|
||||
[signal]
|
||||
(or
|
||||
(when-let [{:keys [vol_ last-only? trap?]} *sig-spy*]
|
||||
(let [sv
|
||||
#?(:cljs (sigs/signal-value signal nil)
|
||||
:clj
|
||||
(if *sig-spy-off-thread?* ; Simulate async handler
|
||||
(deref (enc/promised :user (sigs/signal-value signal nil)))
|
||||
(do (sigs/signal-value signal nil))))]
|
||||
|
||||
(if last-only?
|
||||
(vreset! vol_ sv)
|
||||
(vswap! vol_ #(conj (or % []) sv))))
|
||||
(when trap? :trapped))
|
||||
|
||||
(sigs/call-handlers! *sig-handlers* signal)
|
||||
:dispatched))
|
||||
|
||||
;;;; Signal API helpers
|
||||
|
||||
#?(:clj (defmacro signal-docstring [ rname] (enc/slurp-resource (str "signal-docstrings/" (name rname) ".txt"))))
|
||||
#?(:clj (defmacro defhelp [sym rname] `(enc/def* ~sym {:doc ~(eval `(signal-docstring ~rname))} "See docstring")))
|
||||
|
||||
#?(:clj
|
||||
(defn signal-arglists [macro-id]
|
||||
(case macro-id
|
||||
|
||||
:signal! ; opts => allowed? / unconditional run result (value or throw)
|
||||
'( [& opts-kvs]
|
||||
[{:as opts-map :keys
|
||||
[#_defaults #_elide? #_allow? #_expansion-id, ; Undocumented
|
||||
elidable? location #_location* 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]}])
|
||||
|
||||
:signal-allowed? ; opts => allowed?
|
||||
'( [& opts-kvs]
|
||||
[{:as opts-map :keys
|
||||
[#_defaults #_elide? #_allow? #_expansion-id, ; Undocumented
|
||||
elidable? location #_location* #_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]}])
|
||||
|
||||
:event! ; id + ?level => allowed?
|
||||
'([opts-or-id]
|
||||
[id level]
|
||||
[id
|
||||
{:as opts-map :keys
|
||||
[#_defaults #_elide? #_allow? #_expansion-id,
|
||||
elidable? location #_location* 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]}])
|
||||
|
||||
:log! ; ?level + msg => allowed?
|
||||
'([opts-or-msg]
|
||||
[level msg]
|
||||
[{:as opts-map :keys
|
||||
[#_defaults #_elide? #_allow? #_expansion-id,
|
||||
elidable? location #_location* 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]}
|
||||
msg])
|
||||
|
||||
:trace! ; ?id + run => unconditional run result (value or throw)
|
||||
'([opts-or-run]
|
||||
[id run]
|
||||
[{:as opts-map :keys
|
||||
[#_defaults #_elide? #_allow? #_expansion-id,
|
||||
elidable? location #_location* 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]}
|
||||
run])
|
||||
|
||||
:spy! ; ?level + run => unconditional run result (value or throw)
|
||||
'([opts-or-run]
|
||||
[level run]
|
||||
[{:as opts-map :keys
|
||||
[#_defaults #_elide? #_allow? #_expansion-id,
|
||||
elidable? location #_location* 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]}
|
||||
run])
|
||||
|
||||
:error! ; ?id + error => unconditional given error
|
||||
'([opts-or-error]
|
||||
[id error]
|
||||
[{:as opts-map :keys
|
||||
[#_defaults #_elide? #_allow? #_expansion-id,
|
||||
elidable? location #_location* 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]}
|
||||
error])
|
||||
|
||||
:catch->error! ; ?id + run => unconditional run value or ?catch-val
|
||||
'([opts-or-run]
|
||||
[id run]
|
||||
[{:as opts-map :keys
|
||||
[#_defaults #_elide? #_allow? #_expansion-id, catch-val,
|
||||
elidable? location #_location* 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]}
|
||||
run])
|
||||
|
||||
:uncaught->error! ; ?id => nil
|
||||
'([]
|
||||
[opts-or-id]
|
||||
[{:as opts-map :keys
|
||||
[#_defaults #_elide? #_allow? #_expansion-id,
|
||||
elidable? location #_location* 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]}])
|
||||
|
||||
(enc/unexpected-arg! macro-id))))
|
||||
|
||||
;;;; Signal macro
|
||||
|
||||
(deftype RunResult [value error ^long run-nsecs]
|
||||
#?(:clj clojure.lang.IFn :cljs IFn)
|
||||
(#?(:clj invoke :cljs -invoke) [_] (if error (throw error) value))
|
||||
(#?(:clj invoke :cljs -invoke) [_ signal_]
|
||||
(if error
|
||||
(throw
|
||||
(ex-info "Signal `:run` form error"
|
||||
(enc/try*
|
||||
(do {:taoensso.telemere/signal (force signal_)})
|
||||
(catch :all t {:taoensso.telemere/signal-error t}))
|
||||
error))
|
||||
value)))
|
||||
|
||||
(defn inst+nsecs
|
||||
"Returns given platform instant plus given number of nanosecs."
|
||||
[inst run-nsecs]
|
||||
#?(:clj (.plusNanos ^java.time.Instant inst run-nsecs)
|
||||
:cljs (js/Date. (+ (.getTime inst) (/ run-nsecs 1e6)))))
|
||||
|
||||
(comment (enc/qb 1e6 (inst+nsecs (enc/now-inst) 1e9)))
|
||||
|
||||
#?(:clj
|
||||
(defn- valid-opts! [x]
|
||||
(if (map? x)
|
||||
(do x)
|
||||
(throw
|
||||
;; We require const map keys, but vals may require eval
|
||||
(ex-info "Telemere signal opts must be a map with const (compile-time) keys."
|
||||
{:opts (enc/typed-val x)})))))
|
||||
|
||||
#?(:clj (defn- auto-> [form auto-form] (if (= form :auto) auto-form form)))
|
||||
#?(:clj
|
||||
(defmacro ^:public signal!
|
||||
"Generic low-level signal call, also aliased in Encore."
|
||||
{:doc (signal-docstring :signal!)
|
||||
:arglists (signal-arglists :signal!)}
|
||||
[arg1 & more]
|
||||
(let [opts (valid-opts! (if more (apply hash-map arg1 more) arg1))
|
||||
defaults (enc/merge {:kind :generic, :level :info} (get opts :defaults))
|
||||
opts (enc/merge defaults (dissoc opts :defaults))
|
||||
cljs? (boolean (:ns &env))
|
||||
clj? (not cljs?)
|
||||
{run-form :run} opts
|
||||
|
||||
show-run-val (get opts :run-val '_run-val)
|
||||
show-run-form
|
||||
(when run-form
|
||||
(get opts :run-form
|
||||
(if (and
|
||||
(enc/list-form? run-form)
|
||||
(> (count run-form) 1)
|
||||
(> (count (str run-form)) 32))
|
||||
(list (first run-form) '...)
|
||||
(do run-form))))
|
||||
|
||||
{:keys [#_expansion-id location elide? allow?]}
|
||||
(sigs/filterable-expansion
|
||||
{:sf-arity 4
|
||||
:ct-sig-filter ct-sig-filter
|
||||
:*rt-sig-filter* `*rt-sig-filter*}
|
||||
|
||||
(assoc opts
|
||||
:location* (get opts :location* (enc/get-source &form &env))
|
||||
:bound-forms
|
||||
{:kind '__kind
|
||||
:ns '__ns
|
||||
:id '__id
|
||||
:level '__level}))]
|
||||
|
||||
(if elide?
|
||||
run-form
|
||||
(let [{ns-form :ns
|
||||
line-form :line
|
||||
column-form :column
|
||||
file-form :file} location
|
||||
|
||||
{inst-form :inst
|
||||
level-form :level
|
||||
kind-form :kind
|
||||
id-form :id} opts
|
||||
|
||||
trace? (get opts :trace? (boolean run-form))
|
||||
_
|
||||
(when-not (contains? #{true false nil} trace?)
|
||||
(enc/unexpected-arg! trace?
|
||||
{:msg "Expected constant (compile-time) `:trace?` boolean"
|
||||
:context `signal!}))
|
||||
|
||||
thread-form (when clj? `(enc/thread-info))
|
||||
|
||||
inst-form (get opts :inst :auto)
|
||||
inst-form (auto-> inst-form `(enc/now-inst*))
|
||||
|
||||
parent-form (get opts :parent `*trace-parent*)
|
||||
root-form0 (get opts :root `*trace-root*)
|
||||
|
||||
uid-form (get opts :uid (when trace? :auto))
|
||||
|
||||
signal-delay-form
|
||||
(let [{do-form :do
|
||||
let-form :let
|
||||
msg-form :msg
|
||||
data-form :data
|
||||
error-form :error
|
||||
sample-rate-form :sample-rate} opts
|
||||
|
||||
let-form (or let-form '[])
|
||||
msg-form (parse-msg-form msg-form)
|
||||
|
||||
ctx-form
|
||||
(if-let [ctx+ (get opts :ctx+)]
|
||||
`(taoensso.encore.signals/update-ctx taoensso.telemere/*ctx* ~ctx+)
|
||||
(get opts :ctx `taoensso.telemere/*ctx*))
|
||||
|
||||
middleware-form
|
||||
(if-let [middleware+ (get opts :middleware+)]
|
||||
`(taoensso.encore/comp-middleware taoensso.telemere/*middleware* ~middleware+)
|
||||
(get opts :middleware `taoensso.telemere/*middleware*))
|
||||
|
||||
kvs-form
|
||||
(not-empty
|
||||
(dissoc opts
|
||||
:elidable? :location :location* :inst :uid :middleware :middleware+,
|
||||
:sample-rate :ns :kind :id :level :filter :when #_:rate-limit #_:rate-limit-by,
|
||||
:ctx :ctx+ :parent #_:trace?, :do :let :data :msg :error,
|
||||
:run :run-form :run-val, :elide? :allow? #_:expansion-id :otel/context))
|
||||
|
||||
_ ; Compile-time validation
|
||||
(do
|
||||
(when (and run-form error-form) ; Ambiguous source of error
|
||||
(throw
|
||||
(ex-info "Signals cannot have both `:run` and `:error` opts at the same time"
|
||||
{:run-form run-form
|
||||
:error-form error-form
|
||||
:location location
|
||||
:other-opts (dissoc opts :run :error)})))
|
||||
|
||||
(when-let [e (find opts :msg_)] ; Common typo/confusion
|
||||
(throw
|
||||
(ex-info "Signals cannot have `:msg_` opt (did you mean `:msg`?))"
|
||||
{:msg_ (enc/typed-val (val e))}))))
|
||||
|
||||
signal-form
|
||||
(let [record-form
|
||||
(let [clause [(if run-form :run :no-run) (if clj? :clj :cljs)]]
|
||||
(case clause
|
||||
[:run :clj ] `(Signal. 1 ~'__inst ~'__uid, ~location ~'__ns ~line-form ~column-form ~file-form, (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, ~location ~'__ns ~line-form ~column-form ~file-form, ~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, ~location ~'__ns ~line-form ~column-form ~file-form, (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, ~location ~'__ns ~line-form ~column-form ~file-form, ~sample-rate-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form ~'__root1, ~data-form ~kvs-form ~msg-form, ~error-form nil nil nil nil)
|
||||
(enc/unexpected-arg! clause {:context :signal-constructor-args})))
|
||||
|
||||
record-form
|
||||
(if-not run-form
|
||||
record-form
|
||||
`(let [~(with-meta '_run-result {:tag `RunResult}) ~'__run-result
|
||||
~'_run-nsecs (.-run-nsecs ~'_run-result)
|
||||
~'_run-val (.-value ~'_run-result)
|
||||
~'_run-err (.-error ~'_run-result)
|
||||
~'_end-inst (inst+nsecs ~'__inst ~'_run-nsecs)
|
||||
~'_msg_
|
||||
(let [mf# ~msg-form]
|
||||
(if (fn? mf#) ; Undocumented, handy for `trace!`/`spy!`, etc.
|
||||
(delay (mf# '~show-run-form ~show-run-val ~'_run-err ~'_run-nsecs))
|
||||
mf#))]
|
||||
~record-form))]
|
||||
|
||||
(if-not kvs-form
|
||||
record-form
|
||||
`(let [signal# ~record-form]
|
||||
(reduce-kv assoc signal# (.-kvs signal#)))))]
|
||||
|
||||
`(enc/bound-delay
|
||||
;; Delay (cache) shared by all handlers, incl. `:let` eval,
|
||||
;; signal construction, middleware, etc. Throws caught by handler.
|
||||
~do-form
|
||||
(let [~@let-form ; Allow to throw, eval BEFORE data, msg, etc.
|
||||
signal# ~signal-form]
|
||||
|
||||
;; Final unwrapped signal value visible to users/handler-fns, allow to throw
|
||||
(if-let [sig-middleware# ~middleware-form]
|
||||
(sig-middleware# signal#) ; Apply signal middleware, can throw
|
||||
(do signal#)))))
|
||||
|
||||
;; Trade-off: avoid double `run-form` expansion
|
||||
run-fn-form (when run-form `(fn [] ~run-form))
|
||||
run-form* (when run-form `(~'__run-fn-form))
|
||||
|
||||
into-let-form
|
||||
(enc/cond!
|
||||
(not trace?) ; Don't trace
|
||||
`[~'__otel-context1 nil
|
||||
~'__uid ~(auto-> uid-form `(taoensso.telemere/*uid-fn* (if ~'__root0 false true)))
|
||||
~'__root1 ~'__root0 ; Retain, but don't establish
|
||||
~'__run-result
|
||||
~(when run-form
|
||||
`(let [t0# (enc/now-nano*)]
|
||||
(enc/try*
|
||||
(do (RunResult. ~run-form* nil (- (enc/now-nano*) t0#)))
|
||||
(catch :all t# (RunResult. nil t# (- (enc/now-nano*) t0#))))))]
|
||||
|
||||
;; Trace without OpenTelemetry
|
||||
(or cljs? (not enabled:otel-tracing?))
|
||||
`[~'__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
|
||||
~(when run-form
|
||||
`(binding [*trace-root* ~'__root1
|
||||
*trace-parent* {:id ~'__id, :uid ~'__uid}]
|
||||
(let [t0# (enc/now-nano*)]
|
||||
(enc/try*
|
||||
(do (RunResult. ~run-form* nil (- (enc/now-nano*) t0#)))
|
||||
(catch :all t# (RunResult. nil t# (- (enc/now-nano*) t0#)))))))]
|
||||
|
||||
;; Trace with OpenTelemetry
|
||||
(and clj? enabled:otel-tracing?)
|
||||
`[~'__otel-context0 ~(get opts :otel/context `(otel-context)) ; Context
|
||||
~'__otel-context1 ~(if run-form `(otel-context+span ~'__id ~'__inst ~'__otel-context0) ~'__otel-context0)
|
||||
~'__uid ~(auto-> uid-form `(or (otel-span-id ~'__otel-context1) (com.taoensso.encore.Ids/genHexId16)))
|
||||
~'__root1
|
||||
(or ~'__root0
|
||||
~(when trace?
|
||||
`{:id ~'__id, :uid (or (otel-trace-id ~'__otel-context1) (com.taoensso.encore.Ids/genHexId32))}))
|
||||
|
||||
~'__run-result
|
||||
~(when run-form
|
||||
`(binding [*otel-context* ~'__otel-context1
|
||||
*trace-root* ~'__root1
|
||||
*trace-parent* {:id ~'__id, :uid ~'__uid}]
|
||||
(let [otel-scope# (.makeCurrent ~'__otel-context1)
|
||||
t0# (enc/now-nano*)]
|
||||
(enc/try*
|
||||
(do (RunResult. ~run-form* nil (- (enc/now-nano*) t0#)))
|
||||
(catch :all t# (RunResult. nil t# (- (enc/now-nano*) t0#)))
|
||||
(finally (.close otel-scope#))))))])
|
||||
|
||||
final-form
|
||||
;; Unless otherwise specified, allow errors to throw on call
|
||||
`(let [~'__run-fn-form ~run-fn-form
|
||||
~'__kind ~kind-form
|
||||
~'__ns ~ns-form
|
||||
~'__id ~id-form
|
||||
~'__level ~level-form]
|
||||
|
||||
(enc/if-not ~allow?
|
||||
~run-form*
|
||||
(let [~'__inst ~inst-form
|
||||
~'__thread ~thread-form
|
||||
~'__root0 ~root-form0 ; ?{:keys [id uid]}
|
||||
|
||||
~@into-let-form ; Inject conditional bindings
|
||||
signal# ~signal-delay-form]
|
||||
|
||||
(dispatch-signal!
|
||||
;; Unconditionally send same wrapped signal to all handlers.
|
||||
;; Each handler will use wrapper for handler filtering,
|
||||
;; unwrapping (realizing) only allowed signals.
|
||||
(WrappedSignal. ~'__kind ~'__ns ~'__id ~'__level signal#))
|
||||
|
||||
(if ~'__run-result
|
||||
( ~'__run-result signal#)
|
||||
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
|
||||
(with-signal (signal! {:level :warn :let [x :x] :msg ["Test" "message" x] :data {:a :A :x x} :run (+ 1 2)}))
|
||||
(macroexpand '(signal! {:level :warn :let [x :x] :msg ["Test" "message" x] :data {:a :A :x x} :run (+ 1 2)}))
|
||||
(macroexpand '(signal! {:level :info}))
|
||||
|
||||
(do
|
||||
(println "---")
|
||||
(sigs/with-handler *sig-handlers* "hf1" (fn hf1 [x] (println x)) {}
|
||||
(signal! {:level :info, :run "run"}))))
|
||||
|
||||
#?(:clj
|
||||
(defmacro ^:public signal-allowed?
|
||||
"Returns true iff signal with given opts would meet filtering conditions:
|
||||
(when (signal-allowed? {:level :warn, <...>}) (my-custom-code))
|
||||
|
||||
Allows you to use Telemere's rich filtering system for conditionally
|
||||
executing arbitrary code. Also handy for batching multiple signals
|
||||
under a single set of conditions (incl. rate-limiting, sampling, etc.):
|
||||
|
||||
;; Logs exactly 2 or 0 messages (never 1):
|
||||
(when (signal-allowed? {:level :info, :sample-rate 0.5})
|
||||
(log! {:allow? true} \"Message 1\")
|
||||
(log! {:allow? true} \"Message 2\"))"
|
||||
|
||||
;; Used also for interop (tools.logging, SLF4J), etc.
|
||||
{:arglists (signal-arglists :signal-allowed?)}
|
||||
[arg1 & more]
|
||||
(let [opts (valid-opts! (if more (apply hash-map arg1 more) arg1))
|
||||
|
||||
defaults (get opts :defaults)
|
||||
opts (merge defaults (dissoc opts :defaults))
|
||||
|
||||
{:keys [#_expansion-id #_location elide? allow?]}
|
||||
(sigs/filterable-expansion
|
||||
{:sf-arity 4
|
||||
:ct-sig-filter ct-sig-filter
|
||||
:*rt-sig-filter* `*rt-sig-filter*}
|
||||
(assoc opts :location*
|
||||
(get opts :location* (enc/get-source &form &env))))]
|
||||
|
||||
(if elide? false `(if ~allow? true false)))))
|
||||
|
||||
(comment (macroexpand '(signal-allowed? {:level :info})))
|
||||
|
||||
;;;; Interop
|
||||
|
||||
#?(:clj
|
||||
(do
|
||||
(enc/defonce ^:private interop-checks_
|
||||
"{<source-id> (fn check [])}"
|
||||
(atom
|
||||
{:tools-logging (fn [] {:present? present:tools-logging?, :enabled-by-env? enabled:tools-logging?})
|
||||
:slf4j (fn [] {:present? present:slf4j?, :telemere-provider-present? present:telemere-slf4j?})
|
||||
:open-telemetry (fn [] {:present? present:otel?, :use-tracer? enabled:otel-tracing?})}))
|
||||
|
||||
(defn add-interop-check! [source-id check-fn] (swap! interop-checks_ assoc source-id check-fn))
|
||||
|
||||
(defn ^:public check-interop
|
||||
"Experimental, subject to change.
|
||||
Runs Telemere's registered interop checks and returns info useful
|
||||
for tests/debugging, e.g.:
|
||||
|
||||
{:open-telemetry {:present? false}
|
||||
:tools-logging {:present? false}
|
||||
:slf4j {:present? true
|
||||
:sending->telemere? true
|
||||
:telemere-receiving? true}
|
||||
...}"
|
||||
[]
|
||||
(enc/map-vals (fn [check-fn] (check-fn))
|
||||
@interop-checks_))
|
||||
|
||||
(defn test-interop! [msg test-fn]
|
||||
(let [msg (str "Interop test: " msg " (" (enc/uuid-str) ")")
|
||||
signal
|
||||
(binding [*rt-sig-filter* nil] ; Without runtime filters
|
||||
(with-signal :raw :trap (test-fn msg)))]
|
||||
|
||||
(= (force (get signal :msg_)) msg)))))
|
||||
0
projects/slf4j/.gitignore → slf4j/.gitignore
vendored
0
projects/slf4j/.gitignore → slf4j/.gitignore
vendored
|
|
@ -1,4 +1,4 @@
|
|||
(defproject com.taoensso/telemere-slf4j "1.0.0-RC2"
|
||||
(defproject com.taoensso/telemere-slf4j "1.2.1"
|
||||
:author "Peter Taoussanis <https://www.taoensso.com>"
|
||||
:description "Telemere backend/provider for SLF4J API v2"
|
||||
:url "https://www.taoensso.com/telemere"
|
||||
|
|
@ -16,9 +16,9 @@
|
|||
:profiles
|
||||
{:provided
|
||||
{:dependencies
|
||||
[[org.clojure/clojure "1.12.0"]
|
||||
[org.slf4j/slf4j-api "2.0.16"]
|
||||
[com.taoensso/telemere "1.0.0-RC2"]]}
|
||||
[[org.clojure/clojure "1.12.3"]
|
||||
[org.slf4j/slf4j-api "2.0.17"]
|
||||
[com.taoensso/telemere "1.2.1"]]}
|
||||
|
||||
:dev
|
||||
{:plugins
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
(ns taoensso.telemere.slf4j
|
||||
"Interop support for SLF4Jv2 -> Telemere.
|
||||
"SLF4Jv2 -> Telemere interop.
|
||||
Telemere will attempt to load this ns automatically when possible.
|
||||
|
||||
To use Telemere as your SLF4J backend/provider, just include the
|
||||
|
|
@ -18,7 +18,8 @@
|
|||
|
||||
{:author "Peter Taoussanis (@ptaoussanis)"}
|
||||
(:require
|
||||
[taoensso.encore :as enc :refer [have have?]]
|
||||
[taoensso.truss :as truss]
|
||||
[taoensso.encore :as enc]
|
||||
[taoensso.telemere.impl :as impl])
|
||||
|
||||
(:import
|
||||
|
|
@ -62,13 +63,13 @@
|
|||
|
||||
(comment [(est-marker! "a1" "a2") (get-marker "a1") (= (get-marker "a1") (get-marker "a1"))])
|
||||
|
||||
(def ^:private marker-names
|
||||
(def ^:private get-marker-names
|
||||
"Returns #{<MarkerName>}. Cached => assumes markers NOT modified after creation."
|
||||
;; We use `BasicMarkerFactory` so:
|
||||
;; 1. Our markers are just labels (no other content besides their name).
|
||||
;; 2. Markers with the same name are identical (enabling caching).
|
||||
(enc/fmemoize
|
||||
(fn marker-names [marker-or-markers]
|
||||
(fn get-marker-names [marker-or-markers]
|
||||
(if (instance? org.slf4j.Marker marker-or-markers)
|
||||
|
||||
;; Single marker
|
||||
|
|
@ -78,16 +79,16 @@
|
|||
(if-not (.hasReferences m)
|
||||
acc
|
||||
(enc/reduce-iterator!
|
||||
(fn [acc ^org.slf4j.Marker in]
|
||||
(if-not (.hasReferences in)
|
||||
(conj acc (.getName in))
|
||||
(into acc (marker-names in))))
|
||||
(fn [acc ^org.slf4j.Marker in]
|
||||
(if-not (.hasReferences in)
|
||||
(conj acc (.getName in))
|
||||
(into acc (get-marker-names in))))
|
||||
acc (.iterator m))))
|
||||
|
||||
;; Vector of markers
|
||||
(reduce
|
||||
(fn [acc in] (into acc (marker-names in)))
|
||||
#{} (have vector? marker-or-markers))))))
|
||||
(fn [acc in] (into acc (get-marker-names in)))
|
||||
#{} (truss/have vector? marker-or-markers))))))
|
||||
|
||||
(comment
|
||||
(let [m1 (est-marker! "M1")
|
||||
|
|
@ -96,9 +97,9 @@
|
|||
ms [m1 m2]]
|
||||
|
||||
(enc/qb 1e6 ; [45.52 47.48 44.85]
|
||||
(marker-names m1)
|
||||
(marker-names cm)
|
||||
(marker-names ms))))
|
||||
(get-marker-names m1)
|
||||
(get-marker-names cm)
|
||||
(get-marker-names ms))))
|
||||
|
||||
;;;; Interop fns (called by `TelemereLogger`)
|
||||
|
||||
|
|
@ -107,22 +108,22 @@
|
|||
[logger-name level]
|
||||
(when-debug (println [:slf4j/allowed? (sig-level level) logger-name]))
|
||||
(impl/signal-allowed?
|
||||
{:location {:ns logger-name} ; Typically source class name
|
||||
:kind :slf4j
|
||||
:level (sig-level level)}))
|
||||
{:ns logger-name ; Typically source class name
|
||||
:kind :slf4j
|
||||
:level (sig-level level)}))
|
||||
|
||||
(defn- normalized-log!
|
||||
[logger-name level inst error msg-pattern args marker-names kvs]
|
||||
(when-debug (println [:slf4j/normalized-log! (sig-level level) logger-name]))
|
||||
(impl/signal!
|
||||
{:allow? true ; Pre-filtered by `allowed?` call
|
||||
:location {:ns logger-name} ; Typically source class name
|
||||
:kind :slf4j
|
||||
:level (sig-level level)
|
||||
:inst inst
|
||||
:error error
|
||||
{:allow? true ; Pre-filtered by `allowed?` call
|
||||
:ns logger-name ; Typically source class name
|
||||
:kind :slf4j
|
||||
:level (sig-level level)
|
||||
:inst inst
|
||||
:error error
|
||||
|
||||
:ctx
|
||||
:ctx+
|
||||
(when-let [hmap (org.slf4j.MDC/getCopyOfContextMap)]
|
||||
(clojure.lang.PersistentHashMap/create hmap))
|
||||
|
||||
|
|
@ -131,11 +132,10 @@
|
|||
(org.slf4j.helpers.MessageFormatter/basicArrayFormat
|
||||
msg-pattern args))
|
||||
|
||||
:data
|
||||
(enc/assoc-some nil
|
||||
:slf4j/marker-names marker-names
|
||||
:slf4j/args (when args (vec args))
|
||||
:slf4j/kvs kvs)})
|
||||
:slf4j/args args ; Object[]
|
||||
:slf4j/markers marker-names ; Usu. used for routing, filtering, xfns, etc.
|
||||
:data (when kvs {:slf4j/kvs kvs})})
|
||||
|
||||
nil)
|
||||
|
||||
(defn- log!
|
||||
|
|
@ -143,24 +143,24 @@
|
|||
|
||||
;; Modern "fluent" API calls
|
||||
([logger-name ^org.slf4j.event.LoggingEvent event]
|
||||
(let [inst (or (when-let [ts (.getTimeStamp event)] (java.time.Instant/ofEpochMilli ts)) (enc/now-inst*))
|
||||
level (.getLevel event)
|
||||
error (.getThrowable event)
|
||||
msg-pattern (.getMessage event)
|
||||
args (when-let [args (.getArgumentArray event)] args)
|
||||
markers (when-let [markers (.getMarkers event)] (marker-names (vec markers)))
|
||||
kvs (when-let [kvps (.getKeyValuePairs event)]
|
||||
(reduce
|
||||
(fn [acc ^org.slf4j.event.KeyValuePair kvp]
|
||||
(assoc acc (.-key kvp) (.-value kvp)))
|
||||
nil kvps))]
|
||||
(let [inst (or (when-let [ts (.getTimeStamp event)] (java.time.Instant/ofEpochMilli ts)) (enc/now-inst*))
|
||||
level (.getLevel event)
|
||||
error (.getThrowable event)
|
||||
msg-pattern (.getMessage event)
|
||||
args (when-let [args (.getArgumentArray event)] args)
|
||||
marker-names (when-let [markers (.getMarkers event)] (get-marker-names (vec markers)))
|
||||
kvs (when-let [kvps (.getKeyValuePairs event)]
|
||||
(reduce
|
||||
(fn [acc ^org.slf4j.event.KeyValuePair kvp]
|
||||
(assoc acc (.-key kvp) (.-value kvp)))
|
||||
nil kvps))]
|
||||
|
||||
(when-debug (println [:slf4j/fluent-log-call (sig-level level) logger-name]))
|
||||
(normalized-log! logger-name level inst error msg-pattern args markers kvs)))
|
||||
(normalized-log! logger-name level inst error msg-pattern args marker-names kvs)))
|
||||
|
||||
;; Legacy API calls
|
||||
([logger-name ^org.slf4j.event.Level level error msg-pattern args marker]
|
||||
(let [marker-names (when marker (marker-names marker))]
|
||||
(let [marker-names (when marker (get-marker-names marker))]
|
||||
(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))))
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ And they're represented by plain **Clojure/Script maps** with those attributes (
|
|||
|
||||
Fundamentally **all signals**:
|
||||
|
||||
- Occur or are observed at a particular **location** in your code (file, namespace, line, column).
|
||||
- Occur or are observed at a particular **location** in your code (namespace, line, column).
|
||||
- Occur or are observed *within* a particular **program state** / context.
|
||||
- Convey something of value *about* that **program state** / context.
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ Its name is a combination of _telemetry_ and _telomere_:
|
|||
|
||||
> *Telemetry* derives from the Greek *tele* (remote) and *metron* (measure). It refers to the collection of *in situ* (in position) data, for transmission to other systems for monitoring/analysis. *Logs* are the most common form of software telemetry. So think of telemetry as the *superset of logging-like activities* that help monitor and understand (software) systems.
|
||||
|
||||
> *Telomere* derives from the Greek *télos* (end) and *méros* (part). It refers to a genetic feature commonly found at the end of linear chromosomes that helps to protect chromosome integrity.
|
||||
> *Telomere* derives from the Greek *télos* (end) and *méros* (part). It refers to a genetic feature commonly found at the end of linear chromosomes that helps to protect chromosome integrity (think biological checksum).
|
||||
|
||||
# Setup
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ deps.edn: com.taoensso/telemere {:mvn/version "x-y-z"}
|
|||
And setup your namespace imports:
|
||||
|
||||
```clojure
|
||||
(ns my-app (:require [taoensso.telemere :as t]))
|
||||
(ns my-app (:require [taoensso.telemere :as tel]))
|
||||
```
|
||||
|
||||
# Default config
|
||||
|
|
@ -127,30 +127,34 @@ Interop can be tough to get configured correctly so the [`check-interop`](https:
|
|||
|
||||
## Creating signals
|
||||
|
||||
Use whichever signal creator is most convenient for your needs:
|
||||
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!`.
|
||||
|
||||
| Name | Kind | Args | Returns |
|
||||
| :---------------------------------------------------------------------------------------------------------- | :--------- | :--------------- | :--------------------------- |
|
||||
| [`signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#signal!) | `:generic` | `opts` | Depends on opts |
|
||||
| [`event!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#event!) | `:event` | `id` + `?level` | Signal allowed? |
|
||||
| [`log!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#log!) | `:log` | `?level` + `msg` | Signal allowed? |
|
||||
| [`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 |
|
||||
|
||||
- See relevant docstrings (links above) for usage info.
|
||||
- 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.
|
||||
- See [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cljc) for REPL-ready examples.
|
||||
Several different wrapper macros are provided. The only difference between them:
|
||||
|
||||
1. They create signals with a different `:kind` value (which can be handy for filtering, etc.).
|
||||
2. They have different positional arguments and/or return values optimised for concise calling in different use cases.
|
||||
|
||||
**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 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
|
||||
|
||||
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
|
||||
(t/with-signal
|
||||
(t/log!
|
||||
(tel/with-signal
|
||||
(tel/log!
|
||||
{:let [x "x"]
|
||||
:data {:x x}}
|
||||
["My msg:" x]))
|
||||
|
|
@ -165,31 +169,35 @@ Both have several options, see their docstrings (links above) for details.
|
|||
|
||||
## 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 **creation** is allowed by **signal filters**:
|
||||
- 1. Signal **call filters** pass:
|
||||
- a. Compile time: sample rate, kind, ns, id, level, when form, rate limit
|
||||
- b. Runtime: sample rate, kind, ns, id, level, when form, rate limit
|
||||
|
||||
- 2. Signal **handling** is allowed by **handler filters**:
|
||||
- 2. Signal **handler filters** pass:
|
||||
- a. Compile time: not applicable
|
||||
- b. Runtime: sample rate, kind, ns, id, level, when fn, rate limit
|
||||
|
||||
- 3. **Signal middleware** `(fn [signal]) => ?modified-signal` does not return nil
|
||||
- 4. **Handler middleware** `(fn [signal]) => ?modified-signal` does not return nil
|
||||
- 3. **Call transform** `(fn [signal]) => ?modified-signal` returns non-nil
|
||||
- 4. **Handler transform** `(fn [signal]) => ?modified-signal` returns non-nil
|
||||
|
||||
> 👉 Transform fns provides a flexible way to modify and/or filter signals by arbitrary signal data/content conditions (return nil to skip handling).
|
||||
|
||||
> 👉 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:
|
||||
|
||||
```clojure
|
||||
(t/set-min-level! :info) ; Set global minimum level
|
||||
(t/with-signal (t/event! ::my-id1 :info)) ; => {:keys [inst id ...]}
|
||||
(t/with-signal (t/event! ::my-id1 :debug)) ; => nil (signal not allowed)
|
||||
(tel/set-min-level! :info) ; Set global minimum level
|
||||
(tel/with-signal (tel/log! {:level :info ...})) ; => {:keys [inst id ...]}
|
||||
(tel/with-signal (tel/log! {:level :debug ...})) ; => nil (signal not allowed)
|
||||
|
||||
(t/with-min-level :trace ; Override global minimum level
|
||||
(t/with-signal (t/event! ::my-id1 :debug))) ; => {:keys [inst id ...]}
|
||||
(tel/with-min-level :trace ; Override global minimum level
|
||||
(tel/with-signal (tel/log! {:level :debug ...})) ; => {:keys [inst id ...]}
|
||||
|
||||
;; Disallow all signals in matching namespaces
|
||||
(t/set-ns-filter! {:disallow "some.nosy.namespace.*"})
|
||||
(tel/set-ns-filter! {:disallow "some.nosy.namespace.*"})
|
||||
```
|
||||
|
||||
- Filtering is always O(1), except for rate limits which are O(n_windows).
|
||||
|
|
@ -204,7 +212,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-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 middleware/handlers) |
|
||||
| [`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: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:handler-dispatch-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:handler-dispatch-options) | Signal handler dispatch options |
|
||||
|
|
|
|||
|
|
@ -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"/>
|
||||
|
||||
- `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 **signal 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.).
|
||||
- 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.).
|
||||
|
|
@ -2,21 +2,37 @@ See below for config by topic-
|
|||
|
||||
# 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 **creation** is allowed by **signal filters**:
|
||||
- 1. Signal **call filters** pass:
|
||||
- a. Compile time: sample rate, kind, ns, id, level, when form, rate limit
|
||||
- b. Runtime: sample rate, kind, ns, id, level, when form, rate limit
|
||||
|
||||
- 2. Signal **handling** is allowed by **handler filters**:
|
||||
- 2. Signal **handler filters** pass:
|
||||
- a. Compile time: not applicable
|
||||
- b. Runtime: sample rate, kind, ns, id, level, when fn, rate limit
|
||||
|
||||
- 3. **Signal middleware** `(fn [signal]) => ?modified-signal` does not return nil
|
||||
- 4. **Handler middleware** `(fn [signal]) => ?modified-signal` does not return nil
|
||||
- 3. **Call transform** `(fn [signal]) => ?modified-signal` returns non-nil
|
||||
- 4. **Handler transform** `(fn [signal]) => ?modified-signal` returns non-nil
|
||||
|
||||
> 👉 Transform fns provides a flexible way to modify and/or filter signals by arbitrary signal data/content conditions (return nil to skip handling).
|
||||
|
||||
> 👉 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.
|
||||
|
||||
## 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
|
||||
|
||||
See section [4-Handlers](./4-Handlers).
|
||||
|
|
@ -45,7 +61,7 @@ Verify successful interop with [`check-interop`](https://cljdoc.org/d/com.taoens
|
|||
|
||||
To do this:
|
||||
|
||||
1. Ensure that you have the SLF4J [dependency](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) ( v2+ **only**), and
|
||||
1. Ensure that you have the SLF4J [dependency](https://mvnrepository.com/artifact/org.slf4j/slf4j-api) (v2+ **only**), and
|
||||
2. Ensure that you have the Telemere SLF4J backend [dependency](https://clojars.org/com.taoensso/telemere-slf4j)
|
||||
|
||||
When `com.taoensso/telemere-slf4j` (2) is on your classpath AND no other SLF4J backends are, SLF4J will automatically direct all its logging calls to Telemere.
|
||||
|
|
@ -101,6 +117,13 @@ To do this:
|
|||
|
||||
Aside from configuring the exporters (2), Telemere's OpenTelemetry interop **does not require** any use of or familiarity with the OpenTelemetry Java API or concepts. Just use Telemere as you normally would, and the handler (3) will automatically emit detailed log and trace data to your configured exporters (2).
|
||||
|
||||
Verify successful interop with [`check-interop`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#check-interop):
|
||||
|
||||
```clojure
|
||||
(check-interop) ; =>
|
||||
{:open-telemetry {:present? true, :use-tracer? true, :viable-tracer? true}}
|
||||
```
|
||||
|
||||
## Tufte
|
||||
|
||||
> [Tufte](https://www.taoensso.com/tufte) is a simple performance monitoring library for Clojure/Script by the author of Telemere.
|
||||
|
|
@ -109,7 +132,7 @@ Telemere can easily incorporate Tufte performance data in its signals, just like
|
|||
|
||||
```clojure
|
||||
(let [[_ perf-data] (tufte/profiled <opts> <form>)]
|
||||
(t/log! {:perf-data perf-data} "Performance data"))
|
||||
(tel/log! {:perf-data perf-data} "Performance data"))
|
||||
```
|
||||
|
||||
Telemere and Tufte work great together:
|
||||
|
|
@ -119,12 +142,14 @@ Telemere and Tufte work great together:
|
|||
|
||||
## Truss
|
||||
|
||||
> [Truss](https://www.taoensso.com/truss) is an assertions micro-library for Clojure/Script by the author of Telemere.
|
||||
> [Truss](https://www.taoensso.com/truss) is a micro toolkit for Clojure/Script errors by the author of Telemere.
|
||||
|
||||
Telemere can easily incorporate Truss assertion failure information in its signals, just like any other (error) data.
|
||||
|
||||
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
|
||||
(t/catch->error! <form-with-truss-assertion/s>)
|
||||
(tel/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.
|
||||
|
|
@ -1,10 +1,14 @@
|
|||
Telemere's signal handlers are just **plain functions** that take a signal (map) to **do something with them** (analyse them, write them to console/file/queue/db/etc.).
|
||||
|
||||
Here's a simple handler: `(fn [signal] (println signal))`.
|
||||
Here's a minimal handler: `(fn [signal] (println signal))`.
|
||||
|
||||
A second 0-arg arity will be called when stopping the handler. This is handy for stateful handlers or handlers that need to release resources, e.g.:
|
||||
|
||||
`(fn my-handler ([] (my-stop-code)) ([signal] (println signal))`
|
||||
```
|
||||
(fn my-handler
|
||||
([signal] (println signal)
|
||||
([] (my-stop-code)))
|
||||
```
|
||||
|
||||
Telemere includes a number of signal handlers out-the-box, and more may be available via the [community](./8-Community#handlers-and-tools).
|
||||
|
||||
|
|
@ -23,6 +27,7 @@ See ❤️ links below to **vote on future handlers**:
|
|||
| Console (raw) | - | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console-raw) |
|
||||
| [Datadog](https://www.datadoghq.com/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | [❤️](https://github.com/taoensso/roadmap/issues/12) |
|
||||
| Email | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.postal#handler:postal) | - |
|
||||
| File/s | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:file) | - |
|
||||
| [Graylog](https://graylog.org/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [Jaeger](https://www.jaegertracing.io/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
| [Logstash](https://www.elastic.co/logstash) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - |
|
||||
|
|
@ -43,11 +48,11 @@ There's two kinds of config relevant to all signal handlers:
|
|||
|
||||
## Dispatch opts
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
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 handler middleware in particular is an often 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.
|
||||
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.
|
||||
|
||||
## Handler-specific opts
|
||||
|
||||
|
|
@ -66,11 +71,11 @@ By default it writes formatted strings intended for human consumption:
|
|||
```clojure
|
||||
;; Create a test signal
|
||||
(def my-signal
|
||||
(t/with-signal
|
||||
(t/log! {:id ::my-id, :data {:x1 :x2}} "My message")))
|
||||
(tel/with-signal
|
||||
(tel/log! {:id ::my-id, :data {:x1 :x2}} "My message")))
|
||||
|
||||
;; Create console handler with default opts (writes formatted string)
|
||||
(def my-handler (t/handler:console {}))
|
||||
(def my-handler (tel/handler:console {}))
|
||||
|
||||
;; Test handler, remember it's just a (fn [signal])
|
||||
(my-handler my-signal) ; %>
|
||||
|
|
@ -85,8 +90,8 @@ To instead writes signals as [edn](https://github.com/edn-format/edn):
|
|||
```clojure
|
||||
;; Create console handler which writes signals as edn
|
||||
(def my-handler
|
||||
(t/handler:console
|
||||
{:output-fn (t/pr-signal-fn {:pr-fn :edn})}))
|
||||
(tel/handler:console
|
||||
{:output-fn (tel/pr-signal-fn {:pr-fn :edn})}))
|
||||
|
||||
(my-handler my-signal) ; %>
|
||||
;; {:inst #inst "2024-04-11T10:54:57.202869Z", :msg_ "My message", :ns "examples", ...}
|
||||
|
|
@ -100,9 +105,9 @@ To instead writes signals as JSON:
|
|||
;; Ref. <https://github.com/metosin/jsonista> (or any alt JSON lib)
|
||||
#?(:clj (require '[jsonista.core :as jsonista]))
|
||||
(def my-handler
|
||||
(t/handler:console
|
||||
(tel/handler:console
|
||||
{:output-fn
|
||||
(t/pr-signal-fn
|
||||
(tel/pr-signal-fn
|
||||
{:pr-fn
|
||||
#?(:cljs :json ; Use js/JSON.stringify
|
||||
:clj jsonista/write-value-as-string)})}))
|
||||
|
|
@ -115,27 +120,28 @@ Note that when writing JSON with Clojure, you *must* provide an appropriate `pr-
|
|||
|
||||
### Handler-specific per-signal kvs
|
||||
|
||||
Telemere includes a handy mechanism for including arbitrary app-level data/opts in individual signals for use by custom middleware and/or handlers.
|
||||
Telemere includes a handy mechanism for including arbitrary app-level data/opts in individual signals for use by custom transforms 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.:
|
||||
|
||||
```clojure
|
||||
(t/with-signal
|
||||
(t/event! ::my-id
|
||||
{:my-middleware-data "foo"
|
||||
:my-handler-data "bar"}))
|
||||
(tel/with-signal
|
||||
(tel/log!
|
||||
{...
|
||||
:my-data-for-xfn "foo"
|
||||
:my-data-for-handler "bar"}))
|
||||
|
||||
;; %>
|
||||
;; {;; App-level kvs included inline (assoc'd to signal root)
|
||||
;; :my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"
|
||||
;; :my-data-for-xfn "foo"
|
||||
;; :my-data-for-handler "bar"
|
||||
;; :kvs ; And also collected together under ":kvs" key
|
||||
;; {:my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"}
|
||||
;; {:my-data-for-xfn "foo"
|
||||
;; :my-data-for-handler "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 middleware/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 transforms/handlers.
|
||||
|
||||
# Managing handlers
|
||||
|
||||
|
|
@ -181,9 +187,9 @@ Writing your own signal handlers for Telemere is straightforward, and a reasonab
|
|||
- Handlers just plain Clojure/Script fns of 2 arities:
|
||||
|
||||
```clojure
|
||||
(defn my-basic-handler
|
||||
([]) ; Arity-0 called when stopping the handler
|
||||
(defn my-handler
|
||||
([signal] (println signal)) ; Arity-1 called when handling a signal
|
||||
([] (my-stop-code)) ; Arity-0 called when stopping the handler
|
||||
)
|
||||
```
|
||||
|
||||
|
|
@ -211,17 +217,17 @@ If you're making a customizable handler for use by others, it's often handy to d
|
|||
;; Do option validation and other prep here, i.e. try to keep
|
||||
;; expensive work outside handler function when possible!
|
||||
|
||||
(let [handler-fn ; Fn of exactly 2 arities
|
||||
(let [handler-fn ; Fn of exactly 2 arities (1 and 0)
|
||||
(fn a-handler:my-fancy-handler ; Note fn naming convention
|
||||
|
||||
([] ; Arity-0 called when stopping the handler
|
||||
;; Flush buffers, close files, etc. May just noop.
|
||||
;; Return value is ignored.
|
||||
)
|
||||
|
||||
([signal] ; Arity-1 called when handling a signal
|
||||
;; Do something useful with the given signal (write to
|
||||
;; console/file/queue/db, etc.). Return value is ignored.
|
||||
)
|
||||
|
||||
([] ; Arity-0 called when stopping the handler
|
||||
;; Flush buffers, close files, etc. May just noop.
|
||||
;; Return value is ignored.
|
||||
))]
|
||||
|
||||
;; (Advanced, optional) You can use metadata to provide default
|
||||
|
|
@ -229,8 +235,8 @@ If you're making a customizable handler for use by others, it's often handy to d
|
|||
|
||||
(with-meta handler-fn
|
||||
{:dispatch-opts
|
||||
{:min-level :info
|
||||
:rate-limit
|
||||
{:min-level :info
|
||||
:limit
|
||||
[[1 1000] ; Max 1 signal per second
|
||||
[10 60000] ; Max 10 signals per minute
|
||||
]}}))))
|
||||
|
|
@ -245,7 +251,7 @@ If you're making a customizable handler for use by others, it's often handy to d
|
|||
# Example output
|
||||
|
||||
```clojure
|
||||
(t/log! {:id ::my-id, :data {:x1 :x2}} "My message") =>
|
||||
(tel/log! {:id ::my-id, :data {:x1 :x2}} "My message") =>
|
||||
```
|
||||
|
||||
## Clj console handler
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ That eventually grew into Telemere. And I'm happy enough with the result that I
|
|||
|
||||
I will **continue to maintain and support** Timbre for users that are happy with it, though I've also tried to make [migration](./5-Migrating#from-timbre) as easy as possible.
|
||||
|
||||
Over time, I also intend to back-port many backwards-compatible improvements from Telemere to Timbre. For one, Telemere's core was actually written as a library that will eventually be used by Telemere, Timbre, and also [Tufte](https://taoensso.com/tufte).
|
||||
Over time, I also intend to back-port many backwards-compatible improvements from Telemere to Timbre. For one, Telemere's core was actually written as a library that can eventually be used by Telemere, Timbre, and also [Tufte](https://taoensso.com/tufte).
|
||||
|
||||
This will eventually ease long-term maintenance, increase reliability, and help provide unified capabilities across all 3.
|
||||
|
||||
|
|
@ -60,7 +60,7 @@ They're focused on complementary things. When both are in use:
|
|||
|
||||
> [Babashka](https://github.com/babashka/babashka) is a native Clojure interpreter for scripting with fast startup.
|
||||
|
||||
**No**, not currently - though support should be possible with a little work. The current bottleneck is a dependency on [Encore](https://github.com/taoensso/encore), though that could actually be removed (also offering benefits re: library size).
|
||||
**No**, not currently - though support should be possible with a little work. The current bottleneck is a dependency on [Encore](https://github.com/taoensso/encore), which uses some classes not available in Babashka. With some work it should be possible to remove the dependency, and so also reduce library size.
|
||||
|
||||
If there's interest in this, please [upvote](https://github.com/taoensso/roadmap/issues/22) on my open source roadmap.
|
||||
|
||||
|
|
@ -76,18 +76,18 @@ Examples:
|
|||
|
||||
```clojure
|
||||
;; A fixed message (string arg)
|
||||
(t/log! "A fixed message") ; %> {:msg "A fixed message"}
|
||||
(tel/log! "A fixed message") ; %> {:msg "A fixed message"}
|
||||
|
||||
;; A joined message (vector arg)
|
||||
(let [user-arg "Bob"]
|
||||
(t/log! ["User" (str "`" user-arg "`") "just logged in!"]))
|
||||
(tel/log! ["User" (str "`" user-arg "`") "just logged in!"]))
|
||||
;; %> {:msg_ "User `Bob` just logged in!` ...}
|
||||
|
||||
;; With arg prep
|
||||
(let [user-arg "Bob"
|
||||
usd-balance-str "22.4821"]
|
||||
|
||||
(t/log!
|
||||
(tel/log!
|
||||
{:let
|
||||
[username (clojure.string/upper-case user-arg)
|
||||
usd-balance (parse-double usd-balance-str)]
|
||||
|
|
@ -100,10 +100,10 @@ Examples:
|
|||
|
||||
;; %> {:msg "User BOB has balance: $22" ...}
|
||||
|
||||
(t/log! (str "This message " "was built " "by `str`"))
|
||||
(tel/log! (str "This message " "was built " "by `str`"))
|
||||
;; %> {:msg "This message was built by `str`"}
|
||||
|
||||
(t/log! (format "This message was built by `%s`" "format"))
|
||||
(tel/log! (format "This message was built by `%s`" "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).
|
||||
|
||||
# How does Telemere compare to Mulog?
|
||||
# How does Telemere compare to μ/log?
|
||||
|
||||
> [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.
|
||||
> [μ/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.
|
||||
|
||||
Some **similarities** between Telemere and Mulog:
|
||||
Some **similarities** between Telemere and μ/log:
|
||||
|
||||
- Both emphasize **structured data** rather than string messages
|
||||
- Both offer **tracing** to understand (nested) program flow
|
||||
|
|
@ -127,7 +127,7 @@ Some **similarities** between Telemere and Mulog:
|
|||
- Both are **fast** and offer **async handling**
|
||||
- Both offer a variety of **handlers** and are designed for ease of use
|
||||
|
||||
Some particular **strengths of Mulog** that I'm aware of:
|
||||
Some particular **strengths of μ/log** that I'm aware of:
|
||||
|
||||
- More **established/mature**
|
||||
- Wider **range of handlers** (incl. Kafka, Kinesis, Prometheus, Zipkin, etc.)
|
||||
|
|
@ -137,7 +137,7 @@ Some particular **strengths of Mulog** that I'm aware of:
|
|||
|
||||
Some particular **strengths of Telemere**:
|
||||
|
||||
- Both **Clj and Cljs support** (Mulog is Clj only)
|
||||
- Both **Clj and Cljs support** (μ/log 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 **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
|
||||
|
|
@ -148,7 +148,7 @@ Some particular **strengths of Telemere**:
|
|||
|
||||
**My subjective thoughts**:
|
||||
|
||||
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!
|
||||
μ/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!
|
||||
|
||||
The two libraries have many shared capabilities and objectives.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
- Use **signal middleware** to your advantage.
|
||||
- Use **signal call transforms** to your advantage.
|
||||
|
||||
The result of signal 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.
|
||||
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.
|
||||
|
||||
- Signal and handler **sampling is multiplicative**.
|
||||
|
||||
|
|
@ -90,15 +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%).
|
||||
|
||||
This multiplicative rate is helpfully reflected in each signal's final `:sample-rate` value, making it possible to estimate *unsampled* cardinalities in relevant cases.
|
||||
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.
|
||||
|
||||
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 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.
|
||||
|
||||
- Middleware can return any type, but it's best to return only `nil` or a map.
|
||||
- Middleware can be used to **filter signals** by returning `nil`.
|
||||
- Middleware can be used to **split signals**.
|
||||
- Transforms can be used to **filter signals** by returning `nil`.
|
||||
- Transforms 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.
|
||||
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.
|
||||
|
||||
See also the [`dispatch-signal!`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#dispatch-signal!) util.
|
||||
|
||||
- Levels can be **arbitrary integers**.
|
||||
|
||||
|
|
@ -113,13 +114,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:
|
||||
|
||||
```clojure
|
||||
(t/with-signal (t/log! {:my-key "foo"} "My message")))
|
||||
(tel/with-signal (tel/log! {:my-key "foo"} "My message")))
|
||||
;; => {:my-key "foo", :kvs {:my-key "foo", ...}, ...}
|
||||
```
|
||||
|
||||
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 middleware 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 transforms or handlers.
|
||||
|
||||
- Signal `kind` can be useful in advanced cases.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
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.
|
||||
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.
|
||||
|
||||
**PRs very welcome** to add links to this page!
|
||||
[PRs](../wiki#contributions-welcome) **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)
|
||||
|
||||
# Learning
|
||||
|
||||
Includes videos, tutorials, demo projects, etc.
|
||||
[PRs](../wiki#contributions-welcome) welcome for additions!
|
||||
Includes videos, tutorials, demo projects, etc.
|
||||
|
||||
| Type | Description |
|
||||
| ------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|
|
@ -20,9 +19,9 @@ Includes videos, tutorials, demo projects, etc.
|
|||
|
||||
# Handlers and tools
|
||||
|
||||
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 |
|
||||
| ---- | :------------------------------------------------------------ |
|
||||
| - | Your link here? [PRs](../wiki#contributions-welcome) welcome! |
|
||||
Includes libraries or examples for handlers (see [Writing handlers](./4-Handlers#writing-handlers)), transforms, handler utils (e.g. formatters), tools for analyzing signals, etc.
|
||||
|
||||
| Type | Description |
|
||||
| ------- | :------------------------------------------------------------ |
|
||||
| Handler | [Axiom.co](https://github.com/marksto/telemere.axiom) handler |
|
||||
| - | Your link here? [PRs](../wiki#contributions-welcome) welcome! |
|
||||
|
|
|
|||
|
|
@ -1,17 +1,30 @@
|
|||
Are you a library author/maintainer that's considering **using Telemere in your library**?
|
||||
|
||||
You have **two options** below-
|
||||
You have **a few options** below-
|
||||
|
||||
# Options
|
||||
## 1. Common 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 logging facade like [tools.logging](https://github.com/clojure/tools.logging) or [SLF4J](https://www.slf4j.org/).
|
||||
## Modern logging facade
|
||||
|
||||
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)).
|
||||
[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.
|
||||
|
||||
## 2. Telemere as a transitive dependency
|
||||
Basically:
|
||||
|
||||
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.
|
||||
1. You include the (very small) Trove dependency with your library
|
||||
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.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue