From d75a2db4dd13556e08dde2766da1da6b28dc90a0 Mon Sep 17 00:00:00 2001 From: Peter Taoussanis Date: Sat, 30 Nov 2013 17:38:14 +0700 Subject: [PATCH] Clean up postal appender, add new Carmine (Redis) appender --- src/taoensso/timbre.clj | 2 +- src/taoensso/timbre/appenders/carmine.clj | 76 +++++++++++++++++++++++ src/taoensso/timbre/appenders/postal.clj | 48 +++++++++----- src/taoensso/timbre/appenders/rotor.clj | 1 + 4 files changed, 112 insertions(+), 15 deletions(-) create mode 100644 src/taoensso/timbre/appenders/carmine.clj diff --git a/src/taoensso/timbre.clj b/src/taoensso/timbre.clj index 5858035..fa38a24 100644 --- a/src/taoensso/timbre.clj +++ b/src/taoensso/timbre.clj @@ -62,7 +62,7 @@ ;;; -(def ^:private levels-ordered [:trace :debug :info :warn :error :fatal :report]) +(def levels-ordered [:trace :debug :info :warn :error :fatal :report]) (def ^:private levels-scored (assoc (zipmap levels-ordered (next (range))) nil 0)) (defn error-level? [level] (boolean (#{:error :fatal} level))) ; For appenders, etc. diff --git a/src/taoensso/timbre/appenders/carmine.clj b/src/taoensso/timbre/appenders/carmine.clj new file mode 100644 index 0000000..3cdd111 --- /dev/null +++ b/src/taoensso/timbre/appenders/carmine.clj @@ -0,0 +1,76 @@ +(ns taoensso.timbre.appenders.carmine + "Carmine (Redis) appender. Requires https://github.com/ptaoussanis/carmine." + {:author "Peter Taoussanis"} + (:require [taoensso.carmine :as car] + [taoensso.timbre :as timbre])) + +(defn default-keyfn [level] {:pre [(string? level)]} + (format "carmine:timbre:default:%s" level)) + +(defn make-carmine-appender + "Alpha - subject to change! + Returns a Carmine Redis appender that logs serialized entries as follows: + * Logs only the most recent instance of each unique entry. + * Limits the number of entries per level (configurable). + * Sorts entries by date of most recent occurence. + + See accompanying `query-entries` fn to return deserialized log entries." + [& [appender-opts {:keys [conn keyfn nentries-by-level] + :or {conn {} + keyfn default-keyfn + nentries-by-level {:trace 20 + :debug 20 + :info 50 + :warn 100 + :error 300 + :fatal 500 + :report 500}}}]] + {:pre [(string? (keyfn "debug")) + (every? #(contains? nentries-by-level %) timbre/levels-ordered) + (every? #(and (integer? %) (<= 0 % 100000)) (vals nentries-by-level))]} + + (let [default-appender-opts {:enabled? true :min-level nil}] + (merge default-appender-opts appender-opts + {:fn + (fn [{:keys [level instant] :as apfn-args}] + (let [k (keyfn (name level)) + nmax-entries (nentries-by-level level) + ;; Note that we _exclude_ :instant for uniqueness and efficiency + ;; (we'll use it as zset score): + entry (select-keys apfn-args [:level :throwable :args + :profile-stats :hostname :ns]) + udt (.getTime ^java.util.Date instant)] + (car/wcar conn + (car/zadd k udt entry) + (car/zremrangebyrank k 0 (dec (- nmax-entries))))))}))) + +;;;; Query utils + +(defn query-entries + "Alpha - subject to change! + Returns latest `n` log entries by level as an ordered vector of deserialized + maps. Normal sequence fns can be used to query/transform entries." + [conn level & [n asc? keyfn]] + {:pre [(or (nil? n) (and (integer? n) (<= 1 n 100000)))]} + (let [keyfn (or keyfn default-keyfn) + k (keyfn (name level))] + (->> + (car/wcar conn + (if asc? (car/zrange k 0 (if n (dec n) -1) :withscores) + (car/zrevrange k 0 (if n (dec n) -1) :withscores))) + ;; Reconstitute :instant keys from scores: + (partition 2) + (reduce (fn [v [m-entry score]] + (conj v (assoc m-entry :instant (car/as-long score)))) + [])))) + +;;;; Dev/tests + +(comment + (timbre/log {:timestamp-pattern "yyyy-MMM-dd HH:mm:ss ZZ" + :appenders {:carmine (make-carmine-appender)}} + :info "Hello1" "Hello2") + + (car/wcar {} (car/del (default-keyfn "info"))) + (car/wcar {} (car/keys (default-keyfn "*"))) + (count (query-entries {} :info 2 :asc))) diff --git a/src/taoensso/timbre/appenders/postal.clj b/src/taoensso/timbre/appenders/postal.clj index 1639142..c8a89eb 100644 --- a/src/taoensso/timbre/appenders/postal.clj +++ b/src/taoensso/timbre/appenders/postal.clj @@ -1,5 +1,5 @@ (ns taoensso.timbre.appenders.postal - "Email appender. Depends on https://github.com/drewr/postal." + "Email appender. Requires https://github.com/drewr/postal." {:author "Peter Taoussanis"} (:require [clojure.string :as str] [postal.core :as postal] @@ -7,21 +7,41 @@ (defn- str-trunc [^String s max-len] (if (<= (.length s) max-len) s - (.substring s 0 max-len))) + (.substring s 0 max-len))) (comment (str-trunc "Hello this is a long string" 5)) -(def postal-appender - {:doc (str "Sends an email using com.draines/postal.\n" - "Needs :postal config map in :shared-appender-config, e.g.: - ^{:host \"mail.isp.net\" :user \"jsmith\" :pass \"sekrat!!1\"} - {:from \"Bob's logger \" :to \"foo@example.com\"}") - :min-level :error :enabled? true :async? true - :fmt-output-opts {:nofonts? true} - :rate-limit [5 (* 1000 60 2)] ; 5 calls / 2 mins - :fn (fn [{:keys [ap-config output]}] - (when-let [postal-config (:postal ap-config)] +(defn make-postal-appender + "Returns a Postal email appender. + A Postal config map can be provided here as an argument, or as a :postal key + in :shared-appender-config. + + (make-postal-appender {:enabled? true} + {: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] + :or {subject-len 150}}]] + + (let [default-appender-opts + {:enabled? true + :min-level :warn + :async? true ; Slow! + :rate-limit [5 (* 1000 60 2)] ; 5 calls / 2 mins + :fmt-output-opts {:nofonts? true} ; Disable ANSI-escaped stuff + }] + + (merge default-appender-opts appender-opts + {:fn + (fn [{:keys [ap-config output]}] + (when-let [postal-config (or postal-config (:postal ap-config))] (postal/send-message (assoc postal-config - :subject (-> output (str/trim) (str-trunc 150) (str/replace #"\s+" " ")) - :body output))))}) + :subject (-> (str output) + (str/trim) + (str-trunc subject-len) + (str/replace #"\s+" " ")) + :body output))))}))) + +(def postal-appender "DEPRECATED: Use `make-postal-appender` instead." + (make-postal-appender)) diff --git a/src/taoensso/timbre/appenders/rotor.clj b/src/taoensso/timbre/appenders/rotor.clj index 1707c25..722dfc7 100644 --- a/src/taoensso/timbre/appenders/rotor.clj +++ b/src/taoensso/timbre/appenders/rotor.clj @@ -1,4 +1,5 @@ (ns taoensso.timbre.appenders.rotor + {:author "Yutaka Matsubara"} (:import [java.io File FilenameFilter]) (:require