Level checks hk, fix compile-time logging level (prepor)

This commit is contained in:
Peter Taoussanis 2013-12-03 18:54:43 +07:00
parent b21d5c3d6c
commit e0a9a08b6b
1 changed files with 73 additions and 70 deletions

View File

@ -43,13 +43,11 @@
(comment (stacktrace (Exception. "foo") nil {})) (comment (stacktrace (Exception. "foo") nil {}))
;;;; Logging levels ;;;; Logging levels
;; Level precendence: compile-time > dynamic > atom
(def level-compile-time (def level-compile-time
"Constant, compile-time logging level determined by the `TIMBRE_LOG_LEVEL` "Constant, compile-time logging level determined by the `TIMBRE_LOG_LEVEL`
environment variable. When set, overrules dynamically-configurable logging environment variable. When set, overrules dynamically-configurable logging
level as a performance optimization (e.g. for use in performance sensitive level as a performance optimization."
production environments)."
(keyword (System/getenv "TIMBRE_LOG_LEVEL"))) (keyword (System/getenv "TIMBRE_LOG_LEVEL")))
(def ^:dynamic *level-dynamic* nil) (def ^:dynamic *level-dynamic* nil)
@ -63,28 +61,22 @@
;;; ;;;
(def levels-ordered [:trace :debug :info :warn :error :fatal :report]) (def levels-ordered [:trace :debug :info :warn :error :fatal :report])
(def ^:private levels-scored (assoc (zipmap levels-ordered (next (range))) nil 0)) (def levels-scored (zipmap levels-ordered (next (range))))
(defn error-level? [level] (boolean (#{:error :fatal} level))) ; For appenders, etc.
(defn- level-error? [level] (boolean (#{:error :fatal} level)))
(defn- level-checked-score [level] (defn- level-checked-score [level]
(or (levels-scored level) (or (when (nil? level) 0) ; < any valid level
(levels-scored level)
(throw (Exception. (format "Invalid logging level: %s" level))))) (throw (Exception. (format "Invalid logging level: %s" level)))))
(def ^:private levels-compare (memoize (fn [x y] (- (level-checked-score x) (def ^:private levels-compare (memoize (fn [x y] (- (level-checked-score x)
(level-checked-score y))))) (level-checked-score y)))))
(declare config) (defn level-sufficient? "Precendence: compile-time > dynamic > config > atom."
;; Used in macros, must be public: [level config] (<= 0 (levels-compare level (or level-compile-time
(defn level-sufficient? [level ; & [config] ; Deprecated *level-dynamic*
] (:current-level config)
(>= (levels-compare level @level-atom))))
(or level-compile-time
*level-dynamic*
;; Deprecate config-specified level:
;;(:current-level (or config @config)) ; Don't need compile here
(:current-level @config) ; DEPRECATED, here for backwards comp
@level-atom)) 0))
;;;; Default configuration and appenders ;;;; Default configuration and appenders
@ -136,7 +128,10 @@
The `example-config` code contains further settings and details. The `example-config` code contains further settings and details.
See also `set-config!`, `merge-config!`, `set-level!`." See also `set-config!`, `merge-config!`, `set-level!`."
{;;; Control log filtering by namespace patterns (e.g. ["my-app.*"]). {;; Prefer `level-atom` to in-config level when possible:
;; :current-logging-level :debug
;;; Control log filtering by namespace patterns (e.g. ["my-app.*"]).
;;; Useful for turning off logging in noisy libraries, etc. ;;; Useful for turning off logging in noisy libraries, etc.
:ns-whitelist [] :ns-whitelist []
:ns-blacklist [] :ns-blacklist []
@ -342,15 +337,14 @@
re-pattern (re-find (str ns)) boolean)) re-pattern (re-find (str ns)) boolean))
(def compile-config ; Used in macros, must be public (def compile-config ; Used in macros, must be public
"Returns {:appenders-juxt {<level> <wrapped-juxt or nil>} "Implementation detail.
:ns-filter (fn relevant-ns? [ns])}." Returns {:appenders-juxt {<level> <wrapped-juxt or nil>}
:ns-filter (fn relevant-ns? [ns])}."
(memoize (memoize
;; Careful. The presence of fns actually means that inline config's won't ;; Careful. The presence of fns means that inline config's won't correctly
;; actually be identified as samey. In practice not a major (?) problem ;; be identified as samey. In practice not a major (?) problem since configs
;; since configs will usually be assigned to a var for which we have proper ;; will usually be assigned to a var for which we have proper identity.
;; identity.
(fn [{:keys [appenders] :as config}] (fn [{:keys [appenders] :as config}]
(assert (map? appenders))
{:appenders-juxt {:appenders-juxt
(zipmap levels-ordered (zipmap levels-ordered
(->> levels-ordered (->> levels-ordered
@ -372,26 +366,18 @@
(or (empty? ns-blacklist) (or (empty? ns-blacklist)
(not-any? (partial ns-match? ns) ns-blacklist)))))))}))) (not-any? (partial ns-match? ns) ns-blacklist)))))))})))
(comment (compile-config example-config)) (comment (compile-config example-config)
(compile-config nil))
;;;; Logging macros ;;;; Logging macros
(defmacro logging-enabled? (defn ns-unfiltered? [config & [ns]] ((:ns-filter (compile-config config))
"Returns true iff current logging level is sufficient and current namespace (or ns *ns*)))
unfiltered. The namespace test is runtime, the logging-level test compile-time
iff a compile-time logging level was specified."
[level & [config]]
(if level-compile-time
(when (level-sufficient? level)
`(let [ns-filter# (:ns-filter (compile-config (or ~config @config)))]
(ns-filter# ~(str *ns*))))
`(and (level-sufficient? ~level)
(let [ns-filter# (:ns-filter (compile-config (or ~config @config)))]
(ns-filter# ~(str *ns*))))))
(comment (def compile-time-level :info) (defn logging-enabled? "For 3rd-party utils, etc."
(def compile-time-level nil) [level] (let [config' @config]
(macroexpand-1 '(logging-enabled? :debug))) (and (level-sufficient? level config')
(ns-unfiltered? config'))))
(defn send-to-appenders! "Implementation detail." (defn send-to-appenders! "Implementation detail."
[;; Args provided by both Timbre, tools.logging: [;; Args provided by both Timbre, tools.logging:
@ -407,7 +393,7 @@
:file file ; No tools.logging support :file file ; No tools.logging support
:line line ; No tools.logging support :line line ; No tools.logging support
:level level :level level
:error? (error-level? level) :error? (level-error? level)
:args log-vargs ; No tools.logging support :args log-vargs ; No tools.logging support
:throwable throwable :throwable throwable
:message message ; Timbre: nil, tools.logging: nil or string :message message ; Timbre: nil, tools.logging: nil or string
@ -420,32 +406,40 @@
[base-appender-args msg-type config level & log-args])} [base-appender-args msg-type config level & log-args])}
[base-appender-args msg-type & [s1 s2 :as sigs]] [base-appender-args msg-type & [s1 s2 :as sigs]]
{:pre [(#{:nil :print-str :format} msg-type)]} {:pre [(#{:nil :print-str :format} msg-type)]}
`(let [;;; Support [level & log-args], [config level & log-args] sigs: ;; Compile-time:
s1# ~s1 (when (or (nil? level-compile-time)
default-config?# (or (keyword? s1#) (nil? s1#)) (let [level (cond (levels-scored s1) s1
config# (if default-config?# @config s1#) (levels-scored s2) s2)]
level# (if default-config?# s1# ~s2)] (or (nil? level) ; Also needs to be compile-time
(level-sufficient? level nil))))
(when (logging-enabled? level# config#) ;; Runtime:
(when-let [juxt-fn# (get-in (compile-config config#) `(let [;;; Support [level & log-args], [config level & log-args] sigs:
[:appenders-juxt level#])] s1# ~s1
(let [[x1# & xn# :as xs#] (if default-config?# default-config?# (levels-scored s1#)
(vector ~@(next sigs)) config# (if default-config?# @config s1#)
(vector ~@(nnext sigs))) level# (if default-config?# s1# ~s2)]
has-throwable?# (instance? Throwable x1#) ;; (println "DEBUG: Runtime level check")
log-vargs# (vec (if has-throwable?# xn# xs#))] (when (and (level-sufficient? level# config#)
(send-to-appenders! (ns-unfiltered? config#))
level# (when-let [juxt-fn# (get-in (compile-config config#)
~base-appender-args [:appenders-juxt level#])]
log-vargs# (let [[x1# & xn# :as xs#] (if default-config?#
~(str *ns*) (vector ~@(next sigs))
(when has-throwable?# x1#) (vector ~@(nnext sigs)))
nil ; Timbre generates msg only after middleware has-throwable?# (instance? Throwable x1#)
juxt-fn# log-vargs# (vec (if has-throwable?# xn# xs#))]
~msg-type (send-to-appenders!
(let [file# ~*file*] (when (not= file# "NO_SOURCE_PATH") file#)) level#
;; TODO Waiting on http://dev.clojure.org/jira/browse/CLJ-865: ~base-appender-args
~(:line (meta &form)))))))) log-vargs#
~(str *ns*)
(when has-throwable?# x1#)
nil ; Timbre generates msg only after middleware
juxt-fn#
~msg-type
(let [file# ~*file*] (when (not= file# "NO_SOURCE_PATH") file#))
;; TODO Waiting on http://dev.clojure.org/jira/browse/CLJ-865:
~(:line (meta &form)))))))))
(defmacro log (defmacro log
"Logs using print-style args. Takes optional logging config (defaults to "Logs using print-style args. Takes optional logging config (defaults to
@ -585,4 +579,13 @@
{:min-level :error :enabled? true {:min-level :error :enabled? true
:fmt-output-opts {:nofonts? true} :fmt-output-opts {:nofonts? true}
:fn (fn [{:keys [output]}] (str-println output))}}}) :fn (fn [{:keys [output]}] (str-println output))}}})
(log :report (Exception. "Oh noes") "Hello"))) (log :report (Exception. "Oh noes") "Hello"))
;; compile-time level (enabled log* debug println)
(def level-compile-time :warn)
(debug "hello")
(log :info "hello") ; Discarded at compile-time
(log {} :info) ; Discarded at compile-time
(log (or :info) "hello") ; Discarded at runtime
)