mirror of https://github.com/status-im/timbre.git
Housekeeping
This commit is contained in:
parent
3c824d31da
commit
beec0c9fdd
|
@ -14,7 +14,6 @@
|
|||
;;;; TODO
|
||||
;; - Check for successful cljs compile
|
||||
;; - Cljs default appenders
|
||||
;; - Port appenders
|
||||
;; - Try ease backward comp, update README, CHANGELOG
|
||||
;; - Document shutdown-agents,
|
||||
;; Ref. https://github.com/ptaoussanis/timbre/pull/100/files
|
||||
|
@ -36,13 +35,17 @@
|
|||
|
||||
#+clj
|
||||
(def default-timestamp-opts
|
||||
"Controls (:timestamp_ data)."
|
||||
{:pattern "yy-MMM-dd HH:mm:ss"
|
||||
:locale (java.util.Locale. "en")
|
||||
;; :timezone (java.util.TimeZone/getTimeZone "UTC")
|
||||
:timezone (java.util.TimeZone/getDefault)})
|
||||
|
||||
(declare stacktrace)
|
||||
(defn default-output-fn [data & [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 " "
|
||||
|
@ -54,33 +57,37 @@
|
|||
(declare default-err default-out ensure-spit-dir-exists!)
|
||||
|
||||
(def example-config
|
||||
"Example (+default) Timbre config map.
|
||||
"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).
|
||||
: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.
|
||||
:async? ; Dispatch using agent? Useful for slow appenders
|
||||
:rate-limit ; [[ncalls-limit window-ms] <...>], or nil
|
||||
:data-hash-fn ; Used by rate-limiter, etc.
|
||||
:opts ; Any appender-specific opts
|
||||
:fn ; (fn [data-map]), with keys described below.
|
||||
:fn ; (fn [data-map]), with keys described below
|
||||
|
||||
An appender's fn takes a single data map with keys:
|
||||
:config ; Entire config map (this map)
|
||||
:config ; Entire config map (this map, etc.)
|
||||
:appender-id ; Id of appender currently being dispatched to
|
||||
:appender ; Entire appender map currently being dispatched to
|
||||
:appender-opts ; (:opts (:appender <data-map>)), for convenience
|
||||
:appender-opts ; Duplicates (:opts <appender-map>), for convenience
|
||||
|
||||
:instant ; java.util.Date or js/Date
|
||||
:instant ; Platform date (java.util.Date or js/Date)
|
||||
:level ; Keyword
|
||||
:error-level? ; Is level :error or :fatal?
|
||||
:?ns-str ; String, or nil
|
||||
:?file ; String, or nil (waiting on CLJ-865)
|
||||
:?line ; Integer, or nil ('')
|
||||
:?file ; String, or nil ; Waiting on CLJ-865
|
||||
:?line ; Integer, or nil ; Waiting on CLJ-865
|
||||
|
||||
:?err_ ; Delay - first-argument error
|
||||
:?err_ ; Delay - first-argument platform error
|
||||
:vargs_ ; Delay - raw args vector
|
||||
:hostname_ ; Delay - string (clj only)
|
||||
:msg_ ; Delay - args string
|
||||
|
@ -91,45 +98,46 @@
|
|||
|
||||
<Also, any *context* keys, which get merged into data map>
|
||||
|
||||
DELAYS
|
||||
As a matter of middleware hygiene, prefer using `force` to @/`deref`
|
||||
when retrieving getting delayed values.
|
||||
|
||||
MIDDLEWARE
|
||||
Middleware are fns (applied left->right) that transform the data map
|
||||
dispatched to appender fns. If any middleware returns nil, NO dispatching
|
||||
will occur (i.e. the event will be filtered).
|
||||
Middleware are simple (fn [data]) -> ?data fns (applied left->right) that
|
||||
transform the data map dispatched to appender fns. If any middleware returns
|
||||
nil, NO dispatching will occur (i.e. the event will be filtered).
|
||||
|
||||
The `example-config` source code contains further settings and details.
|
||||
See also `set-config!`, `merge-config!`, `set-level!`."
|
||||
|
||||
(merge
|
||||
{:level :debug
|
||||
{:level :debug ; e/o #{:trace :debug :info :warn :error :fatal :report}
|
||||
|
||||
:whitelist [] ; "my-ns.*", etc.
|
||||
:blacklist [] ;
|
||||
:middleware [] ; (fns [data])->?data, applied left->right
|
||||
:middleware [] ; (fns [data]) -> ?data, applied left->right
|
||||
|
||||
:output-fn default-output-fn
|
||||
#+clj :timestamp-opts #+clj default-timestamp-opts
|
||||
:output-fn default-output-fn ; (fn [data]) -> string
|
||||
#+clj :timestamp-opts
|
||||
#+clj default-timestamp-opts ; {:pattern _ :locale _ :timezone _}
|
||||
|
||||
:appenders
|
||||
#+clj
|
||||
{:println
|
||||
{: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
|
||||
:opts {;; e/o #{:std-err :std-out :auto <stream>}:
|
||||
:stream :auto}
|
||||
|
||||
;; 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
|
||||
out (case stream
|
||||
(nil :auto) (if error? default-err *out*)
|
||||
:std-err default-err
|
||||
:std-out default-out
|
||||
stream)]
|
||||
(binding [*out* out] (println (output-fn data)))))}
|
||||
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."
|
||||
|
@ -148,7 +156,7 @@
|
|||
(set-config! example-config)
|
||||
(infof "Hello %s" "world :-)"))
|
||||
|
||||
(enc/defonce* ^:dynamic *config* example-config)
|
||||
(enc/defonce* ^:dynamic *config* "See `example-config` for info." example-config)
|
||||
(defmacro with-config [config & body] `(binding [*config* ~config] ~@body))
|
||||
(defmacro with-merged-config [config & body]
|
||||
`(binding [*config* (enc/nested-merge *config* ~config)] ~@body))
|
||||
|
@ -157,7 +165,7 @@
|
|||
#+cljs (set! *config* (f *config*))
|
||||
#+clj (alter-var-root #'*config* f))
|
||||
|
||||
(defn set-config! [m] (swap-config! (fn [_] m)))
|
||||
(defn set-config! [m] (swap-config! (fn [_old] m)))
|
||||
(defn merge-config! [m] (swap-config! (fn [old] (enc/nested-merge old m))))
|
||||
|
||||
(defn set-level! [level] (swap-config! (fn [m] (merge m {:level level}))))
|
||||
|
@ -196,6 +204,7 @@
|
|||
;;;; ns filter
|
||||
|
||||
(def ^:private compile-ns-filters
|
||||
"(fn [whitelist blacklist]) -> (fn [ns]) -> ?unfiltered-ns"
|
||||
(let [->re-pattern
|
||||
(fn [x]
|
||||
(enc/cond!
|
||||
|
@ -213,7 +222,7 @@
|
|||
|
||||
white-filter
|
||||
(cond
|
||||
;; (nil? whitelist) (fn [ns] false)
|
||||
;; (nil? whitelist) (fn [ns] false) ; Might be surprising
|
||||
(empty? whitelist*) (fn [ns] true)
|
||||
:else (fn [ns] (some #(re-find % ns) whitelist*)))
|
||||
|
||||
|
@ -222,13 +231,13 @@
|
|||
(empty? blacklist*) (fn [ns] true)
|
||||
:else (fn [ns] (not (some #(re-find % ns) blacklist*))))]
|
||||
|
||||
[white-filter black-filter])))))
|
||||
(fn [ns] (when (and (white-filter ns) (black-filter ns)) ns)))))))
|
||||
|
||||
(def ^:private ns-filter
|
||||
"(fn [whitelist blacklist ns]) -> ?unfiltered-ns"
|
||||
(enc/memoize_
|
||||
(fn [whitelist blacklist ns]
|
||||
(let [[white-filter black-filter] (compile-ns-filters whitelist blacklist)]
|
||||
(when (and (white-filter ns) (black-filter ns)) ns)))))
|
||||
((compile-ns-filters whitelist blacklist) ns))))
|
||||
|
||||
(comment (qb 10000 (ns-filter ["foo.*"] ["foo.baz"] "foo.bar")))
|
||||
|
||||
|
@ -240,17 +249,17 @@
|
|||
|
||||
;;;; Utils
|
||||
|
||||
(defmacro delay-vec [coll] (mapv (fn [in] `(delay ~in)) coll))
|
||||
(comment
|
||||
(qb 10000 (delay :foo) (fn [] :foo))
|
||||
(macroexpand '(delay-vec [(do (println "hi") :x) :y :z])))
|
||||
|
||||
(defn- ->delay [x] (if (delay? x) x (delay x)))
|
||||
(defn- vsplit-err1 [[v1 :as v]] (if-not (enc/error? v1) [nil v] (enc/vsplit-first v)))
|
||||
(comment
|
||||
(vsplit-err1 [:a :b :c])
|
||||
(vsplit-err1 [(Exception.) :a :b :c]))
|
||||
|
||||
(defn default-data-hash-fn [data]
|
||||
(defn default-data-hash-fn
|
||||
"Used for rate limiters, some appenders (e.g. Carmine), etc.
|
||||
Goal: (hash data-1) = (hash data-2) iff data-1 \"the same\" as data-2 for
|
||||
rate-limiting purposes, etc."
|
||||
[data]
|
||||
(let [{:keys [?ns-str ?line vargs_]} data
|
||||
vargs (force vargs_)]
|
||||
(str
|
||||
|
@ -272,7 +281,11 @@
|
|||
|
||||
;;;; Logging core
|
||||
|
||||
(defn log? [level & [?ns-str config]]
|
||||
(defn log?
|
||||
"Would Timbre currently log at the given logging level?
|
||||
* ns filtering requires a compile-time `?ns-str` to be provided.
|
||||
* Non-global config requires an explicit `config` to be provided."
|
||||
[level & [?ns-str config]]
|
||||
(let [config (or config *config*)]
|
||||
(and (level>= level (get-active-level config))
|
||||
(ns-filter (:whitelist config) (:blacklist config) (or ?ns-str ""))
|
||||
|
@ -280,16 +293,18 @@
|
|||
|
||||
(comment (log? :trace))
|
||||
|
||||
(def ^:dynamic *context* "General-purpose dynamic logging context." nil)
|
||||
(def ^:dynamic *context*
|
||||
"General-purpose dynamic logging context. Context will be merged into
|
||||
appender data map at logging time." nil)
|
||||
(defmacro with-context [context & body] `(binding [*context* ~context] ~@body))
|
||||
|
||||
(declare get-hostname)
|
||||
|
||||
(defn log* "Core fn-level logger. Implementation detail."
|
||||
[config level ?ns-str ?file ?line msg-type dvargs & [base-data]]
|
||||
[config level ?ns-str ?file ?line msg-type vargs_ & [base-data]]
|
||||
(when (log? level ?ns-str config)
|
||||
(let [instant (enc/now-dt)
|
||||
vargs*_ (delay (vsplit-err1 (mapv force dvargs)))
|
||||
vargs*_ (delay (vsplit-err1 (force vargs_)))
|
||||
?err_ (delay (get @vargs*_ 0))
|
||||
vargs_ (delay (get @vargs*_ 1))
|
||||
data (merge base-data *context*
|
||||
|
@ -305,7 +320,7 @@
|
|||
#+clj :hostname_ #+clj (delay (get-hostname))
|
||||
:error-level? (#{:error :fatal} level)})
|
||||
msg-fn
|
||||
(fn [vargs_] ; *After* middleware, etc.
|
||||
(fn [vargs_] ; For use *after* middleware, etc.
|
||||
(when-not (nil? msg-type)
|
||||
(when-let [vargs (have [:or nil? vector?] (force vargs_))]
|
||||
(case msg-type
|
||||
|
@ -340,7 +355,7 @@
|
|||
(not (rl-fn data-hash))))))
|
||||
|
||||
(let [{:keys [async?] apfn :fn} appender
|
||||
msg_ (delay (msg-fn (:vargs_ data)))
|
||||
msg_ (delay (or (msg-fn (:vargs_ data)) #_""))
|
||||
output-fn (or (:output-fn appender)
|
||||
(:output-fn config)
|
||||
default-output-fn)
|
||||
|
@ -356,26 +371,25 @@
|
|||
{:locale locale :timezone timezone})
|
||||
(:instant data))))
|
||||
|
||||
data
|
||||
data ; Final data prep before going to appender
|
||||
(merge data
|
||||
{:appender-id id
|
||||
:appender appender
|
||||
:appender-opts (:opts appender)
|
||||
:appender-opts (:opts appender) ; For convenience
|
||||
:msg_ msg_
|
||||
:msg-fn msg-fn
|
||||
:output-fn output-fn
|
||||
#+clj :timestamp_ #+clj timestamp_})]
|
||||
|
||||
(if-not async?
|
||||
(apfn data)
|
||||
(apfn data) ; Allow errors to throw
|
||||
(send-off (get-agent id) (fn [_] (apfn data)))))))
|
||||
nil
|
||||
(enc/clj1098 (:appenders config))))))
|
||||
nil)
|
||||
|
||||
(comment
|
||||
(log* *config* :info
|
||||
nil nil nil :print (delay-vec [(do (println "hi") :x) :y])))
|
||||
(log* *config* :info nil nil nil :print (delay [(do (println "hi") :x) :y])))
|
||||
|
||||
;;;; Logging macros
|
||||
|
||||
|
@ -391,7 +405,7 @@
|
|||
;; TODO Waiting on http://dev.clojure.org/jira/browse/CLJ-865:
|
||||
?line (:line (meta &form))]
|
||||
`(log* ~config ~level ~ns-str ~?file ~?line ~msg-type
|
||||
(delay-vec ~args) ~base-data)))))
|
||||
(delay ~(vec args)) ~base-data)))))
|
||||
|
||||
(defmacro ^:private def-logger [level]
|
||||
(let [level-name (name level)]
|
||||
|
@ -462,7 +476,7 @@
|
|||
(require '[taoensso.timbre.profiling :as profiling
|
||||
:refer (pspy pspy* profile defnp p p*)]))
|
||||
|
||||
;;;; Public utils
|
||||
;;;; Misc public utils
|
||||
|
||||
#+clj
|
||||
(defn color-str [color & xs]
|
||||
|
@ -475,7 +489,6 @@
|
|||
|
||||
#+clj (def default-out (java.io.OutputStreamWriter. System/out))
|
||||
#+clj (def default-err (java.io.PrintWriter. System/err))
|
||||
|
||||
(defmacro with-default-outs [& body]
|
||||
`(binding [*out* default-out, *err* default-err] ~@body))
|
||||
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
(ns taoensso.timbre.appenders.example-appender
|
||||
"An example of how Timbre appenders should be written.
|
||||
Please mention any requirements/dependencies in this docstring."
|
||||
{:author "Peter Taoussanis"} ; <- 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. It may help to look at
|
||||
;; the source code for `taoensso.timbre/default-output-fn`!
|
||||
;;
|
||||
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
|
||||
)
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
;; Limitations inline
|
||||
(write! [_ level throwable message]
|
||||
(let [config *config* ; No support for explicit config
|
||||
(let [config timbre/*config* ; No support for explicit config
|
||||
?ns-str nil ; No support
|
||||
?file nil ; ''
|
||||
?line nil ; ''
|
||||
|
|
Loading…
Reference in New Issue