2012-07-03 16:27:20 +07:00
|
|
|
(ns taoensso.timbre
|
2012-05-28 15:13:11 +07:00
|
|
|
"Simple, flexible, all-Clojure logging. No XML!"
|
|
|
|
{:author "Peter Taoussanis"}
|
2012-07-26 15:01:50 +07:00
|
|
|
(:require [clojure.string :as str]
|
|
|
|
[clj-stacktrace.repl :as stacktrace]
|
|
|
|
[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."
|
|
|
|
[& xs]
|
2012-07-13 16:22:40 +07:00
|
|
|
(print (str (str/join \space xs) \newline))
|
|
|
|
(flush))
|
2012-06-15 18:06:18 +07:00
|
|
|
|
2013-01-03 23:07:22 +07:00
|
|
|
(defn color-str [color-key & xs]
|
|
|
|
(let [ansi-color #(str "\u001b[" (case % :reset "0" :black "30" :red "31"
|
|
|
|
:green "32" :yellow "33" :blue "34"
|
|
|
|
:purple "35" :cyan "36" :white "37"
|
|
|
|
"0") "m")]
|
|
|
|
(str (ansi-color color-key) (apply str xs) (ansi-color :reset))))
|
|
|
|
|
|
|
|
(def red (partial color-str :red))
|
|
|
|
(def green (partial color-str :green))
|
|
|
|
(def yellow (partial color-str :yellow))
|
|
|
|
|
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))
|
|
|
|
|
|
|
|
(defmacro with-err-as-out
|
2013-01-29 16:57:28 +07:00
|
|
|
"Evaluates body with *err* bound to *out*."
|
2013-01-29 16:49:17 +07:00
|
|
|
[& body] `(binding [*err* *out*] ~@body))
|
|
|
|
|
2013-01-03 23:07:22 +07:00
|
|
|
;;;; Default configuration and appenders
|
|
|
|
|
2012-12-28 13:59:17 +07:00
|
|
|
(utils/defonce* config
|
2013-02-04 12:32:32 +07:00
|
|
|
"This map atom controls everything about the way Timbre operates.
|
|
|
|
|
|
|
|
APPENDERS
|
|
|
|
An appender is a map with keys:
|
|
|
|
:doc, :min-level, :enabled?, :async?, :max-message-per-msecs, :fn
|
|
|
|
|
|
|
|
An appender's fn takes a single map argument with keys:
|
|
|
|
:level, :message, :more ; From all logging macros (`info`, etc.)
|
|
|
|
:profiling-stats ; From `profile` macro
|
|
|
|
:ap-config ; `shared-appender-config`
|
|
|
|
:prefix ; Output of `prefix-fn`
|
|
|
|
And also: :instant, :timestamp, :hostname, :ns, :error?
|
|
|
|
|
|
|
|
MIDDLEWARE
|
|
|
|
Middleware are fns (applied right-to-left) that transform the map argument
|
|
|
|
dispatched to appender fns. If any middleware returns nil, no dispatching
|
|
|
|
will occur (i.e. the event will be filtered).
|
|
|
|
|
|
|
|
See source code for examples. See `set-config!`, `merge-config!`, `set-level!`
|
|
|
|
for convenient config editing."
|
2012-05-28 15:13:11 +07:00
|
|
|
(atom {:current-level :debug
|
|
|
|
|
2012-07-26 15:53:21 +07:00
|
|
|
;;; Control log filtering by namespace patterns (e.g. ["my-app.*"]).
|
2012-07-03 16:29:11 +07:00
|
|
|
;;; Useful for turning off logging in noisy libraries, etc.
|
|
|
|
:ns-whitelist []
|
|
|
|
:ns-blacklist []
|
|
|
|
|
2013-02-04 12:32:32 +07:00
|
|
|
;; Fns (applied right-to-left) to transform/filter appender fn args.
|
|
|
|
;; Useful for obfuscating credentials, pattern filtering, etc.
|
|
|
|
:middleware []
|
2013-02-01 15:05:50 +07:00
|
|
|
|
2012-07-26 15:53:21 +07:00
|
|
|
;;; Control :timestamp format
|
|
|
|
:timestamp-pattern "yyyy-MMM-dd HH:mm:ss ZZ" ; SimpleDateFormat pattern
|
|
|
|
:timestamp-locale nil ; A Locale object, or nil
|
|
|
|
|
2012-08-02 14:59:58 +07:00
|
|
|
;; Control :prefix format
|
2012-07-26 15:53:21 +07:00
|
|
|
:prefix-fn
|
|
|
|
(fn [{:keys [level timestamp hostname ns]}]
|
|
|
|
(str timestamp " " hostname " " (-> level name str/upper-case)
|
|
|
|
" [" ns "]"))
|
|
|
|
|
|
|
|
;; Will be provided to all appenders via :ap-config key
|
|
|
|
:shared-appender-config {}
|
|
|
|
|
2012-05-28 15:13:11 +07:00
|
|
|
:appenders
|
|
|
|
{:standard-out
|
|
|
|
{:doc "Prints to *out* or *err* as appropriate. Enabled by default."
|
2012-06-06 00:46:10 +07:00
|
|
|
:min-level nil :enabled? true :async? false
|
2012-05-28 15:13:11 +07:00
|
|
|
:max-message-per-msecs nil
|
2012-07-26 15:53:21 +07:00
|
|
|
:fn (fn [{:keys [error? prefix message more]}]
|
2012-05-28 15:13:11 +07:00
|
|
|
(binding [*out* (if error? *err* *out*)]
|
2012-10-19 15:24:46 +07:00
|
|
|
(apply str-println prefix "-" message more)))}
|
|
|
|
|
|
|
|
:spit
|
|
|
|
{:doc "Spits to (:spit-filename :shared-appender-config) file."
|
|
|
|
:min-level nil :enabled? false :async? false
|
|
|
|
:max-message-per-msecs nil
|
|
|
|
:fn (fn [{:keys [ap-config prefix message more]}]
|
|
|
|
(when-let [filename (:spit-filename ap-config)]
|
|
|
|
(try (spit filename
|
|
|
|
(with-out-str (apply str-println prefix "-"
|
|
|
|
message more))
|
|
|
|
:append true)
|
|
|
|
(catch java.io.IOException _))))}}}))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2013-01-04 14:50:28 +07:00
|
|
|
(defn set-config! [[k & ks] val] (swap! config assoc-in (cons k ks) val))
|
|
|
|
(defn merge-config! [& maps] (apply swap! config utils/deep-merge maps))
|
|
|
|
(defn set-level! [level] (set-config! [:current-level] level))
|
2012-07-03 16:29:11 +07:00
|
|
|
|
2012-05-28 15:13:11 +07:00
|
|
|
;;;; Define and sort logging levels
|
|
|
|
|
2012-07-01 18:08:45 +07:00
|
|
|
(def ^:private ordered-levels [:trace :debug :info :warn :error :fatal :report])
|
2012-10-26 16:15:08 +07:00
|
|
|
(def ^:private scored-levels (assoc (zipmap ordered-levels (range)) nil 0))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2012-10-26 16:15:08 +07:00
|
|
|
(defn error-level? [level] (boolean (#{:error :fatal} level)))
|
|
|
|
|
|
|
|
(defn- checked-level-score [level]
|
|
|
|
(or (scored-levels level)
|
|
|
|
(throw (Exception. (str "Invalid logging level: " level)))))
|
2012-07-26 15:53:21 +07:00
|
|
|
|
2012-05-28 16:25:43 +07:00
|
|
|
(def compare-levels
|
2012-10-26 16:15:08 +07:00
|
|
|
(memoize (fn [x y] (- (checked-level-score x) (checked-level-score y)))))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2012-05-28 16:25:43 +07:00
|
|
|
(defn sufficient-level?
|
2012-05-28 15:13:11 +07:00
|
|
|
[level] (>= (compare-levels level (:current-level @config)) 0))
|
|
|
|
|
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."
|
2012-06-15 18:06:18 +07:00
|
|
|
[{apfn :fn :keys [async? max-message-per-msecs] :as appender}]
|
2013-02-04 12:32:32 +07:00
|
|
|
(->> ; Wrapping applies capabilities bottom-to-top
|
|
|
|
apfn
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2012-05-28 15:34:04 +07:00
|
|
|
;; Wrap for runtime flood-safety support
|
2012-05-28 15:13:11 +07:00
|
|
|
((fn [apfn]
|
|
|
|
(if-not max-message-per-msecs
|
|
|
|
apfn
|
2013-02-05 23:35:14 +07:00
|
|
|
(let [;; {:hash last-appended-time-msecs ...}
|
2012-05-28 15:13:11 +07:00
|
|
|
flood-timers (atom {})]
|
|
|
|
|
2013-02-05 23:35:14 +07:00
|
|
|
(fn [{:keys [ns message] :as apfn-args}]
|
2012-05-28 15:13:11 +07:00
|
|
|
(let [now (System/currentTimeMillis)
|
2013-02-05 23:35:14 +07:00
|
|
|
hash (str ns "/" message)
|
2012-05-28 15:13:11 +07:00
|
|
|
allow? (fn [last-msecs]
|
|
|
|
(if last-msecs
|
|
|
|
(> (- now last-msecs) max-message-per-msecs)
|
|
|
|
true))]
|
|
|
|
|
2013-02-05 23:35:14 +07:00
|
|
|
(when (allow? (@flood-timers hash))
|
2012-05-28 15:13:11 +07:00
|
|
|
(apfn apfn-args)
|
2013-02-05 23:35:14 +07:00
|
|
|
(swap! flood-timers assoc hash now))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
|
|
|
;; Occassionally garbage-collect all expired timers. Note
|
|
|
|
;; that due to snapshotting, garbage-collection can cause
|
|
|
|
;; some appenders to re-append prematurely.
|
|
|
|
(when (< (rand) 0.001)
|
|
|
|
(let [timers-snapshot @flood-timers
|
|
|
|
expired-timers
|
|
|
|
(->> (keys timers-snapshot)
|
|
|
|
(filter #(allow? (timers-snapshot %))))]
|
|
|
|
(when (seq expired-timers)
|
2013-02-04 12:32:32 +07:00
|
|
|
(apply swap! flood-timers dissoc expired-timers))))))))))
|
|
|
|
|
|
|
|
;; Wrap for async (agent) support
|
|
|
|
((fn [apfn]
|
|
|
|
(if-not async?
|
|
|
|
apfn
|
|
|
|
(let [agent (agent nil :error-mode :continue)]
|
|
|
|
(fn [apfn-args] (send-off agent (fn [_] (apfn apfn-args))))))))))
|
|
|
|
|
|
|
|
(defn- make-timestamp-fn
|
|
|
|
"Returns a unary fn that formats instants using given pattern string and an
|
|
|
|
optional Locale."
|
|
|
|
[^String pattern ^Locale locale]
|
|
|
|
(let [format (if locale
|
|
|
|
(SimpleDateFormat. pattern locale)
|
|
|
|
(SimpleDateFormat. pattern))]
|
|
|
|
(fn [^Date instant] (.format ^SimpleDateFormat format instant))))
|
|
|
|
|
|
|
|
(comment ((make-timestamp-fn "yyyy-MMM-dd" nil) (Date.)))
|
|
|
|
|
|
|
|
(def get-hostname
|
|
|
|
(utils/memoize-ttl
|
2013-02-11 11:47:24 +07:00
|
|
|
60000 (fn [] (try (.. java.net.InetAddress getLocalHost getHostName)
|
|
|
|
(catch java.net.UnknownHostException e
|
|
|
|
"UnknownHost")))))
|
2013-02-04 12:32:32 +07:00
|
|
|
|
|
|
|
(defn- wrap-appender-juxt
|
|
|
|
"Wraps compile-time appender juxt with additional runtime capabilities
|
|
|
|
(incl. middleware) controller by compile-time config. Like `wrap-appender-fn`
|
|
|
|
but operates on the entire juxt at once."
|
|
|
|
[juxtfn]
|
|
|
|
(->> ; Wrapping applies capabilities bottom-to-top
|
|
|
|
juxtfn
|
|
|
|
|
|
|
|
;; Wrap to add middleware transforms/filters
|
|
|
|
((fn [juxtfn]
|
|
|
|
(if-let [middleware (seq (:middleware @config))]
|
|
|
|
(let [composed-middleware
|
|
|
|
(apply comp (map (fn [mf] (fn [args] (when args (mf args))))
|
|
|
|
middleware))]
|
|
|
|
(fn [juxtfn-args]
|
|
|
|
(when-let [juxtfn-args (composed-middleware juxtfn-args)]
|
|
|
|
(juxtfn juxtfn-args))))
|
|
|
|
juxtfn)))
|
|
|
|
|
|
|
|
;; Wrap to add compile-time stuff to runtime appender arguments
|
|
|
|
((fn [juxtfn]
|
|
|
|
(let [{ap-config :shared-appender-config
|
|
|
|
:keys [timestamp-pattern timestamp-locale prefix-fn]} @config
|
|
|
|
|
|
|
|
timestamp-fn (make-timestamp-fn timestamp-pattern timestamp-locale)]
|
|
|
|
(fn [{:keys [instant] :as juxtfn-args}]
|
|
|
|
(let [juxtfn-args (merge juxtfn-args {:ap-config ap-config
|
|
|
|
:timestamp (timestamp-fn instant)
|
|
|
|
:hostname (get-hostname)})]
|
|
|
|
(juxtfn (assoc juxtfn-args :prefix (prefix-fn juxtfn-args))))))))))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2012-07-03 16:29:11 +07:00
|
|
|
;;;; Caching
|
|
|
|
|
|
|
|
;;; Appender-fns
|
2012-05-28 15:13:11 +07:00
|
|
|
|
2012-07-03 16:29:11 +07:00
|
|
|
(def appenders-juxt-cache
|
|
|
|
"Per-level, combined relevant appender-fns to allow for fast runtime
|
|
|
|
appender-fn dispatch:
|
2013-02-04 12:32:32 +07:00
|
|
|
{:level (wrapped-juxt wrapped-appender-fn wrapped-appender-fn ...) or nil
|
2012-05-28 15:13:11 +07:00
|
|
|
...}"
|
|
|
|
(atom {}))
|
|
|
|
|
|
|
|
(defn- relevant-appenders
|
|
|
|
[level]
|
|
|
|
(->> (:appenders @config)
|
|
|
|
(filter #(let [{:keys [enabled? min-level]} (val %)]
|
2012-10-26 16:15:08 +07:00
|
|
|
(and enabled? (>= (compare-levels level min-level) 0))))
|
2012-05-28 15:13:11 +07:00
|
|
|
(into {})))
|
|
|
|
|
|
|
|
(comment (relevant-appenders :debug)
|
|
|
|
(relevant-appenders :trace))
|
|
|
|
|
2012-07-03 16:29:11 +07:00
|
|
|
(defn- cache-appenders-juxt!
|
2012-05-28 15:13:11 +07:00
|
|
|
[]
|
2012-07-03 16:29:11 +07:00
|
|
|
(->>
|
|
|
|
(zipmap
|
|
|
|
ordered-levels
|
|
|
|
(->> ordered-levels
|
|
|
|
(map (fn [l] (let [rel-aps (relevant-appenders l)]
|
|
|
|
;; Return nil if no relevant appenders
|
|
|
|
(when-let [ap-ids (keys rel-aps)]
|
|
|
|
(->> ap-ids
|
|
|
|
(map #(wrap-appender-fn (rel-aps %)))
|
2013-02-04 12:32:32 +07:00
|
|
|
(apply juxt)
|
|
|
|
(wrap-appender-juxt))))))))
|
2012-07-03 16:29:11 +07:00
|
|
|
(reset! appenders-juxt-cache)))
|
|
|
|
|
2013-02-04 12:32:32 +07:00
|
|
|
;;; Namespace filter
|
2012-07-03 16:29:11 +07:00
|
|
|
|
|
|
|
(def ns-filter-cache "@ns-filter-cache => (fn relevant-ns? [ns] ...)"
|
|
|
|
(atom (constantly true)))
|
|
|
|
|
|
|
|
(defn- ns-match?
|
|
|
|
[ns match]
|
|
|
|
(-> (str "^" (-> (str match) (.replace "." "\\.") (.replace "*" "(.*)")) "$")
|
|
|
|
re-pattern (re-find (str ns)) boolean))
|
|
|
|
|
|
|
|
(defn- cache-ns-filter!
|
|
|
|
[]
|
|
|
|
(->>
|
|
|
|
(let [{:keys [ns-whitelist ns-blacklist]} @config]
|
|
|
|
(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))))))
|
|
|
|
(reset! ns-filter-cache)))
|
|
|
|
|
2012-07-04 13:47:21 +07:00
|
|
|
;;; Prime initial caches and re-cache on config change
|
2012-07-03 16:29:11 +07:00
|
|
|
|
|
|
|
(cache-appenders-juxt!)
|
|
|
|
(cache-ns-filter!)
|
|
|
|
|
2012-05-28 15:13:11 +07:00
|
|
|
(add-watch
|
2012-07-03 16:29:11 +07:00
|
|
|
config "config-cache-watch"
|
2012-06-12 22:02:24 +07:00
|
|
|
(fn [key ref old-state new-state]
|
|
|
|
(when (not= (dissoc old-state :current-level)
|
|
|
|
(dissoc new-state :current-level))
|
2012-07-03 16:29:11 +07:00
|
|
|
(cache-appenders-juxt!)
|
|
|
|
(cache-ns-filter!))))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
|
|
|
;;;; Define logging macros
|
|
|
|
|
2012-07-03 16:29:11 +07:00
|
|
|
(defmacro logging-enabled?
|
|
|
|
"Returns true when current logging level is sufficient and current namespace
|
|
|
|
is unfiltered."
|
|
|
|
[level]
|
|
|
|
`(and (sufficient-level? ~level) (@ns-filter-cache ~*ns*)))
|
|
|
|
|
|
|
|
(defmacro log*
|
|
|
|
"Prepares given arguments for, and then dispatches to all relevant
|
|
|
|
appender-fns."
|
2012-08-02 14:59:58 +07:00
|
|
|
[level base-args & sigs]
|
2012-07-03 16:29:11 +07:00
|
|
|
`(when-let [juxt-fn# (@appenders-juxt-cache ~level)] ; Any relevant appenders?
|
2012-08-02 14:59:58 +07:00
|
|
|
(let [[x1# & xs#] (list ~@sigs)
|
2012-07-03 16:29:11 +07:00
|
|
|
|
|
|
|
has-throwable?# (instance? Throwable x1#)
|
|
|
|
appender-args#
|
|
|
|
(conj
|
|
|
|
~base-args ; Allow flexibility to inject exta args
|
|
|
|
{:level ~level
|
2012-07-26 15:53:21 +07:00
|
|
|
:error? (error-level? ~level)
|
2012-07-03 16:29:11 +07:00
|
|
|
:instant (Date.)
|
|
|
|
:ns (str ~*ns*)
|
|
|
|
:message (if has-throwable?# (or (first xs#) x1#) x1#)
|
|
|
|
:more (if has-throwable?#
|
|
|
|
(conj (vec (rest xs#))
|
2013-01-03 23:13:55 +07:00
|
|
|
(str "\nStacktrace:\n"
|
|
|
|
(stacktrace/pst-str x1#)))
|
2012-07-03 16:29:11 +07:00
|
|
|
(vec xs#))})]
|
|
|
|
|
|
|
|
(juxt-fn# appender-args#)
|
|
|
|
nil)))
|
|
|
|
|
2012-05-28 16:25:43 +07:00
|
|
|
(defmacro log
|
2012-07-03 16:29:11 +07:00
|
|
|
"When logging is enabled, actually logs given arguments with relevant
|
|
|
|
appender-fns. Generic form of standard level-loggers (trace, info, etc.)."
|
2012-07-01 18:08:45 +07:00
|
|
|
{:arglists '([level message & more] [level throwable message & more])}
|
2012-08-02 14:59:58 +07:00
|
|
|
[level & sigs]
|
2012-07-03 16:29:11 +07:00
|
|
|
`(when (logging-enabled? ~level)
|
2012-08-02 14:59:58 +07:00
|
|
|
(log* ~level {} ~@sigs)))
|
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]
|
2012-05-28 15:13:11 +07:00
|
|
|
`(try
|
2013-02-03 01:09:46 +07:00
|
|
|
(let [result# ~expr] (log ~level ~name result#) result#)
|
2012-05-28 15:13:11 +07:00
|
|
|
(catch Exception e#
|
|
|
|
(log ~level '~expr (str "\n" (stacktrace/pst-str e#)))
|
|
|
|
(throw e#)))))
|
|
|
|
|
2013-02-03 00:07:20 +07:00
|
|
|
(defmacro s ; Alias
|
|
|
|
{:arglists '([expr] [level expr] [level name expr])}
|
|
|
|
[& args] `(spy ~@args))
|
|
|
|
|
2012-05-28 15:13:11 +07:00
|
|
|
(defmacro ^:private def-logger
|
|
|
|
[level]
|
|
|
|
(let [level-name (name level)]
|
|
|
|
`(defmacro ~(symbol level-name)
|
|
|
|
~(str "Log given arguments at " (str/capitalize level-name) " level.")
|
|
|
|
~'{:arglists '([message & more] [throwable message & more])}
|
2012-08-02 14:59:58 +07:00
|
|
|
[& sigs#]
|
|
|
|
`(log ~~level ~@sigs#))))
|
2012-05-28 15:13:11 +07:00
|
|
|
|
|
|
|
(defmacro ^:private def-loggers
|
|
|
|
[] `(do ~@(map (fn [level] `(def-logger ~level)) ordered-levels)))
|
|
|
|
|
|
|
|
(def-loggers) ; Actually define a logger for each logging level
|
|
|
|
|
2013-01-29 16:49:17 +07:00
|
|
|
(defmacro log-errors
|
|
|
|
[& body] `(try ~@body (catch Exception e# (error e#))))
|
|
|
|
|
|
|
|
(defmacro log-and-rethrow-errors
|
|
|
|
[& body] `(try ~@body (catch Exception e# (error e#) (throw e#))))
|
|
|
|
|
2013-02-06 00:37:12 +07:00
|
|
|
(defmacro logged-future [& body] `(future (log-errors ~@body)))
|
|
|
|
|
2013-01-29 16:49:17 +07:00
|
|
|
(comment (log-errors (/ 0))
|
2013-02-06 00:37:12 +07:00
|
|
|
(log-and-rethrow-errors (/ 0))
|
|
|
|
(logged-future (/ 0)))
|
2013-01-29 16:49:17 +07:00
|
|
|
|
2012-05-28 15:13:11 +07:00
|
|
|
;;;; Dev/tests
|
|
|
|
|
|
|
|
(comment
|
2012-07-03 16:29:11 +07:00
|
|
|
(log :fatal "arg1")
|
|
|
|
(log :debug "arg1" "arg2")
|
|
|
|
(log :debug (Exception.) "arg1" "arg2")
|
|
|
|
(log :debug (Exception.))
|
|
|
|
(log :trace "arg1")
|
|
|
|
|
2012-10-26 16:15:08 +07:00
|
|
|
(log (or nil :info) "Booya")
|
|
|
|
|
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))
|
2012-07-03 16:29:11 +07:00
|
|
|
(time (dotimes [n 10000] (trace "This won't log"))) ; Overhead 5ms/10ms
|
2012-05-28 15:13:11 +07:00
|
|
|
(time (dotimes [n 5] (info "foo" "bar")))
|
|
|
|
(spy (* 6 5 4 3 2 1))
|
2012-09-21 21:02:58 +07:00
|
|
|
(spy :debug :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))
|
|
|
|
|
|
|
|
;; Middleware
|
|
|
|
(info {:name "Robert Paulson" :password "Super secret"})
|
|
|
|
(set-config!
|
|
|
|
[:middleware]
|
|
|
|
[(fn [{:keys [hostname message] :as args}]
|
|
|
|
(cond (= hostname "filtered-host") nil ; Filter
|
|
|
|
(map? message)
|
|
|
|
(if (contains? message :password)
|
|
|
|
(assoc args :message (assoc message :password "*****"))
|
|
|
|
args)
|
|
|
|
:else args))]))
|