diff --git a/README.md b/README.md index 29dcf9e..0b20595 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/examples.cljc b/examples.cljc index 1d54b56..6fd3cb2 100644 --- a/examples.cljc +++ b/examples.cljc @@ -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", ...} diff --git a/project.clj b/project.clj index b436211..b4c8855 100644 --- a/project.clj +++ b/project.clj @@ -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 diff --git a/resources/signal-docstrings/signal-formatters.txt b/resources/signal-docstrings/signal-formatters.txt deleted file mode 100644 index 605f804..0000000 --- a/resources/signal-docstrings/signal-formatters.txt +++ /dev/null @@ -1,6 +0,0 @@ -Common signal formatters include: - (utils/format-signal->str-fn) {}) ; For human-readable string output (default) - (utils/format-signal->edn-fn) {}) ; For edn output - (utils/format-signal->json-fn {}) ; For JSON output - - See relevant docstrings for details. diff --git a/src/taoensso/telemere.cljc b/src/taoensso/telemere.cljc index be7e39c..d81d2a0 100644 --- a/src/taoensso/telemere.cljc +++ b/src/taoensso/telemere.cljc @@ -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 diff --git a/src/taoensso/telemere/consoles.cljc b/src/taoensso/telemere/consoles.cljc index 840cda8..6ef76dd 100644 --- a/src/taoensso/telemere/consoles.cljc +++ b/src/taoensso/telemere/consoles.cljc @@ -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. ." ([] (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)) diff --git a/src/taoensso/telemere/files.clj b/src/taoensso/telemere/files.clj index 7c4a860..9c39125 100644 --- a/src/taoensso/telemere/files.clj +++ b/src/taoensso/telemere/files.clj @@ -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 } (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?)] diff --git a/src/taoensso/telemere/postal.clj b/src/taoensso/telemere/postal.clj index 66407a4..f7398c7 100644 --- a/src/taoensso/telemere/postal.clj +++ b/src/taoensso/telemere/postal.clj @@ -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 diff --git a/src/taoensso/telemere/sockets.clj b/src/taoensso/telemere/sockets.clj index 5932054..feaedaf 100644 --- a/src/taoensso/telemere/sockets.clj +++ b/src/taoensso/telemere/sockets.clj @@ -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))] diff --git a/src/taoensso/telemere/utils.cljc b/src/taoensso/telemere/utils.cljc index b8dcc91..4e2c125 100644 --- a/src/taoensso/telemere/utils.cljc +++ b/src/taoensso/telemere/utils.cljc @@ -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` ∈ #{ :edn :json (Cljs only)} + See arglists for more. + + Examples: + (pr-signal-fn :edn {}) + (pr-signal-fn :json {}) ; Cljs only + + ;; To output JSON for Clj, you must provide an appropriate `pr-fn`. + ;; `jsonista` is a good option, Ref. : + (require '[jsonista.core :as jsonista]) + (pr-signal-fn jsonista/write-value-as-string {}) + + 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. : - - (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} diff --git a/test/taoensso/telemere_tests.cljc b/test/taoensso/telemere_tests.cljc index 6e09b0e..f17ba75 100644 --- a/test/taoensso/telemere_tests.cljc +++ b/test/taoensso/telemere_tests.cljc @@ -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 diff --git a/wiki/1-Getting-started.md b/wiki/1-Getting-started.md index 036442b..0d37279 100644 --- a/wiki/1-Getting-started.md +++ b/wiki/1-Getting-started.md @@ -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 | diff --git a/wiki/2-Architecture.md b/wiki/2-Architecture.md index 2b71207..cb99aea 100644 --- a/wiki/2-Architecture.md +++ b/wiki/2-Architecture.md @@ -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 | diff --git a/wiki/4-Handlers.md b/wiki/4-Handlers.md index 20e8662..14afd27 100644 --- a/wiki/4-Handlers.md +++ b/wiki/4-Handlers.md @@ -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