mirror of https://github.com/status-im/timbre.git
Nb simplify appenders config, output API
This commit is contained in:
parent
2452041296
commit
142a2d9d08
24
README.md
24
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 <appender-map>) 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <appender-map>) 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 <appender-opts>) 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 <stream>}
|
||||
}
|
||||
|
||||
: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 <appender-opts>) 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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))})
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 <io-stream>}."
|
||||
|
||||
;; 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))
|
|
@ -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 <appender-map>), 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
|
||||
)
|
|
@ -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 <me@draines.com>\" :to \"foo@example.com\"}})"
|
||||
{:from \"Bob's logger <me@draines.com>\" :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))
|
||||
|
|
Loading…
Reference in New Issue