diff --git a/README.md b/README.md index b60c7fc..9d6343d 100644 --- a/README.md +++ b/README.md @@ -87,24 +87,18 @@ This is the biggest win over Java logging IMO. Here's `timbre/example-config` (a "Example (+default) Timbre v4 config map. APPENDERS - - *** Please see the `taoensso.timbre.appenders.example-appender` ns if you - plan to write your own Timbre appender *** - An appender is a map with keys: - :doc ; Optional docstring :min-level ; Level keyword, or nil (=> no minimum level) :enabled? ; :async? ; Dispatch using agent? Useful for slow appenders :rate-limit ; [[ncalls-limit window-ms] <...>], or nil - :opts ; Any appender-specific opts - :fn ; (fn [data-map]), with keys described below + :output-fn ; Optional override for inherited (fn [data]) -> string + :fn ; (fn [data]) -> side effects, with keys described below An appender's fn takes a single data map with keys: :config ; Entire config map (this map, etc.) :appender-id ; Id of appender currently dispatching :appender ; Entire map of appender currently dispatching - :appender-opts ; Duplicates (:opts ) for convenience :instant ; Platform date (java.util.Date or js/Date) :level ; Keyword @@ -118,7 +112,7 @@ This is the biggest win over Java logging IMO. Here's `timbre/example-config` (a :hostname_ ; Delay - string (clj only) :msg_ ; Delay - args string :timestamp_ ; Delay - string - :output-fn ; (fn [data & [opts]]) -> formatted output string + :output-fn ; (fn [data]) -> formatted output string :profile-stats ; From `profile` macro @@ -141,11 +135,19 @@ This is the biggest win over Java logging IMO. Here's `timbre/example-config` (a :middleware [] ; (fns [data]) -> ?data, applied left->right + ;; Clj only: + :timestamp-opts default-timestamp-opts ; {:pattern _ :locale _ :timezone _} + + :output-fn default-output-fn ; (fn [data]) -> string + :appenders - {:simple-println ; Appender id + {:example-println-appender ; Appender id ;; Appender definition (just a map): - {:min-level nil :enabled? true :async? false + {:enabled? true + :async? false + :min-level nil :rate-limit [[1 250] [10 5000]] ; 1/250ms, 10/5s + :output-fn :inherit :fn ; Appender's fn (fn [data] (let [{:keys [output-fn]} data diff --git a/project.clj b/project.clj index 67ffc1f..8764353 100644 --- a/project.clj +++ b/project.clj @@ -12,7 +12,7 @@ :dependencies [[org.clojure/clojure "1.4.0"] - [com.taoensso/encore "1.31.0"] + [com.taoensso/encore "1.32.0"] [io.aviso/pretty "0.1.18"]] :plugins diff --git a/src/taoensso/timbre.cljx b/src/taoensso/timbre.cljx index b2b6116..490c913 100644 --- a/src/taoensso/timbre.cljx +++ b/src/taoensso/timbre.cljx @@ -1,21 +1,26 @@ (ns taoensso.timbre "Simple, flexible logging for Clojure/Script. No XML." {:author "Peter Taoussanis"} - #+clj (:require [clojure.string :as str] - [io.aviso.exception :as aviso-ex] - [taoensso.encore :as enc :refer (have have? qb)]) - #+cljs (:require [clojure.string :as str] - [taoensso.encore :as enc :refer ()]) - #+cljs (:require-macros [taoensso.encore :as enc :refer (have have?)] - [taoensso.timbre :as timbre-macros :refer ()]) - #+clj (:import [java.util Date Locale] - [java.text SimpleDateFormat] - [java.io File])) + #+clj + (:require + [clojure.string :as str] + [io.aviso.exception :as aviso-ex] + [taoensso.encore :as enc :refer (have have? qb)] + [taoensso.timbre.appenders.core :as core-appenders]) + + #+cljs + (:require + [clojure.string :as str] + [taoensso.encore :as enc :refer () :refer-macros (have have?)] + [taoensso.timbre.appenders.core :as core-appenders]) + + #+cljs + (:require-macros [taoensso.timbre :as timbre-macros :refer ()])) ;;;; Encore version check #+clj -(let [min-encore-version 1.31] +(let [min-encore-version 1.32] (if-let [assert! (ns-resolve 'taoensso.encore 'assert-min-encore-version)] (assert! min-encore-version) (throw @@ -36,42 +41,44 @@ :timezone (java.util.TimeZone/getDefault)}) (declare stacktrace) +(defn default-output-fn "(fn [data]) -> string output." + ([data] (default-output-fn nil data)) + ([opts data] ; Allow partials for common modified fns + (let [{:keys [level ?err_ vargs_ msg_ ?ns-str hostname_ timestamp_]} data + {:keys [no-stacktrace?]} opts] + (str + #+clj (force timestamp_) #+clj " " + #+clj (force hostname_) #+clj " " + (str/upper-case (name level)) " " + "[" (or ?ns-str "?ns") "] - " + (force msg_) + (when-not no-stacktrace? + (when-let [err (force ?err_)] + (str "\n" (stacktrace err opts)))))))) -(defn default-output-fn - "(fn [data & [opts]]) -> string output." - [data & [opts]] - (let [{:keys [level ?err_ vargs_ msg_ ?ns-str hostname_ timestamp_]} data] - (str - #+clj (force timestamp_) #+clj " " - #+clj (force hostname_) #+clj " " - (str/upper-case (name level)) - " [" (or ?ns-str "?ns") "] - " (force msg_) - (when-let [err (force ?err_)] (str "\n" (stacktrace err opts)))))) - -(declare default-err default-out ensure-spit-dir-exists!) +;;; Alias core appenders here for user convenience +(declare default-err default-out) +#+clj (enc/defalias core-appenders/println-appender) +#+clj (enc/defalias core-appenders/spit-appender) +#+cljs (def println-appender core-appenders/println-appender) +#+cljs (def console-?appender core-appenders/console-?appender) (def example-config "Example (+default) Timbre v4 config map. APPENDERS - - *** Please see the `taoensso.timbre.appenders.example-appender` ns if you - plan to write your own Timbre appender *** - An appender is a map with keys: - :doc ; Optional docstring :min-level ; Level keyword, or nil (=> no minimum level) :enabled? ; :async? ; Dispatch using agent? Useful for slow appenders :rate-limit ; [[ncalls-limit window-ms] <...>], or nil - :opts ; Any appender-specific opts - :fn ; (fn [data-map]), with keys described below + :output-fn ; Optional override for inherited (fn [data]) -> string + :fn ; (fn [data]) -> side effects, with keys described below An appender's fn takes a single data map with keys: :config ; Entire config map (this map, etc.) :appender-id ; Id of appender currently dispatching :appender ; Entire map of appender currently dispatching - :appender-opts ; Duplicates (:opts ) for convenience :instant ; Platform date (java.util.Date or js/Date) :level ; Keyword @@ -85,7 +92,7 @@ :hostname_ ; Delay - string (clj only) :msg_ ; Delay - args string :timestamp_ ; Delay - string - :output-fn ; (fn [data & [opts]]) -> formatted output string + :output-fn ; (fn [data]) -> formatted output string :profile-stats ; From `profile` macro @@ -111,71 +118,17 @@ #+clj :timestamp-opts #+clj default-timestamp-opts ; {:pattern _ :locale _ :timezone _} - :output-fn default-output-fn ; (fn [data & [opts]]) -> string + :output-fn default-output-fn ; (fn [data]) -> string :appenders #+clj - {:println ; Appender id - ;; Appender map: - {:doc "Prints to (:stream ) IO stream. Enabled by default." - :min-level nil :enabled? true :async? false :rate-limit nil - - ;; Any custom appender opts: - :opts {:stream :auto ; e/o #{:std-err :std-out :auto } - } - - :fn - (fn [data] - (let [{:keys [output-fn error? appender-opts]} data - {:keys [stream]} appender-opts - stream (case stream - (nil :auto) (if error? default-err *out*) - :std-err default-err - :std-out default-out - stream)] - (binding [*out* stream] (println (output-fn data)))))} - - :spit - {:doc "Spits to (:spit-filename ) file." - :min-level nil :enabled? false :async? false :rate-limit nil - :opts {:spit-filename "timbre-spit.log"} - :fn - (fn [data] - (let [{:keys [output-fn appender-opts]} data - {:keys [spit-filename]} appender-opts] - (when-let [fname (enc/as-?nblank spit-filename)] - (try (ensure-spit-dir-exists! fname) - (spit fname (str (output-fn data) "\n") :append true) - (catch java.io.IOException _)))))}} + {:println (println-appender {:stream :auto}) + ;; :spit (spit-appender {:fname "./timbre-spit.log"}) + } #+cljs - {:console - {:doc "Logs to js/console when it exists. Enabled by default." - :min-level nil :enabled? true :async? false :rate-limit nil - :opts {} - :fn - (let [have-logger? (and (exists? js/console) (.-log js/console)) - have-warn-logger? (and have-logger? (.-warn js/console)) - have-error-logger? (and have-logger? (.-error js/console)) - adjust-level {:fatal (if have-error-logger? :error :info) - :error (if have-error-logger? :error :info) - :warn (if have-warn-logger? :warn :info)}] - (if-not have-logger? - (fn [data] nil) - (fn [data] - (let [{:keys [level appender-opts output-fn vargs_]} data - {:keys []} appender-opts - - vargs (force vargs_) - [v1 vnext] (enc/vsplit-first vargs) - output (if (= v1 :timbre/raw) - (into-array vnext) - (output-fn data))] - - (case (adjust-level level) - :error (.error js/console output) - :warn (.warn js/console output) - (.log js/console output))))))}}}) + {;; :println (println-appender {}) + :console (console-?appender {})}}) (comment (set-config! example-config) @@ -331,6 +284,21 @@ (declare get-hostname) +(defn- inherit-over [k appender config default] + (or + (let [a (get appender k)] (when-not (enc/kw-identical? a :inherit) a)) + (get config k) + default)) + +(defn- inherit-into [k appender config default] + (merge default + (get config k) + (let [a (get appender k)] (when-not (enc/kw-identical? a :inherit) a)))) + +(comment + (inherit-over :foo {:foo :inherit} {:foo :bar} nil) + (inherit-into :foo {:foo {:a :A :b :B :c :C}} {:foo {:a 1 :b 2 :c 3 :d 4}} nil)) + (defn log1-fn "Core fn-level logger. Implementation detail!" [config level ?ns-str ?file ?line msg-type vargs_ ?base-data] @@ -372,51 +340,50 @@ (when-let [data ?data] ; Not filtered by middleware (reduce-kv (fn [_ id appender] - (when - (and - (:enabled? appender) - (level>= level (or (:min-level appender) :trace)) - (let [rate-limit-specs (:rate-limit appender)] - (if (empty? rate-limit-specs) - true - (let [rl-fn (get-rate-limiter id rate-limit-specs) - hash-fn (or (:data-hash-fn appender) - (:data-hash-fn config) - default-data-hash-fn) - data-hash (hash-fn data)] - (not (rl-fn data-hash)))))) + (when (and (:enabled? appender) + (level>= level (or (:min-level appender) :trace))) - (let [{:keys [async?] apfn :fn} appender - msg_ (delay (or (msg-fn (:vargs_ data)) #_"")) - output-fn (or (:output-fn appender) - (:output-fn config) - default-output-fn) + (let [rate-limit-specs (:rate-limit appender) + data-hash-fn (inherit-over :data-hash-fn appender config + default-data-hash-fn) + rate-limit-okay? + (or (empty? rate-limit-specs) + (let [rl-fn (get-rate-limiter id rate-limit-specs) + data-hash (data-hash-fn data)] + (not (rl-fn data-hash))))] - #+clj timestamp_ - #+clj - (delay - (let [timestamp-opts (merge default-timestamp-opts - (:timestamp-opts config) - (:timestamp-opts appender)) - {:keys [pattern locale timezone]} timestamp-opts] - (.format (enc/simple-date-format pattern - {:locale locale :timezone timezone}) - (:instant data)))) + (when rate-limit-okay? + (let [{:keys [async?] apfn :fn} appender + msg_ (delay (or (msg-fn (:vargs_ data)) #_"")) + output-fn (inherit-over :output-fn appender config + default-output-fn) - data ; Final data prep before going to appender - (merge data - {:appender-id id - :appender appender - :appender-opts (:opts appender) ; For convenience - :msg_ msg_ - :msg-fn msg-fn - :output-fn output-fn - #+clj :timestamp_ #+clj timestamp_})] + #+clj timestamp_ + #+clj + (delay + (let [timestamp-opts (inherit-into :timestamp-opts + appender config + default-timestamp-opts) + {:keys [pattern locale timezone]} timestamp-opts] + (.format (enc/simple-date-format pattern + {:locale locale :timezone timezone}) + (:instant data)))) - (if-not async? - (apfn data) ; Allow errors to throw - #+cljs (apfn data) - #+clj (send-off (get-agent id) (fn [_] (apfn data))))))) + data ; Final data prep before going to appender + (merge data + {:appender-id id + :appender appender + ;; :appender-opts (:opts appender) ; For convenience + :msg_ msg_ + :msg-fn msg-fn + :output-fn output-fn + :data-hash-fn data-hash-fn + #+clj :timestamp_ #+clj timestamp_})] + + (if-not async? + (apfn data) ; Allow errors to throw + #+cljs (apfn data) + #+clj (send-off (get-agent id) (fn [_] (apfn data))))))))) nil (enc/clj1098 (:appenders config)))))) nil) @@ -554,19 +521,10 @@ (comment (stacktrace (Exception. "Boo") {:stacktrace-fonts {}})) -#+clj -(def ^:private ensure-spit-dir-exists! - (enc/memoize* (enc/ms :mins 1) - (fn [fname] - (when-not (str/blank? fname) - (let [file (File. ^String fname) - dir (.getParentFile (.getCanonicalFile file))] - (when-not (.exists dir) (.mkdirs dir))))))) - (defmacro sometimes "Handy for sampled logging, etc." [probability & body] `(do (assert (<= 0 ~probability 1) "Probability: 0 <= p <= 1") - (when (< (rand) ~probability) ~@body))) + (when (< (rand) ~probability) ~@body))) ;;;; EXPERIMENTAL shutdown hook ;; Workaround for http://dev.clojure.org/jira/browse/CLJ-124 diff --git a/src/taoensso/timbre/appenders/3rd_party/android.clj b/src/taoensso/timbre/appenders/3rd_party/android.clj index df26a5c..c0e1c65 100644 --- a/src/taoensso/timbre/appenders/3rd_party/android.clj +++ b/src/taoensso/timbre/appenders/3rd_party/android.clj @@ -4,45 +4,54 @@ (:require [clojure.string :as str] [taoensso.timbre :as timbre])) -(defn make-appender +;; TODO Test port to Timbre v4 + +(defn logcat-appender "Returns an appender that writes to Android LogCat. Obviously only works if running within the Android runtime (device or emulator). You may want to disable std-out to prevent printing nested timestamps, etc." - [& [appender-config make-config]] - (let [default-appender-config - {:enabled? true - :min-level :debug}] + [] + {:enabled? true + :async? false + :min-level :debug + :rate-limit nil - (merge default-appender-config appender-config - {:fn - (fn [data] - (let [{:keys [level ?ns-str ?err_ msg_ timestamp_]} data - msg (or (force msg_) "") - timestamp (force timestamp_) - ns (or ?ns-str "") - output (format "%s %s - %s" timestamp - (-> level name str/upper-case) - msg)] + :output-fn ; Drop hostname, ns, stacktrace + (fn [data] + (let [{:keys [level timestamp_ msg_]} data] + (str + (force timestamp_) " " + (str/upper-case (name level)) " " + (force msg_)))) - (if-let [throwable (force ?err_)] - (case level - :trace (android.util.Log/d ns output throwable) - :debug (android.util.Log/d ns output throwable) - :info (android.util.Log/i ns output throwable) - :warn (android.util.Log/w ns output throwable) - :error (android.util.Log/e ns output throwable) - :fatal (android.util.Log/e ns output throwable) - :report (android.util.Log/i ns output throwable)) + :fn + (fn [data] + (let [{:keys [level ?ns-str ?err_ output-fn]} data + ns (str ?ns-str "") + output-str (output-fn data)] - (case level - :trace (android.util.Log/d ns output) - :debug (android.util.Log/d ns output) - :info (android.util.Log/i ns output) - :warn (android.util.Log/w ns output) - :error (android.util.Log/e ns output) - :fatal (android.util.Log/e ns output) - :report (android.util.Log/i ns output)))))}))) + (if-let [throwable (force ?err_)] + (case level + :trace (android.util.Log/d ns output-str throwable) + :debug (android.util.Log/d ns output-str throwable) + :info (android.util.Log/i ns output-str throwable) + :warn (android.util.Log/w ns output-str throwable) + :error (android.util.Log/e ns output-str throwable) + :fatal (android.util.Log/e ns output-str throwable) + :report (android.util.Log/i ns output-str throwable)) + + (case level + :trace (android.util.Log/d ns output-str) + :debug (android.util.Log/d ns output-str) + :info (android.util.Log/i ns output-str) + :warn (android.util.Log/w ns output-str) + :error (android.util.Log/e ns output-str) + :fatal (android.util.Log/e ns output-str) + :report (android.util.Log/i ns output-str)))))}) ;;;; Deprecated -(def make-logcat-appender make-appender) +(defn make-logcat-appender + "DEPRECATED. Please use `logcat-appender` instead." + [& [appender-merge opts]] + (merge (logcat-appender opts) appender-merge)) diff --git a/src/taoensso/timbre/appenders/3rd_party/irc.clj b/src/taoensso/timbre/appenders/3rd_party/irc.clj index fb0b483..0f05c61 100644 --- a/src/taoensso/timbre/appenders/3rd_party/irc.clj +++ b/src/taoensso/timbre/appenders/3rd_party/irc.clj @@ -5,19 +5,7 @@ [irclj.core :as irc] [taoensso.timbre :as timbre])) -(defn default-fmt-output-fn - [{:keys [level ?err_ msg_]}] - (format "[%s] %s%s" - (-> level name (str/upper-case)) - (or (force msg_) "") - (if-let [err (force ?err_)] - (str "\n" (timbre/stacktrace err)) - ""))) - -(def default-appender-config - {:async? true - :enabled? true - :min-level :info}) +;; TODO Test port to Timbre v4 (defn- connect [{:keys [host port pass nick user name chan] :or {port 6667}}] @@ -29,9 +17,7 @@ (irc/join conn chan) conn)) -(defn- ensure-conn [conn conf] - (if-not @conn - (reset! conn @(connect conf)))) +(defn- ensure-conn [conn conf] (if-not @conn (reset! conn @(connect conf)))) (defn- send-message [conn chan output] (let [[fst & rst] (str/split output #"\n")] @@ -39,30 +25,50 @@ (doseq [line rst] (irc/message conn chan ">" line)))) -(defn- make-appender-fn [irc-config conn] - (fn [data] - (let [{:keys [appender-opts]} data] - (when-let [irc-config (or irc-config appender-opts)] - (ensure-conn conn irc-config) - (let [fmt-fn (or (:fmt-output-fn irc-config) - default-fmt-output-fn)] - (send-message conn (:chan irc-config) (fmt-fn data))))))) +;;;; Public -;;; Public +(defn irc-appender + "Returns an IRC appender. + (irc-appender + {:host \"irc.example.org\" :port 6667 :nick \"logger\" + :name \"My Logger\" :chan \"#logs\"})" -(defn make-appender - "Sends IRC messages using irc. - Needs :opts map in appender, e.g.: - {:host \"irc.example.org\" :port 6667 :nick \"logger\" - :name \"My Logger\" :chan \"#logs\"}" - [& [appender-config {:keys [irc-config]}]] - (let [conn (atom nil)] - (merge default-appender-config appender-config - {:conn conn - :fn (make-appender-fn irc-config conn)}))) + [irc-config] + (let [conn (atom nil) + fmt-fn (or (:fmt-output-fn irc-config) default-fmt-output-fn)] + {:enabled? true + :async? true + :min-level :info + :rate-limit nil + + :output-fn + (fn [data] + (let [{:keys [level ?err_ msg_]}] + (format "[%s] %s%s" + (-> level name (str/upper-case)) + (or (force msg_) "") + (if-let [err (force ?err_)] + (str "\n" (timbre/stacktrace err)) + "")))) + + :fn + (fn [data] + (let [{:keys [output-fn]} data] + (ensure-conn conn irc-config) + (send-message conn (:chan irc-config) (output-fn data))))})) + +;;;; Deprecated + +(defn make-irc-appender + "DEPRECATED. Please use `irc-appender` instead." + [& [appender-merge opts]] + (merge (irc-appender (:irc-config opts) (dissoc :irc-config opts)) + appender-merge)) + +;;;; (comment - (timbre/merge-config! {:appenders {:irc (make-appender)}}) + (timbre/merge-config! {:appenders {:irc (irc-appender)}}) (timbre/merge-config! {:appenders {:irc @@ -73,7 +79,3 @@ :name "Lazylus Logus" :chan "bob"}}}}) (timbre/error "A multiple\nline message\nfor you")) - -;;;; Deprecated - -(def make-irc-appender make-appender) diff --git a/src/taoensso/timbre/appenders/3rd_party/mongo.clj b/src/taoensso/timbre/appenders/3rd_party/mongo.clj index 8d6a029..5e1cc90 100644 --- a/src/taoensso/timbre/appenders/3rd_party/mongo.clj +++ b/src/taoensso/timbre/appenders/3rd_party/mongo.clj @@ -5,10 +5,9 @@ [taoensso.timbre :as timbre] [taoensso.encore :as encore])) -(def conn (atom nil)) +;; TODO Test port to Timbre v4 (def default-args {:host "127.0.0.1" :port 27017}) - (defn connect [{:keys [db server write-concern]}] (let [args (merge default-args server) c (mongo/make-connection db args)] @@ -16,35 +15,33 @@ (mongo/set-write-concern c write-concern)) c)) -(defn ensure-conn [config] - (swap! conn #(or % (connect config)))) +(def conn (atom nil)) +(defn ensure-conn [config] (swap! conn #(or % (connect config)))) -(defn log-message [params {:keys [collection logged-keys] - :as config}] - (let [selected-params (if logged-keys - (select-keys params logged-keys) - (dissoc params :config :appender :appender-opts)) - logged-params (encore/map-vals #(str (force %)) selected-params)] +(defn log-message [params {:keys [collection logged-keys] :as config}] + (let [entry {:instant instant + :level level + :?ns-str (str (:?ns-str data)) + :hostname (str (force (:hostname_ data))) + :vargs (str (force (:vargs_ data))) + :?err (str (force (:?err_ data)))}] (mongo/with-mongo (ensure-conn config) - (mongo/insert! collection logged-params)))) + (mongo/insert! collection entry)))) -(defn- make-appender-fn [make-config] - (fn [data] - (let [{:keys [appender-opts]} data] - (when-let [mongo-config appender-opts] - (log-message data mongo-config))))) +(defn congomongo-appender + "Returns a congomongo MongoDB appender. + (congomongo-appender + {:db \"logs\" + :collection \"myapp\" + :logged-keys [:instant :level :msg_] + :write-concern :acknowledged + :server {:host \"127.0.0.1\" + :port 27017}})" -(defn make-appender - "Logs to MongoDB using congomongo. Needs :opts map in appender, e.g.: - {:db \"logs\" - :collection \"myapp\" - :logged-keys [:instant :level :msg_] - :write-concern :acknowledged - :server {:host \"127.0.0.1\" - :port 27017}}" - [& [appender-config make-config]] - (let [default-appender-config - {:min-level :warn :enabled? true :async? true - :rate-limit [[1 1000]]}] - (merge default-appender-config appender-config - {:fn (make-appender-fn make-config)}))) + [congo-config] + {:enabled? true + :async? true + :min-level :warn + :rate-limit [[1 1000]] ; 1/sec + :output-fn :inherit + :fn (fn [data] (log-message data congo-config))}) diff --git a/src/taoensso/timbre/appenders/3rd_party/rolling.clj b/src/taoensso/timbre/appenders/3rd_party/rolling.clj index 5e1365e..dbb74f6 100644 --- a/src/taoensso/timbre/appenders/3rd_party/rolling.clj +++ b/src/taoensso/timbre/appenders/3rd_party/rolling.clj @@ -6,6 +6,8 @@ (:import [java.text SimpleDateFormat] [java.util Calendar])) +;; TODO Test port to Timbre v4 + (defn- rename-old-create-new-log [log old-log] (.renameTo log old-log) (.createNewFile log)) @@ -23,10 +25,7 @@ (rename-old-create-new-log log index-log)))) (rename-old-create-new-log log old-log)))) -(defn- log-cal [date] - (let [now (Calendar/getInstance)] - (.setTime now date) - now)) +(defn- log-cal [date] (let [now (Calendar/getInstance)] (.setTime now date) now)) (defn- prev-period-end-cal [date pattern] (let [cal (log-cal date) @@ -42,38 +41,36 @@ (.set cal Calendar/MILLISECOND 999) cal)) -(defn- make-appender-fn [path pattern] - (fn [data] - (let [{:keys [instant appender-opts output-fn]} data - output (output-fn data) - path (or path (-> appender-opts :path)) - pattern (or pattern (-> appender-opts :pattern) :daily) - prev-cal (prev-period-end-cal instant pattern) - log (io/file path)] - (when log - (try - (if (.exists log) - (if (<= (.lastModified log) (.getTimeInMillis prev-cal)) - (shift-log-period log path prev-cal)) - (.createNewFile log)) - (spit path (with-out-str (println output)) :append true) - (catch java.io.IOException _)))))) +(defn rolling-appender + "Returns a Rolling file appender. Opts: + :path - logfile path. + :pattern - frequency of rotation, e/o {:daily :weekly :monthly}." + [& [{:keys [path pattern] + :or {path "./timbre-rolling.log" + pattern :daily}}]] -(defn make-appender - "Returns a Rolling file appender. - A rolling config map can be provided here as a second argument, or provided in - appender's :opts map. - - (make-rolling-appender {:enabled? true} - {:path \"log/app.log\" - :pattern :daily}) - path: logfile path - pattern: frequency of rotation, available values: :daily (default), :weekly, :monthly" - [& [appender-config {:keys [path pattern]}]] - (let [default-appender-config {:enabled? true :min-level nil}] - (merge default-appender-config appender-config - {:fn (make-appender-fn path pattern)}))) + {:enabled? true + :async? false + :min-level nil + :rate-limit nil + :output-fn :inherit + :fn + (fn [data] + (let [{:keys [instant output-fn]} data + output-str (output-fn data) + prev-cal (prev-period-end-cal instant pattern)] + (when-let [log (io/file path)] + (try + (if (.exists log) + (if (<= (.lastModified log) (.getTimeInMillis prev-cal)) + (shift-log-period log path prev-cal)) + (.createNewFile log)) + (spit path (with-out-str (println output-str)) :append true) + (catch java.io.IOException _)))))}) ;;;; Deprecated -(def make-rolling-appender make-appender) +(defn make-rolling-appender + "DEPRECATED. Please use `rolling-appender` instead." + [& [appender-merge opts]] + (merge (rolling-appender opts) appender-merge)) diff --git a/src/taoensso/timbre/appenders/3rd_party/rotor.clj b/src/taoensso/timbre/appenders/3rd_party/rotor.clj index a3c744d..25d0b3b 100644 --- a/src/taoensso/timbre/appenders/3rd_party/rotor.clj +++ b/src/taoensso/timbre/appenders/3rd_party/rotor.clj @@ -4,6 +4,8 @@ [taoensso.timbre :as timbre]) (:import [java.io File FilenameFilter])) +;; TODO Test port to Timbre v4 + (defn- ^FilenameFilter file-filter "Returns a Java FilenameFilter instance which only matches files with the given `basename`." @@ -43,27 +45,30 @@ (reverse (map vector logs-to-rotate (iterate inc 1)))] (.renameTo log-file (io/file (format "%s.%03d" abs-path n)))))) -(defn make-appender-fn [make-config] - (fn [data] - (let [{:keys [appender-opts output-fn]} data - {:keys [path max-size backlog] - :or {max-size (* 1024 1024) - backlog 5}} appender-opts] - (when path - (try - (when (> (.length (io/file path)) max-size) - (rotate-logs path backlog)) - (spit path (str (output-fn data) "\n") :append true) - (catch java.io.IOException _)))))) +(defn rotor-appender + "Returns a rotating file appender." + [& [{:keys [path max-size backlog] + :or {path "./timbre-rotor.log" + max-size (* 1024 1024) + backlog 5}}]] + {:enabled? true + :async? false + :min-level :warn + :rate-limit nil + :output-fn :inherit + :fn + (fn [data] + (let [{:keys [output-fn]} data] + (when path + (try + (when (> (.length (io/file path)) max-size) + (rotate-logs path backlog)) + (spit path (str (output-fn data) "\n") :append true) + (catch java.io.IOException _)))))}) -(defn make-appender - "Simple Rotating File Appender. - Needs :opts map in appender, e.g.: - {:path \"logs/app.log\" - :max-size (* 512 1024) - :backlog 5}" - [& [appender-config make-config]] - (let [default-appender-config - {:min-level :warn :enabled? true}] - (merge default-appender-config appender-config - {:fn (make-appender-fn make-config)}))) +;;;; Deprecated + +(defn make-rotor-appender + "DEPRECATED. Please use `rotor-appender` instead." + [& [appender-merge opts]] + (merge (rotor-appender opts) appender-merge)) diff --git a/src/taoensso/timbre/appenders/3rd_party/socket.clj b/src/taoensso/timbre/appenders/3rd_party/socket.clj index 03d5817..9d9c653 100644 --- a/src/taoensso/timbre/appenders/3rd_party/socket.clj +++ b/src/taoensso/timbre/appenders/3rd_party/socket.clj @@ -6,7 +6,7 @@ (:import [java.net Socket InetAddress] [java.io BufferedReader InputStreamReader PrintWriter])) -(def conn (atom nil)) +;; TODO Test port to Timbre v4 (defn listener-fun [in out] (loop [lines (-> in @@ -21,32 +21,40 @@ (.setDaemon true) (.start))) +(def conn (atom nil)) (defn connect [{:keys [port listen-addr]}] (let [addr (when (not= :all listen-addr) (InetAddress/getByName listen-addr))] (with-redefs [server.socket/on-thread on-thread-daemon] (create-server port listener-fun 0 ^InetAddress addr)))) -(defn ensure-conn [socket-config] - (swap! conn #(or % (connect socket-config)))) +(defn ensure-conn [socket-config] (swap! conn #(or % (connect socket-config)))) -(defn make-appender-fn [make-config] - (fn [data] - (let [{:keys [appender-opts output-fn ?err_]} data] - (when-let [socket-config appender-opts] - (let [c (ensure-conn socket-config)] - (doseq [sock @(:connections c)] - (let [out (PrintWriter. (.getOutputStream ^Socket sock))] - (binding [*out* out] - (println (output-fn data)))))))))) +(defn socket-appender + "Returns a TCP socket appender. + (socket-appender {:listener-addr :all :port 9000})" + [& [socket-config]] + (let [{:keys [listener-addr port] + :or {listener-addr :all + port 9000}} socket-config] -(defn make-appender - "Logs to a listening socket. - Needs :opts map in appender, e.g.: - {:listen-addr :all - :port 9000}" - [& [appender-config make-config]] - (let [default-appender-config - {:min-level :trace :enabled? true}] - (merge default-appender-config appender-config - {:fn (make-appender-fn make-config)}))) + {:enabled? true + :async? false + :min-level nil + :rate-limit nil + :output-fn :inherit + :fn + (fn [data] + (let [{:keys [output-fn]} data] + (let [c (ensure-conn socket-config)] + (doseq [sock @(:connections c)] + (let [out (PrintWriter. (.getOutputStream ^Socket sock))] + (binding [*out* out] + (println (output-fn data))))))))})) + +;;;; Deprecated + +(defn make-socket-appender + "DEPRECATED. Please use `socket-appender` instead." + [& [appender-merge opts]] + (merge (socket-appender opts) appender-merge)) diff --git a/src/taoensso/timbre/appenders/3rd_party/zmq.clj b/src/taoensso/timbre/appenders/3rd_party/zmq.clj index 5a76fc4..0ca4667 100644 --- a/src/taoensso/timbre/appenders/3rd_party/zmq.clj +++ b/src/taoensso/timbre/appenders/3rd_party/zmq.clj @@ -8,34 +8,35 @@ (doto (zmq/socket context :push) (zmq/connect (format "%s://%s:%d" transport address port)))) -(defn make-appender-fn [socket poller] - (fn [data] - (let [{:keys [appender-opts output-fn]} data - output (output-fn data)] - (loop [] - (zmq/poll poller 500) - (cond - (zmq/check-poller poller 0 :pollout) (zmq/send-str socket output) - (zmq/check-poller poller 0 :pollerr) (System/exit 1) - :else (recur)))))) - -(defn make-appender - "Returns a ØMQ appender. Takes appender options and a map consisting of: - transport: a string representing transport type: tcp, ipc, inproc, pgm/epgm - address: a string containing an address to connect to. - port: a number representing the port to connect to." - [& [appender-config {:keys [transport address port]}]] - (let [default-appender-config - {:enabled? true - :min-level :error - :async? true} - context (zmq/zcontext) +(defm zmq-appender + "Returns a ØMQ appender. Opts: + :transport - string representing transport type: tcp, ipc, inproc, pgm/epgm. + :address - string containing an address to connect to. + :port - number representing the port to connect to." + [{:keys [transport address port]}] + (let [context (zmq/zcontext) socket (make-zmq-socket context transport address port) poller (doto (zmq/poller context) (zmq/register socket :pollout :pollerr))] - (merge default-appender-config appender-config - {:fn (make-appender-fn socket poller)}))) + {:enabled? true + :async? true + :min-level :error + :rate-limit nil + :output-fn :inherit + :fn + (fn [data] + (let [{:keys [output-fn]} data + output-str (output-fn data)] + (loop [] + (zmq/poll poller 500) + (cond + (zmq/check-poller poller 0 :pollout) (zmq/send-str socket output-str) + (zmq/check-poller poller 0 :pollerr) (System/exit 1) + :else (recur)))))})) ;;;; Deprecated -(def make-zmq-appender make-appender) +(defn make-zmq-appender + "DEPRECATED. Please use `zmq-appender` instead." + [& [appender-merge opts]] + (merge (zmq-appender opts) zmq-merge)) diff --git a/src/taoensso/timbre/appenders/carmine.clj b/src/taoensso/timbre/appenders/carmine.clj index b06659e..ea58a7f 100644 --- a/src/taoensso/timbre/appenders/carmine.clj +++ b/src/taoensso/timbre/appenders/carmine.clj @@ -19,11 +19,11 @@ (defn default-keyfn [level] {:pre [(have? string? level)]} (str "carmine:timbre:default:" level)) -(defn make-appender +(defn carmine-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). + * Only the most recent instance of each unique entry is kept (uniqueness + determined by data-hash-fn). * 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 @@ -31,63 +31,68 @@ also offer interesting opportunities here. See accompanying `query-entries` fn to return deserialized log entries." - [& [appender-config - {:keys [conn-opts keyfn data-hash-fn nentries-by-level] + [& [{:keys [conn-opts keyfn 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}]] + :report 100}}}]] + {: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-config {:enabled? true :min-level nil}] - (merge default-appender-config appender-config - {:fn - (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)} + {:enabled? true + :async? false + :min-level nil + :rate-limit nil + :output-fn :inherit + :fn + (fn [data] + (let [{:keys [level instant data-hash-fn]} data + entry-hash (sha48 (data-hash-fn data)) + entry (merge + {:instant instant + :level level + :?ns-str (:?ns-str data) + :hostname (force (:hostname_ data)) + :vargs (force (:vargs_ data)) + :?err (force (:?err_ data))} + (when-let [pstats (:profile-stats data)] + {:profile-stats pstats})) - 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)] + 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 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) + (when (> nmax-entries 0) + (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) - (when (< (rand) 0.01) ; Occasionally GC - ;; This is necessary since we're doing zset->entry-hash->entry - ;; rather than zset->entry. We want the former for the control - ;; it gives us over what should constitute a 'unique' entry. - (car/lua - "-- -ive idx used to prune from the right (lowest score first) - local max_idx = (0 - (tonumber(_:nmax-entries)) - 1) - local entries_to_prune = - redis.call('zrange', _:k-zset, 0, max_idx) - redis.call('zremrangebyrank', _:k-zset, 0, max_idx) -- Prune zset + (when (< (rand) 0.01) ; Occasionally GC + ;; This is necessary since we're doing zset->entry-hash->entry + ;; rather than zset->entry. We want the former for the control + ;; it gives us over what should constitute a 'unique' entry. + (car/lua + "-- -ive idx used to prune from the right (lowest score first) + local max_idx = (0 - (tonumber(_:nmax-entries)) - 1) + local entries_to_prune = + redis.call('zrange', _:k-zset, 0, max_idx) + redis.call('zremrangebyrank', _:k-zset, 0, max_idx) -- Prune zset - for i,entry in pairs(entries_to_prune) do - redis.call('hdel', _:k-hash, entry) -- Prune hash - end - return nil" - {:k-zset k-zset - :k-hash k-hash} - {:nmax-entries nmax-entries}))))))}))) + for i,entry in pairs(entries_to_prune) do + redis.call('hdel', _:k-hash, entry) -- Prune hash + end + return nil" + {:k-zset k-zset + :k-hash k-hash} + {:nmax-entries nmax-entries}))))))}) ;;;; Query utils @@ -128,10 +133,17 @@ (-> (merge m1 m2-or-ex) (dissoc :hash)))) entries-zset entries-hash))) +;;;; Deprecated + +(defn make-carmine-appender + "DEPRECATED. Please use `carmine-appender` instead." + [& [appender-merge opts]] + (merge (carmine-appender opts) appender-merge)) + ;;;; Dev/tests (comment - (timbre/with-merged-config {:appenders {:carmine (make-appender)}} + (timbre/with-merged-config {:appenders {:carmine (carmine-appender)}} (timbre/info "Hello1" "Hello2")) (car/wcar {} (car/keys (default-keyfn "*"))) @@ -144,7 +156,3 @@ (count (query-entries {} :info 2)) (count (query-entries {} :info 2 :asc))) - -;;;; Deprecated - -(def make-carmine-appender make-appender) diff --git a/src/taoensso/timbre/appenders/core.cljx b/src/taoensso/timbre/appenders/core.cljx new file mode 100644 index 0000000..6a8603d --- /dev/null +++ b/src/taoensso/timbre/appenders/core.cljx @@ -0,0 +1,161 @@ +(ns taoensso.timbre.appenders.core + "Core Timbre appenders without any special dependency requirements. These can + be aliased into the main Timbre ns for convenience." + {:author "Peter Taoussanis"} + #+clj + (:require + [clojure.string :as str] + [taoensso.encore :as enc :refer (have have? qb)]) + + #+cljs + (:require + [clojure.string :as str] + [taoensso.encore :as enc :refer () :refer-macros (have have?)])) + +;;;; TODO +;; * Simple official rolling spit appender? + +;;;; Example appender + +#_ +(defn example-appender + "Docstring to explain any special opts to influence appender construction, + etc. Returns the appender map." + [& [{:keys [] :as opts}]] + + {:enabled? true ; Please enable by default + :async? false ; Use agent for appender dispatch? Useful for slow dispatch. + :min-level nil ; nil (no min level), or min logging level keyword + ;; :rate-limit nil + :rate-limit [[5 (enc/ms :mins 1)] ; 5 calls/min + [100 (enc/ms :hours 1)] ; 100 calls/hour + ] + + :output-fn :inherit ; or (fn [data]) -> string + :fn + (fn [data] + (let [;; See `timbre/example-config` for info on all available args: + {:keys [instant level ?err_ vargs_ output-fn + config ; Entire Timbre config map in effect + appender ; Entire appender map in effect + ]} + data + + ;;; Use `force` to realise possibly-delayed args: + ?err (force ?err_) ; ?err non-nil iff first given arg was an error + vargs (force vargs_) ; Vector of raw args (excl. possible first error) + + ;; You'll often want an output string with ns, timestamp, vargs, etc. + ;; A (fn [data]) -> string formatter is provided under the :output-fn + ;; key. Prefer using this fn to your own formatter when possible, + ;; since the user can configure the :output-fn formatter in a + ;; standard way that'll influence all participating appenders. See + ;; `taoensso.timbre/default-output-fn` source for details. + ;; + output-str (output-fn data)] + (println output-str)))}) + +(comment (merge (example-appender) {:min-level :debug})) + +;;;; Println appender (clj & cljs) + +#+clj (enc/declare-remote taoensso.timbre/default-out + taoensso.timbre/default-err) +#+clj (alias 'timbre 'taoensso.timbre) + +(defn println-appender + "Returns a simple `println` appender for Clojure/Script. + Use with ClojureScript requires that `cljs.core/*print-fn*` be set. + + :stream (clj only) - e/o #{:auto :std-err :std-out }." + + ;; Unfortunately no easy way to check if *print-fn* is set. Metadata on the + ;; default throwing fn would be nice... + + [& #+clj [{:keys [stream] :or {stream :auto}}] #+cljs [_opts]] + (let [#+clj stream + #+clj (case stream + :std-err timbre/default-err + :std-out timbre/default-out + stream)] + + {:enabled? true + :async? false + :min-level nil + :rate-limit nil + :output-fn :inherit + :fn + (fn [data] + (let [{:keys [output-fn]} data] + #+cljs (println (output-fn data)) + #+clj + (let [stream (if (= stream :auto) + (if (:error? data) *err* *out*) + stream)] + (binding [*out* stream] (println (output-fn data))))))})) + +(comment (println-appender)) + +;;;; Spit appender (clj only) + +#+clj +(def ^:private ensure-spit-dir-exists! + (enc/memoize* (enc/ms :mins 1) + (fn [fname] + (when-not (str/blank? fname) + (let [file (java.io.File. ^String fname) + dir (.getParentFile (.getCanonicalFile file))] + (when-not (.exists dir) (.mkdirs dir))))))) + +#+clj +(defn spit-appender + "Returns a simple `spit` file appender for Clojure." + [& [{:keys [fname] :or {fname "./timbre-spit.log"}}]] + {:enabled? true + :async? false + :min-level nil + :rate-limit nil + :output-fn :inherit + :fn + (fn [data] + (let [{:keys [output-fn]} data] + (try ; To allow TTL-memoization of dir creator + (ensure-spit-dir-exists! fname) + (spit fname (str (output-fn data) "\n") :append true) + (catch java.io.IOException _))))}) + +(comment (spit-appender)) + +;;;; js/console appender (cljs only) + +#+cljs +(defn console-?appender + "Returns a simple js/console appender for ClojureScript, or nil if no + js/console exists." + [] + (when-let [have-logger? (and (exists? js/console) (.-log js/console))] + (let [have-warn-logger? (and have-logger? (.-warn js/console)) + have-error-logger? (and have-logger? (.-error js/console)) + level->logger {:fatal (if have-error-logger? :error :info) + :error (if have-error-logger? :error :info) + :warn (if have-warn-logger? :warn :info)}] + {:enabled? true + :async? false + :min-level nil + :rate-limit nil + :output-fn :inherit + :fn + (fn [data] + (let [{:keys [level output-fn vargs_]} data + vargs (force vargs_) + [v1 vnext] (enc/vsplit-first vargs) + output (if (= v1 :timbre/raw) + (into-array vnext) + (output-fn data))] + + (case (level->logger level) + :error (.error js/console output) + :warn (.warn js/console output) + (.log js/console output))))}))) + +(comment (console-?appender)) diff --git a/src/taoensso/timbre/appenders/example_appender.clj b/src/taoensso/timbre/appenders/example_appender.clj deleted file mode 100644 index 41f8a23..0000000 --- a/src/taoensso/timbre/appenders/example_appender.clj +++ /dev/null @@ -1,82 +0,0 @@ -(ns taoensso.timbre.appenders.example-appender - "An example of how Timbre library-style appenders should be written for - bundling with Timbre. Please mention any requirements/dependencies in this - docstring, thanks!" - {:author "Your name here"} - (:require [clojure.string :as str] - [taoensso.timbre :as timbre] - [taoensso.encore :as encore])) - -;;;; Any private util fns, etc. - -;; ... - -;;;; - -(defn make-appender-fn - "(fn [make-config-map]) -> (fn [appender-data-map]) -> logging side effects." - [make-config] ; Any config that can influence the appender-fn construction - (let [{:keys []} make-config] - - (fn [data] ; Data map as provided to middleware + appenders - (let [{:keys [instant level ?err_ vargs_ output-fn - - config ; Entire config map in effect - appender ; Entire appender map in effect - - ;; = (:opts ), for convenience. You'll - ;; usually want to store+access runtime appender config - ;; stuff here to let users change config without recreating - ;; their appender fn: - appender-opts - - ;; <...> - ;; See `timbre/example-config` for info on all available args - ]} - data - - {:keys [my-arbitrary-appender-opt1]} appender-opts - - ;;; Use `force` to realise possibly-delayed args: - ?err (force ?err_) ; ?err non-nil iff first given arg was an error - vargs (force vargs_) ; Vector of raw args (excl. possible first error) - - ;; You'll often want a formatted string with ns, timestamp, vargs, etc. - ;; A formatter (fn [logging-data-map & [opts]]) -> string is - ;; provided for you under the :output-fn key. Prefer using this fn - ;; to your own formatter when possible, since the user can - ;; configure the :output-fn formatter in a standard way that'll - ;; influence all participating appenders. Take a look at the - ;; `taoensso.timbre/default-output-fn` source for details. - ;; - any-special-output-fn-opts {} ; Output-fn can use these opts - output-string (output-fn data any-special-output-fn-opts)] - - (println (str my-arbitrary-appender-opt1 output-string)))))) - -(defn make-appender ; Prefer generic name to `make-foo-appender`, etc. - "Your docstring describing the appender, its options, etc." - [& [appender-config make-appender-config]] - (let [default-appender-config - {:doc "My appender docstring" - :enabled? true ; Please enable your appender by default - :min-level :debug - :rate-limit [[5 (encore/ms :mins 1)] ; 5 calls/min - [100 (encore/ms :hours 1)] ; 100 calls/hour - ] - - ;; Any default appender-specific opts. These'll be accessible to your - ;; appender fn under the :appender-opts key for convenience: - :opts {:my-arbitrary-appender-opt1 "hello world, "}} - - ;;; Here we'll prepare the final appender map as described in - ;;; `timbre/example-config`: - appender-config (merge default-appender-config appender-config) - appender-fn (make-appender-fn make-appender-config) - appender (merge appender-config {:fn appender-fn})] - - appender)) - -(comment - ;; Your examples, tests, etc. here - ) diff --git a/src/taoensso/timbre/appenders/postal.clj b/src/taoensso/timbre/appenders/postal.clj index 8585740..af2f5da 100644 --- a/src/taoensso/timbre/appenders/postal.clj +++ b/src/taoensso/timbre/appenders/postal.clj @@ -1,50 +1,45 @@ (ns taoensso.timbre.appenders.postal - "Email appender. Requires https://github.com/drewr/postal." + "Email (Postal) appender. Requires https://github.com/drewr/postal." {:author "Peter Taoussanis"} (:require [clojure.string :as str] [postal.core :as postal] [taoensso.timbre :as timbre] [taoensso.encore :as enc :refer (have have?)])) -(defn make-appender +(defn 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 + (postal-appender ^{:host \"mail.isp.net\" :user \"jsmith\" :pass \"sekrat!!1\"} - {:from \"Bob's logger \" :to \"foo@example.com\"}})" + {:from \"Bob's logger \" :to \"foo@example.com\"})" - [& [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 + [postal-config & + [{:keys [subject-len body-fn] + :or {subject-len 150 + body-fn (fn [output-str] [{:type "text/plain; charset=utf-8" + :content output-str}])}}]] + {:enabled? true + :async? true ; Slow! + :min-level :warn ; Elevated + :rate-limit [[5 (enc/ms :mins 2)] + [50 (enc/ms :hours 24)]] - default-appender-config - {:enabled? true - :min-level :warn - :async? true ; Slow! - :rate-limit [[5 (enc/ms :mins 2)] - [50 (enc/ms :hours 24)]]}] - - (merge default-appender-config appender-config - {:fn - (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)))))))}))) + :output-fn (fn [data] (timbre/default-output-fn {:stacktrace-fonts {}} data)) + :fn + (fn [data] + (let [{:keys [output-fn]} data + output-str (output-fn data)] + (postal/send-message + (assoc postal-config + :subject (-> output-str + (str/trim) + (str/replace #"\s+" " ") + (enc/substr 0 subject-len)) + :body (body-fn output-str)))))}) ;;;; Deprecated -(def make-postal-appender make-appender) +(defn make-postal-appender + "DEPRECATED. Please use `postal-appender` instead." + [& [appender-merge opts]] + (merge (postal-appender (:postal-config opts) (dissoc opts :postal-config)) + appender-merge))