2013-11-29 13:53:38 +07:00
|
|
|
(ns taoensso.timbre "Simple, flexible, all-Clojure logging. No XML!"
|
2012-05-28 15:13:11 +07:00
|
|
|
{:author "Peter Taoussanis"}
|
2012-07-26 15:01:50 +07:00
|
|
|
(:require [clojure.string :as str]
|
2013-11-29 13:26:45 +07:00
|
|
|
[io.aviso.exception :as aviso-ex]
|
2012-07-26 15:01:50 +07:00
|
|
|
[taoensso.timbre.utils :as utils])
|
2012-05-30 16:15:15 +07:00
|
|
|
(:import [java.util Date Locale]
|
2012-07-03 18:48:00 +07:00
|
|
|
[java.text SimpleDateFormat]))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-01-29 16:49:17 +07:00
|
|
|
;;;; Public utils
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2012-06-15 18:06:18 +07:00
|
|
|
(defn str-println
|
2012-07-03 16:29:11 +07:00
|
|
|
"Like `println` but prints all objects to output stream as a single
|
2012-06-15 18:06:18 +07:00
|
|
|
atomic string. This is faster and avoids interleaving race conditions."
|
2013-11-29 13:53:38 +07:00
|
|
|
[& xs] (print (str (str/join \space (filter identity xs)) \newline))
|
|
|
|
(flush))
|
2012-06-15 18:06:18 +07:00
|
|
|
|
2013-05-15 19:37:17 +07:00
|
|
|
(defn color-str [color & xs]
|
2013-11-29 13:53:38 +07:00
|
|
|
(let [ansi-color #(format "\u001b[%sm"
|
|
|
|
(case % :reset "0" :black "30" :red "31"
|
|
|
|
:green "32" :yellow "33" :blue "34"
|
|
|
|
:purple "35" :cyan "36" :white "37"
|
|
|
|
"0"))]
|
2013-05-15 19:37:17 +07:00
|
|
|
(str (ansi-color color) (apply str xs) (ansi-color :reset))))
|
2013-01-03 23:07:22 +07:00
|
|
|
|
2013-01-29 16:49:17 +07:00
|
|
|
(def default-out (java.io.OutputStreamWriter. System/out))
|
|
|
|
(def default-err (java.io.PrintWriter. System/err))
|
|
|
|
|
|
|
|
(defmacro with-default-outs
|
2013-01-29 16:57:28 +07:00
|
|
|
"Evaluates body with Clojure's default *out* and *err* bindings."
|
2013-01-29 16:49:17 +07:00
|
|
|
[& body] `(binding [*out* default-out *err* default-err] ~@body))
|
|
|
|
|
2013-11-29 13:53:38 +07:00
|
|
|
(defmacro with-err-as-out "Evaluates body with *err* bound to *out*."
|
2013-01-29 16:49:17 +07:00
|
|
|
[& body] `(binding [*err* *out*] ~@body))
|
|
|
|
|
2013-11-29 13:26:45 +07:00
|
|
|
(defn stacktrace "Default stacktrace formatter for use by appenders, etc."
|
2013-11-30 16:17:50 +07:00
|
|
|
[throwable & [separator stacktrace-fonts]]
|
2013-05-15 20:09:36 +07:00
|
|
|
(when throwable
|
2013-11-30 16:17:50 +07:00
|
|
|
(str separator
|
|
|
|
(if-let [fonts stacktrace-fonts]
|
|
|
|
(binding [aviso-ex/*fonts* fonts] (aviso-ex/format-exception throwable))
|
|
|
|
(aviso-ex/format-exception throwable)))))
|
|
|
|
|
|
|
|
(comment (stacktrace (Exception. "foo") nil {}))
|
2013-05-15 20:09:36 +07:00
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
;;;; Logging levels
|
|
|
|
;; Level precendence: compile-time > dynamic > atom
|
2013-01-03 23:07:22 +07:00
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
(def level-compile-time
|
2013-08-20 22:13:49 +07:00
|
|
|
"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)."
|
|
|
|
(keyword (System/getenv "TIMBRE_LOG_LEVEL")))
|
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
(def ^:dynamic *level-dynamic* nil)
|
2013-07-10 13:42:00 +07:00
|
|
|
(defmacro with-log-level
|
|
|
|
"Allows thread-local config logging level override. Useful for dev & testing."
|
2013-11-29 14:34:57 +07:00
|
|
|
[level & body] `(binding [*level-dynamic* ~level] ~@body))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
(def level-atom (atom :debug))
|
|
|
|
(defn set-level! [level] (reset! level-atom level))
|
|
|
|
|
|
|
|
;;;
|
2012-07-03 16:29:11 +07:00
|
|
|
|
2013-11-30 17:38:14 +07:00
|
|
|
(def levels-ordered [:trace :debug :info :warn :error :fatal :report])
|
2013-11-29 14:34:57 +07:00
|
|
|
(def ^:private levels-scored (assoc (zipmap levels-ordered (next (range))) nil 0))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
(defn error-level? [level] (boolean (#{:error :fatal} level))) ; For appenders, etc.
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
(defn- level-checked-score [level]
|
|
|
|
(or (levels-scored level)
|
|
|
|
(throw (Exception. (format "Invalid logging level: %s" level)))))
|
2012-10-26 16:15:08 +07:00
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
(def ^:private levels-compare (memoize (fn [x y] (- (level-checked-score x)
|
2013-11-29 23:33:23 +07:00
|
|
|
(level-checked-score y)))))
|
2012-07-26 15:53:21 +07:00
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
(declare config)
|
2013-11-29 23:33:23 +07:00
|
|
|
;; Used in macros, must be public:
|
|
|
|
(defn level-sufficient? [level ; & [config] ; Deprecated
|
|
|
|
]
|
2013-11-29 14:34:57 +07:00
|
|
|
(>= (levels-compare level
|
|
|
|
(or level-compile-time
|
|
|
|
*level-dynamic*
|
2013-11-29 23:33:23 +07:00
|
|
|
;; Deprecate config-specified level:
|
|
|
|
;;(:current-level (or config @config)) ; Don't need compile here
|
2013-11-29 14:34:57 +07:00
|
|
|
(:current-level @config) ; DEPRECATED, here for backwards comp
|
|
|
|
@level-atom)) 0))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
;;;; Default configuration and appenders
|
|
|
|
|
|
|
|
(def example-config
|
|
|
|
"APPENDERS
|
|
|
|
An appender is a map with keys:
|
2013-11-30 20:01:58 +07:00
|
|
|
:doc ; (Optional) string.
|
|
|
|
:min-level ; (Optional) keyword, or nil (no minimum level).
|
|
|
|
:enabled? ; (Optional).
|
|
|
|
:async? ; (Optional) dispatch using agent (good for slow appenders).
|
|
|
|
:rate-limit ; (Optional) [ncalls-limit window-ms].
|
|
|
|
:fmt-output-opts ; (Optional) extra opts passed to `fmt-output-fn`.
|
|
|
|
:fn ; (fn [appender-args-map]), with keys described below.
|
2013-11-29 14:34:57 +07:00
|
|
|
|
|
|
|
An appender's fn takes a single map with keys:
|
2013-11-30 20:19:56 +07:00
|
|
|
:level ; Keyword.
|
2013-11-30 20:01:58 +07:00
|
|
|
:error? ; Is level an 'error' level?
|
2013-11-30 20:19:56 +07:00
|
|
|
:throwable ; java.lang.Throwable.
|
2013-11-30 20:01:58 +07:00
|
|
|
:args ; Raw logging macro args (as given to `info`, etc.).
|
|
|
|
:message ; Stringified logging macro args, or nil.
|
|
|
|
:output ; Output of `fmt-output-fn`, used by built-in appenders
|
2013-11-30 20:19:56 +07:00
|
|
|
; as final, formatted appender output. Appenders may (but
|
|
|
|
; are not obligated to) use this as their output.
|
2013-11-30 20:01:58 +07:00
|
|
|
:ap-config ; `shared-appender-config`.
|
|
|
|
:profile-stats ; From `profile` macro.
|
2013-11-30 20:19:56 +07:00
|
|
|
:instant ; java.util.Date.
|
|
|
|
:timestamp ; String generated from :timestamp-pattern, :timestamp-locale.
|
|
|
|
:hostname ; String.
|
|
|
|
:ns ; String.
|
2013-11-29 14:34:57 +07:00
|
|
|
|
|
|
|
MIDDLEWARE
|
|
|
|
Middleware are fns (applied right-to-left) that transform the map
|
|
|
|
dispatched to appender fns. If any middleware returns nil, no dispatching
|
|
|
|
will occur (i.e. the event will be filtered).
|
|
|
|
|
2013-11-30 20:01:58 +07:00
|
|
|
The `example-config` code contains further settings and details.
|
2013-11-29 14:34:57 +07:00
|
|
|
See also `set-config!`, `merge-config!`, `set-level!`."
|
|
|
|
|
|
|
|
{;;; Control log filtering by namespace patterns (e.g. ["my-app.*"]).
|
|
|
|
;;; Useful for turning off logging in noisy libraries, etc.
|
|
|
|
:ns-whitelist []
|
|
|
|
:ns-blacklist []
|
|
|
|
|
|
|
|
;; Fns (applied right-to-left) to transform/filter appender fn args.
|
|
|
|
;; Useful for obfuscating credentials, pattern filtering, etc.
|
|
|
|
:middleware []
|
|
|
|
|
|
|
|
;;; Control :timestamp format
|
|
|
|
:timestamp-pattern "yyyy-MMM-dd HH:mm:ss ZZ" ; SimpleDateFormat pattern
|
|
|
|
:timestamp-locale nil ; A Locale object, or nil
|
|
|
|
|
|
|
|
:prefix-fn ; DEPRECATED, here for backwards comp
|
|
|
|
(fn [{:keys [level timestamp hostname ns]}]
|
|
|
|
(str timestamp " " hostname " " (-> level name str/upper-case)
|
|
|
|
" [" ns "]"))
|
|
|
|
|
2013-11-30 16:22:33 +07:00
|
|
|
;; Output formatter used by built-in appenders. Custom appenders may (but are
|
|
|
|
;; not required to use) its output (:output). Extra per-appender opts can be
|
|
|
|
;; supplied as an optional second (map) arg.
|
2013-11-29 14:34:57 +07:00
|
|
|
:fmt-output-fn
|
2013-11-30 16:22:33 +07:00
|
|
|
(fn [{:keys [level throwable message timestamp hostname ns]}
|
|
|
|
;; Any extra appender-specific opts:
|
|
|
|
& [{:keys [nofonts?] :as appender-fmt-output-opts}]]
|
2013-11-29 14:34:57 +07:00
|
|
|
;; <timestamp> <hostname> <LEVEL> [<ns>] - <message> <throwable>
|
|
|
|
(format "%s %s %s [%s] - %s%s"
|
|
|
|
timestamp hostname (-> level name str/upper-case) ns (or message "")
|
2013-11-30 16:22:33 +07:00
|
|
|
(or (stacktrace throwable "\n" (when nofonts? {})) "")))
|
2013-11-29 14:34:57 +07:00
|
|
|
|
|
|
|
:shared-appender-config {} ; Provided to all appenders via :ap-config key
|
|
|
|
:appenders
|
|
|
|
{:standard-out
|
|
|
|
{:doc "Prints to *out*/*err*. Enabled by default."
|
2013-11-29 18:31:33 +07:00
|
|
|
:min-level nil :enabled? true :async? false :rate-limit nil
|
2013-11-30 16:22:33 +07:00
|
|
|
:fn (fn [{:keys [error? output]}]
|
2013-11-29 14:34:57 +07:00
|
|
|
(binding [*out* (if error? *err* *out*)]
|
2013-11-30 16:22:33 +07:00
|
|
|
(str-println output)))}
|
2013-11-29 14:34:57 +07:00
|
|
|
|
|
|
|
:spit
|
|
|
|
{:doc "Spits to `(:spit-filename :shared-appender-config)` file."
|
2013-11-29 18:31:33 +07:00
|
|
|
:min-level nil :enabled? false :async? false :rate-limit nil
|
2013-11-30 16:22:33 +07:00
|
|
|
:fn (fn [{:keys [ap-config output]}]
|
2013-11-29 14:34:57 +07:00
|
|
|
(when-let [filename (:spit-filename ap-config)]
|
2013-11-30 16:22:33 +07:00
|
|
|
(try (spit filename output :append true)
|
2013-11-29 14:34:57 +07:00
|
|
|
(catch java.io.IOException _))))}}})
|
|
|
|
|
|
|
|
(utils/defonce* config (atom example-config))
|
|
|
|
(defn set-config! [ks val] (swap! config assoc-in ks val))
|
|
|
|
(defn merge-config! [& maps] (apply swap! config utils/merge-deep maps))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2012-05-30 16:15:15 +07:00
|
|
|
;;;; Appender-fn decoration
|
|
|
|
|
2012-05-28 15:13:11 +07:00
|
|
|
(defn- wrap-appender-fn
|
2012-07-03 16:29:11 +07:00
|
|
|
"Wraps compile-time appender fn with additional runtime capabilities
|
|
|
|
controlled by compile-time config."
|
2013-11-30 16:22:33 +07:00
|
|
|
[config {apfn :fn :keys [async? rate-limit fmt-output-opts] :as appender}]
|
2013-11-29 18:31:33 +07:00
|
|
|
(let [rate-limit (or rate-limit ; Backwards comp:
|
|
|
|
(if-let [x (:max-message-per-msecs appender)] [1 x]
|
|
|
|
(when-let [x (:limit-per-msecs appender)] [1 x])))]
|
|
|
|
|
|
|
|
(assert (or (nil? rate-limit) (vector? rate-limit)))
|
|
|
|
|
2013-05-15 22:37:02 +07:00
|
|
|
(->> ; Wrapping applies per appender, bottom-to-top
|
|
|
|
apfn
|
|
|
|
|
2013-11-30 16:22:33 +07:00
|
|
|
;; Custom appender-level fmt-output-opts
|
|
|
|
((fn [apfn] ; Compile-time:
|
|
|
|
(if-not fmt-output-opts apfn ; Common case (no appender-level fmt opts)
|
|
|
|
(fn [apfn-args] ; Runtime:
|
|
|
|
;; Replace default (juxt-level) output:
|
|
|
|
(apfn (assoc apfn-args :output
|
|
|
|
((:fmt-output-fn config) apfn-args fmt-output-opts)))))))
|
|
|
|
|
2013-05-15 22:37:02 +07:00
|
|
|
;; Rate limit support
|
|
|
|
((fn [apfn]
|
2013-11-29 18:31:33 +07:00
|
|
|
;; Compile-time:
|
|
|
|
(if-not rate-limit apfn
|
|
|
|
(let [[ncalls-limit window-ms] rate-limit
|
|
|
|
limiter-any (utils/rate-limiter ncalls-limit window-ms)
|
|
|
|
;; This is a little hand-wavy but it's a decent general
|
|
|
|
;; strategy and helps us from making this overly complex to
|
|
|
|
;; configure:
|
|
|
|
limiter-specific (utils/rate-limiter (quot ncalls-limit 4)
|
|
|
|
window-ms)]
|
|
|
|
(fn [{:keys [ns args] :as apfn-args}]
|
|
|
|
;; Runtime: (test smaller limit 1st):
|
|
|
|
(when-not (or (limiter-specific (str ns args)) (limiter-any))
|
|
|
|
(apfn apfn-args)))))))
|
2013-05-15 22:37:02 +07:00
|
|
|
|
|
|
|
;; Async (agent) support
|
|
|
|
((fn [apfn]
|
2013-11-29 18:31:33 +07:00
|
|
|
;; Compile-time:
|
2013-11-29 14:34:57 +07:00
|
|
|
(if-not async? apfn
|
2013-05-15 22:37:02 +07:00
|
|
|
(let [agent (agent nil :error-mode :continue)]
|
2013-11-29 18:31:33 +07:00
|
|
|
(fn [apfn-args] ; Runtime:
|
|
|
|
(send-off agent (fn [_] (apfn apfn-args)))))))))))
|
2013-02-04 12:32:32 +07:00
|
|
|
|
|
|
|
(defn- make-timestamp-fn
|
|
|
|
"Returns a unary fn that formats instants using given pattern string and an
|
|
|
|
optional Locale."
|
2013-08-07 12:44:37 +07:00
|
|
|
;; Thread safe SimpleDateTime soln. from instant.clj, Ref. http://goo.gl/CEBJnQ
|
2013-02-04 12:32:32 +07:00
|
|
|
[^String pattern ^Locale locale]
|
2013-08-07 12:44:37 +07:00
|
|
|
(let [format (proxy [ThreadLocal] [] ; For thread safety
|
|
|
|
(initialValue []
|
|
|
|
(if locale
|
|
|
|
(SimpleDateFormat. pattern locale)
|
|
|
|
(SimpleDateFormat. pattern))))]
|
|
|
|
(fn [^Date instant] (.format ^SimpleDateFormat (.get format) instant))))
|
2013-02-04 12:32:32 +07:00
|
|
|
|
|
|
|
(comment ((make-timestamp-fn "yyyy-MMM-dd" nil) (Date.)))
|
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
(def ^:private get-hostname
|
2013-11-06 15:13:14 +07:00
|
|
|
(utils/memoize-ttl 60000
|
|
|
|
(fn []
|
|
|
|
(let [p (promise)]
|
|
|
|
(future ; Android doesn't like this on the main thread
|
|
|
|
(deliver p
|
|
|
|
(try (.. java.net.InetAddress getLocalHost getHostName)
|
|
|
|
(catch java.net.UnknownHostException _
|
|
|
|
"UnknownHost"))))
|
|
|
|
@p))))
|
2013-02-04 12:32:32 +07:00
|
|
|
|
|
|
|
(defn- wrap-appender-juxt
|
|
|
|
"Wraps compile-time appender juxt with additional runtime capabilities
|
2013-02-11 11:55:38 +07:00
|
|
|
(incl. middleware) controlled by compile-time config. Like `wrap-appender-fn`
|
2013-02-04 12:32:32 +07:00
|
|
|
but operates on the entire juxt at once."
|
2013-11-29 23:33:23 +07:00
|
|
|
[config juxtfn]
|
2013-05-15 19:37:17 +07:00
|
|
|
(->> ; Wrapping applies per juxt, bottom-to-top
|
2013-02-04 12:32:32 +07:00
|
|
|
juxtfn
|
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
;; Post-middleware stuff
|
|
|
|
((fn [juxtfn]
|
|
|
|
;; Compile-time:
|
|
|
|
(let [{ap-config :shared-appender-config
|
|
|
|
:keys [timestamp-pattern timestamp-locale
|
2013-11-29 23:33:23 +07:00
|
|
|
prefix-fn fmt-output-fn]} config
|
2013-11-29 14:34:57 +07:00
|
|
|
timestamp-fn (make-timestamp-fn timestamp-pattern timestamp-locale)]
|
|
|
|
(fn [juxtfn-args]
|
|
|
|
;; Runtime:
|
|
|
|
(when-let [{:keys [instant msg-type args]} juxtfn-args]
|
|
|
|
(let [juxtfn-args (if-not msg-type juxtfn-args ; tools.logging
|
|
|
|
(-> juxtfn-args
|
|
|
|
(dissoc :msg-type)
|
|
|
|
(assoc :message
|
|
|
|
(when-not (empty? args)
|
|
|
|
(case msg-type
|
|
|
|
:format (apply format args)
|
|
|
|
:print-str (apply print-str args)
|
2013-11-29 23:33:23 +07:00
|
|
|
:nil nil)))))
|
|
|
|
juxtfn-args (assoc juxtfn-args :timestamp (timestamp-fn instant))
|
|
|
|
juxtfn-args (assoc juxtfn-args
|
|
|
|
;; DEPRECATED, here for backwards comp:
|
2013-11-30 16:22:33 +07:00
|
|
|
:prefix (when-let [f prefix-fn] (f juxtfn-args))
|
|
|
|
:output (when-let [f fmt-output-fn] (f juxtfn-args)))]
|
2013-11-29 23:33:23 +07:00
|
|
|
(juxtfn juxtfn-args)))))))
|
2013-11-29 14:34:57 +07:00
|
|
|
|
2013-05-15 19:37:17 +07:00
|
|
|
;; Middleware transforms/filters support
|
2013-02-04 12:32:32 +07:00
|
|
|
((fn [juxtfn]
|
2013-11-29 14:34:57 +07:00
|
|
|
;; Compile-time:
|
2013-11-29 23:33:23 +07:00
|
|
|
(if-let [middleware (seq (:middleware config))]
|
2013-02-04 12:32:32 +07:00
|
|
|
(let [composed-middleware
|
|
|
|
(apply comp (map (fn [mf] (fn [args] (when args (mf args))))
|
|
|
|
middleware))]
|
|
|
|
(fn [juxtfn-args]
|
2013-11-29 14:34:57 +07:00
|
|
|
;; Runtime:
|
2013-02-04 12:32:32 +07:00
|
|
|
(when-let [juxtfn-args (composed-middleware juxtfn-args)]
|
|
|
|
(juxtfn juxtfn-args))))
|
|
|
|
juxtfn)))
|
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
;; Pre-middleware stuff
|
2013-02-04 12:32:32 +07:00
|
|
|
((fn [juxtfn]
|
2013-11-29 14:34:57 +07:00
|
|
|
;; Compile-time:
|
2013-11-29 23:33:23 +07:00
|
|
|
(let [{ap-config :shared-appender-config} config]
|
2013-11-29 14:34:57 +07:00
|
|
|
(fn [juxtfn-args]
|
|
|
|
;; Runtime:
|
|
|
|
(juxtfn (merge juxtfn-args {:ap-config ap-config
|
|
|
|
:hostname (get-hostname)}))))))))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-11-29 23:33:23 +07:00
|
|
|
;;;; Config compilation
|
2012-07-03 16:29:11 +07:00
|
|
|
|
2013-11-29 23:33:23 +07:00
|
|
|
(defn- relevant-appenders [appenders level]
|
|
|
|
(->> appenders
|
2012-05-28 15:13:11 +07:00
|
|
|
(filter #(let [{:keys [enabled? min-level]} (val %)]
|
2013-11-29 14:34:57 +07:00
|
|
|
(and enabled? (>= (levels-compare level min-level) 0))))
|
2012-05-28 15:13:11 +07:00
|
|
|
(into {})))
|
|
|
|
|
2013-05-15 19:37:17 +07:00
|
|
|
(defn- ns-match? [ns match]
|
2012-07-03 16:29:11 +07:00
|
|
|
(-> (str "^" (-> (str match) (.replace "." "\\.") (.replace "*" "(.*)")) "$")
|
|
|
|
re-pattern (re-find (str ns)) boolean))
|
|
|
|
|
2013-11-29 23:33:23 +07:00
|
|
|
(def compile-config ; Used in macros, must be public
|
|
|
|
"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.
|
|
|
|
(fn [{:keys [appenders] :as config}]
|
2013-11-30 16:22:33 +07:00
|
|
|
(assert (map? appenders))
|
2013-11-29 23:33:23 +07:00
|
|
|
{:appenders-juxt
|
|
|
|
(zipmap levels-ordered
|
|
|
|
(->> levels-ordered
|
|
|
|
(map (fn [l] (let [rel-aps (relevant-appenders appenders l)]
|
|
|
|
;; Return nil if no relevant appenders
|
|
|
|
(when-let [ap-ids (keys rel-aps)]
|
|
|
|
(->> ap-ids
|
|
|
|
(map #(wrap-appender-fn config (rel-aps %)))
|
|
|
|
(apply juxt)
|
|
|
|
(wrap-appender-juxt config))))))))
|
|
|
|
:ns-filter
|
|
|
|
(let [{:keys [ns-whitelist ns-blacklist]} config]
|
|
|
|
(if (and (empty? ns-whitelist) (empty? ns-blacklist))
|
|
|
|
(fn relevant-ns? [ns] true)
|
|
|
|
(memoize
|
|
|
|
(fn relevant-ns? [ns]
|
|
|
|
(and (or (empty? ns-whitelist)
|
|
|
|
(some (partial ns-match? ns) ns-whitelist))
|
|
|
|
(or (empty? ns-blacklist)
|
|
|
|
(not-any? (partial ns-match? ns) ns-blacklist)))))))})))
|
|
|
|
|
|
|
|
(comment (compile-config example-config))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-08-20 22:13:49 +07:00
|
|
|
;;;; Logging macros
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
(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."
|
2013-11-29 23:33:23 +07:00
|
|
|
[level & [config]]
|
2013-11-29 14:34:57 +07:00
|
|
|
(if level-compile-time
|
|
|
|
(when (level-sufficient? level)
|
2013-11-29 23:33:23 +07:00
|
|
|
`(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*))))))
|
2013-11-29 14:34:57 +07:00
|
|
|
|
|
|
|
(comment (def compile-time-level :info)
|
|
|
|
(def compile-time-level nil)
|
|
|
|
(macroexpand-1 '(logging-enabled? :debug)))
|
|
|
|
|
2013-11-29 23:33:23 +07:00
|
|
|
(defn send-to-appenders! "Implementation detail."
|
2013-11-29 14:34:57 +07:00
|
|
|
[;; Args provided by both Timbre, tools.logging:
|
|
|
|
level base-appender-args log-vargs ns throwable message
|
|
|
|
;; Additional args provided by Timbre only:
|
|
|
|
& [juxt-fn msg-type file line]]
|
2013-11-29 23:33:23 +07:00
|
|
|
(when-let [juxt-fn (or juxt-fn (get-in (compile-config @config)
|
|
|
|
[:appenders-juxt level]))]
|
2013-08-07 22:50:39 +07:00
|
|
|
(juxt-fn
|
|
|
|
(conj (or base-appender-args {})
|
|
|
|
{:instant (Date.)
|
|
|
|
:ns ns
|
|
|
|
:file file ; No tools.logging support
|
|
|
|
:line line ; No tools.logging support
|
|
|
|
:level level
|
|
|
|
:error? (error-level? level)
|
|
|
|
:args log-vargs ; No tools.logging support
|
|
|
|
:throwable throwable
|
2013-11-29 14:34:57 +07:00
|
|
|
:message message ; Timbre: nil, tools.logging: nil or string
|
|
|
|
:msg-type msg-type ; Timbre: nnil, tools.logging: nil
|
|
|
|
}))
|
2013-08-07 22:50:39 +07:00
|
|
|
nil))
|
|
|
|
|
2013-11-29 23:33:23 +07:00
|
|
|
(defmacro log* "Implementation detail."
|
|
|
|
{:arglists '([base-appender-args msg-type level & log-args]
|
|
|
|
[base-appender-args msg-type config level & log-args])}
|
|
|
|
[base-appender-args msg-type & [s1 s2 :as sigs]]
|
2013-11-29 14:34:57 +07:00
|
|
|
{:pre [(#{:nil :print-str :format} msg-type)]}
|
2013-11-29 23:33:23 +07:00
|
|
|
`(let [;;; Support [level & log-args], [config level & log-args] sigs:
|
|
|
|
s1# ~s1
|
2013-12-01 01:30:55 +07:00
|
|
|
default-config?# (or (keyword? s1#) (nil? s1#))
|
2013-11-29 23:33:23 +07:00
|
|
|
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))))))))
|
|
|
|
|
|
|
|
(defmacro log
|
|
|
|
"Logs using print-style args. Takes optional logging config (defaults to
|
|
|
|
`timbre/@config`.)"
|
|
|
|
{:arglists '([level & message] [level throwable & message]
|
|
|
|
[config level & message] [config level throwable & message])}
|
|
|
|
[& sigs] `(log* {} :print-str ~@sigs))
|
|
|
|
|
|
|
|
(defmacro logf
|
|
|
|
"Logs using format-style args. Takes optional logging config (defaults to
|
|
|
|
`timbre/@config`.)"
|
|
|
|
{:arglists '([level fmt & fmt-args] [level throwable fmt & fmt-args]
|
|
|
|
[config level fmt & fmt-args] [config level throwable fmt & fmt-args])}
|
|
|
|
[& sigs] `(log* {} :format ~@sigs))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-07-22 22:34:23 +07:00
|
|
|
(defmacro log-errors [& body] `(try ~@body (catch Throwable t# (error t#))))
|
|
|
|
(defmacro log-and-rethrow-errors [& body]
|
|
|
|
`(try ~@body (catch Throwable t# (error t#) (throw t#))))
|
|
|
|
|
|
|
|
(defmacro logged-future [& body] `(future (log-errors ~@body)))
|
|
|
|
|
|
|
|
(comment (log-errors (/ 0))
|
|
|
|
(log-and-rethrow-errors (/ 0))
|
|
|
|
(logged-future (/ 0)))
|
|
|
|
|
2012-05-28 15:13:11 +07:00
|
|
|
(defmacro spy
|
2012-09-21 21:02:58 +07:00
|
|
|
"Evaluates named expression and logs its result. Always returns the result.
|
|
|
|
Defaults to :debug logging level and unevaluated expression as name."
|
2012-05-28 15:13:11 +07:00
|
|
|
([expr] `(spy :debug ~expr))
|
2012-09-21 21:02:58 +07:00
|
|
|
([level expr] `(spy ~level '~expr ~expr))
|
|
|
|
([level name expr]
|
2013-07-22 22:34:23 +07:00
|
|
|
`(log-and-rethrow-errors
|
|
|
|
(let [result# ~expr] (log ~level ~name result#) result#))))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-05-15 19:37:17 +07:00
|
|
|
(defmacro ^:private def-logger [level]
|
2012-05-28 15:13:11 +07:00
|
|
|
(let [level-name (name level)]
|
2013-05-15 20:09:36 +07:00
|
|
|
`(do
|
|
|
|
(defmacro ~(symbol level-name)
|
2013-08-07 22:50:39 +07:00
|
|
|
~(str "Logs at " level " level using print-style args.")
|
2013-05-15 20:09:36 +07:00
|
|
|
~'{:arglists '([& message] [throwable & message])}
|
2013-07-22 22:21:07 +07:00
|
|
|
[& sigs#] `(log ~~level ~@sigs#))
|
2013-05-15 20:09:36 +07:00
|
|
|
|
|
|
|
(defmacro ~(symbol (str level-name "f"))
|
2013-08-07 22:50:39 +07:00
|
|
|
~(str "Logs at " level " level using format-style args.")
|
2013-05-15 20:09:36 +07:00
|
|
|
~'{:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])}
|
|
|
|
[& sigs#] `(logf ~~level ~@sigs#)))))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-05-15 20:09:36 +07:00
|
|
|
(defmacro ^:private def-loggers []
|
2013-11-29 14:34:57 +07:00
|
|
|
`(do ~@(map (fn [level] `(def-logger ~level)) levels-ordered)))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
|
|
|
(def-loggers) ; Actually define a logger for each logging level
|
|
|
|
|
2013-07-22 17:53:36 +07:00
|
|
|
(defn refer-timbre
|
|
|
|
"Shorthand for:
|
2013-11-26 12:58:13 +07:00
|
|
|
(require
|
|
|
|
'[taoensso.timbre :as timbre
|
|
|
|
:refer (log trace debug info warn error fatal report
|
|
|
|
logf tracef debugf infof warnf errorf fatalf reportf
|
|
|
|
spy logged-future with-log-level)])
|
|
|
|
(require '[taoensso.timbre.utils :refer (sometimes)])
|
|
|
|
(require
|
|
|
|
'[taoensso.timbre.profiling :as profiling :refer (pspy profile defnp)])"
|
2013-07-22 17:53:36 +07:00
|
|
|
[]
|
2013-11-26 12:58:13 +07:00
|
|
|
(require
|
|
|
|
'[taoensso.timbre :as timbre
|
|
|
|
:refer (log trace debug info warn error fatal report
|
|
|
|
logf tracef debugf infof warnf errorf fatalf reportf
|
|
|
|
spy logged-future with-log-level)])
|
|
|
|
(require '[taoensso.timbre.utils :refer (sometimes)])
|
|
|
|
(require
|
|
|
|
'[taoensso.timbre.profiling :as profiling :refer (pspy profile defnp)]))
|
2013-07-22 17:53:36 +07:00
|
|
|
|
2013-07-22 22:21:07 +07:00
|
|
|
;;;; Deprecated
|
|
|
|
|
|
|
|
(defmacro logp "DEPRECATED: Use `log` instead."
|
|
|
|
{:arglists '([level & message] [level throwable & message])}
|
|
|
|
[& sigs] `(log ~@sigs)) ; Alias
|
|
|
|
|
|
|
|
(defmacro s "DEPRECATED: Use `spy` instead."
|
|
|
|
{:arglists '([expr] [level expr] [level name expr])}
|
|
|
|
[& args] `(spy ~@args))
|
|
|
|
|
2013-11-29 14:34:57 +07:00
|
|
|
(def red "DEPRECATED: Use `color-str` instead." (partial color-str :red))
|
|
|
|
(def green "DEPRECATED: Use `color-str` instead." (partial color-str :green))
|
|
|
|
(def yellow "DEPRECATED: Use `color-str` instead." (partial color-str :yellow))
|
|
|
|
|
2012-05-28 15:13:11 +07:00
|
|
|
;;;; Dev/tests
|
|
|
|
|
|
|
|
(comment
|
2013-05-15 20:09:36 +07:00
|
|
|
(info)
|
|
|
|
(info "a")
|
|
|
|
(info "a" "b" "c")
|
|
|
|
(info "a" (Exception. "b") "c")
|
|
|
|
(info (Exception. "a") "b" "c")
|
2013-07-22 22:21:07 +07:00
|
|
|
(log (or nil :info) "Booya")
|
2012-07-03 16:29:11 +07:00
|
|
|
|
2013-05-15 20:09:36 +07:00
|
|
|
(info "a%s" "b")
|
|
|
|
(infof "a%s" "b")
|
2012-10-26 16:15:08 +07:00
|
|
|
|
2013-11-29 23:33:23 +07:00
|
|
|
(info {} "a")
|
|
|
|
(log {} :info "a")
|
|
|
|
(log example-config :info "a")
|
|
|
|
|
2012-07-03 16:29:11 +07:00
|
|
|
(set-config! [:ns-blacklist] [])
|
|
|
|
(set-config! [:ns-blacklist] ["taoensso.timbre*"])
|
|
|
|
|
2012-05-28 15:13:11 +07:00
|
|
|
(info "foo" "bar")
|
|
|
|
(trace (Thread/sleep 5000))
|
2013-11-29 23:33:23 +07:00
|
|
|
(time (dotimes [n 10000] (trace "This won't log"))) ; Overhead 5ms->15ms
|
|
|
|
(time (dotimes [n 10000] (when false)))
|
2012-05-28 15:13:11 +07:00
|
|
|
(time (dotimes [n 5] (info "foo" "bar")))
|
2013-11-29 14:34:57 +07:00
|
|
|
(spy :info (* 6 5 4 3 2 1))
|
|
|
|
(spy :info :factorial6 (* 6 5 4 3 2 1))
|
2012-05-28 15:13:11 +07:00
|
|
|
(info (Exception. "noes!") "bar")
|
2013-02-04 12:32:32 +07:00
|
|
|
(spy (/ 4 0))
|
|
|
|
|
2013-07-10 13:42:00 +07:00
|
|
|
(with-log-level :trace (trace "foo"))
|
|
|
|
(with-log-level :debug (trace "foo"))
|
|
|
|
|
2013-02-04 12:32:32 +07:00
|
|
|
;; Middleware
|
|
|
|
(info {:name "Robert Paulson" :password "Super secret"})
|
2013-11-29 14:34:57 +07:00
|
|
|
(set-config! [:middleware] [])
|
|
|
|
(set-config! [:middleware]
|
|
|
|
[(fn [{:keys [hostname message args] :as ap-args}]
|
|
|
|
(if (= hostname "filtered-host") nil ; Filter
|
|
|
|
(assoc ap-args :args
|
|
|
|
;; Replace :password vals in any map args:
|
|
|
|
(mapv (fn [arg] (if-not (map? arg) arg
|
|
|
|
(if-not (contains? arg :password) arg
|
|
|
|
(assoc arg :password "****"))))
|
2013-11-30 16:22:33 +07:00
|
|
|
args))))])
|
|
|
|
|
|
|
|
;; fmt-output-opts
|
|
|
|
(-> (merge example-config
|
|
|
|
{:appenders
|
|
|
|
{:fmt-output-opts-test
|
|
|
|
{:min-level :error :enabled? true
|
|
|
|
:fmt-output-opts {:nofonts? true}
|
|
|
|
:fn (fn [{:keys [output]}] (str-println output))}}})
|
|
|
|
(log :report (Exception. "Oh noes") "Hello")))
|