diff --git a/src/taoensso/timbre.cljx b/src/taoensso/timbre.cljx index 421f9fb..a25e0f2 100644 --- a/src/taoensso/timbre.cljx +++ b/src/taoensso/timbre.cljx @@ -49,7 +49,7 @@ #+clj (force hostname_) #+clj " " (str/upper-case (name level)) " [" (or ?ns-str "?ns") "] - " (force msg_) - (when-let [err (force ?err_)] (str "\n" (stacktrace err)))))) + (when-let [err (force ?err_)] (str "\n" (stacktrace err opts)))))) (declare default-err default-out ensure-spit-dir-exists!) @@ -149,7 +149,10 @@ (infof "Hello %s" "world :-)")) (enc/defonce* ^:dynamic *config* example-config) -(defmacro with-config [config & body] `(binding [*config* ~config] ~@body)) +(defmacro with-config [config & body] `(binding [*config* ~config] ~@body)) +(defmacro with-merged-config [config & body] + `(binding [*config* (enc/nested-merge *config* ~config)] ~@body)) + (defn swap-config! [f] #+cljs (set! *config* (f *config*)) #+clj (alter-var-root #'*config* f)) @@ -165,7 +168,7 @@ ;;;; Levels -(def ^:private ordered-levels [:trace :debug :info :warn :error :fatal :report]) +(def ordered-levels [:trace :debug :info :warn :error :fatal :report]) (def ^:private scored-levels (zipmap ordered-levels (next (range)))) (def ^:private valid-level (let [valid-level-set (set ordered-levels)] diff --git a/src/taoensso/timbre/appenders/carmine.clj b/src/taoensso/timbre/appenders/carmine.clj index 650e94f..be99f3d 100644 --- a/src/taoensso/timbre/appenders/carmine.clj +++ b/src/taoensso/timbre/appenders/carmine.clj @@ -3,7 +3,8 @@ {:author "Peter Taoussanis"} (:require [taoensso.carmine :as car] [taoensso.nippy :as nippy] - [taoensso.timbre :as timbre])) + [taoensso.timbre :as timbre] + [taoensso.encore :as enc :refer (have have?)])) (defn- sha48 "Truncated 160bit SHA hash (48bit Long). Redis can store small collections of @@ -15,51 +16,56 @@ (comment (sha48 {:key "I'm gonna get hashed!"})) -(defn default-keyfn [level] {:pre [(string? level)]} - (format "carmine:timbre:default:%s" level)) +(defn default-keyfn [level] {:pre [(have? string? level)]} + (str "carmine:timbre:default:" level)) -(defn make-carmine-appender - "Alpha - subject to change! - Returns a Carmine Redis appender: - * All raw logging args are preserved in serialized form (even Throwables!). - * Only the most recent instance of each unique entry is kept (hash fn used +(defn make-appender + "Returns a Carmine Redis appender (experimental, subject to change): + * All raw logging args are preserved in serialized form (even Throwables!). + * Only the most recent instance of each unique entry is kept (hash fn used to determine uniqueness is configurable). - * Configurable number of entries to keep per logging level. - * Log is just a value: a vector of Clojure maps: query+manipulate with - standard seq fns: group-by hostname, sort/filter by ns & severity, explore - exception stacktraces, filter by raw arguments, etc. Datomic and `core.logic` - also offer interesting opportunities here. + * Configurable number of entries to keep per logging level. + * Log is just a value: a vector of Clojure maps: query+manipulate with + standard seq fns: group-by hostname, sort/filter by ns & severity, explore + exception stacktraces, filter by raw arguments, etc. Datomic and `core.logic` + also offer interesting opportunities here. See accompanying `query-entries` fn to return deserialized log entries." - [& [appender-opts {:keys [conn-opts keyfn args-hash-fn nentries-by-level] - :or {keyfn default-keyfn - args-hash-fn timbre/default-args-hash-fn - nentries-by-level {:trace 50 - :debug 50 - :info 50 - :warn 100 - :error 100 - :fatal 100 - :report 100}} - :as opts}]] - {:pre [(string? (keyfn "test")) - (every? #(contains? nentries-by-level %) timbre/levels-ordered) - (every? #(and (integer? %) (<= 0 % 100000)) (vals nentries-by-level))]} + [& [appender-config + {:keys [conn-opts keyfn data-hash-fn nentries-by-level] + :or {keyfn default-keyfn + data-hash-fn timbre/default-data-hash-fn + nentries-by-level {:trace 50 + :debug 50 + :info 50 + :warn 100 + :error 100 + :fatal 100 + :report 100}} + :as make-config}]] + {:pre [(have? string? (keyfn "test")) + (have? [:ks>= timbre/ordered-levels] nentries-by-level) + (have? [:and integer? #(<= 0 % 100000)] :in (vals nentries-by-level))]} - (let [default-appender-opts {:enabled? true :min-level nil}] - (merge default-appender-opts appender-opts + (let [default-appender-config {:enabled? true :min-level nil}] + (merge default-appender-config appender-config {:fn - (fn [{:keys [level instant] :as apfn-args}] - (let [entry-hash (sha48 (args-hash-fn apfn-args)) - entry (select-keys apfn-args [:hostname :ns :args :throwable - :profile-stats]) + (fn [data] + (let [{:keys [level instant]} data + entry-hash (sha48 (data-hash-fn data)) + entry {:?ns-str (:?ns-str data) + :hostname (force (:hostname_ data)) + :vargs (force (:vargs_ data)) + :?err (force (:?err_ data)) + :profile-stats (:profile-stats data)} + k-zset (keyfn (name level)) k-hash (str k-zset ":entries") udt (.getTime ^java.util.Date instant) ; Use as zscore nmax-entries (nentries-by-level level)] (when (> nmax-entries 0) - (car/wcar (or conn-opts (:conn opts)) ; :conn is Deprecated + (car/wcar conn-opts (binding [nippy/*final-freeze-fallback* nippy/freeze-fallback-as-str] (car/hset k-hash entry-hash entry)) (car/zadd k-zset udt entry-hash) @@ -91,7 +97,7 @@ maps. Normal sequence fns can be used to query/transform entries. Datomic and core.logic are also useful!" [conn-opts level & [n asc? keyfn]] - {:pre [(or (nil? n) (and (integer? n) (<= 1 n 100000)))]} + {:pre [(have? [:or nil? [:and integer? #(<= 1 % 100000)]] n)]} (let [keyfn (or keyfn default-keyfn) k-zset (keyfn (name level)) k-hash (str k-zset ":entries") @@ -125,9 +131,8 @@ ;;;; Dev/tests (comment - (timbre/log {:timestamp-pattern "yyyy-MMM-dd HH:mm:ss ZZ" - :appenders {:carmine (make-carmine-appender)}} - :info "Hello1" "Hello2") + (timbre/with-merged-config {:appenders {:carmine (make-appender)}} + (timbre/info "Hello1" "Hello2")) (car/wcar {} (car/keys (default-keyfn "*"))) (count (car/wcar {} (car/hgetall (default-keyfn "info:entries")))) diff --git a/src/taoensso/timbre/appenders/postal.clj b/src/taoensso/timbre/appenders/postal.clj index d3483a1..0097f2b 100644 --- a/src/taoensso/timbre/appenders/postal.clj +++ b/src/taoensso/timbre/appenders/postal.clj @@ -3,13 +3,8 @@ {:author "Peter Taoussanis"} (:require [clojure.string :as str] [postal.core :as postal] - [taoensso.timbre :as timbre])) - -(defn- str-trunc [^String s max-len] - (if (<= (.length s) max-len) s - (.substring s 0 max-len))) - -(comment (str-trunc "Hello this is a long string" 5)) + [taoensso.timbre :as timbre] + [taoensso.encore :as enc :refer (have have?)])) (defn make-postal-appender "Returns a Postal email appender. @@ -20,31 +15,32 @@ {:postal-config ^{:host \"mail.isp.net\" :user \"jsmith\" :pass \"sekrat!!1\"} {:from \"Bob's logger \" :to \"foo@example.com\"}})" - [& [appender-opts {:keys [postal-config subject-len body-fn] - :or {subject-len 150 - body-fn (fn [output] [{:type "text/plain; charset=utf-8" - :content output}])}}]] - (let [default-appender-opts + [& [appender-config make-config]] + (let [{:keys [postal-config subject-len body-fn] + :or {subject-len 150 + body-fn (fn [output] [{:type "text/plain; charset=utf-8" + :content output}])}} + make-config + + default-appender-config {:enabled? true :min-level :warn :async? true ; Slow! - :rate-limit [5 (* 1000 60 2)] ; 5 calls / 2 mins - ;; TODO These opts are deprecated! - :fmt-output-opts {:no-fonts? true} ; Disable ANSI-escaped stuff - }] + :rate-limit [[5 (enc/ms :mins 2)] + [50 (enc/ms :hours 24)]]}] - (merge default-appender-opts appender-opts + (merge default-appender-config appender-config {:fn - (fn [{:keys [ap-config output]}] - (when-let [postal-config (or postal-config (:postal ap-config))] - (postal/send-message - (assoc postal-config - :subject (-> (str output) - (str/trim) - (str-trunc subject-len) - (str/replace #"\s+" " ")) - :body (body-fn output)))))}))) - -(def postal-appender "DEPRECATED: Use `make-postal-appender` instead." - (make-postal-appender)) + (fn [data] + (let [{:keys [output-fn appender-opts]} data + {:keys [no-fonts?]} appender-opts] + (when-let [postal-config (or postal-config (:postal appender-opts))] + (let [output (str (output-fn data {:stacktrace-fonts {}}))] + (postal/send-message + (assoc postal-config + :subject (-> output + (str/trim) + (str/replace #"\s+" " ") + (enc/substr 0 subject-len)) + :body (body-fn output)))))))})))