Housekeeping

This commit is contained in:
Peter Taoussanis 2015-05-26 09:22:06 +07:00
parent 3c824d31da
commit beec0c9fdd
3 changed files with 155 additions and 61 deletions

View File

@ -14,7 +14,6 @@
;;;; TODO ;;;; TODO
;; - Check for successful cljs compile ;; - Check for successful cljs compile
;; - Cljs default appenders ;; - Cljs default appenders
;; - Port appenders
;; - Try ease backward comp, update README, CHANGELOG ;; - Try ease backward comp, update README, CHANGELOG
;; - Document shutdown-agents, ;; - Document shutdown-agents,
;; Ref. https://github.com/ptaoussanis/timbre/pull/100/files ;; Ref. https://github.com/ptaoussanis/timbre/pull/100/files
@ -36,13 +35,17 @@
#+clj #+clj
(def default-timestamp-opts (def default-timestamp-opts
"Controls (:timestamp_ data)."
{:pattern "yy-MMM-dd HH:mm:ss" {:pattern "yy-MMM-dd HH:mm:ss"
:locale (java.util.Locale. "en") :locale (java.util.Locale. "en")
;; :timezone (java.util.TimeZone/getTimeZone "UTC") ;; :timezone (java.util.TimeZone/getTimeZone "UTC")
:timezone (java.util.TimeZone/getDefault)}) :timezone (java.util.TimeZone/getDefault)})
(declare stacktrace) (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] (let [{:keys [level ?err_ vargs_ msg_ ?ns-str hostname_ timestamp_]} data]
(str (str
#+clj (force timestamp_) #+clj " " #+clj (force timestamp_) #+clj " "
@ -54,33 +57,37 @@
(declare default-err default-out ensure-spit-dir-exists!) (declare default-err default-out ensure-spit-dir-exists!)
(def example-config (def example-config
"Example (+default) Timbre config map. "Example (+default) Timbre v4 config map.
APPENDERS 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: An appender is a map with keys:
:doc ; Optional docstring. :doc ; Optional docstring
:min-level ; Level keyword, or nil (=> no minimum level). :min-level ; Level keyword, or nil (=> no minimum level)
:enabled? ; :enabled? ;
:async? ; Dispatch using agent? Useful for slow appenders. :async? ; Dispatch using agent? Useful for slow appenders
:rate-limit ; [[ncalls-limit window-ms] <...>], or nil. :rate-limit ; [[ncalls-limit window-ms] <...>], or nil
:data-hash-fn ; Used by rate-limiter, etc. :data-hash-fn ; Used by rate-limiter, etc.
:opts ; Any appender-specific opts :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: 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-id ; Id of appender currently being dispatched to
:appender ; Entire appender map 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 :level ; Keyword
:error-level? ; Is level :error or :fatal? :error-level? ; Is level :error or :fatal?
:?ns-str ; String, or nil :?ns-str ; String, or nil
:?file ; String, or nil (waiting on CLJ-865) :?file ; String, or nil ; Waiting on CLJ-865
:?line ; Integer, or nil ('') :?line ; Integer, or nil ; Waiting on CLJ-865
:?err_ ; Delay - first-argument error :?err_ ; Delay - first-argument platform error
:vargs_ ; Delay - raw args vector :vargs_ ; Delay - raw args vector
:hostname_ ; Delay - string (clj only) :hostname_ ; Delay - string (clj only)
:msg_ ; Delay - args string :msg_ ; Delay - args string
@ -91,45 +98,46 @@
<Also, any *context* keys, which get merged into data map> <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
Middleware are fns (applied left->right) that transform the data map Middleware are simple (fn [data]) -> ?data fns (applied left->right) that
dispatched to appender fns. If any middleware returns nil, NO dispatching transform the data map dispatched to appender fns. If any middleware returns
will occur (i.e. the event will be filtered). nil, NO dispatching will occur (i.e. the event will be filtered).
The `example-config` source code contains further settings and details. The `example-config` source code contains further settings and details.
See also `set-config!`, `merge-config!`, `set-level!`." See also `set-config!`, `merge-config!`, `set-level!`."
(merge (merge
{:level :debug {:level :debug ; e/o #{:trace :debug :info :warn :error :fatal :report}
:whitelist [] ; "my-ns.*", etc. :whitelist [] ; "my-ns.*", etc.
:blacklist [] ; :blacklist [] ;
:middleware [] ; (fns [data]) -> ?data, applied left->right :middleware [] ; (fns [data]) -> ?data, applied left->right
:output-fn default-output-fn :output-fn default-output-fn ; (fn [data]) -> string
#+clj :timestamp-opts #+clj default-timestamp-opts #+clj :timestamp-opts
#+clj default-timestamp-opts ; {:pattern _ :locale _ :timezone _}
:appenders :appenders
#+clj #+clj
{:println {:println ; Appender id
;; Appender <map>:
{:doc "Prints to (:stream <appender-opts>) IO stream. Enabled by default." {:doc "Prints to (:stream <appender-opts>) IO stream. Enabled by default."
:min-level nil :enabled? true :async? false :rate-limit nil :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
(fn [data] (fn [data]
(let [{:keys [output-fn error? appender-opts]} data (let [{:keys [output-fn error? appender-opts]} data
{:keys [stream]} appender-opts {:keys [stream]} appender-opts
out (case stream stream (case stream
(nil :auto) (if error? default-err *out*) (nil :auto) (if error? default-err *out*)
:std-err default-err :std-err default-err
:std-out default-out :std-out default-out
stream)] stream)]
(binding [*out* out] (println (output-fn data)))))} (binding [*out* stream] (println (output-fn data)))))}
:spit :spit
{:doc "Spits to (:spit-filename <appender-opts>) file." {:doc "Spits to (:spit-filename <appender-opts>) file."
@ -148,7 +156,7 @@
(set-config! example-config) (set-config! example-config)
(infof "Hello %s" "world :-)")) (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-config [config & body] `(binding [*config* ~config] ~@body))
(defmacro with-merged-config [config & body] (defmacro with-merged-config [config & body]
`(binding [*config* (enc/nested-merge *config* ~config)] ~@body)) `(binding [*config* (enc/nested-merge *config* ~config)] ~@body))
@ -157,7 +165,7 @@
#+cljs (set! *config* (f *config*)) #+cljs (set! *config* (f *config*))
#+clj (alter-var-root #'*config* f)) #+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 merge-config! [m] (swap-config! (fn [old] (enc/nested-merge old m))))
(defn set-level! [level] (swap-config! (fn [m] (merge m {:level level})))) (defn set-level! [level] (swap-config! (fn [m] (merge m {:level level}))))
@ -196,6 +204,7 @@
;;;; ns filter ;;;; ns filter
(def ^:private compile-ns-filters (def ^:private compile-ns-filters
"(fn [whitelist blacklist]) -> (fn [ns]) -> ?unfiltered-ns"
(let [->re-pattern (let [->re-pattern
(fn [x] (fn [x]
(enc/cond! (enc/cond!
@ -213,7 +222,7 @@
white-filter white-filter
(cond (cond
;; (nil? whitelist) (fn [ns] false) ;; (nil? whitelist) (fn [ns] false) ; Might be surprising
(empty? whitelist*) (fn [ns] true) (empty? whitelist*) (fn [ns] true)
:else (fn [ns] (some #(re-find % ns) whitelist*))) :else (fn [ns] (some #(re-find % ns) whitelist*)))
@ -222,13 +231,13 @@
(empty? blacklist*) (fn [ns] true) (empty? blacklist*) (fn [ns] true)
:else (fn [ns] (not (some #(re-find % ns) blacklist*))))] :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 (def ^:private ns-filter
"(fn [whitelist blacklist ns]) -> ?unfiltered-ns"
(enc/memoize_ (enc/memoize_
(fn [whitelist blacklist ns] (fn [whitelist blacklist ns]
(let [[white-filter black-filter] (compile-ns-filters whitelist blacklist)] ((compile-ns-filters whitelist blacklist) ns))))
(when (and (white-filter ns) (black-filter ns)) ns)))))
(comment (qb 10000 (ns-filter ["foo.*"] ["foo.baz"] "foo.bar"))) (comment (qb 10000 (ns-filter ["foo.*"] ["foo.baz"] "foo.bar")))
@ -240,17 +249,17 @@
;;;; Utils ;;;; Utils
(defmacro delay-vec [coll] (mapv (fn [in] `(delay ~in)) coll)) (defn- ->delay [x] (if (delay? x) x (delay x)))
(comment
(qb 10000 (delay :foo) (fn [] :foo))
(macroexpand '(delay-vec [(do (println "hi") :x) :y :z])))
(defn- vsplit-err1 [[v1 :as v]] (if-not (enc/error? v1) [nil v] (enc/vsplit-first v))) (defn- vsplit-err1 [[v1 :as v]] (if-not (enc/error? v1) [nil v] (enc/vsplit-first v)))
(comment (comment
(vsplit-err1 [:a :b :c]) (vsplit-err1 [:a :b :c])
(vsplit-err1 [(Exception.) :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 (let [{:keys [?ns-str ?line vargs_]} data
vargs (force vargs_)] vargs (force vargs_)]
(str (str
@ -272,7 +281,11 @@
;;;; Logging core ;;;; 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*)] (let [config (or config *config*)]
(and (level>= level (get-active-level config)) (and (level>= level (get-active-level config))
(ns-filter (:whitelist config) (:blacklist config) (or ?ns-str "")) (ns-filter (:whitelist config) (:blacklist config) (or ?ns-str ""))
@ -280,16 +293,18 @@
(comment (log? :trace)) (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)) (defmacro with-context [context & body] `(binding [*context* ~context] ~@body))
(declare get-hostname) (declare get-hostname)
(defn log* "Core fn-level logger. Implementation detail." (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) (when (log? level ?ns-str config)
(let [instant (enc/now-dt) (let [instant (enc/now-dt)
vargs*_ (delay (vsplit-err1 (mapv force dvargs))) vargs*_ (delay (vsplit-err1 (force vargs_)))
?err_ (delay (get @vargs*_ 0)) ?err_ (delay (get @vargs*_ 0))
vargs_ (delay (get @vargs*_ 1)) vargs_ (delay (get @vargs*_ 1))
data (merge base-data *context* data (merge base-data *context*
@ -305,7 +320,7 @@
#+clj :hostname_ #+clj (delay (get-hostname)) #+clj :hostname_ #+clj (delay (get-hostname))
:error-level? (#{:error :fatal} level)}) :error-level? (#{:error :fatal} level)})
msg-fn msg-fn
(fn [vargs_] ; *After* middleware, etc. (fn [vargs_] ; For use *after* middleware, etc.
(when-not (nil? msg-type) (when-not (nil? msg-type)
(when-let [vargs (have [:or nil? vector?] (force vargs_))] (when-let [vargs (have [:or nil? vector?] (force vargs_))]
(case msg-type (case msg-type
@ -340,7 +355,7 @@
(not (rl-fn data-hash)))))) (not (rl-fn data-hash))))))
(let [{:keys [async?] apfn :fn} appender (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 (or (:output-fn appender)
(:output-fn config) (:output-fn config)
default-output-fn) default-output-fn)
@ -356,26 +371,25 @@
{:locale locale :timezone timezone}) {:locale locale :timezone timezone})
(:instant data)))) (:instant data))))
data data ; Final data prep before going to appender
(merge data (merge data
{:appender-id id {:appender-id id
:appender appender :appender appender
:appender-opts (:opts appender) :appender-opts (:opts appender) ; For convenience
:msg_ msg_ :msg_ msg_
:msg-fn msg-fn :msg-fn msg-fn
:output-fn output-fn :output-fn output-fn
#+clj :timestamp_ #+clj timestamp_})] #+clj :timestamp_ #+clj timestamp_})]
(if-not async? (if-not async?
(apfn data) (apfn data) ; Allow errors to throw
(send-off (get-agent id) (fn [_] (apfn data))))))) (send-off (get-agent id) (fn [_] (apfn data)))))))
nil nil
(enc/clj1098 (:appenders config)))))) (enc/clj1098 (:appenders config))))))
nil) nil)
(comment (comment
(log* *config* :info (log* *config* :info nil nil nil :print (delay [(do (println "hi") :x) :y])))
nil nil nil :print (delay-vec [(do (println "hi") :x) :y])))
;;;; Logging macros ;;;; Logging macros
@ -391,7 +405,7 @@
;; TODO Waiting on http://dev.clojure.org/jira/browse/CLJ-865: ;; TODO Waiting on http://dev.clojure.org/jira/browse/CLJ-865:
?line (:line (meta &form))] ?line (:line (meta &form))]
`(log* ~config ~level ~ns-str ~?file ~?line ~msg-type `(log* ~config ~level ~ns-str ~?file ~?line ~msg-type
(delay-vec ~args) ~base-data))))) (delay ~(vec args)) ~base-data)))))
(defmacro ^:private def-logger [level] (defmacro ^:private def-logger [level]
(let [level-name (name level)] (let [level-name (name level)]
@ -462,7 +476,7 @@
(require '[taoensso.timbre.profiling :as profiling (require '[taoensso.timbre.profiling :as profiling
:refer (pspy pspy* profile defnp p p*)])) :refer (pspy pspy* profile defnp p p*)]))
;;;; Public utils ;;;; Misc public utils
#+clj #+clj
(defn color-str [color & xs] (defn color-str [color & xs]
@ -475,7 +489,6 @@
#+clj (def default-out (java.io.OutputStreamWriter. System/out)) #+clj (def default-out (java.io.OutputStreamWriter. System/out))
#+clj (def default-err (java.io.PrintWriter. System/err)) #+clj (def default-err (java.io.PrintWriter. System/err))
(defmacro with-default-outs [& body] (defmacro with-default-outs [& body]
`(binding [*out* default-out, *err* default-err] ~@body)) `(binding [*out* default-out, *err* default-err] ~@body))

View File

@ -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
)

View File

@ -15,7 +15,7 @@
;; Limitations inline ;; Limitations inline
(write! [_ level throwable message] (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 ?ns-str nil ; No support
?file nil ; '' ?file nil ; ''
?line nil ; '' ?line nil ; ''