mirror of
https://github.com/taoensso/telemere.git
synced 2026-02-09 05:23:11 +00:00
Compare commits
11 commits
master
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4011560c60 | ||
|
|
d62bab2247 | ||
|
|
7b7782e340 | ||
|
|
ea6a039980 | ||
|
|
3e1f453d06 | ||
|
|
eed702a480 | ||
|
|
cf22ddf861 | ||
|
|
cbd786be66 | ||
|
|
b6e8c5fd4a | ||
|
|
b271c4f7b8 | ||
|
|
195c16b476 |
21 changed files with 680 additions and 301 deletions
44
CHANGELOG.md
44
CHANGELOG.md
|
|
@ -2,6 +2,50 @@ This project uses [**Break Versioning**](https://www.taoensso.com/break-versioni
|
|||
|
||||
---
|
||||
|
||||
# `v1.0.0-beta6` (2024-05-05)
|
||||
|
||||
> **Dep/s**: [Telemere](https://clojars.org/com.taoensso/telemere/versions/1.0.0-beta6) and [Telemere SLF4J provider](https://clojars.org/com.taoensso/slf4j-telemere/versions/1.0.0-beta6) are on Clojars.
|
||||
> **Versioning**: Telemere uses [Break Versioning](https://www.taoensso.com/break-versioning).
|
||||
|
||||
This is a **maintenance pre-release** intended to fix issues that have come up during the beta. See below for details, and please **report any unexpected problems** on [GitHub](https://github.com/taoensso/telemere/issues) or the [Slack channel](https://www.taoensso.com/telemere/slack), thank you! 🙏
|
||||
|
||||
\- Peter Taoussanis
|
||||
|
||||
## Changes since `v1.0.0-beta1` (2024-04-19)
|
||||
|
||||
* d0a15bac [mod] Don't auto add OpenTelemetry handler
|
||||
* 6d545dfc [mod] Move (simplify) OpenTelemetry ns
|
||||
* c4d9dd09 [mod] Don't include user-level kvs in default signal content handler
|
||||
* d3c63e17 [mod] Rename `clojure.tools.logging` sys val
|
||||
* b271c4f7 [mod] Simplify middleware - don't auto compose
|
||||
|
||||
## Fixes since `v1.0.0-beta1` (2024-04-19)
|
||||
|
||||
* ffea1a30 [fix] Fix broken AOT support, add AOT tests
|
||||
* e222297a [fix] SLF4J broken timestamps, add tests
|
||||
|
||||
## New since `v1.0.0-beta1` (2024-04-19)
|
||||
|
||||
* 2ba23ee7 [new] Add postal (email) handler
|
||||
* cf22ddf8 [new] Add TCP, UDP socket handlers
|
||||
* b6e8c5fd [new] Add experimental `:thread` key to Clj signals
|
||||
* Handlers will now drain their signal queues on shutdown (configurable)
|
||||
* Rate limiter performance improvements (via Encore)
|
||||
* Doc improvements based on questions that've come up on Slack, etc.
|
||||
|
||||
## Everything since `v1.0.-beta5` (2024-04-29)
|
||||
|
||||
* ae823f0d [mod] Rename, refactor signal formatting utils
|
||||
* b271c4f7 [mod] Simplify middleware - don't auto compose
|
||||
* cbd786be [fix] Broken postal handler subject
|
||||
* cf22ddf8 [new] Add TCP, UDP socket handlers
|
||||
* b6e8c5fd [new] Add experimental `:thread` key to Clj signals
|
||||
* ea6a0399 [new] Add `:incl-kvs?` opt to edn and JSON formatters
|
||||
* 3e1f453d [new] Add `:incl-thread?`, `:incl-kvs?` opts to `format-signal->str-fn`
|
||||
* eed702a4 [new] Add `:end-with-newline` opt to signal formatters
|
||||
|
||||
---
|
||||
|
||||
# `v1.0.0-beta5` (2024-04-29)
|
||||
|
||||
> **Dep/s**: [Telemere](https://clojars.org/com.taoensso/telemere/versions/1.0.0-beta5) and [Telemere SLF4J provider](https://clojars.org/com.taoensso/slf4j-telemere/versions/1.0.0-beta5) are on Clojars.
|
||||
|
|
|
|||
18
README.md
18
README.md
|
|
@ -32,6 +32,7 @@ See [here][GitHub releases] for earlier releases.
|
|||
|
||||
- 1st-class **out-the-box interop** with [SLF4J v2](https://www.slf4j.org/), [clojure.tools.logging](https://github.com/clojure/tools.logging), [OpenTelemetry](https://opentelemetry.io/), and [Tufte](https://www.taoensso.com/tufte).
|
||||
- Included [shim](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.timbre) for easy/gradual [migration from Timbre](../../wiki/5-Migrating).
|
||||
- Included [handlers](../../wiki/4-Handlers#included-handlers) for consoles, files, email, Redis, Slack, TCP/UDP sockets, Logstash, etc.
|
||||
|
||||
#### Scaling
|
||||
|
||||
|
|
@ -132,15 +133,14 @@ See relevant docstrings (links below) for usage info-
|
|||
|
||||
### Internal help
|
||||
|
||||
| Var | Help with |
|
||||
| :-------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------- |
|
||||
| [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) | List of signal creators |
|
||||
| [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Options for signal creators |
|
||||
| [`help:signal-content`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) | Signal map content |
|
||||
| [`help:signal-flow`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-flow) | Ordered flow from signal creation to handling |
|
||||
| [`help:signal-filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-filters) | API for configuring signal filters |
|
||||
| [`help:signal-handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-handlers) | API for configuring signal handlers |
|
||||
| [`help:signal-formatters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-formatters) | Signal formatters for use by handlers |
|
||||
| Var | Help with |
|
||||
| :---------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------- |
|
||||
| [`help:signal-creators`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-creators) | List of signal creators |
|
||||
| [`help:signal-options`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-options) | Options for signal creators |
|
||||
| [`help:signal-content`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-content) | Signal map content |
|
||||
| [`help:signal-flow`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-flow) | Ordered flow from signal creation to handling |
|
||||
| [`help:signal-filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-filters) | API for configuring signal filters |
|
||||
| [`help:signal-handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-handlers) | API for configuring signal handlers |
|
||||
|
||||
### Example handler output
|
||||
|
||||
|
|
|
|||
|
|
@ -141,17 +141,19 @@
|
|||
;; Create console which writes signals as edn
|
||||
(def my-handler
|
||||
(t/handler:console
|
||||
{:format-signal-fn (taoensso.telemere.utils/format-signal->edn-fn)}))
|
||||
{:output-fn (t/pr-signal-fn :edn)}))
|
||||
|
||||
(my-handler my-signal) ; =>
|
||||
;; {:inst #inst "2024-04-11T10:54:57.202869Z", :msg_ "My message", :ns "examples", ...}
|
||||
|
||||
;; Create console which writes signals as JSON
|
||||
#?(:clj (require '[jsonista.core :as jsonista]))
|
||||
(def my-handler
|
||||
(t/handler:console
|
||||
{:format-signal-fn
|
||||
(taoensso.telemere.utils/format-signal->json-fn
|
||||
{:pr-json-fn jsonista.core/write-value-as-string})}))
|
||||
{:output-fn
|
||||
(t/pr-signal-fn
|
||||
#?(:cljs :json
|
||||
:clj jsonista.core/write-value-as-string))}))
|
||||
|
||||
(my-handler my-signal) ; =>
|
||||
;; {"inst":"2024-04-11T10:54:57.202869Z","msg_":"My message","ns":"examples", ...}
|
||||
|
|
@ -219,3 +221,19 @@
|
|||
|
||||
(t/log! (format "This message was built by `%s`" "format"))
|
||||
;; %> {:msg "This message was built by `format`"}
|
||||
|
||||
;;; User kvs
|
||||
|
||||
(t/with-signal
|
||||
(t/event! ::my-id
|
||||
{:my-middleware-data "foo"
|
||||
:my-handler-data "bar"}))
|
||||
|
||||
;; %>
|
||||
;; {;; User kvs included inline (assoc'd to signal root)
|
||||
;; :my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"
|
||||
;; :kvs ; And also collected together under ":kvs" key
|
||||
;; {:my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"}
|
||||
;; ... }
|
||||
|
|
|
|||
15
project.clj
15
project.clj
|
|
@ -1,4 +1,4 @@
|
|||
(defproject com.taoensso/telemere "1.0.0-beta5"
|
||||
(defproject com.taoensso/telemere "1.0.0-beta6"
|
||||
:author "Peter Taoussanis <https://www.taoensso.com>"
|
||||
:description "Structured telemetry library for Clojure/Script"
|
||||
:url "https://www.taoensso.com/telemere"
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
:url "https://www.eclipse.org/legal/epl-v10.html"}
|
||||
|
||||
:dependencies
|
||||
[[com.taoensso/encore "3.105.1"]]
|
||||
[[com.taoensso/encore "3.106.0"]]
|
||||
|
||||
:test-paths ["test" #_"src"]
|
||||
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
{;; :default [:base :system :user :provided :dev]
|
||||
:provided {:dependencies [[org.clojure/clojurescript "1.11.132"]
|
||||
[org.clojure/clojure "1.11.3"]]}
|
||||
:c1.12 {:dependencies [[org.clojure/clojure "1.12.0-alpha10"]]}
|
||||
:c1.12 {:dependencies [[org.clojure/clojure "1.12.0-alpha11"]]}
|
||||
:c1.11 {:dependencies [[org.clojure/clojure "1.11.3"]]}
|
||||
:c1.10 {:dependencies [[org.clojure/clojure "1.10.1"]]}
|
||||
|
||||
|
|
@ -45,13 +45,16 @@
|
|||
[[org.clojure/test.check "1.1.1"]
|
||||
[org.clojure/tools.logging "1.3.0"]
|
||||
[org.slf4j/slf4j-api "2.0.13"]
|
||||
[com.taoensso/slf4j-telemere "1.0.0-beta5"]
|
||||
[com.taoensso/slf4j-telemere "1.0.0-beta6"]
|
||||
#_[org.slf4j/slf4j-simple "2.0.13"]
|
||||
#_[org.slf4j/slf4j-nop "2.0.13"]
|
||||
[com.draines/postal "2.0.5"]
|
||||
|
||||
;;; For optional handlers
|
||||
[io.opentelemetry/opentelemetry-api "1.37.0"]
|
||||
#_[io.opentelemetry/opentelemetry-sdk-extension-autoconfigure "1.37.0"]
|
||||
#_[io.opentelemetry/opentelemetry-exporter-otlp "1.37.0"]]
|
||||
#_[io.opentelemetry/opentelemetry-exporter-otlp "1.37.0"]
|
||||
[metosin/jsonista "0.3.8"]
|
||||
[com.draines/postal "2.0.5"]]
|
||||
|
||||
:plugins
|
||||
[[lein-pprint "1.3.2"]
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ Default signal keys:
|
|||
`: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)
|
||||
`:thread` ------ (Clj only) {:keys [group name id]} thread info for thread that called signal creator
|
||||
`:sample-rate` - ?rate ∈ℝ[0,1] for combined signal AND handler sampling (0.75 => allow 75% of signals, nil => allow all)
|
||||
|
||||
<kvs> ---------- Other arb user-level ?kvs given to signal creator. Typically NOT included
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
Common signal formatters include:
|
||||
(utils/format-signal-str->fn) {<opts>}) ; For human-readable string output (default)
|
||||
(utils/format-signal->edn-fn) {<opts>}) ; For edn output
|
||||
(utils/format-signal->json-fn {<opts>}) ; For JSON output
|
||||
|
||||
See relevant docstrings for details.
|
||||
|
|
@ -22,7 +22,7 @@ Signal options (shared by all signal creators):
|
|||
`: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
|
||||
`:middleware` -- ?[(fn [signal])=>modified-signal ...] signal middleware
|
||||
`:middleware` -- Optional (fn [signal]) => ?modified-signal to apply when signal is created
|
||||
`:trace?` ------ Should tracing be enabled for `:run` form?
|
||||
|
||||
<kvs> ---------- Other arb user-level ?kvs to incl. in signal. Typically NOT included in
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{;;:lein true
|
||||
:source-paths ["src" "test"]
|
||||
:dependencies
|
||||
[[com.taoensso/encore "3.105.1"]
|
||||
[[com.taoensso/encore "3.106.0"]
|
||||
[cider/cider-nrepl "0.47.0"]
|
||||
[binaryage/devtools "1.0.7"]]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject com.taoensso/slf4j-telemere "1.0.0-beta5"
|
||||
(defproject com.taoensso/slf4j-telemere "1.0.0-beta6"
|
||||
:author "Peter Taoussanis <https://www.taoensso.com>"
|
||||
:description "Telemere backend/provider for SLF4J API v2"
|
||||
:url "https://www.taoensso.com/telemere"
|
||||
|
|
@ -16,7 +16,7 @@
|
|||
{:dependencies
|
||||
[[org.clojure/clojure "1.11.3"]
|
||||
[org.slf4j/slf4j-api "2.0.13"]
|
||||
[com.taoensso/telemere "1.0.0-beta5"]]}
|
||||
[com.taoensso/telemere "1.0.0-beta6"]]}
|
||||
|
||||
:dev
|
||||
{:plugins
|
||||
|
|
|
|||
|
|
@ -32,9 +32,10 @@
|
|||
(remove-ns 'taoensso.telemere)
|
||||
(:api (enc/interns-overview)))
|
||||
|
||||
(enc/assert-min-encore-version [3 105 1])
|
||||
(enc/assert-min-encore-version [3 106 0])
|
||||
|
||||
;;;; TODO
|
||||
;; - Add handlers: Logstash, Slack, Carmine, Datadog, Kafka
|
||||
;; - Native OpenTelemetry traces and spans
|
||||
;; - Update Tufte (signal API, config API, signal keys, etc.)
|
||||
;; - Update Timbre (signal API, config API, signal keys, backport improvements)
|
||||
|
|
@ -71,13 +72,16 @@
|
|||
enc/chance
|
||||
enc/rate-limiter
|
||||
enc/newline
|
||||
enc/comp-middleware
|
||||
|
||||
impl/msg-splice
|
||||
impl/msg-skip
|
||||
#?(:clj impl/with-signal)
|
||||
#?(:clj impl/with-signals)
|
||||
#?(:clj impl/signal!)
|
||||
utils/error-signal?)
|
||||
utils/error-signal?
|
||||
utils/pr-signal-fn
|
||||
utils/format-signal-fn)
|
||||
|
||||
;;;; Help
|
||||
|
||||
|
|
@ -87,7 +91,6 @@
|
|||
(impl/defhelp help:signal-content :signal-content)
|
||||
(enc/defalias help:signal-filters help:filters) ; Via Encore
|
||||
(enc/defalias help:signal-handlers help:handlers) ; Via Encore
|
||||
(impl/defhelp help:signal-formatters :signal-formatters)
|
||||
|
||||
;;;; Context
|
||||
|
||||
|
|
@ -118,12 +121,12 @@
|
|||
#?(:clj
|
||||
(defmacro set-ctx!
|
||||
"Set `*ctx*` var's root (base) value. See `*ctx*` for details."
|
||||
[root-val] `(enc/set-var-root! *ctx* ~root-val)))
|
||||
[root-ctx-val] `(enc/set-var-root! *ctx* ~root-ctx-val)))
|
||||
|
||||
#?(:clj
|
||||
(defmacro with-ctx
|
||||
"Evaluates given form with given `*ctx*` value. See `*ctx*` for details."
|
||||
[init-val form] `(binding [*ctx* ~init-val] ~form)))
|
||||
[ctx-val form] `(binding [*ctx* ~ctx-val] ~form)))
|
||||
|
||||
(comment (with-ctx "my-ctx" *ctx*))
|
||||
|
||||
|
|
@ -142,14 +145,13 @@
|
|||
|
||||
(comment (with-ctx {:a :A1 :b :B1} (with-ctx+ {:a :A2} *ctx*)))
|
||||
|
||||
;;;; Middleware
|
||||
;;;; Signal middleware
|
||||
|
||||
(enc/defonce ^:dynamic *middleware*
|
||||
"Optional vector of unary middleware fns to apply (sequentially/left-to-right)
|
||||
to each signal before passing it to handlers. If any middleware fn returns nil,
|
||||
aborts immediately without calling handlers.
|
||||
"Optional (fn [signal]) => ?modified-signal to apply (once) when
|
||||
signal is created. When middleware returns nil, skips all handlers.
|
||||
|
||||
Useful for transforming each signal before handling.
|
||||
Compose multiple middleware fns together with `comp-middleware.
|
||||
|
||||
Re/bind dynamic value using `with-middleware`, `binding`.
|
||||
Modify root (base) value using `set-middleware!`."
|
||||
|
|
@ -158,13 +160,13 @@
|
|||
#?(:clj
|
||||
(defmacro set-middleware!
|
||||
"Set `*middleware*` var's root (base) value. See `*middleware*` for details."
|
||||
[root-val] `(enc/set-var-root! *middleware* ~root-val)))
|
||||
[?root-middleware-fn] `(enc/set-var-root! *middleware* ~?root-middleware-fn)))
|
||||
|
||||
#?(:clj
|
||||
(defmacro with-middleware
|
||||
"Evaluates given form with given `*middleware*` value.
|
||||
See `*middleware*` for details."
|
||||
[init-val form] `(binding [*middleware* ~init-val] ~form)))
|
||||
[?middleware-fn form] `(binding [*middleware* ~?middleware-fn] ~form)))
|
||||
|
||||
;;;; Signal creators
|
||||
;; - signal! [ opts] ; => allowed? / run result (value or throw)
|
||||
|
|
|
|||
|
|
@ -18,20 +18,19 @@
|
|||
|
||||
Returns a (fn handler [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Writes a formatted signal string to stream.
|
||||
- Writes formatted signal string to stream.
|
||||
|
||||
A general-purpose `println`-style handler that's well suited for outputting
|
||||
signals formatted as edn, JSON, or human-readable strings.
|
||||
|
||||
Options:
|
||||
`:format-signal-fn` - (fn [signal]) => output, see `help:signal-formatters`
|
||||
|
||||
`:stream` - `java.io.writer`
|
||||
`:output-fn` - (fn [signal]) => output string, see `format-signal-fn` or `pr-signal-fn`
|
||||
`:stream` - `java.io.writer`
|
||||
Defaults to `*err*` if `utils/error-signal?` is true, and `*out*` otherwise."
|
||||
|
||||
([] (handler:console nil))
|
||||
([{:keys [format-signal-fn stream]
|
||||
:or {format-signal-fn (utils/format-signal->str-fn)}}]
|
||||
([{:keys [output-fn stream]
|
||||
:or {output-fn (utils/format-signal-fn)}}]
|
||||
|
||||
(let [stream (case stream :*out* *out*, :*err* *err* stream)
|
||||
error-signal? utils/error-signal?
|
||||
|
|
@ -42,8 +41,8 @@
|
|||
([signal]
|
||||
(let [^java.io.Writer stream
|
||||
(or stream (if (error-signal? signal) *err* *out*))]
|
||||
(when-let [output (format-signal-fn signal)]
|
||||
(.write stream (str output nl))
|
||||
(when-let [output (output-fn signal)]
|
||||
(.write stream (str output))
|
||||
(.flush stream))))))))
|
||||
|
||||
:cljs
|
||||
|
|
@ -52,17 +51,17 @@
|
|||
|
||||
If `js/console` exists, returns a (fn handler [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Writes a formatted signal string to JavaScript console.
|
||||
- Writes formatted signal string to JavaScript console.
|
||||
|
||||
A general-purpose `println`-style handler that's well suited for outputting
|
||||
signals formatted as edn, JSON, or human-readable strings.
|
||||
|
||||
Options:
|
||||
`:format-signal-fn` - (fn [signal]) => output, see `help:signal-formatters`"
|
||||
`:output-fn` - (fn [signal]) => output string, see `format-signal-fn` or `pr-signal-fn`"
|
||||
|
||||
([] (handler:console nil))
|
||||
([{:keys [format-signal-fn]
|
||||
:or {format-signal-fn (utils/format-signal->str-fn)}}]
|
||||
([{:keys [output-fn]
|
||||
:or {output-fn (utils/format-signal-fn)}}]
|
||||
|
||||
(when (exists? js/console)
|
||||
(let [js-console-logger utils/js-console-logger
|
||||
|
|
@ -71,9 +70,9 @@
|
|||
(fn a-handler:console
|
||||
([]) ; Shut down (no-op)
|
||||
([signal]
|
||||
(when-let [output (format-signal-fn signal)]
|
||||
(when-let [output (output-fn signal)]
|
||||
(let [logger (js-console-logger (get signal :level))]
|
||||
(.call logger logger (str output nl)))))))))))
|
||||
(.call logger logger (str output)))))))))))
|
||||
|
||||
#?(:cljs
|
||||
(defn- logger-fn [logger]
|
||||
|
|
@ -96,16 +95,16 @@
|
|||
Ref. <https://github.com/binaryage/cljs-devtools>."
|
||||
|
||||
([] (handler:console-raw nil))
|
||||
([{:keys [format-signal->prelude-fn format-nsecs-fn] :as opts
|
||||
([{:keys [preamble-fn format-nsecs-fn] :as opts
|
||||
:or
|
||||
{format-signal->prelude-fn (utils/format-signal->prelude-fn) ; (fn [signal])
|
||||
format-nsecs-fn (utils/format-nsecs-fn) ; (fn [nanosecs])
|
||||
{preamble-fn (utils/signal-preamble-fn)
|
||||
format-nsecs-fn (utils/format-nsecs-fn) ; (fn [nanosecs])
|
||||
}}]
|
||||
|
||||
(when (and (exists? js/console) (exists? js/console.group))
|
||||
(let [js-console-logger utils/js-console-logger
|
||||
signal-content-handler ; (fn [signal hf vf]
|
||||
(utils/signal-content-handler
|
||||
content-fn ; (fn [signal append-fn val-fn])
|
||||
(utils/signal-content-fn
|
||||
{:format-nsecs-fn format-nsecs-fn
|
||||
:format-error-fn nil
|
||||
:raw-error? true})]
|
||||
|
|
@ -117,8 +116,8 @@
|
|||
logger (js-console-logger level)]
|
||||
|
||||
;; Unfortunately groups have no level
|
||||
(.group js/console (format-signal->prelude-fn signal))
|
||||
(signal-content-handler signal (logger-fn logger) identity)
|
||||
(.group js/console (preamble-fn signal))
|
||||
(content-fn signal (logger-fn logger) identity)
|
||||
|
||||
(when-let [stack (and error (.-stack (enc/ex-root error)))]
|
||||
(.call logger logger stack))
|
||||
|
|
|
|||
|
|
@ -270,7 +270,7 @@
|
|||
|
||||
Returns a (fn handler [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Writes a formatted signal string to file.
|
||||
- Writes formatted signal string to file.
|
||||
|
||||
Signals will be appended to file specified by `path`.
|
||||
Depending on options, archives may be maintained:
|
||||
|
|
@ -284,9 +284,10 @@
|
|||
`/logs/telemere.log-2020-01-01m.8.gz` ; Archive for Jan 2020, part 8 (oldest entries)
|
||||
|
||||
Options:
|
||||
`:format-signal-fn`- (fn [signal]) => output, see `help:signal-formatters`.
|
||||
`:path` - Path string of the target output file (default `logs/telemere.log`).
|
||||
`:interval` - ∈ #{nil :daily :weekly :monthly} (default `:monthly`).
|
||||
`:output-fn`- (fn [signal]) => output string, see `format-signal-fn` or `pr-signal-fn`
|
||||
`: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)
|
||||
|
|
@ -300,7 +301,7 @@
|
|||
|
||||
([] (handler:file nil))
|
||||
([{:keys
|
||||
[format-signal-fn
|
||||
[output-fn
|
||||
path interval
|
||||
max-file-size
|
||||
max-num-parts
|
||||
|
|
@ -308,7 +309,7 @@
|
|||
gzip-archives?]
|
||||
|
||||
:or
|
||||
{format-signal-fn (utils/format-signal->str-fn)
|
||||
{output-fn (utils/format-signal-fn)
|
||||
path "logs/telemere.log" ; Main path, we'll ALWAYS write to this exact file
|
||||
interval :monthly
|
||||
max-file-size (* 1024 1024 4) ; 4MB
|
||||
|
|
@ -362,9 +363,8 @@
|
|||
(fn a-handler:file
|
||||
([] (locking lock (fw))) ; Close writer
|
||||
([signal]
|
||||
(when-let [output (format-signal-fn signal)]
|
||||
(let [output-str (str output utils/newline)
|
||||
new-interval? (when interval (new-interval!?))
|
||||
(when-let [output (output-fn signal)]
|
||||
(let [new-interval? (when interval (new-interval!?))
|
||||
>max-file-size? (when max-file-size (>max-file-size?))
|
||||
reset-stream? (or new-interval? >max-file-size?)]
|
||||
|
||||
|
|
@ -387,7 +387,7 @@
|
|||
max-num-parts gzip-archives? nil)))
|
||||
|
||||
(when reset-stream? (fw :writer/reset!))
|
||||
(do (fw output-str))))))))))
|
||||
(do (fw output))))))))))
|
||||
|
||||
(comment
|
||||
(manage-test-files! :create)
|
||||
|
|
|
|||
|
|
@ -201,14 +201,6 @@
|
|||
(defmacro with-tracing
|
||||
"Wraps `form` with tracing iff const boolean `trace?` is true."
|
||||
[trace? id uid form]
|
||||
|
||||
;; Not much motivation to support runtime `trace?` form, but easy
|
||||
;; to add support later if desired
|
||||
(when-not (enc/const-form? trace?)
|
||||
(enc/unexpected-arg! trace?
|
||||
{:msg "Expected constant (compile-time) `:trace?` value"
|
||||
:context `with-tracing}))
|
||||
|
||||
(if trace?
|
||||
`(binding [*trace-parent* (TraceParent. ~id ~uid)] ~form)
|
||||
(do form))))
|
||||
|
|
@ -223,7 +215,7 @@
|
|||
(defrecord Signal
|
||||
;; Telemere's main public data type, we avoid nesting and duplication
|
||||
[^long schema inst uid,
|
||||
location ns line column file,
|
||||
location ns line column file #?(:clj thread),
|
||||
sample-rate, kind id level, ctx parent,
|
||||
data msg_ error run-form run-val,
|
||||
end-inst run-nsecs kvs]
|
||||
|
|
@ -359,7 +351,7 @@
|
|||
^Signal
|
||||
;; Note all dynamic vals passed as explicit args for better control
|
||||
[inst uid,
|
||||
location ns line column file,
|
||||
location ns line column file #?(:clj thread :cljs _thread),
|
||||
sample-rate, kind id level, ctx parent,
|
||||
kvs data msg_,
|
||||
run-form run-result error]
|
||||
|
|
@ -379,14 +371,14 @@
|
|||
msg_)]
|
||||
|
||||
(Signal. 1 inst uid,
|
||||
location ns line column file,
|
||||
location ns line column file #?(:clj thread),
|
||||
sample-rate, kind id level, ctx parent,
|
||||
data msg_,
|
||||
run-err run-form run-val,
|
||||
end-inst run-nsecs kvs))
|
||||
|
||||
(Signal. 1 inst uid,
|
||||
location ns line column file,
|
||||
location ns line column file #?(:clj thread),
|
||||
sample-rate, kind id level, ctx parent,
|
||||
data msg_, error nil nil nil nil kvs))]
|
||||
|
||||
|
|
@ -395,10 +387,10 @@
|
|||
(do signal))))
|
||||
|
||||
(comment
|
||||
(enc/qb 1e6 ; 55.67
|
||||
(enc/qb 1e6 ; 66.8
|
||||
(new-signal
|
||||
nil nil nil nil nil nil nil nil nil nil
|
||||
nil nil nil nil nil nil nil nil nil)))
|
||||
nil nil nil nil nil nil nil nil nil nil)))
|
||||
|
||||
;;;; Signal API helpers
|
||||
|
||||
|
|
@ -548,6 +540,17 @@
|
|||
|
||||
;;;; Signal macro
|
||||
|
||||
#?(:clj
|
||||
(defn thread-info
|
||||
"Returns {:keys [group name id]} for current thread."
|
||||
[]
|
||||
(when-let [t (Thread/currentThread)]
|
||||
{:group (when-let [g (.getThreadGroup t)] (.getName g))
|
||||
:name (.getName t)
|
||||
:id (.getId t)})))
|
||||
|
||||
(comment (enc/qb 1e6 (thread-info))) ; 44.49
|
||||
|
||||
#?(:clj
|
||||
(defmacro ^:public signal!
|
||||
"Generic low-level signal call, also aliased in Encore."
|
||||
|
|
@ -557,6 +560,7 @@
|
|||
(have? map? opts) ; We require const map keys, but vals may require eval
|
||||
(let [defaults (get opts :defaults)
|
||||
opts (merge defaults (dissoc opts :defaults))
|
||||
clj? (not (:ns &env))
|
||||
{run-form :run} opts
|
||||
|
||||
{:keys [#_expansion-id location elide? allow?]}
|
||||
|
|
@ -580,15 +584,24 @@
|
|||
kind-form :kind
|
||||
id-form :id} opts
|
||||
|
||||
trace? (get opts :trace? (boolean run-form))
|
||||
trace? (get opts :trace? (boolean run-form))
|
||||
_
|
||||
(when-not (contains? #{true false nil} trace?)
|
||||
;; Not much motivation to support runtime `trace?` form, but easy
|
||||
;; to add support later if desired
|
||||
(enc/unexpected-arg! trace?
|
||||
{:msg "Expected constant (compile-time) `:trace?` boolean"
|
||||
:context `with-tracing}))
|
||||
|
||||
inst-form (get opts :inst :auto)
|
||||
inst-form (if (= inst-form :auto) `(enc/now-inst*) inst-form)
|
||||
inst-form (get opts :inst :auto)
|
||||
inst-form (if (= inst-form :auto) `(enc/now-inst*) inst-form)
|
||||
|
||||
uid-form (get opts :uid (when trace? :auto/uuid))
|
||||
uid-form (parse-uid-form uid-form)
|
||||
uid-form (get opts :uid (when trace? :auto/uuid))
|
||||
uid-form (parse-uid-form uid-form)
|
||||
|
||||
signal-form
|
||||
thread-form (if clj? `(thread-info) nil)
|
||||
|
||||
signal-delay-form
|
||||
(let [{do-form :do
|
||||
let-form :let
|
||||
msg-form :msg
|
||||
|
|
@ -599,8 +612,9 @@
|
|||
let-form (or let-form '[])
|
||||
msg-form (parse-msg-form msg-form)
|
||||
|
||||
ctx-form (get opts :ctx `taoensso.telemere/*ctx*)
|
||||
parent-form (get opts :parent (when trace? `taoensso.telemere.impl/*trace-parent*))
|
||||
ctx-form (get opts :ctx `taoensso.telemere/*ctx*)
|
||||
parent-form (get opts :parent (when trace? `taoensso.telemere.impl/*trace-parent*))
|
||||
middleware-form (get opts :middleware `taoensso.telemere/*middleware*)
|
||||
|
||||
kvs-form
|
||||
(not-empty
|
||||
|
|
@ -610,6 +624,7 @@
|
|||
:ctx :parent #_:trace?, :do :let :data :msg :error :run
|
||||
:elide? :allow? #_:expansion-id))]
|
||||
|
||||
;; Compile-time validation
|
||||
(when (and run-form error-form)
|
||||
(throw ; Prevent ambiguity re: source of error
|
||||
(ex-info "Signals cannot have both `:run` and `:error` opts at the same time"
|
||||
|
|
@ -618,60 +633,57 @@
|
|||
:location location
|
||||
:other-opts (dissoc opts :run :error)})))
|
||||
|
||||
;; Eval let bindings AFTER call filtering but BEFORE data, msg
|
||||
`(do
|
||||
`(delay
|
||||
;; Delay (cache) shared by all handlers. Covers signal `:let` eval, signal construction,
|
||||
;; middleware (possibly expensive), etc. Throws here will be caught by handler.
|
||||
~do-form
|
||||
(let ~let-form ; Allow to throw during `signal-value_` deref
|
||||
(new-signal ~'__inst ~'__uid
|
||||
~location ~'__ns ~line-form ~column-form ~file-form,
|
||||
~sample-rate-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form,
|
||||
~kvs-form ~data-form ~msg-form,
|
||||
'~run-form ~'__run-result ~error-form))))
|
||||
(let [~@let-form ; Allow to throw, eval BEFORE data, msg, etc.
|
||||
~'__signal
|
||||
(new-signal ~'__inst ~'__uid
|
||||
~location ~'__ns ~line-form ~column-form ~file-form ~'__thread,
|
||||
~sample-rate-form, ~'__kind ~'__id ~'__level, ~ctx-form ~parent-form,
|
||||
~kvs-form ~data-form ~msg-form,
|
||||
'~run-form ~'__run-result ~error-form)]
|
||||
|
||||
run-fn-form (when run-form `(fn [] (~run-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)))))]
|
||||
|
||||
;; Could avoid double `run-form` expansion with a fn wrap (>0 cost)
|
||||
;; `(let [~'run-fn-form ~run-fn-form]
|
||||
;; (if-not ~allow?
|
||||
;; (run-fn-form)
|
||||
;; (let [...])))
|
||||
;; (let [run-fn-form (when run-form `(fn [] (~run-form)))]
|
||||
;; `(let [~'run-fn-form ~run-fn-form]
|
||||
;; (if-not ~allow?
|
||||
;; (run-fn-form)
|
||||
;; (let [...]))))
|
||||
|
||||
`(enc/if-not ~allow? ; Allow to throw at call
|
||||
~run-form
|
||||
(let [~'__inst ~inst-form ; Allow to throw at call
|
||||
~'__level ~level-form ; ''
|
||||
~'__kind ~kind-form ; ''
|
||||
~'__id ~id-form ; ''
|
||||
~'__uid ~uid-form ; ''
|
||||
~'__ns ~ns-form ; ''
|
||||
(let [~'__inst ~inst-form ; Allow to throw at call
|
||||
~'__level ~level-form ; ''
|
||||
~'__kind ~kind-form ; ''
|
||||
~'__id ~id-form ; ''
|
||||
~'__uid ~uid-form ; ''
|
||||
~'__ns ~ns-form ; ''
|
||||
~'__thread ~thread-form ; ''
|
||||
|
||||
~'__call-middleware ~(get opts :middleware `taoensso.telemere/*middleware*)
|
||||
~'__run-result ; Non-throwing (traps)
|
||||
~(when run-form
|
||||
`(let [~'__t0 (enc/now-nano*)]
|
||||
`(let [t0# (enc/now-nano*)]
|
||||
(with-tracing ~trace? ~'__id ~'__uid
|
||||
(enc/try*
|
||||
(do (RunResult. ~run-form nil (- (enc/now-nano*) ~'__t0)))
|
||||
(catch :all ~'__t (RunResult. nil ~'__t (- (enc/now-nano*) ~'__t0)))))))
|
||||
(do (RunResult. ~run-form nil (- (enc/now-nano*) t0#)))
|
||||
(catch :all t# (RunResult. nil t# (- (enc/now-nano*) t0#)))))))
|
||||
|
||||
~'__signal_
|
||||
(delay
|
||||
;; Cache shared by all handlers. Covers signal `:let` eval, signal construction,
|
||||
;; middleware (possibly expensive), etc.
|
||||
signal_# ~signal-delay-form]
|
||||
|
||||
;; The unwrapped signal value actually visible to users/handler-fns, realized only
|
||||
;; AFTER handler filtering. Allowed to throw on deref (handler will catch).
|
||||
(let [~'__signal ~signal-form] ; Can throw
|
||||
(if ~'__call-middleware
|
||||
((sigs/get-middleware-fn ~'__call-middleware) ~'__signal) ; Can throw
|
||||
(do ~'__signal))))]
|
||||
|
||||
;; Unconditionally send same wrapped signal to all handlers.
|
||||
;; Each handler will then use wrapper for filtering, unwrapping allowed signals.
|
||||
(dispatch-signal! (WrappedSignal. ~'__ns ~'__kind ~'__id ~'__level ~'__signal_))
|
||||
(dispatch-signal! ; Runner preserves dynamic bindings when async.
|
||||
;; Unconditionally send same wrapped signal to all handlers. Each handler will
|
||||
;; use wrapper for handler filtering, unwrapping (realizing) only allowed signals.
|
||||
(WrappedSignal. ~'__ns ~'__kind ~'__id ~'__level signal_#))
|
||||
|
||||
(if ~'__run-result
|
||||
(do (~'__run-result ~'__signal_))
|
||||
(do (~'__run-result signal_#))
|
||||
true))))))))
|
||||
|
||||
(comment
|
||||
|
|
|
|||
|
|
@ -13,23 +13,23 @@
|
|||
|
||||
;;;; Implementation
|
||||
|
||||
(defn format-signal->subject-fn
|
||||
(defn signal-subject-fn
|
||||
"Experimental, subject to change.
|
||||
Returns a (fn format [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Returns a formatted email subject like:
|
||||
\"INFO EVENT :taoensso.telemere.postal/ev-id1 - msg\""
|
||||
([] (format-signal->subject-fn nil))
|
||||
([] (signal-subject-fn nil))
|
||||
([{:keys [max-len subject-signal-key]
|
||||
:or
|
||||
{max-len 128
|
||||
subject-signal-key :postal/subject}}]
|
||||
|
||||
(fn format-signal->subject [signal]
|
||||
(fn signal-subject [signal]
|
||||
(or
|
||||
(get signal subject-signal-key) ; Custom subject
|
||||
|
||||
;; Simplified `format-signal->prelude-fn`
|
||||
;; Simplified `utils/signal-preamble-fn`
|
||||
(let [{:keys [level kind #_ns id msg_]} signal
|
||||
sb (enc/str-builder)
|
||||
s+spc (enc/sb-appender sb " ")]
|
||||
|
|
@ -41,9 +41,7 @@
|
|||
|
||||
(enc/get-substr-by-len (str sb) 0 max-len))))))
|
||||
|
||||
(comment
|
||||
((format-signal->subject-fn)
|
||||
(tel/with-signal (tel/event! ::ev-id1 #_{:postal/subject "My subject"}))))
|
||||
(comment ((signal-subject-fn) (tel/with-signal (tel/event! ::ev-id1 #_{:postal/subject "My subject"}))))
|
||||
|
||||
;;;; Handler
|
||||
|
||||
|
|
@ -55,7 +53,7 @@
|
|||
|
||||
Returns a (fn handler [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Sends an email with formatted signal content to the configured recipient.
|
||||
- Sends formatted signal string as email to specified recipient.
|
||||
|
||||
Useful for emailing important alerts to admins, etc.
|
||||
|
||||
|
|
@ -80,8 +78,8 @@
|
|||
:cc \"engineering@example.com\"
|
||||
:X-MyHeader \"A custom header\"}
|
||||
|
||||
`:format-signal-fn` - (fn [signal]) => output, see `help:signal-formatters`
|
||||
`:format-signal->subject-fn` - (fn [signal]) => email subject string
|
||||
`:subject-fn` - (fn [signal]) => email subject string
|
||||
`:body-fn` - (fn [signal]) => email body content string, see `format-signal-fn` or `pr-signal-fn`
|
||||
|
||||
Tips:
|
||||
|
||||
|
|
@ -107,12 +105,12 @@
|
|||
([{:keys
|
||||
[postal/conn-opts
|
||||
postal/msg-opts
|
||||
format-signal-fn
|
||||
format-signal->subject-fn]
|
||||
subject-fn
|
||||
body-fn]
|
||||
|
||||
:or
|
||||
{format-signal-fn (utils/format-signal->str-fn)
|
||||
format-signal->subject-fn (format-signal->subject-fn)}}]
|
||||
{subject-fn (signal-subject-fn)
|
||||
body-fn (utils/format-signal-fn)}}]
|
||||
|
||||
(when-not conn-opts (throw (ex-info "No `:postal/conn-opts` was provided" {})))
|
||||
(when-not msg-opts (throw (ex-info "No `:postal/msg-opts` was provided" {})))
|
||||
|
|
@ -121,12 +119,16 @@
|
|||
(defn a-handler:postal
|
||||
([]) ; Shut down (no-op)
|
||||
([signal]
|
||||
(let [msg
|
||||
(assoc msg-opts
|
||||
:subject (format-signal->subject-fn)
|
||||
:body
|
||||
[{:type "text/plain; charset=utf-8"
|
||||
:content (format-signal-fn signal)}])
|
||||
(enc/when-let [subject (subject-fn signal)
|
||||
body (body-fn signal)]
|
||||
(let [msg
|
||||
(assoc msg-opts
|
||||
:subject (str subject)
|
||||
:body
|
||||
(if (string? body)
|
||||
[{:type "text/plain; charset=utf-8"
|
||||
:content (str body)}]
|
||||
body))
|
||||
|
||||
[result ex]
|
||||
(try
|
||||
|
|
@ -136,4 +138,4 @@
|
|||
success? (= (get result :code) 0)]
|
||||
|
||||
(when-not success?
|
||||
(throw (ex-info "Failed to send email" result ex)))))))))
|
||||
(throw (ex-info "Failed to send email" result ex))))))))))
|
||||
|
|
|
|||
106
src/taoensso/telemere/sockets.clj
Normal file
106
src/taoensso/telemere/sockets.clj
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
(ns taoensso.telemere.sockets
|
||||
"Basic TCP/UDP socket handlers."
|
||||
(:require
|
||||
[taoensso.encore :as enc :refer [have have?]]
|
||||
[taoensso.telemere.utils :as utils])
|
||||
|
||||
(:import
|
||||
[java.net Socket InetAddress]
|
||||
[java.net DatagramSocket DatagramPacket InetSocketAddress]
|
||||
[java.io PrintWriter]))
|
||||
|
||||
(comment
|
||||
(require '[taoensso.telemere :as tel])
|
||||
(remove-ns 'taoensso.telemere.sockets)
|
||||
(:api (enc/interns-overview)))
|
||||
|
||||
;;;; Implementation
|
||||
|
||||
;;;; Handlers
|
||||
|
||||
(defn handler:tcp-socket
|
||||
"Experimental, subject to change. Feedback welcome!
|
||||
|
||||
Returns a (fn handler [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Sends formatted signal string to specified TCP socket.
|
||||
|
||||
Options:
|
||||
`host` - Destination TCP socket hostname string
|
||||
`port` - Destination TCP socket port int
|
||||
|
||||
`:socket-opts` - {:keys [ssl? connect-timeout-msecs]}
|
||||
`:output-fn` - (fn [signal]) => output string, see `format-signal-fn` or `pr-signal-fn`
|
||||
|
||||
Limitations:
|
||||
- Failed writes will be retried only once.
|
||||
- Writes lock on a single underlying socket, so IO won't benefit from adding
|
||||
extra handler threads. Let me know if there's demand for socket pooling."
|
||||
|
||||
([host port] (handler:tcp-socket host port nil))
|
||||
([host port
|
||||
{:keys [socket-opts output-fn]
|
||||
:or {output-fn (utils/format-signal-fn)}}]
|
||||
|
||||
(let [sw (utils/tcp-socket-writer host port socket-opts)]
|
||||
(defn a-handler:tcp-socket
|
||||
([] (sw)) ; Shut down
|
||||
([signal]
|
||||
(when-let [output (output-fn signal)]
|
||||
(sw output)))))))
|
||||
|
||||
(defn handler:udp-socket
|
||||
"Experimental, subject to change. Feedback welcome!
|
||||
|
||||
Returns a (fn handler [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Sends formatted signal string to specified UDP socket.
|
||||
|
||||
Options:
|
||||
`host` - Destination UDP socket hostname string
|
||||
`port` - Destination UDP socket port int
|
||||
|
||||
`:output-fn` - (fn [signal]) => output string, see `format-signal-fn` or `pr-signal-fn`
|
||||
`:max-packet-bytes` - Max packet size (in bytes) before truncating output (default 512)
|
||||
`:truncation-warning-fn` - Optional (fn [{:keys [max actual signal]}]) to call whenever
|
||||
output is truncated. Should be appropriately rate-limited!
|
||||
|
||||
Limitations:
|
||||
- Due to UDP limitations, truncates output to `max-packet-bytes`!
|
||||
- Failed writes will be retried only once.
|
||||
- Writes lock on a single underlying socket, so IO won't benefit from adding
|
||||
extra handler threads. Let me know if there's demand for socket pooling.
|
||||
- No DTLS (Datagram Transport Layer Security) support,
|
||||
please let me know if there's demand."
|
||||
|
||||
([host port] (handler:udp-socket host port nil))
|
||||
([host port
|
||||
{:keys [output-fn max-packet-bytes truncation-warning-fn]
|
||||
:or
|
||||
{output-fn (utils/format-signal-fn)
|
||||
max-packet-bytes 512}}]
|
||||
|
||||
(let [max-packet-bytes (int max-packet-bytes)
|
||||
socket (DatagramSocket.) ; No need to change socket once created
|
||||
lock (Object.)]
|
||||
|
||||
(.connect socket (InetSocketAddress. (str host) (int port)))
|
||||
|
||||
(defn a-handler:udp-socket
|
||||
([] (.close socket)) ; Shut down
|
||||
([signal]
|
||||
(when-let [output (output-fn signal)]
|
||||
(let [ba (enc/str->utf8-ba (str output))
|
||||
ba-len (alength ba)
|
||||
packet (DatagramPacket. ba (min ba-len max-packet-bytes))]
|
||||
|
||||
(when (and truncation-warning-fn (> ba-len max-packet-bytes))
|
||||
;; Fn should be appropriately rate-limited
|
||||
(truncation-warning-fn {:max max-packet-bytes, :actual ba-len, :signal signal}))
|
||||
|
||||
(locking lock
|
||||
(try
|
||||
(.send (DatagramSocket.) packet)
|
||||
(catch Exception _ ; Retry once
|
||||
(Thread/sleep 250)
|
||||
(.send (DatagramSocket.) packet)))))))))))
|
||||
|
|
@ -4,7 +4,8 @@
|
|||
(:require
|
||||
[clojure.string :as str]
|
||||
#?(:clj [clojure.java.io :as jio])
|
||||
[taoensso.encore :as enc :refer [have have?]]))
|
||||
[taoensso.encore :as enc :refer [have have?]]
|
||||
[taoensso.telemere.impl :as impl]))
|
||||
|
||||
(comment
|
||||
(require '[taoensso.telemere :as tel])
|
||||
|
|
@ -52,7 +53,7 @@
|
|||
|
||||
;;;; Public misc
|
||||
|
||||
(enc/defaliases enc/newline enc/pr-edn #?(:cljs enc/pr-json))
|
||||
(enc/defaliases enc/newline enc/pr-edn #?(:cljs enc/pr-json) #?(:clj impl/thread-info))
|
||||
|
||||
#?(:clj (defn thread-name "Returns string name of current thread." ^String [] (.getName (Thread/currentThread))))
|
||||
#?(:clj (defn thread-id "Returns long id of current thread." ^long [] (.getId (Thread/currentThread))))
|
||||
|
|
@ -249,7 +250,7 @@
|
|||
:writer/state {:file file, :stream (.deref stream_)}
|
||||
(when (open?_)
|
||||
(let [content content-or-action
|
||||
ba (.getBytes (str content) java.nio.charset.StandardCharsets/UTF_8)]
|
||||
ba (enc/str->utf8-ba (str content))]
|
||||
(locking lock
|
||||
(try
|
||||
(file-exists!)
|
||||
|
|
@ -260,6 +261,133 @@
|
|||
|
||||
(comment (def fw1 (file-writer "test.txt" true)) (fw1 "x") (fw1))
|
||||
|
||||
;;;; Sockets
|
||||
|
||||
#?(:clj
|
||||
(defn- default-socket-fn
|
||||
"Returns conected `java.net.Socket`, or throws."
|
||||
^java.net.Socket [host port connect-timeout-msecs]
|
||||
(let [addr (java.net.InetSocketAddress. ^String host (int port))
|
||||
socket (java.net.Socket.)]
|
||||
|
||||
(if connect-timeout-msecs
|
||||
(.connect socket addr (int connect-timeout-msecs))
|
||||
(.connect socket addr))
|
||||
|
||||
socket)))
|
||||
|
||||
#?(:clj
|
||||
(let [factory_ (delay (javax.net.ssl.SSLSocketFactory/getDefault))]
|
||||
(defn- default-ssl-socket-fn
|
||||
"Returns connected SSL `java.net.Socket`, or throws."
|
||||
^java.net.Socket [^java.net.Socket socket ^String host port]
|
||||
(.createSocket ^javax.net.ssl.SSLSocketFactory @factory_
|
||||
socket host (int port) true))))
|
||||
|
||||
#?(:clj
|
||||
(defn tcp-socket-writer
|
||||
"Experimental, subject to change. Feedback welcome!
|
||||
|
||||
Connects to specified TCP socket and returns a stateful fn of 2 arities:
|
||||
[content] => Writes given content to socket, or no-ops if closed.
|
||||
[] => Closes the writer.
|
||||
|
||||
Useful for basic handlers that write to a TCP socket, etc.
|
||||
|
||||
Options:
|
||||
`: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`
|
||||
|
||||
Notes:
|
||||
- Writer should be manually closed after use (with zero-arity call).
|
||||
- Flushes after every write.
|
||||
- Will retry failed writes once, then drop.
|
||||
- Thread safe, locks on single socket stream.
|
||||
- Advanced users may want a custom implementation using a connection
|
||||
pool and/or more sophisticated retry semantics, etc."
|
||||
|
||||
[host port
|
||||
{:keys
|
||||
[ssl? connect-timeout-msecs,
|
||||
socket-fn ssl-socket-fn] :as opts
|
||||
|
||||
:or
|
||||
{connect-timeout-msecs 3000
|
||||
socket-fn default-socket-fn
|
||||
ssl-socket-fn default-ssl-socket-fn}}]
|
||||
|
||||
(let [new-conn! ; => [<java.net.Socket> <java.io.OutputStream>], or throws
|
||||
(fn []
|
||||
(try
|
||||
(let [^java.net.Socket socket
|
||||
(let [socket (socket-fn host port connect-timeout-msecs)]
|
||||
(if ssl?
|
||||
(ssl-socket-fn socket host port)
|
||||
(do socket)))]
|
||||
|
||||
[socket (.getOutputStream socket)])
|
||||
|
||||
(catch Exception ex
|
||||
(throw (ex-info "Failed to create connection" opts ex)))))
|
||||
|
||||
conn_ (volatile! (new-conn!))
|
||||
open?_ (enc/latom true)
|
||||
|
||||
close!
|
||||
(fn []
|
||||
(when (compare-and-set! open?_ true false)
|
||||
(when-let [[^java.net.Socket socket] (.deref conn_)]
|
||||
(.close socket)
|
||||
(vreset! conn_ nil)
|
||||
true)))
|
||||
|
||||
reset!
|
||||
(fn []
|
||||
(close!)
|
||||
(vreset! conn_ (new-conn!))
|
||||
(reset! open?_ true)
|
||||
true)
|
||||
|
||||
write-ba!
|
||||
(fn [^bytes ba-content]
|
||||
(when-let [[_ ^java.io.OutputStream output] (.deref conn_)]
|
||||
(.write output ba-content)
|
||||
(.flush output)
|
||||
true))
|
||||
|
||||
conn-okay!
|
||||
(let [rl (enc/rate-limiter-once-per 250)]
|
||||
(fn []
|
||||
(or
|
||||
(rl)
|
||||
(when-let [[^java.net.Socket socket] (.deref conn_)]
|
||||
(and
|
||||
(not (.isClosed socket))
|
||||
(do (.isConnected socket))))
|
||||
(throw (java.io.IOException. "Bad connection")))))
|
||||
|
||||
lock (Object.)]
|
||||
|
||||
(fn a-tcp-socket-writer
|
||||
([] (when (open?_) (locking lock (close!))))
|
||||
([content-or-action]
|
||||
(case content-or-action ; Undocumented, for dev/testing
|
||||
:writer/open? (open?_)
|
||||
:writer/reset! (locking lock (reset!))
|
||||
:writer/state {:conn (.deref conn_)}
|
||||
(when (open?_)
|
||||
(let [content content-or-action
|
||||
ba (enc/str->utf8-ba (str content))]
|
||||
(locking lock
|
||||
(try
|
||||
(conn-okay!)
|
||||
(write-ba! ba)
|
||||
(catch Exception _ ; Retry once
|
||||
(reset!)
|
||||
(write-ba! ba))))))))))))
|
||||
|
||||
;;;; Formatters
|
||||
|
||||
(defn format-nsecs-fn
|
||||
|
|
@ -324,17 +452,21 @@
|
|||
(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"}))))))
|
||||
|
||||
(defn format-signal->prelude-fn
|
||||
;;;;
|
||||
|
||||
(defn signal-preamble-fn
|
||||
"Experimental, subject to change.
|
||||
Returns a (fn format [signal]) that:
|
||||
Returns a (fn preamble [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Returns a formatted prelude string like:
|
||||
\"2024-03-26T11:14:51.806Z INFO EVENT Hostname taoensso.telemere(2,21) ::ev-id - msg\""
|
||||
([] (format-signal->prelude-fn nil))
|
||||
- Returns a signal preamble string like:
|
||||
\"2024-03-26T11:14:51.806Z INFO EVENT Hostname taoensso.telemere(2,21) ::ev-id - msg\"
|
||||
|
||||
See arglists for options."
|
||||
([] (signal-preamble-fn nil))
|
||||
([{:keys [format-inst-fn]
|
||||
:or {format-inst-fn (format-inst-fn)}}]
|
||||
|
||||
(fn format-signal->prelude [signal]
|
||||
(fn signal-preamble [signal]
|
||||
(let [{:keys [inst level kind ns id msg_]} signal
|
||||
sb (enc/str-builder)
|
||||
s+spc (enc/sb-appender sb " ")]
|
||||
|
|
@ -358,129 +490,162 @@
|
|||
(when-let [msg (force msg_)] (s+spc "- " msg))
|
||||
(str sb)))))
|
||||
|
||||
(comment ((format-signal->prelude-fn) (tel/with-signal (tel/event! ::ev-id))))
|
||||
(comment ((signal-preamble-fn) (tel/with-signal (tel/event! ::ev-id))))
|
||||
|
||||
(defn signal-content-fn
|
||||
"Experimental, subject to change.
|
||||
Returns a (fn content [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Returns a signal content string (incl. data, ctx, etc.)
|
||||
|
||||
See arglists for options."
|
||||
([] (signal-content-fn nil))
|
||||
([{:keys
|
||||
[incl-thread? incl-kvs? raw-error?,
|
||||
format-nsecs-fn format-error-fn]
|
||||
|
||||
(defn ^:no-doc signal-content-handler
|
||||
"Private, don't use.
|
||||
Returns a (fn handle [signal handle-fn value-fn]) for internal use.
|
||||
Content equivalent to `format-signal->prelude-fn`."
|
||||
([] (signal-content-handler nil))
|
||||
([{:keys [format-nsecs-fn format-error-fn raw-error?]
|
||||
:or
|
||||
{format-nsecs-fn (format-nsecs-fn) ; (fn [nanosecs])
|
||||
format-error-fn (format-error-fn) ; (fn [error])
|
||||
}}]
|
||||
|
||||
(let [err-start (str newline "<<< error <<<" newline)
|
||||
err-stop (str newline ">>> error >>>")]
|
||||
(let [nl newline
|
||||
err-start (str nl "<<< error <<<" nl)
|
||||
err-stop (str nl ">>> error >>>")]
|
||||
|
||||
(fn a-signal-content-handler [signal hf vf]
|
||||
(let [{:keys [uid parent data #_kvs ctx sample-rate]} signal]
|
||||
(when sample-rate (hf "sample: " (vf sample-rate)))
|
||||
(when uid (hf " uid: " (vf uid)))
|
||||
(when parent (hf "parent: " (vf parent)))
|
||||
(when data (hf " data: " (vf data)))
|
||||
#_(when kvs (hf " kvs: " (vf kvs))) ; Don't auto include in output
|
||||
(when ctx (hf " ctx: " (vf ctx))))
|
||||
(fn signal-content
|
||||
([signal]
|
||||
(let [sb (enc/str-builder)
|
||||
s++ (enc/sb-appender sb nl)]
|
||||
(signal-content signal s++ enc/pr-edn*)
|
||||
(str sb)))
|
||||
|
||||
(let [{:keys [run-form error]} signal]
|
||||
(when run-form
|
||||
(let [{:keys [run-val run-nsecs]} signal
|
||||
run-time (when run-nsecs (when-let [ff format-nsecs-fn] (ff run-nsecs)))
|
||||
run-info
|
||||
(if error
|
||||
{:form run-form
|
||||
:time run-time
|
||||
:nsecs run-nsecs}
|
||||
;; Undocumented, advanced arity
|
||||
([signal append-fn val-fn]
|
||||
(let [af append-fn
|
||||
vf val-fn]
|
||||
|
||||
{:form run-form
|
||||
:time run-time
|
||||
:nsecs run-nsecs
|
||||
:val run-val
|
||||
#?@(:clj [:val-type (enc/class-sym run-val)])})]
|
||||
(let [{:keys [uid parent data kvs ctx #?(:clj thread) sample-rate]} signal]
|
||||
(when sample-rate (af " sample: " (vf sample-rate)))
|
||||
(when uid (af " uid: " (vf uid)))
|
||||
(when parent (af " parent: " (vf parent)))
|
||||
#?(:clj (when (and thread incl-thread?) (af " thread: " (vf thread))))
|
||||
(when data (af " data: " (vf data)))
|
||||
(when (and kvs incl-kvs?) (af " kvs: " (vf kvs)))
|
||||
(when ctx (af " ctx: " (vf ctx))))
|
||||
|
||||
(hf " run: " (vf run-info))))
|
||||
(let [{:keys [run-form error]} signal]
|
||||
(when run-form
|
||||
(let [{:keys [run-val run-nsecs]} signal
|
||||
run-time (when run-nsecs (when-let [ff format-nsecs-fn] (ff run-nsecs)))
|
||||
run-info
|
||||
(if error
|
||||
{:form run-form
|
||||
:time run-time
|
||||
:nsecs run-nsecs}
|
||||
|
||||
(when error
|
||||
(if raw-error?
|
||||
(hf " error: " error)
|
||||
(when-let [ff format-error-fn]
|
||||
(hf err-start (ff error) err-stop)))))))))
|
||||
{:form run-form
|
||||
:time run-time
|
||||
:nsecs run-nsecs
|
||||
:val run-val
|
||||
#?@(:clj [:val-type (enc/class-sym run-val)])})]
|
||||
(af " run: " (vf run-info))))
|
||||
|
||||
;;;; Signal formatters
|
||||
(when error
|
||||
(if raw-error?
|
||||
(af " error: " error)
|
||||
(when-let [ff format-error-fn]
|
||||
(af err-start (ff error) err-stop)))))))))))
|
||||
|
||||
(defn format-signal->edn-fn
|
||||
(comment ((signal-content-fn) (tel/with-signal (tel/event! ::ev-id {:data {:k1 "v1"}}))))
|
||||
|
||||
(defn pr-signal-fn
|
||||
"Experimental, subject to change.
|
||||
Returns a (fn format->edn [signal]) that:
|
||||
Returns a (fn pr-signal [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Returns edn string of the (minified) signal."
|
||||
([] (format-signal->edn-fn nil))
|
||||
([{:keys [pr-edn-fn prep-fn]
|
||||
- Returns machine-readable serialized string of the (minified) signal.
|
||||
|
||||
Options include:
|
||||
`pr-fn` ∈ #{<unary-fn> :edn :json (Cljs only)}
|
||||
See arglists for more.
|
||||
|
||||
Examples:
|
||||
(pr-signal-fn :edn {<opts>})
|
||||
(pr-signal-fn :json {<opts>}) ; Cljs only
|
||||
|
||||
;; To output JSON for Clj, you must provide an appropriate `pr-fn`.
|
||||
;; `jsonista` is a good option, Ref. <https://github.com/metosin/jsonista>:
|
||||
(require '[jsonista.core :as jsonista])
|
||||
(pr-signal-fn jsonista/write-value-as-string {<opts>})
|
||||
|
||||
See also `format-signal-fn` for human-readable output."
|
||||
([pr-fn] (pr-signal-fn pr-fn nil))
|
||||
([pr-fn
|
||||
{:keys [incl-thread? incl-kvs? incl-newline?, prep-fn]
|
||||
:or
|
||||
{pr-edn-fn pr-edn
|
||||
prep-fn (comp error-in-signal->maps minify-signal)}}]
|
||||
{incl-newline? true
|
||||
prep-fn
|
||||
(comp error-in-signal->maps
|
||||
minify-signal)}}]
|
||||
|
||||
(fn format-signal->edn [signal]
|
||||
(let [signal* (if prep-fn (prep-fn signal) signal)]
|
||||
(pr-edn-fn signal*)))))
|
||||
(let [nl newline
|
||||
pr-fn
|
||||
(or
|
||||
(case pr-fn
|
||||
:edn pr-edn
|
||||
#?@(:cljs [:json pr-json])
|
||||
|
||||
(comment ((format-signal->edn-fn) {:level :info, :msg "msg"}))
|
||||
(if (fn? pr-fn)
|
||||
(do pr-fn)
|
||||
(enc/unexpected-arg! pr-fn
|
||||
{:context `pr-signal-fn
|
||||
:param 'pr-fn
|
||||
:expected
|
||||
#?(:clj '#{:edn unary-fn}
|
||||
:cljs '#{:edn :json unary-fn})}))
|
||||
|
||||
(defn format-signal->json-fn
|
||||
(have fn? pr-fn)))]
|
||||
|
||||
(fn pr-signal [signal]
|
||||
(let [not-map? (not (map? signal))
|
||||
signal (if (or incl-kvs? not-map?) signal (dissoc signal :kvs))
|
||||
signal (if (or incl-thread? not-map?) signal (dissoc signal :thread))
|
||||
signal (if prep-fn (prep-fn signal) signal)
|
||||
output (pr-fn signal)]
|
||||
|
||||
(if incl-newline?
|
||||
(str output nl)
|
||||
(do output)))))))
|
||||
|
||||
(comment ((pr-signal-fn :edn) (tel/with-signal (tel/event! ::ev-id {:kvs {:k1 "v1"}}))))
|
||||
|
||||
(defn format-signal-fn
|
||||
"Experimental, subject to change.
|
||||
Returns a (fn format->json [signal]) that:
|
||||
Returns a (fn format [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Returns JSON string of the (minified) signal.
|
||||
- Returns human-readable formatted string.
|
||||
|
||||
(Clj only): An appropriate `:pr-json-fn` MUST be provided."
|
||||
([] (format-signal->json-fn nil))
|
||||
([{:keys [pr-json-fn prep-fn]
|
||||
See also `pr-signal-fn` for machine-readable output."
|
||||
([] (format-signal-fn nil))
|
||||
([{:keys [incl-newline? preamble-fn content-fn]
|
||||
:or
|
||||
{#?@(:cljs [pr-json-fn pr-json])
|
||||
prep-fn (comp error-in-signal->maps minify-signal)}}]
|
||||
{incl-newline? true
|
||||
preamble-fn (signal-preamble-fn)
|
||||
content-fn (signal-content-fn)}}]
|
||||
|
||||
(when-not pr-json-fn
|
||||
(throw
|
||||
(ex-info (str "No `" `format-signal->json-fn "` `:pr-json-fn` was provided") {})))
|
||||
(let [nl newline]
|
||||
(fn format-signal [signal]
|
||||
(let [preamble (when preamble-fn (preamble-fn signal))
|
||||
content (when content-fn (content-fn signal))]
|
||||
|
||||
(fn format-signal->json [signal]
|
||||
(let [signal* (if prep-fn (prep-fn signal) signal)]
|
||||
(pr-json-fn signal*)))))
|
||||
|
||||
(comment ((format-signal->json-fn) {:level :info, :msg "msg"}))
|
||||
|
||||
(defn format-signal->str-fn
|
||||
"Experimental, subject to change.
|
||||
Returns a (fn format->str [signal]) that:
|
||||
- Takes a Telemere signal.
|
||||
- Returns a formatted string intended for text consoles, etc."
|
||||
([] (format-signal->str-fn nil))
|
||||
([{:keys [format-signal->prelude-fn
|
||||
format-nsecs-fn format-error-fn]
|
||||
:or
|
||||
{format-signal->prelude-fn (format-signal->prelude-fn) ; (fn [signal])
|
||||
format-nsecs-fn (format-nsecs-fn) ; (fn [nanosecs])
|
||||
format-error-fn (format-error-fn) ; (fn [error])
|
||||
}}]
|
||||
|
||||
(let [signal-content-handler ; (fn [signal hf vf]
|
||||
(signal-content-handler
|
||||
{:format-nsecs-fn format-nsecs-fn
|
||||
:format-error-fn format-error-fn})]
|
||||
|
||||
(fn format-signal->str [signal]
|
||||
(let [sb (enc/str-builder)
|
||||
s+ (partial enc/sb-append sb)
|
||||
s++ (partial enc/sb-append sb (str newline " "))]
|
||||
|
||||
(when-let [ff format-signal->prelude-fn] (s+ (ff signal))) ; Prelude
|
||||
(signal-content-handler signal s++ enc/pr-edn*) ; Content
|
||||
(str sb))))))
|
||||
(if preamble
|
||||
(if incl-newline? (str preamble nl content nl) (str preamble nl content))
|
||||
(if incl-newline? (str content nl) (str content))))))))
|
||||
|
||||
(comment
|
||||
(tel/with-ctx {:c :C}
|
||||
(println
|
||||
((format-signal->str-fn)
|
||||
((format-signal-fn)
|
||||
(tel/with-signal
|
||||
(tel/event! ::ev-id
|
||||
{:user-k1 #{:a :b :c}
|
||||
|
|
|
|||
|
|
@ -229,10 +229,10 @@
|
|||
|
||||
(testing "Call middleware"
|
||||
(let [c (enc/counter)
|
||||
[[rv1 _] [sv1]] (with-sigs :raw nil (sig! {:level :info, :run (c), :middleware [#(assoc % :m1 (c)) #(assoc % :m2 (c))]}))
|
||||
[[rv2 _] [sv2]] (with-sigs :raw nil (sig! {:level :info, :run (c), :middleware [#(assoc % :m1 (c)) #(assoc % :m2 (c))], :allow? false}))
|
||||
[[rv3 _] [sv3]] (with-sigs :raw nil (sig! {:level :info, :run (c), :middleware [#(assoc % :m1 (c)) #(assoc % :m2 (c))]}))
|
||||
[[rv4 _] [sv4]] (with-sigs :raw nil (sig! {:level :info, :middleware [(fn [_] "signal-value")]}))]
|
||||
[[rv1 _] [sv1]] (with-sigs :raw nil (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c)))}))
|
||||
[[rv2 _] [sv2]] (with-sigs :raw nil (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c))), :allow? false}))
|
||||
[[rv3 _] [sv3]] (with-sigs :raw nil (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c)))}))
|
||||
[[rv4 _] [sv4]] (with-sigs :raw nil (sig! {:level :info, :middleware (fn [_] "signal-value")}))]
|
||||
|
||||
[(is (= rv1 0)) (is (sm? sv1 {:m1 1 :m2 2}))
|
||||
(is (= rv2 3)) (is (nil? sv2))
|
||||
|
|
@ -240,6 +240,10 @@
|
|||
(is (= rv4 true)) (is (= sv4 "signal-value"))
|
||||
(is (= @c 7) "3x run + 4x middleware")]))
|
||||
|
||||
(testing "Binding conveyance"
|
||||
(binding [*dynamic-var* :foo]
|
||||
(is (sm? (with-sig (sig! {:level :info, :data {:dynamic-var *dynamic-var*}})) {:data {:dynamic-var :foo}}))))
|
||||
|
||||
#?(:clj
|
||||
(testing "Printing"
|
||||
(let [sv1 (with-sig (sig! {:level :info, :run (+ 1 2), :my-k1 :my-v1}))
|
||||
|
|
@ -256,31 +260,31 @@
|
|||
(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 [#(assoc % :hm1 (c)) #(assoc % :hm2 (c))]})
|
||||
wh2 (sigs/wrap-handler :hid2 (fn [sv] (reset! sv-h2_ sv)) nil {:async nil, :middleware [#(assoc % :hm1 (c)) #(assoc % :hm2 (c))]})]
|
||||
wh1 (sigs/wrap-handler :hid1 (fn [sv] (reset! sv-h1_ sv)) nil {:async nil, :middleware (tel/comp-middleware #(assoc % :hm1 (c)) #(assoc % :hm2 (c)))})
|
||||
wh2 (sigs/wrap-handler :hid2 (fn [sv] (reset! sv-h2_ sv)) nil {:async nil, :middleware (tel/comp-middleware #(assoc % :hm1 (c)) #(assoc % :hm2 (c)))})]
|
||||
|
||||
;; Note that call middleware 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 [#(assoc % :m1 (c)) #(assoc % :m2 (c))]})
|
||||
rv1 (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c)))})
|
||||
sv1-h1 @sv-h1_
|
||||
sv1-h2 @sv-h2_
|
||||
c1 @c
|
||||
|
||||
;; 1x run
|
||||
rv2 (sig! {:level :info, :run (c), :middleware [#(assoc % :m1 (c)) #(assoc % :m2 (c))], :allow? false})
|
||||
rv2 (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c))), :allow? false})
|
||||
sv2-h1 @sv-h1_
|
||||
sv2-h2 @sv-h2_
|
||||
c2 @c ; 8
|
||||
|
||||
;; 1x run + 4x handler middleware + 2x call middleware = 7x
|
||||
rv3 (sig! {:level :info, :run (c), :middleware [#(assoc % :m1 (c)) #(assoc % :m2 (c))]})
|
||||
rv3 (sig! {:level :info, :run (c), :middleware (tel/comp-middleware #(assoc % :m1 (c)) #(assoc % :m2 (c)))})
|
||||
sv3-h1 @sv-h1_
|
||||
sv3-h2 @sv-h2_
|
||||
c3 @c ; 15
|
||||
|
||||
;; 4x handler middleware
|
||||
rv4 (sig! {:level :info, :middleware [(fn [_] {:my-sig-val? true})]})
|
||||
rv4 (sig! {:level :info, :middleware (fn [_] {:my-sig-val? true})})
|
||||
sv4-h1 @sv-h1_
|
||||
sv4-h2 @sv-h2_
|
||||
c4 @c]
|
||||
|
|
@ -317,7 +321,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))]}
|
||||
:middleware (fn [sv] (if *throwing-handler-middleware?* (ex1!) sv))}
|
||||
|
||||
[(is (->> (sig! {:level :info, :when (ex1!)}) (throws? :ex-info "Ex1")) "`~filterable-expansion/allow` throws at call")
|
||||
(is (->> (sig! {:level :info, :inst (ex1!)}) (throws? :ex-info "Ex1")) "`~inst-form` throws at call")
|
||||
|
|
@ -335,7 +339,7 @@
|
|||
|
||||
(testing "Throwing call middleware"
|
||||
(reset-state!)
|
||||
[(is (true? (sig! {:level :info, :middleware [(fn [_] (ex1!))]})))
|
||||
[(is (true? (sig! {:level :info, :middleware (fn [_] (ex1!))})))
|
||||
(is (= @sv_ :nx))
|
||||
(is (sm? @error_ {:handler-id :hid1, :error pex1?}))])
|
||||
|
||||
|
|
@ -670,15 +674,16 @@
|
|||
#?(: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")))
|
||||
|
||||
(let [sig (with-sig (tel/event! ::ev-id {:inst t0}))
|
||||
prelude ((utils/format-signal->prelude-fn) sig)] ; "2024-06-09T21:15:20.170Z INFO EVENT taoensso.telemere-tests(592,35) ::ev-id"
|
||||
[(is (enc/str-starts-with? prelude "2024-06-09T21:15:20.170Z INFO EVENT"))
|
||||
(is (enc/str-ends-with? prelude "::ev-id"))
|
||||
(is (string? (re-find #"taoensso.telemere-tests\(\d+,\d+\)" prelude)))])
|
||||
(testing "signal-preamble-fn"
|
||||
(let [sig (with-sig (tel/event! ::ev-id {:inst t0}))
|
||||
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-06-09T21:15:20.170Z INFO EVENT"))
|
||||
(is (enc/str-ends-with? preamble "::ev-id"))
|
||||
(is (string? (re-find #"taoensso.telemere-tests\(\d+,\d+\)" preamble)))]))
|
||||
|
||||
(testing "format-signal->edn-fn"
|
||||
(testing "pr-signal-fn/edn"
|
||||
(let [sig (update (with-sig (tel/event! ::ev-id {:inst t0})) :inst enc/inst->udt)
|
||||
sig* (enc/read-edn ((utils/format-signal->edn-fn) sig))]
|
||||
sig* (enc/read-edn ((tel/pr-signal-fn :edn) sig))]
|
||||
(is
|
||||
(enc/submap? sig*
|
||||
{:schema 1, :kind :event, :id ::ev-id, :level :info,
|
||||
|
|
@ -688,20 +693,20 @@
|
|||
:column pnat-int?}))))
|
||||
|
||||
#?(:cljs
|
||||
(testing "format-signal->json-fn"
|
||||
(testing "pr-signal-fn/json"
|
||||
(let [sig (with-sig (tel/event! ::ev-id {:inst t0}))
|
||||
sig* (enc/read-json ((utils/format-signal->json-fn) sig))]
|
||||
sig* (enc/read-json ((tel/pr-signal-fn :json) sig))]
|
||||
(is
|
||||
(enc/submap? sig*
|
||||
{"schema" 1, "kind" "event", "id" "taoensso.telemere-tests/ev-id",
|
||||
"level" "info", "ns" "taoensso.telemere-tests",
|
||||
"level" "info", "ns" "taoensso.telemere-tests",
|
||||
"inst" t0s
|
||||
"line" pnat-int?
|
||||
"column" pnat-int?})))))
|
||||
|
||||
(testing "format-signal->str-fn"
|
||||
(testing "format-signal-fn"
|
||||
(let [sig (with-sig (tel/event! ::ev-id {:inst t0}))]
|
||||
(is (enc/str-starts-with? ((utils/format-signal->str-fn) sig)
|
||||
(is (enc/str-starts-with? ((tel/format-signal-fn) sig)
|
||||
"2024-06-09T21:15:20.170Z INFO EVENT"))))])])
|
||||
|
||||
;;;; File handler
|
||||
|
|
|
|||
|
|
@ -216,4 +216,3 @@ Telemere includes extensive internal help docstrings:
|
|||
| [`help:signal-flow`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-flow) | Ordered flow from signal creation to handling |
|
||||
| [`help:signal-filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-filters) | API for configuring signal filters |
|
||||
| [`help:signal-handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-handlers) | API for configuring signal handlers |
|
||||
| [`help:signal-formatters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-formatters) | Signal formatters for use by handlers |
|
||||
|
|
|
|||
|
|
@ -28,4 +28,3 @@ For more info see:
|
|||
| [`help:signal-flow`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-flow) | Ordered flow from signal creation to handling |
|
||||
| [`help:signal-filters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-filters) | API for configuring signal filters |
|
||||
| [`help:signal-handlers`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-handlers) | API for configuring signal handlers |
|
||||
| [`help:signal-formatters`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#help:signal-formatters) | Signal formatters for use by handlers |
|
||||
|
|
|
|||
|
|
@ -2,19 +2,26 @@ Signal handlers process created signals to *do something with them* (analyse the
|
|||
|
||||
# Included handlers
|
||||
|
||||
The following signal handlers are currently included out-the-box:
|
||||
A number of signal handlers are included out-the box. Alphabetically:
|
||||
|
||||
| Name | Platform | Output target | Output format |
|
||||
| :------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------- |
|
||||
| [`handler:console`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | Clj | `*out*` or `*err*` | Formatted string [1] |
|
||||
| [`handler:console`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | Cljs | Browser console | Formatted string [1] |
|
||||
| [`handler:console-raw`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console-raw) | Cljs | Browser console | Raw signal data [2] |
|
||||
| [`handler:file`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:file) | Clj | File/s on disk | Formatted string [1] |
|
||||
| [`handler:postal`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.postal#handler:postal) | Clj | Email (via [postal](https://github.com/drewr/postal)) | Formatted string [1] |
|
||||
| [`handler:carmine`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.carmine#handler:carmine) [0] | Clj | [Redis](https://redis.io/) (via [Carmine](https://www.taoensso.com/carmine)) | Serialized signals [1] |
|
||||
| [`handler:console`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | Clj | `*out*` or `*err*` | String [2] |
|
||||
| [`handler:console`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | Cljs | Browser console | String [2] |
|
||||
| [`handler:console-raw`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console-raw) | Cljs | Browser console | Raw signals [3] |
|
||||
| [`handler:file`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:file) | Clj | File/s on disk | String [2] |
|
||||
| [`handler:logstash`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.logstash#handler:logstash) [0] | Clj | [Logstash](https://www.elastic.co/logstash) | TODO |
|
||||
| [`handler:open-telemetry-logger`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry-logger) | Clj | [OpenTelemetry](https://opentelemetry.io/) [Java client](https://github.com/open-telemetry/opentelemetry-java) | [LogRecord](https://opentelemetry.io/docs/specs/otel/logs/data-model/) |
|
||||
| [`handler:postal`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.postal#handler:postal) | Clj | Email (via [postal](https://github.com/drewr/postal)) | String [2] |
|
||||
| [`handler:slack`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.slack#handler:slack) [0] | Clj | [Slack](https://slack.com/) (via [clj-slack](https://github.com/julienXX/clj-slack)) | String [2] |
|
||||
| [`handler:tcp-socket`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:tcp-socket) | Clj | TCP socket | String [2] |
|
||||
| [`handler:udp-socket`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:udp-socket) | Clj | UDP socket | String [2] |
|
||||
|
||||
- \[1] [Configurable](https://cljdoc.org/d/com.taoensso/telemere/1.0.0-beta3/api/taoensso.telemere#help:signal-formatters): human-readable (default), [edn](https://github.com/edn-format/edn), [JSON](https://www.json.org/), etc.
|
||||
- \[2] For use with browser formatting tools like [cljs-devtools](https://github.com/binaryage/cljs-devtools).
|
||||
- \[0] Coming soon
|
||||
- \[1] Uses [Nippy](https://taoensso.com/nippy) to support all Clojure's rich data types
|
||||
- \[2] [Human-readable](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#format-signal-fn) (default), or [machine-readable](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#pr-signal-fn) ([edn](https://github.com/edn-format/edn), [JSON](https://www.json.org/), etc.).
|
||||
- \[3] For use with browser formatting tools like [cljs-devtools](https://github.com/binaryage/cljs-devtools).
|
||||
- See relevant docstrings (links above) for features, usage, etc.
|
||||
- See section [8-Community](8-Community.md) for more (community-supported) handlers.
|
||||
- If there's other handlers you'd like to see, feel free to [ping me](https://github.com/taoensso/telemere/issues), or ask on the [`#telemere` Slack channel](https://www.taoensso.com/telemere/slack). It helps to know what people most need!
|
||||
|
|
@ -69,7 +76,7 @@ To instead writes signals as edn:
|
|||
;; Create console which writes edn
|
||||
(def my-handler
|
||||
(t/handler:console
|
||||
{:format-signal-fn (taoensso.telemere.utils/format-signal->edn-fn)}))
|
||||
{:output-fn (t/pr-signal-fn :edn)}))
|
||||
|
||||
(my-handler my-signal) ; =>
|
||||
;; {:inst #inst "2024-04-11T10:54:57.202869Z", :msg_ "My message", :ns "examples", ...}
|
||||
|
|
@ -78,18 +85,41 @@ To instead writes signals as edn:
|
|||
To instead writes signals as JSON:
|
||||
|
||||
```clojure
|
||||
;; Create console which writes JSON
|
||||
;; Create console which writes signals as JSON
|
||||
#?(:clj (require '[jsonista.core :as jsonista]))
|
||||
(def my-handler
|
||||
(t/handler:console
|
||||
{:format-signal-fn
|
||||
(taoensso.telemere.utils/format-signal->json-fn
|
||||
{:pr-json-fn jsonista.core/write-value-as-string})}))
|
||||
|
||||
(my-handler my-signal) ; =>
|
||||
;; {"inst":"2024-04-11T10:54:57.202869Z","msg_":"My message","ns":"examples", ...}
|
||||
{:output-fn
|
||||
(t/pr-signal-fn
|
||||
#?(:cljs :json
|
||||
:clj jsonista.core/write-value-as-string))}))
|
||||
```
|
||||
|
||||
Note that when writing JSON with Clojure, you *must* specify a `pr-json-fn`. This lets you plug in the JSON serializer of your choice ([jsonista](https://github.com/metosin/jsonista) is my default recommendation).
|
||||
Note that when writing JSON with Clojure, you *must* provide an appropriate `pr-fn`. This lets you plug in the JSON serializer of your choice ([jsonista](https://github.com/metosin/jsonista) is my default recommendation).
|
||||
|
||||
### Handler-specific per-signal kvs
|
||||
|
||||
Telemere includes a handy mechanism for including arbitrary user-level data/opts in individual signals for use by custom middleware and/or handlers.
|
||||
|
||||
Any *non-standard* (user) 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"}))
|
||||
|
||||
;; %>
|
||||
;; {;; User kvs included inline (assoc'd to signal root)
|
||||
;; :my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"
|
||||
;; :kvs ; And also collected together under ":kvs" key
|
||||
;; {:my-middleware-data "foo"
|
||||
;; :my-handler-data "bar"}
|
||||
;; ... }
|
||||
```
|
||||
|
||||
These user-level data/opts are typically NOT included by default in handler output, making them a great way to convey data/opts to custom middleware/handlers.
|
||||
|
||||
# Managing handlers
|
||||
|
||||
|
|
|
|||
|
|
@ -18,11 +18,11 @@ Why release Telemere as a *new library* instead of just updating Timbre?
|
|||
|
||||
Timbre was first released 12+ years ago, and has mostly attempted to keep breaks in that time minimal. Which means that its fundamental design is now 12+ years old.
|
||||
|
||||
I've learnt a lot since then, and would write Timbre differently if I were doing it again today. There's many improvements I've wanted to make over the years, but held back both because of the effort involved and because of not wanting to break Timbre users that are happy with it the way it is.
|
||||
I've learnt a lot since then, and would write Timbre differently if I were doing it again today. There's many refinements I've wanted to make over the years, but held back both because of the effort involved and because of not wanting to break Timbre users that are happy with it the way it is.
|
||||
|
||||
Since receiving [open source funding](https://www.taoensso.com/my-work), undertaking larger projects became feasible - so I decided to experiment with a proof-of-concept rewrite free of all historical constraints.
|
||||
|
||||
That eventually grew into Telemere.
|
||||
That eventually grew into Telemere. And I'm happy enough with the result that I feel confident in saying that there's nothing Timbre does better than Telemere, but plenty that Telemere does better than Timbre. Telemere is easier to use, faster, more robust, and significantly more flexible. It offers a better platform for what will be (I hope) the next many years of service.
|
||||
|
||||
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.
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue