[mod] Rename, refactor signal formatting utils

- Simplified some util name (only relevant to folks customizing handler behaviour)
- Merged `format-signal->edn-fn`, `format-signal->json-fn` to single `pr-signal-fn`
This commit is contained in:
Peter Taoussanis 2024-05-03 13:43:10 +02:00
parent f3659146bf
commit 21cb44e709
14 changed files with 245 additions and 249 deletions

View file

@ -133,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

View file

@ -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", ...}

View file

@ -53,6 +53,7 @@
[io.opentelemetry/opentelemetry-api "1.37.0"]
#_[io.opentelemetry/opentelemetry-sdk-extension-autoconfigure "1.37.0"]
#_[io.opentelemetry/opentelemetry-exporter-otlp "1.37.0"]
[metosin/jsonista "0.3.8"]
[com.draines/postal "2.0.5"]]
:plugins

View file

@ -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.

View file

@ -79,7 +79,9 @@
#?(: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
@ -89,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

View file

@ -24,14 +24,13 @@
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?]
@ -41,7 +40,7 @@
([signal]
(let [^java.io.Writer stream
(or stream (if (error-signal? signal) *err* *out*))]
(when-let [output (format-signal-fn signal)]
(when-let [output (output-fn signal)]
(.write stream (str output))
(.flush stream))))))))
@ -57,11 +56,11 @@
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]
@ -69,7 +68,7 @@
(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)))))))))))
@ -94,16 +93,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})]
@ -115,8 +114,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))

View file

@ -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,7 +363,7 @@
(fn a-handler:file
([] (locking lock (fw))) ; Close writer
([signal]
(when-let [output (format-signal-fn signal)]
(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?)]

View file

@ -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
@ -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,14 +119,16 @@
(defn a-handler:postal
([]) ; Shut down (no-op)
([signal]
(enc/when-let [content (format-signal-fn signal)
subject (format-signal->subject-fn signal)]
(enc/when-let [subject (subject-fn signal)
body (body-fn signal)]
(let [msg
(assoc msg-opts
:subject (str subject)
:body
[{:type "text/plain; charset=utf-8"
:content (str content)}])
(if (string? body)
[{:type "text/plain; charset=utf-8"
:content (str body)}]
body))
[result ex]
(try

View file

@ -29,8 +29,8 @@
`host` - Destination TCP socket hostname string
`port` - Destination TCP socket port int
`:socket-opts` - {:keys [ssl? connect-timeout-msecs]}
`:format-signal-fn`- (fn [signal]) => output, see `help:signal-formatters`.
`: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.
@ -39,14 +39,14 @@
([host port] (handler:tcp-socket host port nil))
([host port
{:keys [socket-opts format-signal-fn]
:or {format-signal-fn (utils/format-signal->str-fn)}}]
{: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 (format-signal-fn signal)]
(when-let [output (output-fn signal)]
(sw output)))))))
(defn handler:udp-socket
@ -60,7 +60,7 @@
`host` - Destination UDP socket hostname string
`port` - Destination UDP socket port int
`:format-signal-fn` - (fn [signal]) => output, see `help:signal-formatters`.
`: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!
@ -75,10 +75,10 @@
([host port] (handler:udp-socket host port nil))
([host port
{:keys [max-packet-bytes truncation-warning-fn format-signal-fn]
{:keys [output-fn max-packet-bytes truncation-warning-fn]
:or
{max-packet-bytes 512
format-signal-fn (utils/format-signal->str-fn)}}]
{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
@ -89,7 +89,7 @@
(defn a-handler:udp-socket
([] (.close socket)) ; Shut down
([signal]
(when-let [output (format-signal-fn 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))]

View file

@ -452,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 " ")]
@ -484,173 +488,169 @@
(when id (s+spc (format-id ns id)))
(when-let [msg (force msg_)] (s+spc "- " msg))
(str sb)))))
(comment ((format-signal->prelude-fn) (tel/with-signal (tel/event! ::ev-id))))
(when-not (zero? (enc/sb-length sb))
(str sb))))))
(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))
(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
[format-nsecs-fn
format-error-fn
raw-error?
incl-thread?
incl-kvs?]
[incl-thread? incl-kvs? raw-error?,
format-nsecs-fn format-error-fn]
: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 #?(:clj thread) sample-rate]} signal]
(when sample-rate (hf "sample: " (vf sample-rate)))
(when uid (hf " uid: " (vf uid)))
(when parent (hf "parent: " (vf parent)))
#?(:clj (when (and thread incl-thread?) (hf "thread: " (vf thread))))
(when data (hf " data: " (vf data)))
(when (and kvs incl-kvs?) (hf " kvs: " (vf kvs)))
(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*)
(when-not (zero? (enc/sb-length sb))
(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)))
((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
[incl-kvs? end-with-newline?,
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
{end-with-newline? true,
pr-edn-fn pr-edn
prep-fn (comp error-in-signal->maps minify-signal)}}]
(let [nl newline]
(fn format-signal->edn [signal]
(let [signal (if (or incl-kvs? (not (map? signal))) signal (dissoc signal :kvs))
signal (if prep-fn (prep-fn signal) signal)
output (pr-edn-fn signal)]
(if end-with-newline?
(str output nl)
(do output)))))))
(comment ((format-signal->edn-fn) {:level :info, :msg "msg", :kvs {:k1 :v1}}))
(defn format-signal->json-fn
"Experimental, subject to change.
Returns a (fn format->json [signal]) that:
- Takes a Telemere signal.
- Returns JSON string of the (minified) signal.
(Clj only): An appropriate `:pr-json-fn` MUST be provided.
jsonista is one good option, Ref. <https://github.com/metosin/jsonista>:
(require '[jsonista.core :as jsonista])
(format-signal->json-fn {:pr-json-fn jsonista/write-value-as-string ...})"
([] (format-signal->json-fn nil))
([{:keys
[incl-kvs? end-with-newline?,
pr-json-fn prep-fn]
:or
{end-with-newline? true,
#?@(:cljs [pr-json-fn pr-json])
prep-fn (comp error-in-signal->maps minify-signal)}}]
(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->json [signal]
(let [signal (if (or incl-kvs? (not (map? signal))) signal (dissoc signal :kvs))
signal (if prep-fn (prep-fn signal) signal)
output (pr-json-fn signal)]
(if end-with-newline?
(str output nl)
(do output)))))))
(comment ((format-signal->json-fn) {:level :info, :msg "msg", :kvs {:k1 :v1}}))
(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
incl-thread?
incl-kvs?
end-with-newline?]
: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])
end-with-newline? true}}]
{incl-newline? true
prep-fn
(comp error-in-signal->maps
minify-signal)}}]
(let [nl newline
signal-content-handler ; (fn [signal hf vf]
(signal-content-handler
{:format-nsecs-fn format-nsecs-fn
:format-error-fn format-error-fn
:incl-thread? incl-thread?
:incl-kvs? incl-kvs?})]
pr-fn
(or
(case pr-fn
:edn pr-edn
#?@(:cljs [:json pr-json])
(fn format-signal->str [signal]
(let [sb (enc/str-builder)
s+ (partial enc/sb-append sb)
s++ (partial enc/sb-append sb (str newline " "))]
(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})}))
(when-let [ff format-signal->prelude-fn] (s+ (ff signal))) ; Prelude
(signal-content-handler signal s++ enc/pr-edn*) ; Content
(when end-with-newline? (enc/sb-append sb nl))
(str sb))))))
(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 [signal]) that:
- Takes a Telemere signal.
- Returns human-readable formatted string.
See also `pr-signal-fn` for machine-readable output."
([] (format-signal-fn nil))
([{:keys [incl-newline? preamble-fn content-fn]
:or
{incl-newline? true
preamble-fn (signal-preamble-fn)
content-fn (signal-content-fn)}}]
(let [nl newline]
(fn format-signal [signal]
(let [preamble (when preamble-fn (preamble-fn signal))
content (when content-fn (content-fn signal))]
(if (and preamble content)
(str preamble nl content (when incl-newline? nl))
(str preamble content (when incl-newline? nl))))))))
(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}

View file

@ -674,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,
@ -692,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

View file

@ -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 |

View file

@ -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 |

View file

@ -6,20 +6,21 @@ A number of signal handlers are included out-the box. Alphabetically:
| Name | Platform | Output target | Output format |
| :------------------------------------------------------------------------------------------------------------------------------------------------------- | :------- | :------------------------------------------------------------------------------------------------------------- | :--------------------------------------------------------------------- |
| [`handler:carmine`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.carmine#handler:carmine) | 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*` | Formatted string [2] |
| [`handler:console`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | Cljs | Browser console | Formatted string [2] |
| [`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 | Formatted string [2] |
| [`handler:logstash`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.logstash#handler:logstash) | Clj | [Logstash](https://www.elastic.co/logstash) | TODO |
| [`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)) | Formatted string [2] |
| [`handler:slack`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.slack#handler:slack) | Clj | [Slack](https://slack.com/) (via [clj-slack](https://github.com/julienXX/clj-slack)) | Formatted string [2] |
| [`handler:tcp-socket`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:tcp-socket) | Clj | TCP socket | Formatted string [2] |
| [`handler:udp-socket`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:udp-socket) | Clj | UDP socket | Formatted string [2] |
| [`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] |
- \[0] Coming soon
- \[1] Uses [Nippy](https://taoensso.com/nippy) to support all Clojure's rich data types
- \[2] [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] [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.
@ -75,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", ...}
@ -84,18 +85,17 @@ 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