diff --git a/README.md b/README.md index 661a2c5..6030454 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,13 @@ ### Structured telemetry library for Clojure/Script -**Telemere** is a next-generation replacement for [Timbre](https://www.taoensso.com/timbre) that offers a simple **unified API** for **structured and traditional logging**, **tracing**, and **basic performance monitoring**. +**Telemere** is a next-generation replacement for [Timbre](https://www.taoensso.com/timbre) that offers one simple **unified API** for **traditional logging**, **structured logging**, **tracing**, and **basic performance monitoring**. -It helps enable Clojure/Script systems that are **observable**, **robust**, and **debuggable** - and it represents the refinement and culmination of ideas brewing over 12+ years in [Timbre](https://www.taoensso.com/timbre), [Tufte](https://www.taoensso.com/tufte), [Truss](https://www.taoensso.com/truss), etc. +Friendly enough for complete beginners, but flexible enough for the most complex and performance-sensitive real-world projects. + +It helps enable Clojure/Script systems that are easily **observable**, **robust**, and **debuggable** - and it represents the refinement and culmination of ideas brewing over 12+ years in [Timbre](https://www.taoensso.com/timbre), [Tufte](https://www.taoensso.com/tufte), [Truss](https://www.taoensso.com/truss), etc. + +Supports Clojure, ClojureScript, [GraalVM](https://en.wikipedia.org/wiki/GraalVM), but not ([yet](../../wiki/6-FAQ#does-telemere-work-with-babashka)) [Babashka](https://github.com/babashka/babashka). See [here](../../wiki/1-Getting-started) for **full introduction**. @@ -25,6 +29,8 @@ See [here][GitHub releases] for earlier releases. ```clojure (require '[taoensso.telemere :as t]) +;; (Just works / no config necessary for typical use cases) + ;; Without structured data (t/log! :info "Hello world!") ; %> Basic log signal (has message) (t/event! ::my-id :debug) ; %> Basic event signal (just id) @@ -33,11 +39,12 @@ See [here][GitHub releases] for earlier releases. (t/log! {:level :info, :data {...}} "Hello again!") (t/event! ::my-id {:level :debug, :data {...}}) -;; Trace (can interop with OpenTelemetry) +;; Trace (auto interops with OpenTelemetry) +;; Tracks form runtime, return value, and (nested) parent tree (t/trace! {:id ::my-id :data {...}} (do-some-work)) -;; Check signal content for debug/tests +;; Check resulting signal content for debug/tests (t/with-signal (t/event! ::my-id)) ; => {:keys [ns level id data msg_ ...]} ;; Transform signals @@ -166,6 +173,25 @@ See for intro and basic usage: :rate-limit {"1 per sec" [1 1000]} ;; See `t/help:handler-dispatch-options` for more }) + +;; Print human-readable output to console +(t/add-handler! :my-console-handler + (t/handler:console + {:output-fn (t/format-signal-fn {...})})) + +;; Print edn to console +(t/add-handler! :my-console-handler + (t/handler:console + {:output-fn (t/pr-signal-fn {:pr-fn :edn})})) + +;; Print JSON to console +;; Ref. (or any alt JSON lib) +#?(:clj (require '[jsonista.core :as jsonista])) +(t/add-handler! :my-console-handler + (t/handler:console + {:output-fn + #?(:cljs :json ; Use js/JSON.stringify + :clj jsonista/write-value-as-string)})) ``` See [examples.cljc](https://github.com/taoensso/telemere/blob/master/examples.cljc) for REPL-ready snippets! @@ -203,26 +229,26 @@ Detailed help is available without leaving your IDE: ### Included handlers See ✅ links below for **features and usage**, -See 👍 links below to **vote on future handlers**: +See ❤️ links below to **vote on future handlers**: -| Target (↓) | Clj | Cljs | -| :--------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: | -| [Apache Kafka](https://kafka.apache.org/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| [AWS Kinesis](https://aws.amazon.com/kinesis/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| Console | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | -| Console (raw) | - | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console-raw) | -| [Datadog](https://www.datadoghq.com/) | [👍](https://github.com/taoensso/roadmap/issues/12) | [👍](https://github.com/taoensso/roadmap/issues/12) | -| Email | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.postal#handler:postal) | - | -| [Graylog](https://graylog.org/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| [Jaeger](https://www.jaegertracing.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| [Logstash](https://www.elastic.co/logstash) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| [OpenTelemetry](https://opentelemetry.io/) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry) | [👍](https://github.com/taoensso/roadmap/issues/12) | -| [Redis](https://redis.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| SQL | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| [Slack](https://slack.com/) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.slack#handler:slack) | - | -| TCP socket | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:tcp-socket) | - | -| UDP socket | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:udp-socket) | - | -| [Zipkin](https://zipkin.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | +| Target (↓) | Clj | Cljs | +| :--------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: | +| [Apache Kafka](https://kafka.apache.org/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| [AWS Kinesis](https://aws.amazon.com/kinesis/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| Console | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | +| Console (raw) | - | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console-raw) | +| [Datadog](https://www.datadoghq.com/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | [❤️](https://github.com/taoensso/roadmap/issues/12) | +| Email | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.postal#handler:postal) | - | +| [Graylog](https://graylog.org/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| [Jaeger](https://www.jaegertracing.io/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| [Logstash](https://www.elastic.co/logstash) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| [OpenTelemetry](https://opentelemetry.io/) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry) | [❤️](https://github.com/taoensso/roadmap/issues/12) | +| [Redis](https://redis.io/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| SQL | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| [Slack](https://slack.com/) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.slack#handler:slack) | - | +| TCP socket | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:tcp-socket) | - | +| UDP socket | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:udp-socket) | - | +| [Zipkin](https://zipkin.io/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | You can also easily [write your own handlers](../../wiki/4-Handlers#writing-handlers). diff --git a/examples.cljc b/examples.cljc index 1a5f06f..9d6d0b0 100644 --- a/examples.cljc +++ b/examples.cljc @@ -8,6 +8,8 @@ (require '[taoensso.telemere :as t]) +;; (Just works / no config necessary for typical use cases) + ;; Without structured data (t/log! :info "Hello world!") ; %> Basic log signal (has message) (t/event! ::my-id :debug) ; %> Basic event signal (just id) @@ -16,11 +18,12 @@ (t/log! {:level :info, :data {}} "Hello again!") (t/event! ::my-id {:level :debug, :data {}}) -;; Trace (can interop with OpenTelemetry) +;; Trace (auto interops with OpenTelemetry) +;; Tracks form runtime, return value, and (nested) parent tree (t/trace! {:id ::my-id :data {}} (do-some-work)) -;; Check signal content for debug/tests +;; Check resulting signal content for debug/tests (t/with-signal (t/event! ::my-id)) ; => {:keys [ns level id data msg_ ...]} ;; Transform signals @@ -93,6 +96,25 @@ ;; See `t/help:handler-dispatch-options` for more }) +;; Print human-readable output to console +(t/add-handler! :my-console-handler + (t/handler:console + {:output-fn (t/format-signal-fn {})})) + +;; Print edn to console +(t/add-handler! :my-console-handler + (t/handler:console + {:output-fn (t/pr-signal-fn {:pr-fn :edn})})) + +;; Print JSON to console +;; Ref. (or any alt JSON lib) +#?(:clj (require '[jsonista.core :as jsonista])) +(t/add-handler! :my-console-handler + (t/handler:console + {:output-fn + #?(:cljs :json ; Use js/JSON.stringify + :clj jsonista/write-value-as-string)})) + ;;;; Docstring examples (t/with-signal (t/event! ::my-id)) @@ -170,7 +192,7 @@ (t/log! {:id ::my-id, :data {:x1 :x2}} "My message"))) ;; Create console handler with default opts (writes formatted string) -(def my-handler (t/handler:console)) +(def my-handler (t/handler:console {})) ;; Test handler, remember it's just a (fn [signal]) (my-handler my-signal) ; %> @@ -186,14 +208,15 @@ ;; {:inst #inst "2024-04-11T10:54:57.202869Z", :msg_ "My message", :ns "examples", ...} ;; Create console handler which writes signals as JSON +;; Ref. (or any alt JSON lib) #?(:clj (require '[jsonista.core :as jsonista])) (def my-handler (t/handler:console {:output-fn (t/pr-signal-fn {:pr-fn - #?(:cljs :json - :clj jsonista.core/write-value-as-string)})})) + #?(:cljs :json ; Use js/JSON.stringify + :clj jsonista/write-value-as-string)})})) (my-handler my-signal) ; %> ;; {"inst":"2024-04-11T10:54:57.202869Z","msg_":"My message","ns":"examples", ...} diff --git a/src/taoensso/telemere/consoles.cljc b/src/taoensso/telemere/consoles.cljc index 316924c..4f96337 100644 --- a/src/taoensso/telemere/consoles.cljc +++ b/src/taoensso/telemere/consoles.cljc @@ -97,8 +97,10 @@ Ref. . Options: - `:preamble-fn` - (fn [signal]) => string. - `:format-nsecs-fn` - (fn [nanosecs]) => string." + `:preamble-fn` - (fn [signal]) => string, see [1]. + `:format-nsecs-fn` - (fn [nanosecs]) => string. + + [1] `taoensso.telemere.utils/signal-preamble-fn`, etc." ([] (handler:console-raw nil)) ([{:keys [preamble-fn format-nsecs-fn] :as opts diff --git a/src/taoensso/telemere/utils.cljc b/src/taoensso/telemere/utils.cljc index 2a9654a..732e2dc 100644 --- a/src/taoensso/telemere/utils.cljc +++ b/src/taoensso/telemere/utils.cljc @@ -637,14 +637,17 @@ excluded by default: #{:location :kvs :file :host :thread} Examples: - (pr-signal-fn {:pr-fn :edn ...}) ; Outputs edn - (pr-signal-fn {:pr-fn :json ...}) ; Outputs JSON (Cljs only) - To output JSON for Clj, you must provide an appropriate `:pr-fn`. - `jsonista` offers one good option, Ref. : + ;; To print as edn: + (pr-signal-fn {:pr-fn :edn}) - (require '[jsonista.core :as jsonista]) - (pr-signal-fn {:pr-fn jsonista/write-value-as-string ...}) + ;; To print as JSON: + ;; Ref. (or any alt JSON lib) + #?(:clj (require '[jsonista.core :as jsonista])) + (pr-signal-fn + {:pr-fn + #?(:cljs :json ; Use js/JSON.stringify + :clj jsonista/write-value-as-string)}) Motivation: Why use this util instead of just directly using the print function @@ -654,7 +657,8 @@ This util efficiently clean signals of such noise, helping reduce storage/transmission size, and making key info easier to see. - See also `format-signal-fn` for human-readable output." + See also `format-signal-fn` for an alternative to `pr-signal-fn` + that produces human-readable output." ([] (pr-signal-fn nil)) ([{:keys [pr-fn, incl-kvs? incl-nils? incl-newline? incl-keys] :as opts :or @@ -750,10 +754,15 @@ Options: `:incl-newline?` - Include terminating system newline? (default true) - `:preamble-fn` - (fn [signal]) => signal preamble string. - `:content-fn` - (fn [signal]) => signal content string. + `:preamble-fn` - (fn [signal]) => signal preamble string, see [1]. + `:content-fn` - (fn [signal]) => signal content string, see [2]. + + [1] `taoensso.telemere.utils/signal-preamble-fn`, etc. + [2] `taoensso.telemere.utils/signal-content-fn`, etc. + + See also `pr-signal-fn` for an alternative to `format-signal-fn` + that produces machine-readable output (edn, JSON, etc.)." - See also `pr-signal-fn` for machine-readable output." ([] (format-signal-fn nil)) ([{:keys [incl-newline? preamble-fn content-fn] :or diff --git a/wiki/4-Handlers.md b/wiki/4-Handlers.md index 67b84ae..e46d3fb 100644 --- a/wiki/4-Handlers.md +++ b/wiki/4-Handlers.md @@ -7,26 +7,26 @@ You can also easily [write your own handlers](#writing-handlers) for any output # Included handlers See ✅ links below for **features and usage**, -See 👍 links below to **vote on future handlers**: +See ❤️ links below to **vote on future handlers**: | Target (↓) | Clj | Cljs | | :--------------------------------------------- | :-----------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------: | -| [Apache Kafka](https://kafka.apache.org/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| [AWS Kinesis](https://aws.amazon.com/kinesis/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | +| [Apache Kafka](https://kafka.apache.org/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| [AWS Kinesis](https://aws.amazon.com/kinesis/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | | Console | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console) | | Console (raw) | - | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console-raw) | -| [Datadog](https://www.datadoghq.com/) | [👍](https://github.com/taoensso/roadmap/issues/12) | [👍](https://github.com/taoensso/roadmap/issues/12) | +| [Datadog](https://www.datadoghq.com/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | [❤️](https://github.com/taoensso/roadmap/issues/12) | | Email | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.postal#handler:postal) | - | -| [Graylog](https://graylog.org/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| [Jaeger](https://www.jaegertracing.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| [Logstash](https://www.elastic.co/logstash) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| [OpenTelemetry](https://opentelemetry.io/) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry) | [👍](https://github.com/taoensso/roadmap/issues/12) | -| [Redis](https://redis.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | -| SQL | [👍](https://github.com/taoensso/roadmap/issues/12) | - | +| [Graylog](https://graylog.org/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| [Jaeger](https://www.jaegertracing.io/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| [Logstash](https://www.elastic.co/logstash) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| [OpenTelemetry](https://opentelemetry.io/) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.open-telemetry#handler:open-telemetry) | [❤️](https://github.com/taoensso/roadmap/issues/12) | +| [Redis](https://redis.io/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | +| SQL | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | | [Slack](https://slack.com/) | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.slack#handler:slack) | - | | TCP socket | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:tcp-socket) | - | | UDP socket | [✅](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.sockets#handler:udp-socket) | - | -| [Zipkin](https://zipkin.io/) | [👍](https://github.com/taoensso/roadmap/issues/12) | - | +| [Zipkin](https://zipkin.io/) | [❤️](https://github.com/taoensso/roadmap/issues/12) | - | # Configuring handlers @@ -51,7 +51,7 @@ Note that it's common for Telemere handlers to be customized by providing *Cloju See the [utils namespace](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere.utils) for tools useful for customizing and writing signal handlers. -### Example +### Console handler The standard Clj/s console handler ([`handler:console`](https://cljdoc.org/d/com.taoensso/telemere/CURRENT/api/taoensso.telemere#handler:console)) writes signals **as strings** to `*out*`/`*err` or browser console. @@ -64,7 +64,7 @@ By default it writes formatted strings intended for human consumption: (t/log! {:id ::my-id, :data {:x1 :x2}} "My message"))) ;; Create console handler with default opts (writes formatted string) -(def my-handler (t/handler:console)) +(def my-handler (t/handler:console {})) ;; Test handler, remember it's just a (fn [signal]) (my-handler my-signal) ; %> @@ -72,10 +72,12 @@ By default it writes formatted strings intended for human consumption: ;; data: {:x1 :x2} ``` -To instead writes signals as edn: +#### edn output + +To instead writes signals as [edn](https://github.com/edn-format/edn): ```clojure -;; Create console which writes edn +;; Create console handler which writes signals as edn (def my-handler (t/handler:console {:output-fn (t/pr-signal-fn {:pr-fn :edn})})) @@ -84,18 +86,20 @@ To instead writes signals as edn: ;; {:inst #inst "2024-04-11T10:54:57.202869Z", :msg_ "My message", :ns "examples", ...} ``` +#### JSON output + To instead writes signals as JSON: ```clojure -;; Create console which writes signals as JSON +;; Ref. (or any alt JSON lib) #?(:clj (require '[jsonista.core :as jsonista])) (def my-handler (t/handler:console {:output-fn (t/pr-signal-fn {:pr-fn - #?(:cljs :json - :clj jsonista.core/write-value-as-string)})})) + #?(:cljs :json ; Use js/JSON.stringify + :clj jsonista/write-value-as-string)})})) (my-handler my-signal) ; %> ;; {"inst":"2024-04-11T10:54:57.202869Z","msg_":"My message","ns":"examples", ...}