2016-01-14 05:17:22 +00:00
< a href = "https://www.taoensso.com" title = "More stuff by @ptaoussanis at www.taoensso.com" >
2016-01-14 07:18:33 +00:00
< img src = "https://www.taoensso.com/taoensso-open-source.png" alt = "Taoensso open-source" width = "400" / > < / a >
2016-01-14 05:17:22 +00:00
**[CHANGELOG]** | [API] | current [Break Version]:
2012-06-29 07:13:28 +00:00
```clojure
2016-07-19 04:42:03 +00:00
[com.taoensso/timbre "4.7.0"] ; Stable
2012-06-29 07:13:28 +00:00
```
2016-06-17 14:15:14 +00:00
> Please consider helping to [support my continued open-source Clojure/Script work]?
>
> Even small contributions can add up + make a big difference to help sustain my time writing, maintaining, and supporting Timbre and other Clojure/Script libraries. **Thank you!**
>
> \- Peter Taoussanis
2016-03-10 07:10:01 +00:00
2016-01-14 05:17:22 +00:00
# Timbre
## A pure Clojure/Script logging library
2012-07-13 10:37:40 +00:00
2016-06-17 14:15:14 +00:00
Java logging is a Kafkaesque mess of complexity that buys you _nothing_ . It can be comically hard to get even the simplest logging working, and it just gets worse at scale.
2015-05-26 05:43:22 +00:00
2016-06-17 14:15:14 +00:00
Timbre offers an **all Clojure/Script** alternative that's fast, deeply flexible, easy to configure, and that **works out the box** . No XML.
Supports optional interop with [tools.logging ](https://github.com/ptaoussanis/timbre/blob/master/src/taoensso/timbre/tools/logging.clj ) and [log4j/logback/slf4j ](https://github.com/fzakaria/slf4j-timbre ).
Happy hacking!
2012-05-28 08:13:11 +00:00
2016-01-14 05:17:22 +00:00
## Features
2016-06-17 14:15:14 +00:00
* Full **Clojure** + **ClojureScript** support (v4+).
* No XML or properties files. **A single, simple config map** , and you're set.
* Simple, flexible **fn appender model** with **middleware** .
* **Great performance** at any scale.
* Filter logging by levels, **namespace whitelist/blacklist patterns** , and more.
* **Zero overhead** with **complete Clj+Cljs elision** for compile-time level/ns filters.
* Useful built-in appenders for **out-the-box** Clj+Cljs logging.
* Powerful, easy-to-configure **rate limits** and **async logging** .
* [Logs as Clojure values][] (v3+).
* Small, simple, cross-platform codebase.
2012-05-28 08:13:11 +00:00
2014-04-03 10:06:45 +00:00
## 3rd-party tools, appenders, etc.
2015-12-22 06:17:13 +00:00
Link | Description
------------------------ | -----------------------------------------------------
[@fzakaria/slf4j-timbre] | Route log4j/logback/sfl4j log output to Timbre
2016-06-17 14:15:14 +00:00
[@palletops/log-config] | Library to help manage Timbre logging config
2015-12-22 06:17:13 +00:00
Your link here? | **PR's welcome!**
2014-04-03 10:06:45 +00:00
2013-06-01 12:41:07 +00:00
## Getting started
2012-05-28 08:13:11 +00:00
2016-01-14 05:17:22 +00:00
Add the necessary dependency to your project:
```clojure
2016-07-19 04:42:03 +00:00
[com.taoensso/timbre "4.7.0"]
2016-01-14 05:17:22 +00:00
```
2012-05-28 08:13:11 +00:00
2016-01-14 05:17:22 +00:00
And setup your namespace imports:
2012-06-29 07:13:28 +00:00
```clojure
2016-01-14 05:17:22 +00:00
(ns my-clj-ns ; Clojure namespace
2015-05-29 02:18:07 +00:00
(:require
[taoensso.timbre :as timbre
:refer (log trace debug info warn error fatal report
2015-06-13 12:25:30 +00:00
logf tracef debugf infof warnf errorf fatalf reportf
2016-07-11 08:12:37 +00:00
spy get-env log-env)]))
2015-05-29 02:18:07 +00:00
2016-01-14 05:17:22 +00:00
(ns my-cljs-ns ; ; ClojureScript namespace
2015-05-29 02:18:07 +00:00
(:require
[taoensso.timbre :as timbre
:refer-macros (log trace debug info warn error fatal report
2015-06-13 12:25:30 +00:00
logf tracef debugf infof warnf errorf fatalf reportf
spy get-env log-env)]))
2012-05-28 08:13:11 +00:00
```
2016-06-17 14:15:14 +00:00
> You can also call `(timbre/refer-timbre)` to configure Clj ns referrals **automatically**.
2015-05-26 05:43:22 +00:00
2012-11-04 16:43:55 +00:00
### Logging
2012-05-28 08:13:11 +00:00
2015-05-29 05:37:52 +00:00
By default, Timbre gives you basic `println` and `js/console` (v4+) output at a `:debug` log level:
2012-05-28 10:49:54 +00:00
```clojure
2013-11-30 13:19:56 +00:00
(info "This will print") => nil
2015-06-13 12:25:30 +00:00
%> 15-Jun-13 19:18:33 localhost INFO [my-app.core] - This will print
2012-07-03 11:48:00 +00:00
2013-11-30 13:19:56 +00:00
(spy :info (* 5 4 3 2 1)) => 120
2015-06-13 12:25:30 +00:00
%> 15-Jun-13 19:19:13 localhost INFO [my-app.core] - (* 5 4 3 2 1) => 120
(defn my-mult [x y] (info "Lexical env:" (get-env)) (* x y)) => #'my-mult
(my-mult 4 7) => 28
%> 15-Jun-13 19:21:53 localhost INFO [my-app.core] - Lexical env: {x 4, y 7}
2012-05-28 10:49:54 +00:00
2015-05-26 14:43:28 +00:00
(trace "This won't print due to insufficient log level") => nil
2012-05-28 10:49:54 +00:00
```
2016-01-30 03:42:50 +00:00
First-argument exceptions generate a nicely cleaned-up stack trace using [io.aviso.exception][] (Clj only):
2012-05-28 10:49:54 +00:00
```clojure
(info (Exception. "Oh noes") "arg1" "arg2")
2015-06-13 12:25:30 +00:00
%> 15-Jun-13 19:22:55 localhost INFO [my-app.core] - arg1 arg2
java.lang.Exception: On noes
< Stacktrace >
2012-05-28 10:49:54 +00:00
```
2012-05-28 08:13:11 +00:00
2016-01-14 05:17:22 +00:00
Other utils include: `log-errors` , `log-and-rethrow-errors` , `logged-future` , and `handle-uncaught-jvm-exceptions!` (please see the [API] for details).
2015-09-28 07:08:41 +00:00
2015-09-28 12:03:11 +00:00
#### Disabling stacktrace colors
ANSI colors are enabled by default for stacktraces. To turn these off (e.g. for log files or emails), you can add the following entry to your top-level config **or** individual appender map/s:
2015-09-28 07:08:41 +00:00
```clojure
:output-fn (partial timbre/default-output-fn {:stacktrace-fonts {}})
```
2016-06-17 14:15:14 +00:00
And/or you can set the `TIMBRE_DEFAULT_STACKTRACE_FONTS` environment variable (supports edn).
2016-07-19 04:42:03 +00:00
### Data flow
Timbre's inherently a very simple design, no magic. It's just Clojure data and functions:
1. Unfiltered logging calls generate a **data map** : `{:level _ :?ns-str _ ...}`
2. The resulting data map passes through any **middleware fns** , `(fn [data]) -> ?data`
3. The resulting data map is sent to all **appender fns** , `(fn [data]) -> ?effects`
2012-05-28 08:13:11 +00:00
### Configuration
2016-07-19 04:42:03 +00:00
This is the biggest win over Java logging IMO.
**All** of Timbre's behaviour is controlled through a single, simple Clojure map.
2015-05-26 05:43:22 +00:00
2016-02-27 02:30:50 +00:00
> See `timbre/example-config` for Timbre's default config map
2013-11-30 13:19:56 +00:00
```clojure
(def example-config
2016-02-27 02:30:50 +00:00
"An example Timbre v4 config map.
2015-05-26 05:43:22 +00:00
APPENDERS
An appender is a map with keys:
:min-level ; Level keyword, or nil (=> no minimum level)
:enabled? ;
NB Refactor appenders API, implementation
Appender arg changes:
- REMOVED: :data-output-fn, :hash-arg_
- Deprecated: :?err_, :vargs_ (no point in delaying these now)
- New: :?err, :vargs, :?msg-fmt, :hash_, :output_, :?meta
Implementation notes:
- Have unified all vargs processing during margs phase
- vargs no longer contains message format; now extracted as separate :?msg-fmt
- :output-fn and :timezone_ are now cached between appenders when possible
- :output_ now provides a convenient, cache-aware way of getting formatted ouput
- Hash stuff is now non-configurable but far more predictable; if appenders
have specific needs beyond :hash_, they should use a custom (fn [data]).
2016-06-20 11:21:48 +00:00
:async? ; Dispatch using agent? Useful for slow appenders (clj only)
2015-05-26 05:43:22 +00:00
:rate-limit ; [[ncalls-limit window-ms] < ... > ], or nil
2015-05-27 07:10:01 +00:00
:output-fn ; Optional override for inherited (fn [data]) -> string
NB Refactor appenders API, implementation
Appender arg changes:
- REMOVED: :data-output-fn, :hash-arg_
- Deprecated: :?err_, :vargs_ (no point in delaying these now)
- New: :?err, :vargs, :?msg-fmt, :hash_, :output_, :?meta
Implementation notes:
- Have unified all vargs processing during margs phase
- vargs no longer contains message format; now extracted as separate :?msg-fmt
- :output-fn and :timezone_ are now cached between appenders when possible
- :output_ now provides a convenient, cache-aware way of getting formatted ouput
- Hash stuff is now non-configurable but far more predictable; if appenders
have specific needs beyond :hash_, they should use a custom (fn [data]).
2016-06-20 11:21:48 +00:00
:timestamp-opts ; Optional override for inherited {:pattern _ :locale _ :timezone _}
2016-06-10 04:43:44 +00:00
:ns-whitelist ; Optional, stacks with active config's whitelist
:ns-blacklist ; Optional, stacks with active config's blacklist
2016-07-19 04:26:17 +00:00
:middleware-fn ; Optional, stacks with active config's middleware
NB Refactor appenders API, implementation
Appender arg changes:
- REMOVED: :data-output-fn, :hash-arg_
- Deprecated: :?err_, :vargs_ (no point in delaying these now)
- New: :?err, :vargs, :?msg-fmt, :hash_, :output_, :?meta
Implementation notes:
- Have unified all vargs processing during margs phase
- vargs no longer contains message format; now extracted as separate :?msg-fmt
- :output-fn and :timezone_ are now cached between appenders when possible
- :output_ now provides a convenient, cache-aware way of getting formatted ouput
- Hash stuff is now non-configurable but far more predictable; if appenders
have specific needs beyond :hash_, they should use a custom (fn [data]).
2016-06-20 11:21:48 +00:00
:fn ; (fn [data]) -> side effects, with keys described below
2015-05-26 05:43:22 +00:00
An appender's fn takes a single data map with keys:
:config ; Entire config map (this map, etc.)
2015-05-26 14:43:28 +00:00
:appender-id ; Id of appender currently dispatching
:appender ; Entire map of appender currently dispatching
2015-05-26 05:43:22 +00:00
:instant ; Platform date (java.util.Date or js/Date)
:level ; Keyword
2015-05-26 14:43:28 +00:00
:error-level? ; Is level e/o #{:error :fatal}?
2016-02-27 02:30:50 +00:00
:?ns-str ; String, or nil
:?file ; String, or nil
2015-05-26 05:43:22 +00:00
:?line ; Integer, or nil ; Waiting on CLJ-865
NB Refactor appenders API, implementation
Appender arg changes:
- REMOVED: :data-output-fn, :hash-arg_
- Deprecated: :?err_, :vargs_ (no point in delaying these now)
- New: :?err, :vargs, :?msg-fmt, :hash_, :output_, :?meta
Implementation notes:
- Have unified all vargs processing during margs phase
- vargs no longer contains message format; now extracted as separate :?msg-fmt
- :output-fn and :timezone_ are now cached between appenders when possible
- :output_ now provides a convenient, cache-aware way of getting formatted ouput
- Hash stuff is now non-configurable but far more predictable; if appenders
have specific needs beyond :hash_, they should use a custom (fn [data]).
2016-06-20 11:21:48 +00:00
:?err ; First-arg platform error, or nil
:vargs ; Vector of raw args
:output_ ; Forceable - final formatted output string created
; by calling (output-fn < this-data-map > )
:msg_ ; Forceable - args as a string
:timestamp_ ; Forceable - string
:hostname_ ; Forceable - string (clj only)
2015-05-27 07:10:01 +00:00
:output-fn ; (fn [data]) -> formatted output string
2015-07-20 11:40:20 +00:00
; (see `default-output-fn` for details)
2015-07-17 12:01:01 +00:00
:context ; *context* value at log time (see `with-context` )
2015-05-26 05:43:22 +00:00
2016-06-20 11:22:05 +00:00
**NB** - any keys not specifically documented here should be
considered private / subject to change without notice.
2015-05-26 05:43:22 +00:00
MIDDLEWARE
Middleware are simple (fn [data]) -> ?data fns (applied left->right) that
2016-06-20 11:22:05 +00:00
transform the data map dispatched to appender fns. If any middleware
returns nil, NO dispatch will occur (i.e. the event will be filtered).
2015-05-26 05:43:22 +00:00
The `example-config` source code contains further settings and details.
2013-11-30 13:19:56 +00:00
See also `set-config!` , `merge-config!` , `set-level!` ."
2015-05-26 07:28:07 +00:00
{:level :debug ; e/o #{:trace :debug :info :warn :error :fatal :report}
;; Control log filtering by namespaces/patterns. Useful for turning off
;; logging in noisy libraries, etc.:
2015-05-26 15:40:56 +00:00
:ns-whitelist [] #_ ["my-app.foo-ns"]
:ns-blacklist [] #_ ["taoensso.*"]
2015-05-26 07:28:07 +00:00
:middleware [] ; (fns [data]) -> ?data, applied left->right
2015-05-27 07:10:01 +00:00
;; Clj only:
:timestamp-opts default-timestamp-opts ; {:pattern _ :locale _ :timezone _}
:output-fn default-output-fn ; (fn [data]) -> string
2015-05-26 07:28:07 +00:00
:appenders
2016-02-27 02:30:50 +00:00
{;; The standard println appender:
;; :println (println-appender {:stream :auto})
:an-example-custom-println-appender
;; Inline appender definition (just a map):
{:enabled? true
:async? false
:min-level nil
:rate-limit [[1 250] [10 5000]] ; 1/250ms, 10/5s
:output-fn :inherit
:fn ; Appender's (fn [data]) -> side effects
(fn [data]
2016-06-17 14:15:14 +00:00
(let [{:keys [output_]} data
formatted-output-str (force output_)]
2016-02-27 02:30:50 +00:00
(println formatted-output-str)))}}})
2012-07-03 09:30:50 +00:00
```
2013-11-30 13:19:56 +00:00
A few things to note:
2016-06-17 14:15:14 +00:00
2016-01-14 05:17:22 +00:00
* Appenders are _trivial_ to write & configure - **they're just fns** . It's Timbre's job to dispatch useful args to appenders when appropriate, it's their job to do something interesting with them.
* Being 'just fns', appenders have basically limitless potential: write to your database, send a message over the network, check some other state (e.g. environment config) before making a choice, etc.
2012-05-28 10:49:54 +00:00
2015-05-29 05:37:52 +00:00
#### Log levels and ns filters
2015-05-26 14:43:28 +00:00
The **log level** may be set:
2016-06-17 14:15:14 +00:00
* At compile-time: (`TIMBRE_LEVEL` environment variable).
* Statically using: `timbre/set-level!` /`timbre/merge-level!`.
* Dynamically using: `timbre/with-level` .
2015-05-26 11:15:28 +00:00
2015-05-29 05:37:52 +00:00
The **ns filters** may be set:
2016-06-17 14:15:14 +00:00
* At compile-time: (`TIMBRE_NS_WHITELIST`, `TIMBRE_NS_BLACKLIST` env vars).
* Statically using: `timbre/set-config!` /`timbre/merge-config!`.
* Dynamically using: `timbre/with-config` .
2015-05-29 05:37:52 +00:00
2015-07-29 06:42:19 +00:00
There are also variants of the core logging macros that take an **explicit config arg** :
2016-06-17 14:15:14 +00:00
2015-07-29 06:42:19 +00:00
```clojure
(timbre/log* < config-map > < level > < & args>) ; or
(timbre/logf* < config-map > < level > < & args>)
```
2013-11-30 10:03:04 +00:00
2015-05-29 05:37:52 +00:00
Logging calls excluded by a compile-time option (e.g. during Cljs compilation) will be **entirely elided from your codebase** , e.g.:
2016-06-17 14:15:14 +00:00
2015-05-29 05:37:52 +00:00
```bash
#!/bin/bash
# edn values welcome:
export TIMBRE_LEVEL=':warn' # Elide all lower logging calls
export TIMBRE_NS_WHITELIST='["my-app.*"]' # Elide all other ns logging calls
export TIMBRE_NS_BLACKLIST='["my-app.foo" "my-app.bar.*"]'
lein cljsbuild once # Compile js with appropriate logging calls excluded
lein uberjar # Compile jar ''
```
2013-06-01 12:41:07 +00:00
### Built-in appenders
2012-07-13 10:07:23 +00:00
2016-01-14 05:17:22 +00:00
#### Redis ([Carmine]) appender (v3+)
2012-07-13 10:07:23 +00:00
```clojure
2016-01-14 05:17:22 +00:00
;; [com.taoensso/carmine < latest-version > ] ; Add to project.clj deps
2015-02-23 14:49:32 +00:00
;; (:require [taoensso.timbre.appenders (carmine :as car-appender)]) ; Add to ns
2013-12-01 09:49:31 +00:00
2015-08-11 12:53:03 +00:00
(timbre/merge-config! {:appenders {:carmine (car-appender/carmine-appender)}})
2012-07-13 10:07:23 +00:00
```
2013-12-01 09:49:31 +00:00
This gives us a high-performance Redis appender:
2016-06-17 14:15:14 +00:00
2016-06-20 11:22:05 +00:00
* **All raw logging args are preserved** in serialized form (even errors).
* Configurable number of entries to keep per log level.
* Only the most recent instance of each **unique entry** is kept.
* Resulting **log is just a Clojure value** : a vector of log entries (maps).
Clojure has a rich selection of built-in and 3rd party tools for querying values like this.
2013-12-01 09:49:31 +00:00
2016-06-20 11:22:05 +00:00
See also `car-appender/query-entries` .
2013-12-01 09:49:31 +00:00
2016-01-14 05:17:22 +00:00
#### Email ([Postal]) appender
2012-05-28 10:49:54 +00:00
```clojure
2016-01-14 05:17:22 +00:00
;; [com.draines/postal < latest-version > ] ; Add to project.clj deps
2013-05-15 09:41:41 +00:00
;; (:require [taoensso.timbre.appenders (postal :as postal-appender)]) ; Add to ns
2012-07-13 10:07:23 +00:00
2015-05-26 05:43:22 +00:00
(timbre/merge-config!
2015-08-11 12:53:03 +00:00
{:appenders
{:postal
(postal-appender/postal-appender
^{:host "mail.isp.net" :user "jsmith" :pass "sekrat!!1"}
{:from "me@draines.com" :to "foo@example.com"})}})
2012-05-28 10:49:54 +00:00
```
2013-12-01 09:49:31 +00:00
#### File appender
2013-04-19 13:28:02 +00:00
2013-11-30 13:19:56 +00:00
```clojure
2015-06-10 22:05:29 +00:00
;; (:require [taoensso.timbre.appenders.core :as appenders]) ; Add to ns
2015-05-26 05:43:22 +00:00
(timbre/merge-config!
2015-06-10 22:05:29 +00:00
{:appenders {:spit (appenders/spit-appender {:fname "/path/my-file.log"})}})
2013-11-30 13:19:56 +00:00
```
2013-06-05 12:52:23 +00:00
2013-11-30 13:19:56 +00:00
#### Other included appenders
2012-05-28 10:49:54 +00:00
2016-06-17 14:15:14 +00:00
A number of 3rd-party appenders are included out-the-box [here ](https://github.com/ptaoussanis/timbre/tree/master/src/taoensso/timbre/appenders/3rd_party ). **Please see the relevant docstring for details** . Thanks goes to the respective authors!
2012-07-03 11:48:00 +00:00
2016-06-17 14:15:14 +00:00
Just give me a shout if you've got an appender you'd like to have added.
## Profiling
2016-07-11 08:12:37 +00:00
As of v4.6.0, Timbre's profiling features have been enhanced and exported to a dedicated profiling library called [Tufte].
2012-07-03 11:48:00 +00:00
2016-07-11 08:12:37 +00:00
Timbre's old profiling features **will be kept for backwards compatibility** throughout v4.x, but future development will be focused exclusively on Tufte.
2012-07-03 11:48:00 +00:00
2016-07-11 08:12:37 +00:00
Tufte has out-the-box support for integration with Timbre, and [migration ](https://github.com/ptaoussanis/tufte#how-does-tufte-compare-to-the-profiling-in-timbre ) is usually simple.
2012-07-03 11:48:00 +00:00
2016-07-11 08:12:37 +00:00
Sorry for the inconvenience!
2012-05-28 08:13:11 +00:00
2016-01-14 05:17:22 +00:00
## This project supports the ![ClojureWerkz-logo] goals
2013-06-01 12:41:07 +00:00
2016-06-17 14:15:14 +00:00
[ClojureWerkz] is a growing collection of open-source, **batteries-included Clojure libraries** that emphasise modern targets, great documentation, and thorough testing.
2012-06-16 06:53:38 +00:00
2016-01-14 05:17:22 +00:00
## Contacting me / contributions
2012-11-05 17:48:42 +00:00
2016-01-14 05:17:22 +00:00
Please use the project's [GitHub issues page] for all questions, ideas, etc. **Pull requests welcome** . See the project's [GitHub contributors page] for a list of contributors.
2014-02-25 07:38:19 +00:00
2016-01-14 05:17:22 +00:00
Otherwise, you can reach me at [Taoensso.com]. Happy hacking!
2012-05-28 08:13:11 +00:00
2016-01-14 05:17:22 +00:00
\- [Peter Taoussanis]
2012-05-28 08:13:11 +00:00
## License
2016-01-14 05:17:22 +00:00
Distributed under the [EPL v1.0] \(same as Clojure).
Copyright © 2015-2016 [Peter Taoussanis].
2014-02-22 18:32:35 +00:00
2016-01-14 05:17:22 +00:00
<!-- - Standard links -->
[Taoensso.com]: https://www.taoensso.com
[Peter Taoussanis]: https://www.taoensso.com
[@ptaoussanis]: https://www.taoensso.com
[More by @ptaoussanis ]: https://www.taoensso.com
2015-03-20 08:20:23 +00:00
[Break Version]: https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md
2016-06-17 14:15:14 +00:00
[support my continued open-source Clojure/Script work]: http://taoensso.com/clojure/backers
2015-12-22 06:17:13 +00:00
2016-01-14 05:17:22 +00:00
<!-- - Standard links (repo specific) -->
[CHANGELOG]: https://github.com/ptaoussanis/timbre/releases
[API]: http://ptaoussanis.github.io/timbre/
[GitHub issues page]: https://github.com/ptaoussanis/timbre/issues
[GitHub contributors page]: https://github.com/ptaoussanis/timbre/graphs/contributors
[EPL v1.0]: https://raw.githubusercontent.com/ptaoussanis/timbre/master/LICENSE
[Hero]: https://raw.githubusercontent.com/ptaoussanis/timbre/master/hero.png "Title"
<!-- - Unique links -->
2016-06-17 14:15:14 +00:00
[logging profiler]: #profiling
2016-01-14 05:17:22 +00:00
[Logs as Clojure values]: #redis -carmine-appender-v3
2015-12-22 06:17:13 +00:00
[@palletops/log-config]: https://github.com/palletops/log-config
[@fzakaria/slf4j-timbre]: https://github.com/fzakaria/slf4j-timbre
2016-01-14 05:17:22 +00:00
[io.aviso.exception]: https://github.com/AvisoNovate/pretty
[Carmine]: https://github.com/ptaoussanis/carmine
2016-07-11 08:12:37 +00:00
[Tufte]: https://github.com/ptaoussanis/tufte
2016-01-14 05:17:22 +00:00
[Postal]: https://github.com/drewr/postal
[ClojureWerkz-logo]: https://raw.github.com/clojurewerkz/clojurewerkz.org/master/assets/images/logos/clojurewerkz_long_h_50.png
2016-01-26 13:42:25 +00:00
[ClojureWerkz]: http://clojurewerkz.org/