mirror of https://github.com/status-im/timbre.git
Merge branch 'dev': v0.8.0.
This commit is contained in:
commit
2f52e709e7
|
@ -1,6 +1,6 @@
|
|||
language: clojure
|
||||
lein: lein2
|
||||
script: lein2 all test
|
||||
script: lein2 test-all
|
||||
jdk:
|
||||
- openjdk7
|
||||
- openjdk6
|
||||
|
|
41
README.md
41
README.md
|
@ -1,13 +1,16 @@
|
|||
Current [semantic](http://semver.org/) version:
|
||||
|
||||
```clojure
|
||||
[com.taoensso/timbre "0.7.0"]
|
||||
[com.taoensso/timbre "0.8.0"]
|
||||
```
|
||||
|
||||
**Breaking changes** since _0.6.x_ (see updated README examples for any necessary changes):
|
||||
* Affecting **users of the standard email appender**:
|
||||
* Postal appender moved to own ns: `taoensso.timbre.appenders.postal`.
|
||||
* `com.draines/postal` no longer automatically included as a dependency.
|
||||
**Breaking changes** since _0.7.x_:
|
||||
* Affecting users with their own/non-default config:
|
||||
* Appender has been renamed: `:standard-out-or-err` -> `:standard-out`. Old `:standard-out` appender has been removed.
|
||||
* Config option has been renamed: `[:shared-appender-config :timestamp-pattern]` -> `[:timestamp-pattern]`.
|
||||
* Config option has been renamed: `[:shared-appender-config :locale]` -> `[:timestamp-locale]`.
|
||||
* Affecting appender authors using `timbre/prefixed-message`:
|
||||
* This fn has been removed. Please see the new `:prefix-fn` config option and `:prefix` appender argument for a cleaner alternative.
|
||||
|
||||
# Timbre, a (sane) logging library for Clojure
|
||||
|
||||
|
@ -42,7 +45,7 @@ lein2 all test
|
|||
Depend on Timbre in your `project.clj`:
|
||||
|
||||
```clojure
|
||||
[com.taoensso/timbre "0.7.0"]
|
||||
[com.taoensso/timbre "0.8.0"]
|
||||
```
|
||||
|
||||
and `use` the library:
|
||||
|
@ -59,11 +62,11 @@ By default, Timbre gives you basic print output to `*out*`/`*err*` at a `debug`
|
|||
```clojure
|
||||
(info "This will print")
|
||||
=> nil
|
||||
%> 2012-May-28 17:26:11:444 +0700 INFO [my-app] - This will print
|
||||
%> 2012-May-28 17:26:11:444 +0700 localhost INFO [my-app] - This will print
|
||||
|
||||
(spy :info (* 5 4 3 2 1))
|
||||
=> 120
|
||||
%> 2012-May-28 17:26:14:138 +0700 INFO [my-app] - (* 5 4 3 2 1) 120
|
||||
%> 2012-May-28 17:26:14:138 +0700 localhost INFO [my-app] - (* 5 4 3 2 1) 120
|
||||
|
||||
(trace "This won't print due to insufficient logging level")
|
||||
=> nil
|
||||
|
@ -83,7 +86,7 @@ First-argument exceptions generate a stack trace:
|
|||
|
||||
```clojure
|
||||
(info (Exception. "Oh noes") "arg1" "arg2")
|
||||
%> 2012-May-28 17:35:16:132 +0700 INFO [my-app] - arg1 arg2
|
||||
%> 2012-May-28 17:35:16:132 +0700 localhost INFO [my-app] - arg1 arg2
|
||||
java.lang.Exception: Oh noes
|
||||
NO_SOURCE_FILE:1 my-app/eval6409
|
||||
Compiler.java:6511 clojure.lang.Compiler.eval
|
||||
|
@ -102,13 +105,14 @@ Configuring Timbre couldn't be simpler. Let's check out (some of) the defaults:
|
|||
:ns-whitelist []
|
||||
:ns-blacklist []
|
||||
|
||||
:timestamp-pattern "yyyy-MMM-dd HH:mm:ss ZZ"
|
||||
:timestamp-locale nil
|
||||
|
||||
:appenders
|
||||
{:standard-out { <...> }
|
||||
<...> }
|
||||
|
||||
:shared-appender-config
|
||||
{:timestamp-pattern "yyyy-MMM-dd HH:mm:ss ZZ"
|
||||
:locale nil}}
|
||||
:shared-appender-config {}}
|
||||
```
|
||||
|
||||
Easily adjust the current logging level:
|
||||
|
@ -120,10 +124,8 @@ Easily adjust the current logging level:
|
|||
And the default timestamp formatting for log messages:
|
||||
|
||||
```clojure
|
||||
(timbre/set-config! [:shared-appender-config :timestamp-pattern]
|
||||
"yyyy-MMM-dd HH:mm:ss ZZ")
|
||||
(timbre/set-config! [:shared-appender-config :locale]
|
||||
(java.util.Locale/GERMAN))
|
||||
(timbre/set-config! [:timestamp-pattern] "yyyy-MMM-dd HH:mm:ss ZZ")
|
||||
(timbre/set-config! [:timestamp-locale] (java.util.Locale/GERMAN))
|
||||
```
|
||||
|
||||
Filter logging output by namespaces:
|
||||
|
@ -178,10 +180,9 @@ Writing a custom appender is dead-easy:
|
|||
:enabled? true
|
||||
:async? false
|
||||
:max-message-per-msecs nil ; No rate limiting
|
||||
:fn (fn [{:keys [ap-config level error? instant timestamp
|
||||
ns message more] :as args}]
|
||||
:fn (fn [{:keys [ap-config level prefix message more] :as args}]
|
||||
(when-not (:my-production-mode? ap-config)
|
||||
(apply println timestamp "Hello world!" message more)))
|
||||
(apply println prefix "Hello world!" message more)))
|
||||
```
|
||||
|
||||
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.
|
||||
|
@ -222,7 +223,7 @@ The `profile` macro can now be used to log times for any wrapped forms:
|
|||
```clojure
|
||||
(profile :info :Arithmetic (dotimes [n 100] (my-fn)))
|
||||
=> "Done!"
|
||||
%> 2012-Jul-03 20:46:17 +0700 INFO [my-app] - Profiling my-app/Arithmetic
|
||||
%> 2012-Jul-03 20:46:17 +0700 localhost INFO [my-app] - Profiling my-app/Arithmetic
|
||||
Name Calls Min Max MAD Mean Total% Total
|
||||
my-app/slow-sleep 100 2ms 2ms 31μs 2ms 57 231ms
|
||||
my-app/fast-sleep 100 1ms 1ms 27μs 1ms 29 118ms
|
||||
|
|
14
project.clj
14
project.clj
|
@ -1,18 +1,20 @@
|
|||
(defproject com.taoensso/timbre "0.7.0"
|
||||
(defproject com.taoensso/timbre "0.8.0"
|
||||
:description "Simple, flexible, all-Clojure logging. No XML!"
|
||||
:url "https://github.com/ptaoussanis/timbre"
|
||||
:license {:name "Eclipse Public License"}
|
||||
:dependencies [[org.clojure/clojure "1.3.0"]
|
||||
[clj-stacktrace "0.2.4"]]
|
||||
: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-master-SNAPSHOT"]]}}
|
||||
: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-master-SNAPSHOT"]]}
|
||||
:dev {:dependencies []}
|
||||
:test {:dependencies []}}
|
||||
:repositories {"sonatype" {:url "http://oss.sonatype.org/content/repositories/releases"
|
||||
:snapshots false
|
||||
:releases {:checksum :fail :update :always}}
|
||||
"sonatype-snapshots" {:url "http://oss.sonatype.org/content/repositories/snapshots"
|
||||
:snapshots true
|
||||
:releases {:checksum :fail :update :always}}}
|
||||
:aliases {"all" ["with-profile" "1.3:1.4:1.5"]}
|
||||
:aliases {"test-all" ["with-profile" "test,1.3:test,1.4:test,1.5" "test"]}
|
||||
:min-lein-version "2.0.0"
|
||||
:warn-on-reflection true)
|
||||
:warn-on-reflection true)
|
|
@ -1,19 +1,14 @@
|
|||
(ns taoensso.timbre
|
||||
"Simple, flexible, all-Clojure logging. No XML!"
|
||||
{:author "Peter Taoussanis"}
|
||||
(:require [clojure.string :as str]
|
||||
[clj-stacktrace.repl :as stacktrace])
|
||||
(:require [clojure.string :as str]
|
||||
[clj-stacktrace.repl :as stacktrace]
|
||||
[taoensso.timbre.utils :as utils])
|
||||
(:import [java.util Date Locale]
|
||||
[java.text SimpleDateFormat]))
|
||||
|
||||
;;;; Default configuration and appenders
|
||||
|
||||
(defn prefixed-message
|
||||
"timestamp LEVEL [ns] - message"
|
||||
[{:keys [level timestamp ns message]}]
|
||||
(str timestamp " " (-> level name str/upper-case)
|
||||
" [" ns "] - " message))
|
||||
|
||||
(defn str-println
|
||||
"Like `println` but prints all objects to output stream as a single
|
||||
atomic string. This is faster and avoids interleaving race conditions."
|
||||
|
@ -30,38 +25,42 @@
|
|||
:doc, :min-level, :enabled?, :async?, :max-message-per-msecs, :fn?
|
||||
|
||||
An appender's fn takes a single map argument with keys:
|
||||
:ap-config, :level, :error?, :instant, :timestamp, :ns, :message, :more,
|
||||
:profiling-stats (when applicable)
|
||||
:level, :message, :more ; From all logging macros (`info`, etc.)
|
||||
:profiling-stats ; From `profile` macro
|
||||
:ap-config ; `shared-appender-config`
|
||||
:prefix ; Output of `prefix-fn`
|
||||
|
||||
Other keys include: :instant, :timestamp, :hostname, :ns, :error?
|
||||
|
||||
See source code for examples."}
|
||||
(atom {:current-level :debug
|
||||
|
||||
;;; Allow log filtering by namespace patterns (e.g. ["my-app.*"]).
|
||||
;;; Control log filtering by namespace patterns (e.g. ["my-app.*"]).
|
||||
;;; Useful for turning off logging in noisy libraries, etc.
|
||||
:ns-whitelist []
|
||||
:ns-blacklist []
|
||||
|
||||
;;; Control :timestamp format
|
||||
:timestamp-pattern "yyyy-MMM-dd HH:mm:ss ZZ" ; SimpleDateFormat pattern
|
||||
:timestamp-locale nil ; A Locale object, or nil
|
||||
|
||||
;;; Control :prefix format
|
||||
: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 {}
|
||||
|
||||
:appenders
|
||||
{:standard-out
|
||||
{:doc "Prints everything to *out*."
|
||||
:min-level nil :enabled? false :async? false
|
||||
:max-message-per-msecs nil
|
||||
:fn (fn [{:keys [more] :as args}]
|
||||
(apply str-println (prefixed-message args) more))}
|
||||
|
||||
:standard-out-or-err
|
||||
{: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? more] :as args}]
|
||||
:fn (fn [{:keys [error? prefix message more]}]
|
||||
(binding [*out* (if error? *err* *out*)]
|
||||
(apply str-println (prefixed-message args) more)))}}
|
||||
|
||||
;; Will be given to all appenders via :ap-config key
|
||||
:shared-appender-config
|
||||
{:timestamp-pattern "yyyy-MMM-dd HH:mm:ss ZZ" ; SimpleDateFormat pattern
|
||||
:locale nil ; A Locale object, or nil
|
||||
}}))
|
||||
(apply str-println prefix "-" message more)))}}}))
|
||||
|
||||
(defn set-config! [[k & ks] val] (swap! config assoc-in (cons k ks) val))
|
||||
(defn set-level! [level] (set-config! [:current-level] level))
|
||||
|
@ -74,6 +73,8 @@
|
|||
[x] (when-not (some #{x} ordered-levels)
|
||||
(throw (Exception. (str "Invalid logging level: " x)))))
|
||||
|
||||
(defn error-level? [x] (boolean (#{:error :fatal} x)))
|
||||
|
||||
(def compare-levels
|
||||
(memoize (fn [x y] (- (scored-levels x) (scored-levels y)))))
|
||||
|
||||
|
@ -93,20 +94,25 @@
|
|||
|
||||
(comment ((make-timestamp-fn "yyyy-MMM-dd" nil) (Date.)))
|
||||
|
||||
(def get-hostname
|
||||
(utils/memoize-ttl
|
||||
60000 (fn [] (.. java.net.InetAddress getLocalHost getHostName))))
|
||||
|
||||
(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] :as appender}]
|
||||
(->
|
||||
;; Wrap to add compile-time stuff to runtime appender arguments
|
||||
(let [{:keys [timestamp-pattern locale] :as ap-config}
|
||||
(@config :shared-appender-config)
|
||||
timestamp-fn (make-timestamp-fn timestamp-pattern locale)]
|
||||
(let [{:keys [timestamp-pattern timestamp-locale prefix-fn] :as ap-config}
|
||||
@config
|
||||
timestamp-fn (make-timestamp-fn timestamp-pattern timestamp-locale)]
|
||||
|
||||
(fn [{:keys [instant] :as apfn-args}]
|
||||
(apfn (assoc apfn-args
|
||||
:ap-config ap-config
|
||||
:timestamp (timestamp-fn instant)))))
|
||||
(let [apfn-args (merge apfn-args {:ap-config ap-config
|
||||
:timestamp (timestamp-fn instant)
|
||||
:hostname (get-hostname)})]
|
||||
(apfn (assoc apfn-args :prefix (prefix-fn apfn-args))))))
|
||||
|
||||
;; Wrap for asynchronicity support
|
||||
((fn [apfn]
|
||||
|
@ -237,7 +243,7 @@
|
|||
(conj
|
||||
~base-args ; Allow flexibility to inject exta args
|
||||
{:level ~level
|
||||
:error? (>= (compare-levels ~level :error) 0)
|
||||
:error? (error-level? ~level)
|
||||
:instant (Date.)
|
||||
:ns (str ~*ns*)
|
||||
:message (if has-throwable?# (or (first xs#) x1#) x1#)
|
||||
|
|
|
@ -12,11 +12,11 @@
|
|||
^{: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 (* 60 60 2)
|
||||
:fn (fn [{:keys [ap-config more] :as args}]
|
||||
:max-message-per-msecs (* 1000 60 10) ; 1 email per message per 10 mins
|
||||
:fn (fn [{:keys [ap-config prefix message more]}]
|
||||
(when-let [postal-config (:postal ap-config)]
|
||||
(postal/send-message
|
||||
(assoc postal-config
|
||||
:subject (timbre/prefixed-message args)
|
||||
:subject (str prefix " - " message)
|
||||
:body (if (seq more) (str/join " " more)
|
||||
"<no additional arguments>")))))})
|
|
@ -0,0 +1,17 @@
|
|||
(ns taoensso.timbre.utils
|
||||
{:author "Peter Taoussanis"})
|
||||
|
||||
(defn memoize-ttl
|
||||
"Like `memoize` but invalidates the cache for a set of arguments after TTL
|
||||
msecs has elapsed."
|
||||
[ttl f]
|
||||
(let [cache (atom {})]
|
||||
(fn [& args]
|
||||
(let [{:keys [time-cached d-result]} (@cache args)
|
||||
now (System/currentTimeMillis)]
|
||||
|
||||
(if (and time-cached (< (- now time-cached) ttl))
|
||||
@d-result
|
||||
(let [d-result (delay (apply f args))]
|
||||
(swap! cache assoc args {:time-cached now :d-result d-result})
|
||||
@d-result))))))
|
Loading…
Reference in New Issue