Skip to content
Browse files

Merge branch 'dev'

  • Loading branch information...
2 parents 6c0ac29 + 4343492 commit f0d71af005defe54d93574f434d7292b01024688 @ptaoussanis committed
View
2 .gitignore
@@ -4,8 +4,10 @@
/checkouts
/logs
/docs
+/doc
pom.xml
*.jar
*.class
*.sh
.lein*
+.env
View
66 README.md
@@ -1,14 +1,12 @@
Current [semantic](http://semver.org/) version:
```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
-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.
-
-Timbre is an attempt to make **simple logging simple** and more **complex logging reasonable**. No XML!
+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!
## What's In The Box?
* 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).
* Flexible **fn-centric appender model** with **middleware**.
* 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**.
+ * **[tools.logging](https://github.com/clojure/tools.logging)** support (optional).
* Dead-simple, logging-level-aware **logging profiler**.
## 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`:
```clojure
-[com.taoensso/timbre "1.6.0"]
+[com.taoensso/timbre "2.0.0"]
```
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-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-locale nil
@@ -117,59 +116,40 @@ Filter logging output by namespaces:
(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
-[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
-(:require [taoensso.timbre.appenders (postal :as postal-appender)])
-```
-
-Then adjust your Timbre config:
+;; [com.draines/postal "1.9.2"] ; Add to project.clj dependencies
+;; (:require [taoensso.timbre.appenders (postal :as postal-appender)]) ; Add to ns
-```clojure
(timbre/set-config! [:appenders :postal] postal-appender/postal-appender)
(timbre/set-config! [:shared-appender-config :postal]
^{:host "mail.isp.net" :user "jsmith" :pass "sekrat!!1"}
{:from "me@draines.com" :to "foo@example.com"})
-```
-
-Rate-limit to one email per message per minute:
-
-```clojure
-(timbre/set-config! [:appenders :postal :max-message-per-msecs] 60000)
-```
-And make sure emails are sent asynchronously:
+;; 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 :async?] true)
```
-### IRC Appender
-
-To enable the standard [irclj](https://github.com/flatland/irclj)-based IRC appender, add the irclj dependency to your `project.clj`:
+#### IRC ([irclj](https://github.com/flatland/irclj)) Appender
```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:
-
-```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! [:appenders :irc] irc-appender/irc-appender)
(timbre/set-config! [:shared-appender-config :irc]
{:host "irc.example.org"
:port 6667
@@ -189,10 +169,10 @@ Writing a custom appender is dead-easy:
:min-level :debug
:enabled? true
:async? false
- :max-message-per-msecs nil ; No rate limiting
- :fn (fn [{:keys [ap-config level prefix message more] :as args}]
+ :limit-per-msecs nil ; No rate limit
+ :fn (fn [{:keys [ap-config level prefix throwable message] :as args}]
(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.
View
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"
:url "https://github.com/ptaoussanis/timbre"
: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"]
[clj-stacktrace "0.2.5"]]
- :profiles {:1.3 {:dependencies [[org.clojure/clojure "1.3.0"]]}
- :1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]}
- :1.5 {:dependencies [[org.clojure/clojure "1.5.0-alpha3"]]}
+ :profiles {:1.4 {:dependencies [[org.clojure/clojure "1.4.0"]]}
+ :1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]}
:dev {: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"
- :warn-on-reflection true)
+ :warn-on-reflection true)
View
280 src/taoensso/timbre.clj
@@ -16,12 +16,12 @@
(print (str (str/join \space xs) \newline))
(flush))
-(defn color-str [color-key & xs]
+(defn color-str [color & 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))))
+ (str (ansi-color color) (apply str xs) (ansi-color :reset))))
(def red (partial color-str :red))
(def green (partial color-str :green))
@@ -38,6 +38,10 @@
"Evaluates body with *err* bound to *out*."
[& 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
(utils/defonce* config
@@ -45,13 +49,15 @@
APPENDERS
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:
- :level, :message, :more ; From all logging macros (`info`, etc.)
- :profiling-stats ; From `profile` macro
- :ap-config ; `shared-appender-config`
- :prefix ; Output of `prefix-fn`
+ :level, :throwable
+ :message, ; Stringified logging macro args, or nil
+ :args, ; Raw logging macro args (`info`, etc.)
+ :ap-config ; `shared-appender-config`
+ :prefix ; Output of `prefix-fn`
+ :profiling-stats ; From `profile` macro
And also: :instant, :timestamp, :hostname, :ns, :error?
MIDDLEWARE
@@ -88,21 +94,20 @@
:appenders
{:standard-out
{:doc "Prints to *out* or *err* as appropriate. Enabled by default."
- :min-level nil :enabled? true :async? false
- :max-message-per-msecs nil
- :fn (fn [{:keys [error? prefix message more]}]
+ :min-level nil :enabled? true :async? false :limit-per-msecs nil
+ :fn (fn [{:keys [error? prefix throwable message]}]
(binding [*out* (if error? *err* *out*)]
- (apply str-println prefix "-" message more)))}
+ (str-println prefix "-" message (stacktrace throwable))))}
: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]}]
+ :min-level nil :enabled? false :async? false :limit-per-msecs nil
+ :fn (fn [{:keys [ap-config prefix throwable message]}]
(when-let [filename (:spit-filename ap-config)]
(try (spit filename
- (with-out-str (apply str-println prefix "-"
- message more))
+ (with-out-str
+ (str-println prefix "-" message
+ (stacktrace throwable)))
:append true)
(catch java.io.IOException _))))}}}))
@@ -132,53 +137,49 @@
(defn- wrap-appender-fn
"Wraps compile-time appender fn with additional runtime capabilities
controlled by compile-time config."
- [{apfn :fn :keys [async? max-message-per-msecs prefix-fn] :as appender}]
- (->> ; Wrapping applies capabilities bottom-to-top
- apfn
-
- ;; Wrap for per-appender prefix-fn support
- ((fn [apfn]
- (if-not prefix-fn
- apfn
- (fn [apfn-args]
- (apfn (assoc apfn-args
- :prefix (prefix-fn apfn-args)))))))
-
- ;; Wrap for runtime flood-safety support
- ((fn [apfn]
- (if-not max-message-per-msecs
- apfn
- (let [;; {:hash last-appended-time-msecs ...}
- flood-timers (atom {})]
-
- (fn [{:keys [ns message] :as apfn-args}]
- (let [now (System/currentTimeMillis)
- hash (str ns "/" message)
- allow? (fn [last-msecs]
- (or (not last-msecs)
- (> (- now last-msecs) max-message-per-msecs)))]
-
- (when (allow? (@flood-timers hash))
- (apfn apfn-args)
- (swap! flood-timers assoc hash now))
-
- ;; 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)
- (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))))))))))
+ [{apfn :fn :keys [async? limit-per-msecs prefix-fn] :as appender}]
+ (let [limit-per-msecs (or (:max-message-per-msecs appender)
+ limit-per-msecs)] ; Backwards-compatibility
+ (->> ; Wrapping applies per appender, bottom-to-top
+ apfn
+
+ ;; Per-appender prefix-fn support (cmp. default prefix-fn)
+ ;; TODO Currently undocumented, candidate for removal
+ ((fn [apfn]
+ (if-not prefix-fn
+ apfn
+ (fn [apfn-args]
+ (apfn (assoc apfn-args
+ :prefix (prefix-fn apfn-args)))))))
+
+ ;; Rate limit support
+ ((fn [apfn]
+ (if-not limit-per-msecs
+ apfn
+ (let [timers (atom {})] ; {:hash last-appended-time-msecs ...}
+ (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)))]
+
+ (when-not (limit? (@timers hash))
+ (apfn apfn-args)
+ (swap! timers assoc hash now))
+
+ (when (< (rand) 0.001) ; Occasionally garbage collect
+ (when-let [expired-timers (->> (keys @timers)
+ (remove #(limit? (@timers %)))
+ (seq))]
+ (apply swap! timers dissoc expired-timers)))))))))
+
+ ;; 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
@@ -202,10 +203,10 @@
(incl. middleware) controlled by compile-time config. Like `wrap-appender-fn`
but operates on the entire juxt at once."
[juxtfn]
- (->> ; Wrapping applies capabilities bottom-to-top
+ (->> ; Wrapping applies per juxt, bottom-to-top
juxtfn
- ;; Wrap to add middleware transforms/filters
+ ;; Middleware transforms/filters support
((fn [juxtfn]
(if-let [middleware (seq (:middleware @config))]
(let [composed-middleware
@@ -216,7 +217,7 @@
(juxtfn juxtfn-args))))
juxtfn)))
- ;; Wrap to add compile-time stuff to runtime appender arguments
+ ;; Add compile-time stuff to runtime appender args
((fn [juxtfn]
(let [{ap-config :shared-appender-config
:keys [timestamp-pattern timestamp-locale prefix-fn]} @config
@@ -233,14 +234,13 @@
;;; Appender-fns
(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:
{:level (wrapped-juxt wrapped-appender-fn wrapped-appender-fn ...) or nil
...}"
(atom {}))
-(defn- relevant-appenders
- [level]
+(defn- relevant-appenders [level]
(->> (:appenders @config)
(filter #(let [{:keys [enabled? min-level]} (val %)]
(and enabled? (>= (compare-levels level min-level) 0))))
@@ -249,8 +249,7 @@
(comment (relevant-appenders :debug)
(relevant-appenders :trace))
-(defn- cache-appenders-juxt!
- []
+(defn- cache-appenders-juxt! []
(->>
(zipmap
ordered-levels
@@ -269,13 +268,11 @@
(def ns-filter-cache "@ns-filter-cache => (fn relevant-ns? [ns] ...)"
(atom (constantly true)))
-(defn- ns-match?
- [ns match]
+(defn- ns-match? [ns match]
(-> (str "^" (-> (str match) (.replace "." "\\.") (.replace "*" "(.*)")) "$")
re-pattern (re-find (str ns)) boolean))
-(defn- cache-ns-filter!
- []
+(defn- cache-ns-filter! []
(->>
(let [{:keys [ns-whitelist ns-blacklist]} @config]
(memoize
@@ -304,41 +301,63 @@
(defn logging-enabled?
"Returns true when current logging level is sufficient and current namespace
is unfiltered."
- [level]
- (and (sufficient-level? level) (@ns-filter-cache *ns*)))
+ [level] (and (sufficient-level? level) (@ns-filter-cache *ns*)))
(defmacro log*
- "Prepares given arguments for, and then dispatches to all relevant
- appender-fns."
- [level base-args & sigs]
- `(when-let [juxt-fn# (@appenders-juxt-cache ~level)] ; Any relevant appenders?
- (let [[x1# & xs#] (list ~@sigs)
-
- has-throwable?# (instance? Throwable x1#)
- appender-args#
- (conj
- ~base-args ; Allow flexibility to inject exta args
- {:level ~level
- :error? (error-level? ~level)
- :instant (Date.)
- :ns ~(str *ns*)
- :message (if has-throwable?# (or (first xs#) x1#) x1#)
- :more (if has-throwable?#
- (conj (vec (rest xs#))
- (str "\nStacktrace:\n"
- (stacktrace/pst-str x1#)))
- (vec xs#))})]
-
- (juxt-fn# appender-args#)
- nil)))
+ "Implementation detail - subject to change..
+ Prepares given arguments for, and then dispatches to all level-relevant
+ appender-fns. "
+
+ ;; For tools.logging.impl/Logger support
+ ([base-appender-args level log-vargs ns throwable message juxt-fn]
+ `(when-let [juxt-fn# (or ~juxt-fn (@appenders-juxt-cache ~level))]
+ (juxt-fn#
+ (conj (or ~base-appender-args {})
+ {:instant (Date.)
+ :ns ~ns
+ :level ~level
+ :error? (error-level? ~level)
+ :args ~log-vargs ; No native tools.logging support
+ :throwable ~throwable
+ :message ~message}))
+ true))
+
+ ([base-appender-args level log-args message-fn]
+ `(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
- "When logging is enabled, actually logs given arguments with relevant
- appender-fns. Generic form of standard level-loggers (trace, info, etc.)."
- {:arglists '([level message & more] [level throwable message & more])}
+ "When logging is enabled, actually logs given arguments with level-relevant
+ appender-fns."
+ {:arglists '([level & args] [level throwable & args])}
+ [level & sigs]
+ `(when (logging-enabled? ~level)
+ (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)))
+ (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
"Evaluates named expression and logs its result. Always returns the result.
@@ -347,36 +366,41 @@
([level expr] `(spy ~level '~expr ~expr))
([level name expr]
`(try
- (let [result# ~expr] (log ~level ~name result#) result#)
+ (let [result# ~expr] (logp ~level ~name result#) result#)
(catch Exception e#
- (log ~level '~expr (str "\n" (stacktrace/pst-str e#)))
+ (logp ~level '~expr (str "\n" (stacktrace/pst-str e#)))
(throw e#)))))
(defmacro s ; Alias
{:arglists '([expr] [level expr] [level name expr])}
[& args] `(spy ~@args))
-(defmacro ^:private def-logger
- [level]
+(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])}
- [& sigs#]
- `(log ~~level ~@sigs#))))
+ `(do
+ (defmacro ~(symbol level-name)
+ ~(str "Log given arguments at " level " level using print-style args.")
+ ~'{:arglists '([& message] [throwable & message])}
+ [& sigs#] `(logp ~~level ~@sigs#))
-(defmacro ^:private def-loggers
- [] `(do ~@(map (fn [level] `(def-logger ~level)) ordered-levels)))
+ (defmacro ~(symbol (str level-name "f"))
+ ~(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
-(defmacro log-errors
- [& body] `(try ~@body (catch Exception e# (error e#))))
+(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#))))
+(defmacro log-and-rethrow-errors [& body]
+ `(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))
(log-and-rethrow-errors (/ 0))
@@ -385,13 +409,15 @@
;;;; Dev/tests
(comment
- (log :fatal "arg1")
- (log :debug "arg1" "arg2")
- (log :debug (Exception.) "arg1" "arg2")
- (log :debug (Exception.))
- (log :trace "arg1")
-
- (log (or nil :info) "Booya")
+ (info)
+ (info "a")
+ (info "a" "b" "c")
+ (info "a" (Exception. "b") "c")
+ (info (Exception. "a") "b" "c")
+ (logp (or nil :info) "Booya")
+
+ (info "a%s" "b")
+ (infof "a%s" "b")
(set-config! [:ns-blacklist] [])
(set-config! [:ns-blacklist] ["taoensso.timbre*"])
View
20 src/taoensso/timbre/appenders/irc.clj
@@ -1,6 +1,5 @@
(ns taoensso.timbre.appenders.irc
- "IRC appender for irclj.
- Ref: https://github.com/flatland/irclj."
+ "IRC appender. Depends on https://github.com/flatland/irclj."
{:author "Emlyn Corrin"}
(:require [clojure.string :as str]
[irclj.core :as irclj]
@@ -21,26 +20,27 @@
(defn ensure-conn [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)
- lines (str/split message #"\n")]
+ lines (-> (str message (timbre/stacktrace throwable "\n"))
+ (str/split #"\n"))]
(irclj/message conn chan prefix (first lines))
(doseq [line (rest lines)]
(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)]
(send-message
(assoc irc-config
- :prefix prefix
- :message message))))
+ :prefix prefix
+ :throwable throwable
+ :message message))))
(def irc-appender
{:doc (str "Sends IRC messages using irclj.\n"
"Needs :irc config map in :shared-appender-config, e.g.:
{:host \"irc.example.org\" :port 6667 :nick \"logger\"
:name \"My Logger\" :chan \"#logs\"")
- :min-level :info :enabled? true :async? false
- :max-message-per-msecs nil ; no rate limit by default
+ :min-level :info :enabled? true :async? false :limit-per-msecs nil
:prefix-fn (fn [{:keys [level]}] (-> level name str/upper-case))
- :fn appender-fn})
+ :fn appender-fn})
View
18 src/taoensso/timbre/appenders/postal.clj
@@ -1,6 +1,5 @@
(ns taoensso.timbre.appenders.postal
- "Email appender for com.draines/postal.
- Ref: https://github.com/drewr/postal."
+ "Email appender. Depends on https://github.com/drewr/postal."
{:author "Peter Taoussanis"}
(:require [clojure.string :as str]
[postal.core :as postal]
@@ -12,11 +11,12 @@
^{:host \"mail.isp.net\" :user \"jsmith\" :pass \"sekrat!!1\"}
{:from \"Bob's logger <me@draines.com>\" :to \"foo@example.com\"}")
:min-level :error :enabled? true :async? true
- :max-message-per-msecs (* 1000 60 10) ; 1 email per message per 10 mins
- :fn (fn [{:keys [ap-config prefix message more]}]
+ :limit-per-msecs (* 1000 60 10) ; 1 subject / 10 mins
+ :fn (fn [{:keys [ap-config prefix throwable args]}]
(when-let [postal-config (:postal ap-config)]
- (postal/send-message
- (assoc postal-config
- :subject (str prefix " - " message)
- :body (if (seq more) (str/join " " more)
- "<no additional arguments>")))))})
+ (let [[subject & body] args]
+ (postal/send-message
+ (assoc postal-config
+ :subject (str prefix " - " subject)
+ :body (str (str/join \space body)
+ (timbre/stacktrace throwable "\n")))))))})
View
9 src/taoensso/timbre/frequencies.clj
@@ -28,10 +28,11 @@
(let [name (utils/fq-keyword name)]
`(let [{result# :result stats# :stats} (with-fdata ~level ~@body)]
(when stats#
- (timbre/log* ~level
- {:frequency-stats stats#}
- (str "Frequencies " ~name)
- (str "\n" (format-fdata stats#))))
+ (timbre/log* {:frequency-stats stats#}
+ ~level
+ [(str "Frequencies " ~name)
+ (str "\n" (format-fdata stats#))]
+ print-str))
result#)))
(defmacro sampling-log-frequencies
View
9 src/taoensso/timbre/profiling.clj
@@ -29,10 +29,11 @@
(let [name (utils/fq-keyword name)]
`(let [{result# :result stats# :stats} (with-pdata ~level ~@body)]
(when stats#
- (timbre/log* ~level
- {:profile-stats stats#}
- (str "Profiling " ~name)
- (str "\n" (format-pdata stats#))))
+ (timbre/log* {:profile-stats stats#}
+ ~level
+ [(str "Profiling " ~name)
+ (str "\n" (format-pdata stats#))]
+ print-str))
result#)))
(defmacro sampling-profile
View
24 src/taoensso/timbre/tools/logging.clj
@@ -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))))

0 comments on commit f0d71af

Please sign in to comment.
Something went wrong with that request. Please try again.