mirror of https://github.com/status-im/timbre.git
Initial commit.
* Working library. * Basic README. Signed-off-by: Peter Taoussanis <p.taoussanis@gmail.com>
This commit is contained in:
parent
17b82085f9
commit
d74de51a05
|
@ -1,5 +1,9 @@
|
||||||
pom.xml
|
/docs/
|
||||||
*jar
|
|
||||||
/lib/
|
/lib/
|
||||||
|
/logs/
|
||||||
/classes/
|
/classes/
|
||||||
.lein-deps-sum
|
/target/
|
||||||
|
*.jar
|
||||||
|
*.sh
|
||||||
|
.lein*
|
||||||
|
pom.xml
|
49
README.md
49
README.md
|
@ -1,4 +1,47 @@
|
||||||
timbre
|
# Timbre, a logging library for Clojure
|
||||||
======
|
|
||||||
|
|
||||||
Simple, flexible, all-Clojure logging. No XML!
|
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 possible**.
|
||||||
|
|
||||||
|
## What's In The Box?
|
||||||
|
* Small, uncomplicated **all-Clojure** library.
|
||||||
|
* **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**.
|
||||||
|
* Tunable **flood control**.
|
||||||
|
* **Asynchronous** logging support.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Timbre was built in a day after I finally lost my patience trying to configure Log4j. I tried to keep the design simple and sensible, but I didn't spend much time thinking about it so there may still be room for improvement. In particular, **the configuration and appender formats are still subject to change**.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
### Leiningen
|
||||||
|
|
||||||
|
Depend on `[timbre "0.5.0-SNAPSHOT"]` in your `project.clj` and `use` the library:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(ns my-app
|
||||||
|
(:use [timbre.core :as timbre :only (trace debug info warn error fatal spy)])
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start Logging
|
||||||
|
|
||||||
|
TODO: Out-the-box std-out examples
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
TODO: Config format (assoc) & appenders. Recommend just viewing source for now.
|
||||||
|
|
||||||
|
## Contact & Contribution
|
||||||
|
|
||||||
|
Reach me (Peter Taoussanis) at *p.taoussanis at gmail.com* for questions/comments/suggestions/whatever. I'm very open to ideas if you have any! Seriously: try me ;)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Distributed under the [Eclipse Public License](http://www.eclipse.org/legal/epl-v10.html), the same as Clojure.
|
|
@ -0,0 +1,261 @@
|
||||||
|
<?xml version="1.0" encoding="ISO-8859-1" ?>
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
|
||||||
|
<title>Eclipse Public License - Version 1.0</title>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
size: 8.5in 11.0in;
|
||||||
|
margin: 0.25in 0.5in 0.25in 0.5in;
|
||||||
|
tab-interval: 0.5in;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
p.list {
|
||||||
|
margin-left: 0.5in;
|
||||||
|
margin-top: 0.05em;
|
||||||
|
margin-bottom: 0.05em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body lang="EN-US">
|
||||||
|
|
||||||
|
<p align=center><b>Eclipse Public License - v 1.0</b></p>
|
||||||
|
|
||||||
|
<p>THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
|
||||||
|
PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR
|
||||||
|
DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS
|
||||||
|
AGREEMENT.</p>
|
||||||
|
|
||||||
|
<p><b>1. DEFINITIONS</b></p>
|
||||||
|
|
||||||
|
<p>"Contribution" means:</p>
|
||||||
|
|
||||||
|
<p class="list">a) in the case of the initial Contributor, the initial
|
||||||
|
code and documentation distributed under this Agreement, and</p>
|
||||||
|
<p class="list">b) in the case of each subsequent Contributor:</p>
|
||||||
|
<p class="list">i) changes to the Program, and</p>
|
||||||
|
<p class="list">ii) additions to the Program;</p>
|
||||||
|
<p class="list">where such changes and/or additions to the Program
|
||||||
|
originate from and are distributed by that particular Contributor. A
|
||||||
|
Contribution 'originates' from a Contributor if it was added to the
|
||||||
|
Program by such Contributor itself or anyone acting on such
|
||||||
|
Contributor's behalf. Contributions do not include additions to the
|
||||||
|
Program which: (i) are separate modules of software distributed in
|
||||||
|
conjunction with the Program under their own license agreement, and (ii)
|
||||||
|
are not derivative works of the Program.</p>
|
||||||
|
|
||||||
|
<p>"Contributor" means any person or entity that distributes
|
||||||
|
the Program.</p>
|
||||||
|
|
||||||
|
<p>"Licensed Patents" mean patent claims licensable by a
|
||||||
|
Contributor which are necessarily infringed by the use or sale of its
|
||||||
|
Contribution alone or when combined with the Program.</p>
|
||||||
|
|
||||||
|
<p>"Program" means the Contributions distributed in accordance
|
||||||
|
with this Agreement.</p>
|
||||||
|
|
||||||
|
<p>"Recipient" means anyone who receives the Program under
|
||||||
|
this Agreement, including all Contributors.</p>
|
||||||
|
|
||||||
|
<p><b>2. GRANT OF RIGHTS</b></p>
|
||||||
|
|
||||||
|
<p class="list">a) Subject to the terms of this Agreement, each
|
||||||
|
Contributor hereby grants Recipient a non-exclusive, worldwide,
|
||||||
|
royalty-free copyright license to reproduce, prepare derivative works
|
||||||
|
of, publicly display, publicly perform, distribute and sublicense the
|
||||||
|
Contribution of such Contributor, if any, and such derivative works, in
|
||||||
|
source code and object code form.</p>
|
||||||
|
|
||||||
|
<p class="list">b) Subject to the terms of this Agreement, each
|
||||||
|
Contributor hereby grants Recipient a non-exclusive, worldwide,
|
||||||
|
royalty-free patent license under Licensed Patents to make, use, sell,
|
||||||
|
offer to sell, import and otherwise transfer the Contribution of such
|
||||||
|
Contributor, if any, in source code and object code form. This patent
|
||||||
|
license shall apply to the combination of the Contribution and the
|
||||||
|
Program if, at the time the Contribution is added by the Contributor,
|
||||||
|
such addition of the Contribution causes such combination to be covered
|
||||||
|
by the Licensed Patents. The patent license shall not apply to any other
|
||||||
|
combinations which include the Contribution. No hardware per se is
|
||||||
|
licensed hereunder.</p>
|
||||||
|
|
||||||
|
<p class="list">c) Recipient understands that although each Contributor
|
||||||
|
grants the licenses to its Contributions set forth herein, no assurances
|
||||||
|
are provided by any Contributor that the Program does not infringe the
|
||||||
|
patent or other intellectual property rights of any other entity. Each
|
||||||
|
Contributor disclaims any liability to Recipient for claims brought by
|
||||||
|
any other entity based on infringement of intellectual property rights
|
||||||
|
or otherwise. As a condition to exercising the rights and licenses
|
||||||
|
granted hereunder, each Recipient hereby assumes sole responsibility to
|
||||||
|
secure any other intellectual property rights needed, if any. For
|
||||||
|
example, if a third party patent license is required to allow Recipient
|
||||||
|
to distribute the Program, it is Recipient's responsibility to acquire
|
||||||
|
that license before distributing the Program.</p>
|
||||||
|
|
||||||
|
<p class="list">d) Each Contributor represents that to its knowledge it
|
||||||
|
has sufficient copyright rights in its Contribution, if any, to grant
|
||||||
|
the copyright license set forth in this Agreement.</p>
|
||||||
|
|
||||||
|
<p><b>3. REQUIREMENTS</b></p>
|
||||||
|
|
||||||
|
<p>A Contributor may choose to distribute the Program in object code
|
||||||
|
form under its own license agreement, provided that:</p>
|
||||||
|
|
||||||
|
<p class="list">a) it complies with the terms and conditions of this
|
||||||
|
Agreement; and</p>
|
||||||
|
|
||||||
|
<p class="list">b) its license agreement:</p>
|
||||||
|
|
||||||
|
<p class="list">i) effectively disclaims on behalf of all Contributors
|
||||||
|
all warranties and conditions, express and implied, including warranties
|
||||||
|
or conditions of title and non-infringement, and implied warranties or
|
||||||
|
conditions of merchantability and fitness for a particular purpose;</p>
|
||||||
|
|
||||||
|
<p class="list">ii) effectively excludes on behalf of all Contributors
|
||||||
|
all liability for damages, including direct, indirect, special,
|
||||||
|
incidental and consequential damages, such as lost profits;</p>
|
||||||
|
|
||||||
|
<p class="list">iii) states that any provisions which differ from this
|
||||||
|
Agreement are offered by that Contributor alone and not by any other
|
||||||
|
party; and</p>
|
||||||
|
|
||||||
|
<p class="list">iv) states that source code for the Program is available
|
||||||
|
from such Contributor, and informs licensees how to obtain it in a
|
||||||
|
reasonable manner on or through a medium customarily used for software
|
||||||
|
exchange.</p>
|
||||||
|
|
||||||
|
<p>When the Program is made available in source code form:</p>
|
||||||
|
|
||||||
|
<p class="list">a) it must be made available under this Agreement; and</p>
|
||||||
|
|
||||||
|
<p class="list">b) a copy of this Agreement must be included with each
|
||||||
|
copy of the Program.</p>
|
||||||
|
|
||||||
|
<p>Contributors may not remove or alter any copyright notices contained
|
||||||
|
within the Program.</p>
|
||||||
|
|
||||||
|
<p>Each Contributor must identify itself as the originator of its
|
||||||
|
Contribution, if any, in a manner that reasonably allows subsequent
|
||||||
|
Recipients to identify the originator of the Contribution.</p>
|
||||||
|
|
||||||
|
<p><b>4. COMMERCIAL DISTRIBUTION</b></p>
|
||||||
|
|
||||||
|
<p>Commercial distributors of software may accept certain
|
||||||
|
responsibilities with respect to end users, business partners and the
|
||||||
|
like. While this license is intended to facilitate the commercial use of
|
||||||
|
the Program, the Contributor who includes the Program in a commercial
|
||||||
|
product offering should do so in a manner which does not create
|
||||||
|
potential liability for other Contributors. Therefore, if a Contributor
|
||||||
|
includes the Program in a commercial product offering, such Contributor
|
||||||
|
("Commercial Contributor") hereby agrees to defend and
|
||||||
|
indemnify every other Contributor ("Indemnified Contributor")
|
||||||
|
against any losses, damages and costs (collectively "Losses")
|
||||||
|
arising from claims, lawsuits and other legal actions brought by a third
|
||||||
|
party against the Indemnified Contributor to the extent caused by the
|
||||||
|
acts or omissions of such Commercial Contributor in connection with its
|
||||||
|
distribution of the Program in a commercial product offering. The
|
||||||
|
obligations in this section do not apply to any claims or Losses
|
||||||
|
relating to any actual or alleged intellectual property infringement. In
|
||||||
|
order to qualify, an Indemnified Contributor must: a) promptly notify
|
||||||
|
the Commercial Contributor in writing of such claim, and b) allow the
|
||||||
|
Commercial Contributor to control, and cooperate with the Commercial
|
||||||
|
Contributor in, the defense and any related settlement negotiations. The
|
||||||
|
Indemnified Contributor may participate in any such claim at its own
|
||||||
|
expense.</p>
|
||||||
|
|
||||||
|
<p>For example, a Contributor might include the Program in a commercial
|
||||||
|
product offering, Product X. That Contributor is then a Commercial
|
||||||
|
Contributor. If that Commercial Contributor then makes performance
|
||||||
|
claims, or offers warranties related to Product X, those performance
|
||||||
|
claims and warranties are such Commercial Contributor's responsibility
|
||||||
|
alone. Under this section, the Commercial Contributor would have to
|
||||||
|
defend claims against the other Contributors related to those
|
||||||
|
performance claims and warranties, and if a court requires any other
|
||||||
|
Contributor to pay any damages as a result, the Commercial Contributor
|
||||||
|
must pay those damages.</p>
|
||||||
|
|
||||||
|
<p><b>5. NO WARRANTY</b></p>
|
||||||
|
|
||||||
|
<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
|
||||||
|
PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
|
||||||
|
OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION,
|
||||||
|
ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
|
||||||
|
OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely
|
||||||
|
responsible for determining the appropriateness of using and
|
||||||
|
distributing the Program and assumes all risks associated with its
|
||||||
|
exercise of rights under this Agreement , including but not limited to
|
||||||
|
the risks and costs of program errors, compliance with applicable laws,
|
||||||
|
damage to or loss of data, programs or equipment, and unavailability or
|
||||||
|
interruption of operations.</p>
|
||||||
|
|
||||||
|
<p><b>6. DISCLAIMER OF LIABILITY</b></p>
|
||||||
|
|
||||||
|
<p>EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
|
||||||
|
NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
|
||||||
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
|
||||||
|
WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR
|
||||||
|
DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED
|
||||||
|
HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.</p>
|
||||||
|
|
||||||
|
<p><b>7. GENERAL</b></p>
|
||||||
|
|
||||||
|
<p>If any provision of this Agreement is invalid or unenforceable under
|
||||||
|
applicable law, it shall not affect the validity or enforceability of
|
||||||
|
the remainder of the terms of this Agreement, and without further action
|
||||||
|
by the parties hereto, such provision shall be reformed to the minimum
|
||||||
|
extent necessary to make such provision valid and enforceable.</p>
|
||||||
|
|
||||||
|
<p>If Recipient institutes patent litigation against any entity
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that the
|
||||||
|
Program itself (excluding combinations of the Program with other
|
||||||
|
software or hardware) infringes such Recipient's patent(s), then such
|
||||||
|
Recipient's rights granted under Section 2(b) shall terminate as of the
|
||||||
|
date such litigation is filed.</p>
|
||||||
|
|
||||||
|
<p>All Recipient's rights under this Agreement shall terminate if it
|
||||||
|
fails to comply with any of the material terms or conditions of this
|
||||||
|
Agreement and does not cure such failure in a reasonable period of time
|
||||||
|
after becoming aware of such noncompliance. If all Recipient's rights
|
||||||
|
under this Agreement terminate, Recipient agrees to cease use and
|
||||||
|
distribution of the Program as soon as reasonably practicable. However,
|
||||||
|
Recipient's obligations under this Agreement and any licenses granted by
|
||||||
|
Recipient relating to the Program shall continue and survive.</p>
|
||||||
|
|
||||||
|
<p>Everyone is permitted to copy and distribute copies of this
|
||||||
|
Agreement, but in order to avoid inconsistency the Agreement is
|
||||||
|
copyrighted and may only be modified in the following manner. The
|
||||||
|
Agreement Steward reserves the right to publish new versions (including
|
||||||
|
revisions) of this Agreement from time to time. No one other than the
|
||||||
|
Agreement Steward has the right to modify this Agreement. The Eclipse
|
||||||
|
Foundation is the initial Agreement Steward. The Eclipse Foundation may
|
||||||
|
assign the responsibility to serve as the Agreement Steward to a
|
||||||
|
suitable separate entity. Each new version of the Agreement will be
|
||||||
|
given a distinguishing version number. The Program (including
|
||||||
|
Contributions) may always be distributed subject to the version of the
|
||||||
|
Agreement under which it was received. In addition, after a new version
|
||||||
|
of the Agreement is published, Contributor may elect to distribute the
|
||||||
|
Program (including its Contributions) under the new version. Except as
|
||||||
|
expressly stated in Sections 2(a) and 2(b) above, Recipient receives no
|
||||||
|
rights or licenses to the intellectual property of any Contributor under
|
||||||
|
this Agreement, whether expressly, by implication, estoppel or
|
||||||
|
otherwise. All rights in the Program not expressly granted under this
|
||||||
|
Agreement are reserved.</p>
|
||||||
|
|
||||||
|
<p>This Agreement is governed by the laws of the State of New York and
|
||||||
|
the intellectual property laws of the United States of America. No party
|
||||||
|
to this Agreement will bring a legal action under this Agreement more
|
||||||
|
than one year after the cause of action arose. Each party waives its
|
||||||
|
rights to a jury trial in any resulting litigation.</p>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,17 @@
|
||||||
|
(defproject timbre "0.5.0-SNAPSHOT"
|
||||||
|
:description "Simple, flexible, all-Clojure logging. No XML!"
|
||||||
|
:url "https://github.com/ptaoussanis/timbre"
|
||||||
|
:license {:name "Eclipse Public License"}
|
||||||
|
:dependencies [[com.draines/postal "1.7.1"]]
|
||||||
|
: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"]]}}
|
||||||
|
: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"]}
|
||||||
|
:min-lein-version "2.0.0"
|
||||||
|
:warn-on-reflection true)
|
|
@ -0,0 +1,241 @@
|
||||||
|
(ns timbre.core
|
||||||
|
"Simple, flexible, all-Clojure logging. No XML!"
|
||||||
|
{: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))
|
||||||
|
|
||||||
|
;;;; Default configuration and appenders
|
||||||
|
|
||||||
|
(def config
|
||||||
|
"This map atom controls everything about the way Timbre operates. In
|
||||||
|
particular note the flexibility to add arbitrary appenders.
|
||||||
|
|
||||||
|
An appender is a map with keys:
|
||||||
|
: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
|
||||||
|
|
||||||
|
See source code for examples."
|
||||||
|
(atom {:current-level :debug
|
||||||
|
|
||||||
|
:appenders
|
||||||
|
{:standard-out
|
||||||
|
{: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))}
|
||||||
|
|
||||||
|
: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]}]
|
||||||
|
(binding [*out* (if error? *err* *out*)]
|
||||||
|
(apply println (prefixed-message level instant ns message)
|
||||||
|
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]}]
|
||||||
|
(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)
|
||||||
|
"<no additional arguments>")))))}}
|
||||||
|
|
||||||
|
;; 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}}))
|
||||||
|
|
||||||
|
;;;; Define and sort logging levels
|
||||||
|
|
||||||
|
(def ^:private ordered-levels [:trace :debug :info :warn :error :fatal])
|
||||||
|
(def ^:private scored-levels (zipmap ordered-levels (range)))
|
||||||
|
|
||||||
|
(def ^:private compare-levels
|
||||||
|
(memoize (fn [x y] (- (scored-levels x) (scored-levels y)))))
|
||||||
|
|
||||||
|
(defn- sufficient-level?
|
||||||
|
[level] (>= (compare-levels level (:current-level @config)) 0))
|
||||||
|
|
||||||
|
;;;; Appender-fn decoration: flood control, async, etc.
|
||||||
|
|
||||||
|
(defn- wrap-appender-fn
|
||||||
|
"Wraps compile-time appender fn with additional capabilities controlled by
|
||||||
|
compile-time config:
|
||||||
|
* Asynchronicity.
|
||||||
|
* Runtime flood-safety control."
|
||||||
|
[appender-id {apfn :fn :keys [async? max-message-per-msecs] :as appender}]
|
||||||
|
(->
|
||||||
|
apfn
|
||||||
|
|
||||||
|
;; Wrap async
|
||||||
|
((fn [apfn]
|
||||||
|
(if-not async?
|
||||||
|
apfn
|
||||||
|
(let [agent (agent nil :error-mode :continue)]
|
||||||
|
(fn [apfn-args] (send-off agent (fn [_] (apfn apfn-args))))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Wrap flood-control
|
||||||
|
((fn [apfn]
|
||||||
|
(if-not max-message-per-msecs
|
||||||
|
apfn
|
||||||
|
(let [ ;; {:msg last-appended-time-msecs ...}
|
||||||
|
flood-timers (atom {})]
|
||||||
|
|
||||||
|
(fn [{:keys [message] :as apfn-args}]
|
||||||
|
(let [now (System/currentTimeMillis)
|
||||||
|
allow? (fn [last-msecs]
|
||||||
|
(if last-msecs
|
||||||
|
(> (- now last-msecs) max-message-per-msecs)
|
||||||
|
true))]
|
||||||
|
|
||||||
|
(when (allow? (@flood-timers message))
|
||||||
|
(apfn apfn-args)
|
||||||
|
(swap! flood-timers assoc message 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))))))))))))
|
||||||
|
|
||||||
|
;;;; Appender-fn caching
|
||||||
|
|
||||||
|
(def juxt-cache
|
||||||
|
"Per-level, combined relevant appender-fns:
|
||||||
|
{:level (juxt wrapped-appender-fn wrapped-appender-fn ...) or nil
|
||||||
|
...}"
|
||||||
|
(atom {}))
|
||||||
|
|
||||||
|
(defn- relevant-appenders
|
||||||
|
[level]
|
||||||
|
(->> (:appenders @config)
|
||||||
|
(filter #(let [{:keys [enabled? min-level]} (val %)]
|
||||||
|
(and enabled? (>= (compare-levels level min-level) 0))))
|
||||||
|
(into {})))
|
||||||
|
|
||||||
|
(comment (relevant-appenders :debug)
|
||||||
|
(relevant-appenders :trace))
|
||||||
|
|
||||||
|
(defn- cache-appenders!
|
||||||
|
"Prime 'juxt-cache' for fast runtime appender-fn dispatch."
|
||||||
|
[]
|
||||||
|
(->> (zipmap
|
||||||
|
ordered-levels
|
||||||
|
(->> ordered-levels
|
||||||
|
(map (fn [l] (let [rel-aps (relevant-appenders l)]
|
||||||
|
;; Return nil if no relevant appenders
|
||||||
|
(when-let [ap-ids (keys rel-aps)]
|
||||||
|
(->> ap-ids
|
||||||
|
(map (fn [n] (wrap-appender-fn
|
||||||
|
n (rel-aps n))))
|
||||||
|
(apply juxt))))))))
|
||||||
|
(reset! juxt-cache)))
|
||||||
|
|
||||||
|
(cache-appenders!) ; Actually cache default appenders now
|
||||||
|
|
||||||
|
;; Automatically re-cache any time appenders change
|
||||||
|
(add-watch
|
||||||
|
config "appender-watch"
|
||||||
|
(fn [key ref old new]
|
||||||
|
(when-not (= (:appenders old) (:appenders new))
|
||||||
|
(cache-appenders!))))
|
||||||
|
|
||||||
|
;;;; Define logging macros
|
||||||
|
|
||||||
|
(defmacro ^:private log
|
||||||
|
"Dispatches given arguments to relevant appender-fns iff logging level is
|
||||||
|
sufficient."
|
||||||
|
{:arglists '([message & more] [throwable message & more])}
|
||||||
|
[level & args]
|
||||||
|
`(let [level# ~level]
|
||||||
|
(when (sufficient-level? level#)
|
||||||
|
(when-let [juxt-fn# (@juxt-cache level#)] ; Any relevant appenders?
|
||||||
|
(let [[x1# & xs#] (list ~@args)
|
||||||
|
|
||||||
|
has-throwable?# (instance? Throwable x1#)
|
||||||
|
appender-args#
|
||||||
|
{;; TODO Too much overhead for this?
|
||||||
|
:ap-config (@config :shared-appender-config)
|
||||||
|
:level level#
|
||||||
|
:error? (>= (compare-levels level# :error) 0)
|
||||||
|
:instant (java.util.Date.)
|
||||||
|
:ns (str ~*ns*)
|
||||||
|
:message (if has-throwable?# (first xs#) x1#)
|
||||||
|
:more (if has-throwable?#
|
||||||
|
(conj (vec (rest xs#))
|
||||||
|
(str "\n" (stacktrace/pst-str x1#)))
|
||||||
|
(vec xs#))}]
|
||||||
|
|
||||||
|
(juxt-fn# appender-args#)
|
||||||
|
nil)))))
|
||||||
|
|
||||||
|
(comment (log :fatal "arg1")
|
||||||
|
(log :debug "arg1" "arg2")
|
||||||
|
(log :debug (Exception.) "arg1" "arg2")
|
||||||
|
(log :trace "arg1"))
|
||||||
|
|
||||||
|
(defmacro spy
|
||||||
|
"Evaluates expression and logs its form and result. Returns the result.
|
||||||
|
Defaults to :debug logging level."
|
||||||
|
([expr] `(spy :debug ~expr))
|
||||||
|
([level expr]
|
||||||
|
`(try
|
||||||
|
(let [r# ~expr] (log ~level '~expr ~expr) r#)
|
||||||
|
(catch Exception e#
|
||||||
|
(log ~level '~expr (str "\n" (stacktrace/pst-str e#)))
|
||||||
|
(throw e#)))))
|
||||||
|
|
||||||
|
(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])}
|
||||||
|
[& args#]
|
||||||
|
`(log ~~level ~@args#))))
|
||||||
|
|
||||||
|
(defmacro ^:private def-loggers
|
||||||
|
[] `(do ~@(map (fn [level] `(def-logger ~level)) ordered-levels)))
|
||||||
|
|
||||||
|
(def-loggers) ; Actually define a logger for each logging level
|
||||||
|
|
||||||
|
;;;; Dev/tests
|
||||||
|
|
||||||
|
(comment
|
||||||
|
(info "foo" "bar")
|
||||||
|
(trace (Thread/sleep 5000))
|
||||||
|
(time (dotimes [n 10000] (trace "foo" "bar"))) ; Minimum overhead +/- 17ms
|
||||||
|
(time (dotimes [n 5] (info "foo" "bar")))
|
||||||
|
(spy (* 6 5 4 3 2 1))
|
||||||
|
(info (Exception. "noes!") "bar")
|
||||||
|
(spy (/ 4 0)))
|
Loading…
Reference in New Issue