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 {}))
;;;; Logging levels
;; Level precendence: compile-time > dynamic > atom
(def level-compile-time
"Constant, compile-time logging level determined by the `TIMBRE_LOG_LEVEL`
environment variable. When set, overrules dynamically-configurable logging
level as a performance optimization (e.g. for use in performance sensitive
production environments)."
level as a performance optimization."
(keyword (System/getenv "TIMBRE_LOG_LEVEL")))
(def ^:dynamic *level-dynamic* nil)
@ -63,28 +61,22 @@
;;;
(def levels-ordered [:trace :debug :info :warn :error :fatal :report])
(def ^:private levels-scored (assoc (zipmap levels-ordered (next (range))) nil 0))
(defn error-level? [level] (boolean (#{:error :fatal} level))) ; For appenders, etc.
(def levels-scored (zipmap levels-ordered (next (range))))
(defn- level-error? [level] (boolean (#{:error :fatal} 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)))))
(def ^:private levels-compare (memoize (fn [x y] (- (level-checked-score x)
(level-checked-score y)))))
(declare config)
;; Used in macros, must be public:
(defn level-sufficient? [level ; & [config] ; Deprecated
]
(>= (levels-compare level
(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))
(defn level-sufficient? "Precendence: compile-time > dynamic > config > atom."
[level config] (<= 0 (levels-compare level (or level-compile-time
*level-dynamic*
(:current-level config)
@level-atom))))
;;;; Default configuration and appenders
@ -136,7 +128,10 @@
The `example-config` code contains further settings and details.
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.
:ns-whitelist []
:ns-blacklist []
@ -342,15 +337,14 @@
re-pattern (re-find (str ns)) boolean))
(def compile-config ; Used in macros, must be public
"Returns {:appenders-juxt {<level> <wrapped-juxt or nil>}
:ns-filter (fn relevant-ns? [ns])}."
"Implementation detail.
Returns {:appenders-juxt {<level> <wrapped-juxt or nil>}
:ns-filter (fn relevant-ns? [ns])}."
(memoize
;; Careful. The presence of fns actually means that inline config's won't
;; actually be identified as samey. In practice not a major (?) problem
;; since configs will usually be assigned to a var for which we have proper
;; identity.
;; Careful. The presence of fns means that inline config's won't correctly
;; be identified as samey. In practice not a major (?) problem since configs
;; will usually be assigned to a var for which we have proper identity.
(fn [{:keys [appenders] :as config}]
(assert (map? appenders))
{:appenders-juxt
(zipmap levels-ordered
(->> levels-ordered
@ -372,26 +366,18 @@
(or (empty? 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
(defmacro logging-enabled?
"Returns true iff current logging level is sufficient and current namespace
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*))))))
(defn ns-unfiltered? [config & [ns]] ((:ns-filter (compile-config config))
(or ns *ns*)))
(comment (def compile-time-level :info)
(def compile-time-level nil)
(macroexpand-1 '(logging-enabled? :debug)))
(defn logging-enabled? "For 3rd-party utils, etc."
[level] (let [config' @config]
(and (level-sufficient? level config')
(ns-unfiltered? config'))))
(defn send-to-appenders! "Implementation detail."
[;; Args provided by both Timbre, tools.logging:
@ -407,7 +393,7 @@
:file file ; No tools.logging support
:line line ; No tools.logging support
:level level
:error? (error-level? level)
:error? (level-error? level)
:args log-vargs ; No tools.logging support
:throwable throwable
: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 & [s1 s2 :as sigs]]
{:pre [(#{:nil :print-str :format} msg-type)]}
`(let [;;; Support [level & log-args], [config level & log-args] sigs:
s1# ~s1
default-config?# (or (keyword? s1#) (nil? s1#))
config# (if default-config?# @config s1#)
level# (if default-config?# s1# ~s2)]
(when (logging-enabled? level# config#)
(when-let [juxt-fn# (get-in (compile-config config#)
[:appenders-juxt level#])]
(let [[x1# & xn# :as xs#] (if default-config?#
(vector ~@(next sigs))
(vector ~@(nnext sigs)))
has-throwable?# (instance? Throwable x1#)
log-vargs# (vec (if has-throwable?# xn# xs#))]
(send-to-appenders!
level#
~base-appender-args
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))))))))
;; Compile-time:
(when (or (nil? level-compile-time)
(let [level (cond (levels-scored s1) s1
(levels-scored s2) s2)]
(or (nil? level) ; Also needs to be compile-time
(level-sufficient? level nil))))
;; Runtime:
`(let [;;; Support [level & log-args], [config level & log-args] sigs:
s1# ~s1
default-config?# (levels-scored s1#)
config# (if default-config?# @config s1#)
level# (if default-config?# s1# ~s2)]
;; (println "DEBUG: Runtime level check")
(when (and (level-sufficient? level# config#)
(ns-unfiltered? config#))
(when-let [juxt-fn# (get-in (compile-config config#)
[:appenders-juxt level#])]
(let [[x1# & xn# :as xs#] (if default-config?#
(vector ~@(next sigs))
(vector ~@(nnext sigs)))
has-throwable?# (instance? Throwable x1#)
log-vargs# (vec (if has-throwable?# xn# xs#))]
(send-to-appenders!
level#
~base-appender-args
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
"Logs using print-style args. Takes optional logging config (defaults to
@ -585,4 +579,13 @@
{:min-level :error :enabled? true
:fmt-output-opts {:nofonts? true}
: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
)