From dde1b89b03dd70129177ee2f4a4041bb9e6822c3 Mon Sep 17 00:00:00 2001
From: Peter Taoussanis
Date: Wed, 30 May 2012 16:15:15 +0700
Subject: [PATCH] Added time-format and locale shared-config options.
* Also updated README.
Signed-off-by: Peter Taoussanis
---
README.md | 14 ++++++--
src/timbre/core.clj | 79 ++++++++++++++++++++++++++-------------------
2 files changed, 58 insertions(+), 35 deletions(-)
diff --git a/README.md b/README.md
index 37fc2b8..52d3951 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ Timbre is an attempt to make **simple logging simple** and more **complex loggin
## What's In The Box?
* 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**).
* Flexible **fn-centric appender model**.
* Sensible built-in appenders including simple **email appender**.
@@ -71,6 +71,15 @@ Easily adjust the current logging level:
(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:
```clojure
@@ -105,7 +114,8 @@ Writing a custom appender is easy:
:enabled? true
:async? false
: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)
(apply println instant "Hello world!" message more)))
```
diff --git a/src/timbre/core.clj b/src/timbre/core.clj
index 3f84737..219e6c3 100644
--- a/src/timbre/core.clj
+++ b/src/timbre/core.clj
@@ -3,23 +3,18 @@
{:author "Peter Taoussanis"}
(:require [clojure.string :as str]
[clj-stacktrace.repl :as stacktrace]
- [postal.core :as postal]))
-
-;;;; Appender-fn helpers
-
-(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))
+ [postal.core :as postal])
+ (:import [java.util Date Locale]
+ [java.text SimpleDateFormat]))
;;;; Default configuration and appenders
+(defn prefixed-message
+ " LEVEL [ns] - message"
+ [{:keys [level instant instant-formatter ns message]}]
+ (str (instant-formatter instant) " " (-> level name str/upper-case)
+ " [" ns "] - " message))
+
(def config
"This map atom controls everything about the way Timbre operates. In
particular note the flexibility to add arbitrary appenders.
@@ -28,7 +23,8 @@
: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, :ns, :message, :more
+ :ap-config, :level, :error?, :instant, :instant-formatter, :ns,
+ :message, :more
See source code for examples."
(atom {:current-level :debug
@@ -38,36 +34,37 @@
{:doc "Prints everything to *out*."
:min-level :debug :enabled? false :async? false
:max-message-per-msecs nil
- :fn (fn [{:keys [level instant ns message more]}]
- (apply println (prefixed-message level instant ns message)
- more))}
+ :fn (fn [{:keys [level instant ns message more] :as args}]
+ (apply println (prefixed-message args) more))}
:standard-out-or-err
{:doc "Prints to *out* or *err* as appropriate. Enabled by default."
:min-level :debug :enabled? true :async? false
: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*)]
- (apply println (prefixed-message level instant ns message)
- more)))}
+ (apply println (prefixed-message args) more)))}
:postal
{:doc (str "Sends an email using com.draines/postal.\n"
"Needs :postal config map in :shared-appender-config.")
:min-level :error :enabled? false :async? true
: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)]
(postal/send-message
(assoc postal-config
- :subject (prefixed-message level instant ns message)
- :body (if (seq more) (str/join " " more)
- "")))))}}
+ :subject (prefixed-message args)
+ :body (if (seq more) (str/join " " more)
+ "")))))}}
- ;; Example :postal map:
- ;; ^{:host "mail.isp.net" :user "jsmith" :pass "sekrat!!1"}
- ;; {:from "me@draines.com" :to "foo@example.com"}
- :shared-appender-config {:postal nil}}))
+ :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"}
+ ;; {:from "me@draines.com" :to "foo@example.com"}
+ :postal nil}}))
(defn set-config! [ks val] (swap! config assoc-in ks val))
(defn set-level! [level] (set-config! [:current-level] level))
@@ -83,16 +80,32 @@
(defn sufficient-level?
[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
"Wraps compile-time appender fn with additional capabilities controlled by
compile-time config."
[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]
- (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
((fn [apfn]
@@ -189,7 +202,7 @@
appender-args#
{:level level#
:error? (>= (compare-levels level# :error) 0)
- :instant (java.util.Date.)
+ :instant (Date.)
:ns (str ~*ns*)
:message (if has-throwable?# (or (first xs#) x1#) x1#)
:more (if has-throwable?#