Added time-format and locale shared-config options.

* Also updated README.

Signed-off-by: Peter Taoussanis <p.taoussanis@gmail.com>
This commit is contained in:
Peter Taoussanis 2012-05-30 16:15:15 +07:00
parent 5fbbe9bfb5
commit dde1b89b03
2 changed files with 58 additions and 35 deletions

View File

@ -8,7 +8,7 @@ Timbre is an attempt to make **simple logging simple** and more **complex loggin
## What's In The Box? ## What's In The Box?
* Small, uncomplicated **all-Clojure** library. * Small, uncomplicated **all-Clojure** library.
* **Map-based config**: no arcane XML or properties files. * **Super-simple map-based config**: no arcane XML or properties files.
* Decent performance (**low overhead**). * Decent performance (**low overhead**).
* Flexible **fn-centric appender model**. * Flexible **fn-centric appender model**.
* Sensible built-in appenders including simple **email appender**. * Sensible built-in appenders including simple **email appender**.
@ -71,6 +71,15 @@ Easily adjust the current logging level:
(timbre/set-level! :warn) (timbre/set-level! :warn)
``` ```
And the default instant formatting for log messages:
```clojure
(timbre/set-config! [:shared-appender-config :instant-pattern]
"yyyy-MMM-dd HH:mm:ss ZZ")
(timbre/set-config! [:shared-appender-config :locale]
(java.util.Locale/GERMAN))
```
Enable the standard [Postal](https://github.com/drewr/postal)-based email appender: Enable the standard [Postal](https://github.com/drewr/postal)-based email appender:
```clojure ```clojure
@ -105,7 +114,8 @@ Writing a custom appender is easy:
:enabled? true :enabled? true
:async? false :async? false
:max-message-per-msecs nil ; No rate limiting :max-message-per-msecs nil ; No rate limiting
:fn (fn [{:keys [ap-config level error? instant ns message more] :as args}] :fn (fn [{:keys [ap-config level error? instant instant-formatter
ns message more] :as args}]
(when-not (:production-mode? ap-config) (when-not (:production-mode? ap-config)
(apply println instant "Hello world!" message more))) (apply println instant "Hello world!" message more)))
``` ```

View File

@ -3,23 +3,18 @@
{:author "Peter Taoussanis"} {:author "Peter Taoussanis"}
(:require [clojure.string :as str] (:require [clojure.string :as str]
[clj-stacktrace.repl :as stacktrace] [clj-stacktrace.repl :as stacktrace]
[postal.core :as postal])) [postal.core :as postal])
(:import [java.util Date Locale]
;;;; Appender-fn helpers [java.text SimpleDateFormat]))
(defn instant-str
"2012-May-26 15:26:06:081 +0700"
[instant]
(format "%1$tY-%1$tb-%1$td %1$tH:%1$tM:%1$tS:%1$tL %1tz" instant))
(defn prefixed-message
"2012-May-26 15:26:06:081 +0700 LEVEL [ns] - message"
[level instant ns message]
(str (instant-str instant) " " (-> level name str/upper-case)
" [" ns "] - " message))
;;;; Default configuration and appenders ;;;; Default configuration and appenders
(defn prefixed-message
"<formatted-instant> LEVEL [ns] - message"
[{:keys [level instant instant-formatter ns message]}]
(str (instant-formatter instant) " " (-> level name str/upper-case)
" [" ns "] - " message))
(def config (def config
"This map atom controls everything about the way Timbre operates. In "This map atom controls everything about the way Timbre operates. In
particular note the flexibility to add arbitrary appenders. particular note the flexibility to add arbitrary appenders.
@ -28,7 +23,8 @@
:doc, :min-level, :enabled?, :async?, :max-message-per-msecs, :fn? :doc, :min-level, :enabled?, :async?, :max-message-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:
:ap-config, :level, :error?, :instant, :ns, :message, :more :ap-config, :level, :error?, :instant, :instant-formatter, :ns,
:message, :more
See source code for examples." See source code for examples."
(atom {:current-level :debug (atom {:current-level :debug
@ -38,36 +34,37 @@
{:doc "Prints everything to *out*." {:doc "Prints everything to *out*."
:min-level :debug :enabled? false :async? false :min-level :debug :enabled? false :async? false
:max-message-per-msecs nil :max-message-per-msecs nil
:fn (fn [{:keys [level instant ns message more]}] :fn (fn [{:keys [level instant ns message more] :as args}]
(apply println (prefixed-message level instant ns message) (apply println (prefixed-message args) more))}
more))}
:standard-out-or-err :standard-out-or-err
{:doc "Prints to *out* or *err* as appropriate. Enabled by default." {:doc "Prints to *out* or *err* as appropriate. Enabled by default."
:min-level :debug :enabled? true :async? false :min-level :debug :enabled? true :async? false
:max-message-per-msecs nil :max-message-per-msecs nil
:fn (fn [{:keys [level error? instant ns message more]}] :fn (fn [{:keys [level error? instant ns message more] :as args}]
(binding [*out* (if error? *err* *out*)] (binding [*out* (if error? *err* *out*)]
(apply println (prefixed-message level instant ns message) (apply println (prefixed-message args) more)))}
more)))}
:postal :postal
{:doc (str "Sends an email using com.draines/postal.\n" {:doc (str "Sends an email using com.draines/postal.\n"
"Needs :postal config map in :shared-appender-config.") "Needs :postal config map in :shared-appender-config.")
:min-level :error :enabled? false :async? true :min-level :error :enabled? false :async? true
:max-message-per-msecs (* 60 60 2) :max-message-per-msecs (* 60 60 2)
:fn (fn [{:keys [ap-config level instant ns message more]}] :fn (fn [{:keys [ap-config level instant ns message more] :as args}]
(when-let [postal-config (:postal ap-config)] (when-let [postal-config (:postal ap-config)]
(postal/send-message (postal/send-message
(assoc postal-config (assoc postal-config
:subject (prefixed-message level instant ns message) :subject (prefixed-message args)
:body (if (seq more) (str/join " " more) :body (if (seq more) (str/join " " more)
"<no additional arguments>")))))}} "<no additional arguments>")))))}}
;; Example :postal map: :shared-appender-config
{:instant-pattern "yyyy-MMM-dd HH:mm:ss ZZ" ; SimpleDateFormat pattern
:locale nil ; A Locale object, or nil
;; A Postal message map, or nil.
;; ^{: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"}
:shared-appender-config {:postal nil}})) :postal nil}}))
(defn set-config! [ks val] (swap! config assoc-in ks val)) (defn set-config! [ks val] (swap! config assoc-in ks val))
(defn set-level! [level] (set-config! [:current-level] level)) (defn set-level! [level] (set-config! [:current-level] level))
@ -83,16 +80,32 @@
(defn sufficient-level? (defn sufficient-level?
[level] (>= (compare-levels level (:current-level @config)) 0)) [level] (>= (compare-levels level (:current-level @config)) 0))
;;;; Appender-fn decoration: flood control, async, etc. ;;;; Appender-fn decoration
(defn- make-instant-formatter
"Returns unary fn to format an instant using given pattern string and optional
locale."
[^String pattern ^Locale locale]
(let [format (if locale
(SimpleDateFormat. pattern locale)
(SimpleDateFormat. pattern))]
(fn [^Date instant] (.format ^SimpleDateFormat format instant))))
(comment ((make-instant-formatter "yyyy-MMM-dd" nil) (Date.)))
(defn- wrap-appender-fn (defn- wrap-appender-fn
"Wraps compile-time appender fn with additional capabilities controlled by "Wraps compile-time appender fn with additional capabilities controlled by
compile-time config." compile-time config."
[appender-id {apfn :fn :keys [async? max-message-per-msecs] :as appender}] [appender-id {apfn :fn :keys [async? max-message-per-msecs] :as appender}]
(-> (->
;; Wrap to add shared appender config to args ;; Wrap to add compile-time stuff to runtime appender arguments
(fn [apfn-args] (fn [apfn-args]
(apfn (assoc apfn-args :ap-config (@config :shared-appender-config)))) (let [{:keys [instant-pattern locale] :as ap-config}
(@config :shared-appender-config)]
(apfn (assoc apfn-args
:ap-config ap-config ; Shared appender config map
:instant-formatter
(make-instant-formatter instant-pattern locale)))))
;; Wrap for asynchronicity support ;; Wrap for asynchronicity support
((fn [apfn] ((fn [apfn]
@ -189,7 +202,7 @@
appender-args# appender-args#
{:level level# {:level level#
:error? (>= (compare-levels level# :error) 0) :error? (>= (compare-levels level# :error) 0)
:instant (java.util.Date.) :instant (Date.)
:ns (str ~*ns*) :ns (str ~*ns*)
:message (if has-throwable?# (or (first xs#) x1#) x1#) :message (if has-throwable?# (or (first xs#) x1#) x1#)
:more (if has-throwable?# :more (if has-throwable?#