mirror of https://github.com/status-im/timbre.git
Merge branch 'dev'
This commit is contained in:
commit
f0d71af005
|
@ -4,8 +4,10 @@
|
||||||
/checkouts
|
/checkouts
|
||||||
/logs
|
/logs
|
||||||
/docs
|
/docs
|
||||||
|
/doc
|
||||||
pom.xml
|
pom.xml
|
||||||
*.jar
|
*.jar
|
||||||
*.class
|
*.class
|
||||||
*.sh
|
*.sh
|
||||||
.lein*
|
.lein*
|
||||||
|
.env
|
||||||
|
|
66
README.md
66
README.md
|
@ -1,14 +1,12 @@
|
||||||
Current [semantic](http://semver.org/) version:
|
Current [semantic](http://semver.org/) version:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
[com.taoensso/timbre "1.6.0"]
|
[com.taoensso/timbre "2.0.0"] ; See commit history for breaking changes since 1.x
|
||||||
```
|
```
|
||||||
|
|
||||||
# Timbre, a (sane) Clojure logging & profiling library
|
# Timbre, a (sane) Clojure logging & profiling library
|
||||||
|
|
||||||
Logging with Java can be maddeningly, unnecessarily hard. Particularly if all you want is something *simple that works out-the-box*. [tools.logging](https://github.com/clojure/tools.logging) helps, but it doesn't save you from the mess of logger dependencies and configuration hell.
|
Logging with Java can be maddeningly, unnecessarily hard. Particularly if all you want is something *simple that works out-the-box*. Timbre is an attempt to make **simple logging simple** and more **complex logging reasonable**. No XML!
|
||||||
|
|
||||||
Timbre is an attempt to make **simple logging simple** and more **complex logging reasonable**. No XML!
|
|
||||||
|
|
||||||
## What's In The Box?
|
## What's In The Box?
|
||||||
* Small, uncomplicated **all-Clojure** library.
|
* Small, uncomplicated **all-Clojure** library.
|
||||||
|
@ -16,8 +14,9 @@ Timbre is an attempt to make **simple logging simple** and more **complex loggin
|
||||||
* **Decent performance** (low overhead).
|
* **Decent performance** (low overhead).
|
||||||
* Flexible **fn-centric appender model** with **middleware**.
|
* Flexible **fn-centric appender model** with **middleware**.
|
||||||
* Sensible built-in appenders including simple **email appender**.
|
* Sensible built-in appenders including simple **email appender**.
|
||||||
* Tunable **flood control** and **asynchronous** logging support.
|
* Tunable **rate limit** and **asynchronous** logging support.
|
||||||
* Robust **namespace filtering**.
|
* Robust **namespace filtering**.
|
||||||
|
* **[tools.logging](https://github.com/clojure/tools.logging)** support (optional).
|
||||||
* Dead-simple, logging-level-aware **logging profiler**.
|
* Dead-simple, logging-level-aware **logging profiler**.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
@ -27,7 +26,7 @@ Timbre is an attempt to make **simple logging simple** and more **complex loggin
|
||||||
Depend on Timbre in your `project.clj`:
|
Depend on Timbre in your `project.clj`:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
[com.taoensso/timbre "1.6.0"]
|
[com.taoensso/timbre "2.0.0"]
|
||||||
```
|
```
|
||||||
|
|
||||||
and `use` the library:
|
and `use` the library:
|
||||||
|
@ -86,7 +85,7 @@ Configuring Timbre couldn't be simpler. Let's check out (some of) the defaults:
|
||||||
:ns-whitelist []
|
:ns-whitelist []
|
||||||
:ns-blacklist []
|
:ns-blacklist []
|
||||||
|
|
||||||
:middleware [] ; As of 1.4.0, see source code
|
:middleware [] ; As of 1.4.0, see source for details
|
||||||
|
|
||||||
:timestamp-pattern "yyyy-MMM-dd HH:mm:ss ZZ"
|
:timestamp-pattern "yyyy-MMM-dd HH:mm:ss ZZ"
|
||||||
:timestamp-locale nil
|
:timestamp-locale nil
|
||||||
|
@ -117,59 +116,40 @@ Filter logging output by namespaces:
|
||||||
(timbre/set-config! [:ns-whitelist] ["some.library.core" "my-app.*"])
|
(timbre/set-config! [:ns-whitelist] ["some.library.core" "my-app.*"])
|
||||||
```
|
```
|
||||||
|
|
||||||
### Email Appender
|
### Built-in Appenders
|
||||||
|
|
||||||
To enable the standard [Postal](https://github.com/drewr/postal)-based email appender, add the Postal dependency to your `project.clj`:
|
#### File Appender
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
[com.draines/postal "1.9.2"]
|
(timbre/set-config! [:appenders :spit :enabled?] true)
|
||||||
|
(timbre/set-config! [:shared-appender-config :spit-filename] "/path/my-file.log")
|
||||||
```
|
```
|
||||||
|
|
||||||
And add the appender to your `ns` declaration:
|
#### Email ([Postal](https://github.com/drewr/postal)) Appender
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(:require [taoensso.timbre.appenders (postal :as postal-appender)])
|
;; [com.draines/postal "1.9.2"] ; Add to project.clj dependencies
|
||||||
```
|
;; (:require [taoensso.timbre.appenders (postal :as postal-appender)]) ; Add to ns
|
||||||
|
|
||||||
Then adjust your Timbre config:
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
(timbre/set-config! [:appenders :postal] postal-appender/postal-appender)
|
(timbre/set-config! [:appenders :postal] postal-appender/postal-appender)
|
||||||
(timbre/set-config! [:shared-appender-config :postal]
|
(timbre/set-config! [:shared-appender-config :postal]
|
||||||
^{:host "mail.isp.net" :user "jsmith" :pass "sekrat!!1"}
|
^{:host "mail.isp.net" :user "jsmith" :pass "sekrat!!1"}
|
||||||
{:from "me@draines.com" :to "foo@example.com"})
|
{:from "me@draines.com" :to "foo@example.com"})
|
||||||
```
|
|
||||||
|
|
||||||
Rate-limit to one email per message per minute:
|
;; Rate limit to one email per message per minute
|
||||||
|
(timbre/set-config! [:appenders :postal :limit-per-msecs] 60000)
|
||||||
|
|
||||||
```clojure
|
;; Make sure emails are sent asynchronously
|
||||||
(timbre/set-config! [:appenders :postal :max-message-per-msecs] 60000)
|
|
||||||
```
|
|
||||||
|
|
||||||
And make sure emails are sent asynchronously:
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
(timbre/set-config! [:appenders :postal :async?] true)
|
(timbre/set-config! [:appenders :postal :async?] true)
|
||||||
```
|
```
|
||||||
|
|
||||||
### IRC Appender
|
#### IRC ([irclj](https://github.com/flatland/irclj)) Appender
|
||||||
|
|
||||||
To enable the standard [irclj](https://github.com/flatland/irclj)-based IRC appender, add the irclj dependency to your `project.clj`:
|
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
[irclj "0.5.0-alpha2"]
|
;; [irclj "0.5.0-alpha2"] ; Add to project.clj dependencies
|
||||||
```
|
;; (:require [taoensso.timbre.appenders (irc :as irc-appender)]) ; Add to ns
|
||||||
|
|
||||||
And add the appender to your `ns` declaration:
|
(timbre/set-config! [:appenders :irc] irc-appender/irc-appender)
|
||||||
|
|
||||||
```clojure
|
|
||||||
(:require [taoensso.timbre.appenders.irc :refer [irc-appender]])
|
|
||||||
```
|
|
||||||
|
|
||||||
Then adjust your Timbre config:
|
|
||||||
|
|
||||||
```clojure
|
|
||||||
(timbre/set-config! [:appenders :irc] irc-appender)
|
|
||||||
(timbre/set-config! [:shared-appender-config :irc]
|
(timbre/set-config! [:shared-appender-config :irc]
|
||||||
{:host "irc.example.org"
|
{:host "irc.example.org"
|
||||||
:port 6667
|
:port 6667
|
||||||
|
@ -189,10 +169,10 @@ Writing a custom appender is dead-easy:
|
||||||
:min-level :debug
|
:min-level :debug
|
||||||
:enabled? true
|
:enabled? true
|
||||||
:async? false
|
:async? false
|
||||||
:max-message-per-msecs nil ; No rate limiting
|
:limit-per-msecs nil ; No rate limit
|
||||||
:fn (fn [{:keys [ap-config level prefix message more] :as args}]
|
:fn (fn [{:keys [ap-config level prefix throwable message] :as args}]
|
||||||
(when-not (:my-production-mode? ap-config)
|
(when-not (:my-production-mode? ap-config)
|
||||||
(apply println prefix "Hello world!" message more)))
|
(println prefix "Hello world!" message)))
|
||||||
```
|
```
|
||||||
|
|
||||||
And because appender fns are just regular Clojure fns, you have *unlimited power*: write to your database, send a message over the network, check some other state (e.g. environment config) before making a choice, etc.
|
And because appender fns are just regular Clojure fns, you have *unlimited power*: write to your database, send a message over the network, check some other state (e.g. environment config) before making a choice, etc.
|
||||||
|
|
13
project.clj
13
project.clj
|
@ -1,15 +1,14 @@
|
||||||
(defproject com.taoensso/timbre "1.6.0"
|
(defproject com.taoensso/timbre "2.0.0"
|
||||||
:description "Clojure logging & profiling library"
|
:description "Clojure logging & profiling library"
|
||||||
:url "https://github.com/ptaoussanis/timbre"
|
:url "https://github.com/ptaoussanis/timbre"
|
||||||
:license {:name "Eclipse Public License"}
|
:license {:name "Eclipse Public License"}
|
||||||
:dependencies [[org.clojure/clojure "1.3.0"]
|
:dependencies [[org.clojure/clojure "1.4.0"]
|
||||||
[org.clojure/tools.macro "0.1.1"]
|
[org.clojure/tools.macro "0.1.1"]
|
||||||
[clj-stacktrace "0.2.5"]]
|
[clj-stacktrace "0.2.5"]]
|
||||||
:profiles {:1.3 {:dependencies [[org.clojure/clojure "1.3.0"]]}
|
:profiles {:1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]}
|
||||||
:1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]}
|
:1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]}
|
||||||
:1.5 {:dependencies [[org.clojure/clojure "1.5.0-alpha3"]]}
|
|
||||||
:dev {:dependencies []}
|
:dev {:dependencies []}
|
||||||
:test {:dependencies []}}
|
:test {:dependencies []}}
|
||||||
:aliases {"test-all" ["with-profile" "test,1.3:test,1.4:test,1.5" "test"]}
|
:aliases {"test-all" ["with-profile" "test,1.4:test,1.5" "test"]}
|
||||||
:min-lein-version "2.0.0"
|
:min-lein-version "2.0.0"
|
||||||
:warn-on-reflection true)
|
:warn-on-reflection true)
|
|
@ -16,12 +16,12 @@
|
||||||
(print (str (str/join \space xs) \newline))
|
(print (str (str/join \space xs) \newline))
|
||||||
(flush))
|
(flush))
|
||||||
|
|
||||||
(defn color-str [color-key & xs]
|
(defn color-str [color & xs]
|
||||||
(let [ansi-color #(str "\u001b[" (case % :reset "0" :black "30" :red "31"
|
(let [ansi-color #(str "\u001b[" (case % :reset "0" :black "30" :red "31"
|
||||||
:green "32" :yellow "33" :blue "34"
|
:green "32" :yellow "33" :blue "34"
|
||||||
:purple "35" :cyan "36" :white "37"
|
:purple "35" :cyan "36" :white "37"
|
||||||
"0") "m")]
|
"0") "m")]
|
||||||
(str (ansi-color color-key) (apply str xs) (ansi-color :reset))))
|
(str (ansi-color color) (apply str xs) (ansi-color :reset))))
|
||||||
|
|
||||||
(def red (partial color-str :red))
|
(def red (partial color-str :red))
|
||||||
(def green (partial color-str :green))
|
(def green (partial color-str :green))
|
||||||
|
@ -38,6 +38,10 @@
|
||||||
"Evaluates body with *err* bound to *out*."
|
"Evaluates body with *err* bound to *out*."
|
||||||
[& body] `(binding [*err* *out*] ~@body))
|
[& body] `(binding [*err* *out*] ~@body))
|
||||||
|
|
||||||
|
(defn stacktrace [throwable & [separator]]
|
||||||
|
(when throwable
|
||||||
|
(str (when-let [s separator] s) (stacktrace/pst-str throwable))))
|
||||||
|
|
||||||
;;;; Default configuration and appenders
|
;;;; Default configuration and appenders
|
||||||
|
|
||||||
(utils/defonce* config
|
(utils/defonce* config
|
||||||
|
@ -45,13 +49,15 @@
|
||||||
|
|
||||||
APPENDERS
|
APPENDERS
|
||||||
An appender is a map with keys:
|
An appender is a map with keys:
|
||||||
:doc, :min-level, :enabled?, :async?, :max-message-per-msecs, :fn
|
:doc, :min-level, :enabled?, :async?, :limit-per-msecs, :fn
|
||||||
|
|
||||||
An appender's fn takes a single map argument with keys:
|
An appender's fn takes a single map argument with keys:
|
||||||
:level, :message, :more ; From all logging macros (`info`, etc.)
|
:level, :throwable
|
||||||
:profiling-stats ; From `profile` macro
|
:message, ; Stringified logging macro args, or nil
|
||||||
:ap-config ; `shared-appender-config`
|
:args, ; Raw logging macro args (`info`, etc.)
|
||||||
:prefix ; Output of `prefix-fn`
|
:ap-config ; `shared-appender-config`
|
||||||
|
:prefix ; Output of `prefix-fn`
|
||||||
|
:profiling-stats ; From `profile` macro
|
||||||
And also: :instant, :timestamp, :hostname, :ns, :error?
|
And also: :instant, :timestamp, :hostname, :ns, :error?
|
||||||
|
|
||||||
MIDDLEWARE
|
MIDDLEWARE
|
||||||
|
@ -88,21 +94,20 @@
|
||||||
:appenders
|
:appenders
|
||||||
{:standard-out
|
{:standard-out
|
||||||
{:doc "Prints to *out* or *err* as appropriate. Enabled by default."
|
{:doc "Prints to *out* or *err* as appropriate. Enabled by default."
|
||||||
:min-level nil :enabled? true :async? false
|
:min-level nil :enabled? true :async? false :limit-per-msecs nil
|
||||||
:max-message-per-msecs nil
|
:fn (fn [{:keys [error? prefix throwable message]}]
|
||||||
:fn (fn [{:keys [error? prefix message more]}]
|
|
||||||
(binding [*out* (if error? *err* *out*)]
|
(binding [*out* (if error? *err* *out*)]
|
||||||
(apply str-println prefix "-" message more)))}
|
(str-println prefix "-" message (stacktrace throwable))))}
|
||||||
|
|
||||||
:spit
|
:spit
|
||||||
{:doc "Spits to (:spit-filename :shared-appender-config) file."
|
{:doc "Spits to (:spit-filename :shared-appender-config) file."
|
||||||
:min-level nil :enabled? false :async? false
|
:min-level nil :enabled? false :async? false :limit-per-msecs nil
|
||||||
:max-message-per-msecs nil
|
:fn (fn [{:keys [ap-config prefix throwable message]}]
|
||||||
:fn (fn [{:keys [ap-config prefix message more]}]
|
|
||||||
(when-let [filename (:spit-filename ap-config)]
|
(when-let [filename (:spit-filename ap-config)]
|
||||||
(try (spit filename
|
(try (spit filename
|
||||||
(with-out-str (apply str-println prefix "-"
|
(with-out-str
|
||||||
message more))
|
(str-println prefix "-" message
|
||||||
|
(stacktrace throwable)))
|
||||||
:append true)
|
:append true)
|
||||||
(catch java.io.IOException _))))}}}))
|
(catch java.io.IOException _))))}}}))
|
||||||
|
|
||||||
|
@ -132,53 +137,49 @@
|
||||||
(defn- wrap-appender-fn
|
(defn- wrap-appender-fn
|
||||||
"Wraps compile-time appender fn with additional runtime capabilities
|
"Wraps compile-time appender fn with additional runtime capabilities
|
||||||
controlled by compile-time config."
|
controlled by compile-time config."
|
||||||
[{apfn :fn :keys [async? max-message-per-msecs prefix-fn] :as appender}]
|
[{apfn :fn :keys [async? limit-per-msecs prefix-fn] :as appender}]
|
||||||
(->> ; Wrapping applies capabilities bottom-to-top
|
(let [limit-per-msecs (or (:max-message-per-msecs appender)
|
||||||
apfn
|
limit-per-msecs)] ; Backwards-compatibility
|
||||||
|
(->> ; Wrapping applies per appender, bottom-to-top
|
||||||
|
apfn
|
||||||
|
|
||||||
;; Wrap for per-appender prefix-fn support
|
;; Per-appender prefix-fn support (cmp. default prefix-fn)
|
||||||
((fn [apfn]
|
;; TODO Currently undocumented, candidate for removal
|
||||||
(if-not prefix-fn
|
((fn [apfn]
|
||||||
apfn
|
(if-not prefix-fn
|
||||||
(fn [apfn-args]
|
apfn
|
||||||
(apfn (assoc apfn-args
|
(fn [apfn-args]
|
||||||
:prefix (prefix-fn apfn-args)))))))
|
(apfn (assoc apfn-args
|
||||||
|
:prefix (prefix-fn apfn-args)))))))
|
||||||
|
|
||||||
;; Wrap for runtime flood-safety support
|
;; Rate limit support
|
||||||
((fn [apfn]
|
((fn [apfn]
|
||||||
(if-not max-message-per-msecs
|
(if-not limit-per-msecs
|
||||||
apfn
|
apfn
|
||||||
(let [;; {:hash last-appended-time-msecs ...}
|
(let [timers (atom {})] ; {:hash last-appended-time-msecs ...}
|
||||||
flood-timers (atom {})]
|
(fn [{ns :ns [x1 & _] :args :as apfn-args}]
|
||||||
|
(let [now (System/currentTimeMillis)
|
||||||
|
hash (str ns "/" x1)
|
||||||
|
limit? (fn [last-msecs]
|
||||||
|
(and last-msecs (<= (- now last-msecs)
|
||||||
|
limit-per-msecs)))]
|
||||||
|
|
||||||
(fn [{:keys [ns message] :as apfn-args}]
|
(when-not (limit? (@timers hash))
|
||||||
(let [now (System/currentTimeMillis)
|
(apfn apfn-args)
|
||||||
hash (str ns "/" message)
|
(swap! timers assoc hash now))
|
||||||
allow? (fn [last-msecs]
|
|
||||||
(or (not last-msecs)
|
|
||||||
(> (- now last-msecs) max-message-per-msecs)))]
|
|
||||||
|
|
||||||
(when (allow? (@flood-timers hash))
|
(when (< (rand) 0.001) ; Occasionally garbage collect
|
||||||
(apfn apfn-args)
|
(when-let [expired-timers (->> (keys @timers)
|
||||||
(swap! flood-timers assoc hash now))
|
(remove #(limit? (@timers %)))
|
||||||
|
(seq))]
|
||||||
|
(apply swap! timers dissoc expired-timers)))))))))
|
||||||
|
|
||||||
;; Occassionally garbage-collect all expired timers. Note
|
;; Async (agent) support
|
||||||
;; that due to snapshotting, garbage-collection can cause
|
((fn [apfn]
|
||||||
;; some appenders to re-append prematurely.
|
(if-not async?
|
||||||
(when (< (rand) 0.001)
|
apfn
|
||||||
(let [timers-snapshot @flood-timers
|
(let [agent (agent nil :error-mode :continue)]
|
||||||
expired-timers
|
(fn [apfn-args] (send-off agent (fn [_] (apfn apfn-args)))))))))))
|
||||||
(->> (keys timers-snapshot)
|
|
||||||
(filter #(allow? (timers-snapshot %))))]
|
|
||||||
(when (seq expired-timers)
|
|
||||||
(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
|
(defn- make-timestamp-fn
|
||||||
"Returns a unary fn that formats instants using given pattern string and an
|
"Returns a unary fn that formats instants using given pattern string and an
|
||||||
|
@ -202,10 +203,10 @@
|
||||||
(incl. middleware) controlled by compile-time config. Like `wrap-appender-fn`
|
(incl. middleware) controlled by compile-time config. Like `wrap-appender-fn`
|
||||||
but operates on the entire juxt at once."
|
but operates on the entire juxt at once."
|
||||||
[juxtfn]
|
[juxtfn]
|
||||||
(->> ; Wrapping applies capabilities bottom-to-top
|
(->> ; Wrapping applies per juxt, bottom-to-top
|
||||||
juxtfn
|
juxtfn
|
||||||
|
|
||||||
;; Wrap to add middleware transforms/filters
|
;; Middleware transforms/filters support
|
||||||
((fn [juxtfn]
|
((fn [juxtfn]
|
||||||
(if-let [middleware (seq (:middleware @config))]
|
(if-let [middleware (seq (:middleware @config))]
|
||||||
(let [composed-middleware
|
(let [composed-middleware
|
||||||
|
@ -216,7 +217,7 @@
|
||||||
(juxtfn juxtfn-args))))
|
(juxtfn juxtfn-args))))
|
||||||
juxtfn)))
|
juxtfn)))
|
||||||
|
|
||||||
;; Wrap to add compile-time stuff to runtime appender arguments
|
;; Add compile-time stuff to runtime appender args
|
||||||
((fn [juxtfn]
|
((fn [juxtfn]
|
||||||
(let [{ap-config :shared-appender-config
|
(let [{ap-config :shared-appender-config
|
||||||
:keys [timestamp-pattern timestamp-locale prefix-fn]} @config
|
:keys [timestamp-pattern timestamp-locale prefix-fn]} @config
|
||||||
|
@ -233,14 +234,13 @@
|
||||||
;;; Appender-fns
|
;;; Appender-fns
|
||||||
|
|
||||||
(def appenders-juxt-cache
|
(def appenders-juxt-cache
|
||||||
"Per-level, combined relevant appender-fns to allow for fast runtime
|
"Per-level, combined level-relevant appender-fns to allow for fast runtime
|
||||||
appender-fn dispatch:
|
appender-fn dispatch:
|
||||||
{:level (wrapped-juxt wrapped-appender-fn wrapped-appender-fn ...) or nil
|
{:level (wrapped-juxt wrapped-appender-fn wrapped-appender-fn ...) or nil
|
||||||
...}"
|
...}"
|
||||||
(atom {}))
|
(atom {}))
|
||||||
|
|
||||||
(defn- relevant-appenders
|
(defn- relevant-appenders [level]
|
||||||
[level]
|
|
||||||
(->> (:appenders @config)
|
(->> (:appenders @config)
|
||||||
(filter #(let [{:keys [enabled? min-level]} (val %)]
|
(filter #(let [{:keys [enabled? min-level]} (val %)]
|
||||||
(and enabled? (>= (compare-levels level min-level) 0))))
|
(and enabled? (>= (compare-levels level min-level) 0))))
|
||||||
|
@ -249,8 +249,7 @@
|
||||||
(comment (relevant-appenders :debug)
|
(comment (relevant-appenders :debug)
|
||||||
(relevant-appenders :trace))
|
(relevant-appenders :trace))
|
||||||
|
|
||||||
(defn- cache-appenders-juxt!
|
(defn- cache-appenders-juxt! []
|
||||||
[]
|
|
||||||
(->>
|
(->>
|
||||||
(zipmap
|
(zipmap
|
||||||
ordered-levels
|
ordered-levels
|
||||||
|
@ -269,13 +268,11 @@
|
||||||
(def ns-filter-cache "@ns-filter-cache => (fn relevant-ns? [ns] ...)"
|
(def ns-filter-cache "@ns-filter-cache => (fn relevant-ns? [ns] ...)"
|
||||||
(atom (constantly true)))
|
(atom (constantly true)))
|
||||||
|
|
||||||
(defn- ns-match?
|
(defn- ns-match? [ns match]
|
||||||
[ns match]
|
|
||||||
(-> (str "^" (-> (str match) (.replace "." "\\.") (.replace "*" "(.*)")) "$")
|
(-> (str "^" (-> (str match) (.replace "." "\\.") (.replace "*" "(.*)")) "$")
|
||||||
re-pattern (re-find (str ns)) boolean))
|
re-pattern (re-find (str ns)) boolean))
|
||||||
|
|
||||||
(defn- cache-ns-filter!
|
(defn- cache-ns-filter! []
|
||||||
[]
|
|
||||||
(->>
|
(->>
|
||||||
(let [{:keys [ns-whitelist ns-blacklist]} @config]
|
(let [{:keys [ns-whitelist ns-blacklist]} @config]
|
||||||
(memoize
|
(memoize
|
||||||
|
@ -304,41 +301,63 @@
|
||||||
(defn logging-enabled?
|
(defn logging-enabled?
|
||||||
"Returns true when current logging level is sufficient and current namespace
|
"Returns true when current logging level is sufficient and current namespace
|
||||||
is unfiltered."
|
is unfiltered."
|
||||||
[level]
|
[level] (and (sufficient-level? level) (@ns-filter-cache *ns*)))
|
||||||
(and (sufficient-level? level) (@ns-filter-cache *ns*)))
|
|
||||||
|
|
||||||
(defmacro log*
|
(defmacro log*
|
||||||
"Prepares given arguments for, and then dispatches to all relevant
|
"Implementation detail - subject to change..
|
||||||
appender-fns."
|
Prepares given arguments for, and then dispatches to all level-relevant
|
||||||
[level base-args & sigs]
|
appender-fns. "
|
||||||
`(when-let [juxt-fn# (@appenders-juxt-cache ~level)] ; Any relevant appenders?
|
|
||||||
(let [[x1# & xs#] (list ~@sigs)
|
|
||||||
|
|
||||||
has-throwable?# (instance? Throwable x1#)
|
;; For tools.logging.impl/Logger support
|
||||||
appender-args#
|
([base-appender-args level log-vargs ns throwable message juxt-fn]
|
||||||
(conj
|
`(when-let [juxt-fn# (or ~juxt-fn (@appenders-juxt-cache ~level))]
|
||||||
~base-args ; Allow flexibility to inject exta args
|
(juxt-fn#
|
||||||
{:level ~level
|
(conj (or ~base-appender-args {})
|
||||||
:error? (error-level? ~level)
|
{:instant (Date.)
|
||||||
:instant (Date.)
|
:ns ~ns
|
||||||
:ns ~(str *ns*)
|
:level ~level
|
||||||
:message (if has-throwable?# (or (first xs#) x1#) x1#)
|
:error? (error-level? ~level)
|
||||||
:more (if has-throwable?#
|
:args ~log-vargs ; No native tools.logging support
|
||||||
(conj (vec (rest xs#))
|
:throwable ~throwable
|
||||||
(str "\nStacktrace:\n"
|
:message ~message}))
|
||||||
(stacktrace/pst-str x1#)))
|
true))
|
||||||
(vec xs#))})]
|
|
||||||
|
|
||||||
(juxt-fn# appender-args#)
|
([base-appender-args level log-args message-fn]
|
||||||
nil)))
|
`(when-let [juxt-fn# (@appenders-juxt-cache ~level)]
|
||||||
|
(let [[x1# & xn# :as xs#] (vector ~@log-args)
|
||||||
|
has-throwable?# (and xn# (instance? Throwable x1#))
|
||||||
|
log-vargs# (vec (if has-throwable?# xn# xs#))]
|
||||||
|
(log* ~base-appender-args
|
||||||
|
~level
|
||||||
|
log-vargs#
|
||||||
|
~(str *ns*)
|
||||||
|
(when has-throwable?# x1#)
|
||||||
|
(when-let [mf# ~message-fn] (apply mf# log-vargs#))
|
||||||
|
juxt-fn#)))))
|
||||||
|
|
||||||
(defmacro log
|
(defmacro log
|
||||||
"When logging is enabled, actually logs given arguments with relevant
|
"When logging is enabled, actually logs given arguments with level-relevant
|
||||||
appender-fns. Generic form of standard level-loggers (trace, info, etc.)."
|
appender-fns."
|
||||||
{:arglists '([level message & more] [level throwable message & more])}
|
{:arglists '([level & args] [level throwable & args])}
|
||||||
[level & sigs]
|
[level & sigs]
|
||||||
`(when (logging-enabled? ~level)
|
`(when (logging-enabled? ~level)
|
||||||
(log* ~level {} ~@sigs)))
|
(log* {} ~level ~sigs nil)))
|
||||||
|
|
||||||
|
(defmacro logp
|
||||||
|
"When logging is enabled, actually logs given arguments with level-relevant
|
||||||
|
appender-fns using print-style :message."
|
||||||
|
{:arglists '([level & message] [level throwable & message])}
|
||||||
|
[level & sigs]
|
||||||
|
`(when (logging-enabled? ~level)
|
||||||
|
(log* {} ~level ~sigs print-str)))
|
||||||
|
|
||||||
|
(defmacro logf
|
||||||
|
"When logging is enabled, actually logs given arguments with level-relevant
|
||||||
|
appender-fns using format-style :message."
|
||||||
|
{:arglists '([level fmt & fmt-args] [level throwable fmt & fmt-args])}
|
||||||
|
[level & sigs]
|
||||||
|
`(when (logging-enabled? ~level)
|
||||||
|
(log* {} ~level ~sigs format)))
|
||||||
|
|
||||||
(defmacro spy
|
(defmacro spy
|
||||||
"Evaluates named expression and logs its result. Always returns the result.
|
"Evaluates named expression and logs its result. Always returns the result.
|
||||||
|
@ -347,36 +366,41 @@
|
||||||
([level expr] `(spy ~level '~expr ~expr))
|
([level expr] `(spy ~level '~expr ~expr))
|
||||||
([level name expr]
|
([level name expr]
|
||||||
`(try
|
`(try
|
||||||
(let [result# ~expr] (log ~level ~name result#) result#)
|
(let [result# ~expr] (logp ~level ~name result#) result#)
|
||||||
(catch Exception e#
|
(catch Exception e#
|
||||||
(log ~level '~expr (str "\n" (stacktrace/pst-str e#)))
|
(logp ~level '~expr (str "\n" (stacktrace/pst-str e#)))
|
||||||
(throw e#)))))
|
(throw e#)))))
|
||||||
|
|
||||||
(defmacro s ; Alias
|
(defmacro s ; Alias
|
||||||
{:arglists '([expr] [level expr] [level name expr])}
|
{:arglists '([expr] [level expr] [level name expr])}
|
||||||
[& args] `(spy ~@args))
|
[& args] `(spy ~@args))
|
||||||
|
|
||||||
(defmacro ^:private def-logger
|
(defmacro ^:private def-logger [level]
|
||||||
[level]
|
|
||||||
(let [level-name (name level)]
|
(let [level-name (name level)]
|
||||||
`(defmacro ~(symbol level-name)
|
`(do
|
||||||
~(str "Log given arguments at " (str/capitalize level-name) " level.")
|
(defmacro ~(symbol level-name)
|
||||||
~'{:arglists '([message & more] [throwable message & more])}
|
~(str "Log given arguments at " level " level using print-style args.")
|
||||||
[& sigs#]
|
~'{:arglists '([& message] [throwable & message])}
|
||||||
`(log ~~level ~@sigs#))))
|
[& sigs#] `(logp ~~level ~@sigs#))
|
||||||
|
|
||||||
(defmacro ^:private def-loggers
|
(defmacro ~(symbol (str level-name "f"))
|
||||||
[] `(do ~@(map (fn [level] `(def-logger ~level)) ordered-levels)))
|
~(str "Log given arguments at " level " level using format-style args.")
|
||||||
|
~'{:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])}
|
||||||
|
[& sigs#] `(logf ~~level ~@sigs#)))))
|
||||||
|
|
||||||
|
(defmacro ^:private def-loggers []
|
||||||
|
`(do ~@(map (fn [level] `(def-logger ~level)) ordered-levels)))
|
||||||
|
|
||||||
(def-loggers) ; Actually define a logger for each logging level
|
(def-loggers) ; Actually define a logger for each logging level
|
||||||
|
|
||||||
(defmacro log-errors
|
(defmacro log-errors [& body]
|
||||||
[& body] `(try ~@body (catch Exception e# (error e#))))
|
`(try ~@body (catch Exception e# (error e#))))
|
||||||
|
|
||||||
(defmacro log-and-rethrow-errors
|
(defmacro log-and-rethrow-errors [& body]
|
||||||
[& body] `(try ~@body (catch Exception e# (error e#) (throw e#))))
|
`(try ~@body (catch Exception e# (error e#) (throw e#))))
|
||||||
|
|
||||||
(defmacro logged-future [& body] `(future (log-errors ~@body)))
|
(defmacro logged-future [& body]
|
||||||
|
`(future (log-errors ~@body)))
|
||||||
|
|
||||||
(comment (log-errors (/ 0))
|
(comment (log-errors (/ 0))
|
||||||
(log-and-rethrow-errors (/ 0))
|
(log-and-rethrow-errors (/ 0))
|
||||||
|
@ -385,13 +409,15 @@
|
||||||
;;;; Dev/tests
|
;;;; Dev/tests
|
||||||
|
|
||||||
(comment
|
(comment
|
||||||
(log :fatal "arg1")
|
(info)
|
||||||
(log :debug "arg1" "arg2")
|
(info "a")
|
||||||
(log :debug (Exception.) "arg1" "arg2")
|
(info "a" "b" "c")
|
||||||
(log :debug (Exception.))
|
(info "a" (Exception. "b") "c")
|
||||||
(log :trace "arg1")
|
(info (Exception. "a") "b" "c")
|
||||||
|
(logp (or nil :info) "Booya")
|
||||||
|
|
||||||
(log (or nil :info) "Booya")
|
(info "a%s" "b")
|
||||||
|
(infof "a%s" "b")
|
||||||
|
|
||||||
(set-config! [:ns-blacklist] [])
|
(set-config! [:ns-blacklist] [])
|
||||||
(set-config! [:ns-blacklist] ["taoensso.timbre*"])
|
(set-config! [:ns-blacklist] ["taoensso.timbre*"])
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
(ns taoensso.timbre.appenders.irc
|
(ns taoensso.timbre.appenders.irc
|
||||||
"IRC appender for irclj.
|
"IRC appender. Depends on https://github.com/flatland/irclj."
|
||||||
Ref: https://github.com/flatland/irclj."
|
|
||||||
{:author "Emlyn Corrin"}
|
{:author "Emlyn Corrin"}
|
||||||
(:require [clojure.string :as str]
|
(:require [clojure.string :as str]
|
||||||
[irclj.core :as irclj]
|
[irclj.core :as irclj]
|
||||||
|
@ -21,26 +20,27 @@
|
||||||
(defn ensure-conn [conf]
|
(defn ensure-conn [conf]
|
||||||
(swap! conn #(or % (connect conf))))
|
(swap! conn #(or % (connect conf))))
|
||||||
|
|
||||||
(defn send-message [{:keys [message prefix chan] :as config}]
|
(defn send-message [{:keys [prefix throwable message chan] :as config}]
|
||||||
(let [conn (ensure-conn config)
|
(let [conn (ensure-conn config)
|
||||||
lines (str/split message #"\n")]
|
lines (-> (str message (timbre/stacktrace throwable "\n"))
|
||||||
|
(str/split #"\n"))]
|
||||||
(irclj/message conn chan prefix (first lines))
|
(irclj/message conn chan prefix (first lines))
|
||||||
(doseq [line (rest lines)]
|
(doseq [line (rest lines)]
|
||||||
(irclj/message conn chan ">" line))))
|
(irclj/message conn chan ">" line))))
|
||||||
|
|
||||||
(defn appender-fn [{:keys [ap-config prefix message]}]
|
(defn appender-fn [{:keys [ap-config prefix throwable message]}]
|
||||||
(when-let [irc-config (:irc ap-config)]
|
(when-let [irc-config (:irc ap-config)]
|
||||||
(send-message
|
(send-message
|
||||||
(assoc irc-config
|
(assoc irc-config
|
||||||
:prefix prefix
|
:prefix prefix
|
||||||
:message message))))
|
:throwable throwable
|
||||||
|
:message message))))
|
||||||
|
|
||||||
(def irc-appender
|
(def irc-appender
|
||||||
{:doc (str "Sends IRC messages using irclj.\n"
|
{:doc (str "Sends IRC messages using irclj.\n"
|
||||||
"Needs :irc config map in :shared-appender-config, e.g.:
|
"Needs :irc config map in :shared-appender-config, e.g.:
|
||||||
{:host \"irc.example.org\" :port 6667 :nick \"logger\"
|
{:host \"irc.example.org\" :port 6667 :nick \"logger\"
|
||||||
:name \"My Logger\" :chan \"#logs\"")
|
:name \"My Logger\" :chan \"#logs\"")
|
||||||
:min-level :info :enabled? true :async? false
|
:min-level :info :enabled? true :async? false :limit-per-msecs nil
|
||||||
:max-message-per-msecs nil ; no rate limit by default
|
|
||||||
:prefix-fn (fn [{:keys [level]}] (-> level name str/upper-case))
|
:prefix-fn (fn [{:keys [level]}] (-> level name str/upper-case))
|
||||||
:fn appender-fn})
|
:fn appender-fn})
|
|
@ -1,6 +1,5 @@
|
||||||
(ns taoensso.timbre.appenders.postal
|
(ns taoensso.timbre.appenders.postal
|
||||||
"Email appender for com.draines/postal.
|
"Email appender. Depends on https://github.com/drewr/postal."
|
||||||
Ref: https://github.com/drewr/postal."
|
|
||||||
{:author "Peter Taoussanis"}
|
{:author "Peter Taoussanis"}
|
||||||
(:require [clojure.string :as str]
|
(:require [clojure.string :as str]
|
||||||
[postal.core :as postal]
|
[postal.core :as postal]
|
||||||
|
@ -12,11 +11,12 @@
|
||||||
^{:host \"mail.isp.net\" :user \"jsmith\" :pass \"sekrat!!1\"}
|
^{:host \"mail.isp.net\" :user \"jsmith\" :pass \"sekrat!!1\"}
|
||||||
{:from \"Bob's logger <me@draines.com>\" :to \"foo@example.com\"}")
|
{:from \"Bob's logger <me@draines.com>\" :to \"foo@example.com\"}")
|
||||||
:min-level :error :enabled? true :async? true
|
:min-level :error :enabled? true :async? true
|
||||||
:max-message-per-msecs (* 1000 60 10) ; 1 email per message per 10 mins
|
:limit-per-msecs (* 1000 60 10) ; 1 subject / 10 mins
|
||||||
:fn (fn [{:keys [ap-config prefix message more]}]
|
:fn (fn [{:keys [ap-config prefix throwable args]}]
|
||||||
(when-let [postal-config (:postal ap-config)]
|
(when-let [postal-config (:postal ap-config)]
|
||||||
(postal/send-message
|
(let [[subject & body] args]
|
||||||
(assoc postal-config
|
(postal/send-message
|
||||||
:subject (str prefix " - " message)
|
(assoc postal-config
|
||||||
:body (if (seq more) (str/join " " more)
|
:subject (str prefix " - " subject)
|
||||||
"<no additional arguments>")))))})
|
:body (str (str/join \space body)
|
||||||
|
(timbre/stacktrace throwable "\n")))))))})
|
|
@ -28,10 +28,11 @@
|
||||||
(let [name (utils/fq-keyword name)]
|
(let [name (utils/fq-keyword name)]
|
||||||
`(let [{result# :result stats# :stats} (with-fdata ~level ~@body)]
|
`(let [{result# :result stats# :stats} (with-fdata ~level ~@body)]
|
||||||
(when stats#
|
(when stats#
|
||||||
(timbre/log* ~level
|
(timbre/log* {:frequency-stats stats#}
|
||||||
{:frequency-stats stats#}
|
~level
|
||||||
(str "Frequencies " ~name)
|
[(str "Frequencies " ~name)
|
||||||
(str "\n" (format-fdata stats#))))
|
(str "\n" (format-fdata stats#))]
|
||||||
|
print-str))
|
||||||
result#)))
|
result#)))
|
||||||
|
|
||||||
(defmacro sampling-log-frequencies
|
(defmacro sampling-log-frequencies
|
||||||
|
|
|
@ -29,10 +29,11 @@
|
||||||
(let [name (utils/fq-keyword name)]
|
(let [name (utils/fq-keyword name)]
|
||||||
`(let [{result# :result stats# :stats} (with-pdata ~level ~@body)]
|
`(let [{result# :result stats# :stats} (with-pdata ~level ~@body)]
|
||||||
(when stats#
|
(when stats#
|
||||||
(timbre/log* ~level
|
(timbre/log* {:profile-stats stats#}
|
||||||
{:profile-stats stats#}
|
~level
|
||||||
(str "Profiling " ~name)
|
[(str "Profiling " ~name)
|
||||||
(str "\n" (format-pdata stats#))))
|
(str "\n" (format-pdata stats#))]
|
||||||
|
print-str))
|
||||||
result#)))
|
result#)))
|
||||||
|
|
||||||
(defmacro sampling-profile
|
(defmacro sampling-profile
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
(ns taoensso.timbre.tools.logging
|
||||||
|
"clojure.tools.logging.impl/Logger implementation"
|
||||||
|
(:require [taoensso.timbre :as timbre]))
|
||||||
|
|
||||||
|
(deftype Logger [logger-ns]
|
||||||
|
clojure.tools.logging.impl/Logger
|
||||||
|
(enabled? [_ level] (timbre/logging-enabled? level))
|
||||||
|
(write! [_ level throwable message]
|
||||||
|
;; tools.logging message may be a string (for `logp`/`logf` calls) or raw
|
||||||
|
;; argument (for `log` calls). Note that without an :args equivalent for
|
||||||
|
;; `write!`, the best we can do is `[message]`. This inconsistency means
|
||||||
|
;; that :args consumers (like the rate limiter and Postal appender) will
|
||||||
|
;; necessarily behave differently under tools.logging.
|
||||||
|
(timbre/log* {} level [message] logger-ns throwable
|
||||||
|
(when (string? message) message) nil)))
|
||||||
|
|
||||||
|
(deftype LoggerFactory []
|
||||||
|
clojure.tools.logging.impl/LoggerFactory
|
||||||
|
(name [_] "Timbre")
|
||||||
|
(get-logger [_ logger-ns] (->Logger logger-ns)))
|
||||||
|
|
||||||
|
(defn use-timbre []
|
||||||
|
(alter-var-root clojure.tools.logging/*logger-factory*
|
||||||
|
(constantly (->LoggerFactory))))
|
Loading…
Reference in New Issue