diff --git a/CHANGELOG.md b/CHANGELOG.md index bbf0f69..60ea7f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +> This project uses [Break Versioning](https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md) as of **Aug 16, 2014**. + +## v3.3.0 / 2014 May 8 + + * **CHANGE**: Update IRC appender to Timbre v3 style (@crisptrutski). + * **FIX** [#47]: correctly format nanosecond profiling times. + * **FIX** [#77]: profile ids now use correct (compile-time rather than runtime) ns prefix. + * **NEW**: Add zmq appender (@angusiguess). + * **NEW** [#75]: Make defnp support multi-arity functions (@maurolopes) + + ## v3.2.1 / 2014 May 7 * **FIX**: missing tools.reader upstream dependency (@ducky427). @@ -5,7 +16,7 @@ ## v3.2.0 / 2014 May 6 - * [#60] **FIX**: `defnp` no longer generates an Eastwood warning (@ducky427). + * **FIX** [#60]: `defnp` no longer generates an Eastwood warning (@ducky427). * **CHANGE**: Improved profiling memory efficiency (max memory use, was previously unbounded). * **CHANGE**: Profiling: make larger call numbers easier to read. * [#63]: **NEW**: Add support for thread-local configuration (@jameswarren). @@ -13,19 +24,19 @@ ## v3.1.6 / 2014 Mar 16 - * [#56] FIX: `defnp`/`p` head retention issue (kyptin). + * **FIX** [#56]: `defnp`/`p` head retention issue (@kyptin). ## v3.1.5 / 2014 Mar 15 - * FIX: `profiling/p*` was defined incorrectly (kyptin). + * **FIX**: `profiling/p*` was defined incorrectly (@kyptin). ## v3.1.4 / 2014 Mar 13 - * NEW: Add `profiling/p*` macro. - * CHANGE: Include `p`, `p*` in `refer-timbre` imports. - * FIX: rotor appender not rotating (iantruslove, kurtharriger). + * **NEW**: Add `profiling/p*` macro. + * **CHANGE**: Include `p`, `p*` in `refer-timbre` imports. + * **FIX**: rotor appender not rotating (@iantruslove, @kurtharriger). ## v3.1.3 / 2014 Mar 11 diff --git a/README.md b/README.md index d67cb5e..2127cd4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ -**[API docs][]** | **[CHANGELOG][]** | [other Clojure libs][] | [Twitter][] | [contact/contributing](#contact--contributing) | current ([semantic][]) version: +**[API docs][]** | **[CHANGELOG][]** | [other Clojure libs][] | [Twitter][] | [contact/contrib](#contact--contributing) | current [Break Version][]: ```clojure -[com.taoensso/timbre "3.2.1"] ; Stable +[com.taoensso/timbre "3.3.0"] ``` v3 is a **major, backwards-compatible release**. Please see the [CHANGELOG][] for details. Appender authors: please see [here](https://github.com/ptaoussanis/timbre/issues/41) about migrating Timbre 2.x appenders to 3.x's recommended style. @@ -34,7 +34,7 @@ Logging with Java can be maddeningly, unnecessarily hard. Particularly if all yo Add the necessary dependency to your [Leiningen][] `project.clj` and use the supplied ns-import helper: ```clojure -[com.taoensso/timbre "3.2.1"] ; project.clj +[com.taoensso/timbre "3.3.0"] ; project.clj (ns my-app (:require [taoensso.timbre :as timbre])) ; Your ns (timbre/refer-timbre) ; Provides useful Timbre aliases in this ns @@ -295,7 +295,8 @@ Copyright © 2012-2014 Peter Taoussanis. Distributed under the [Eclipse Publ [CHANGELOG]: [other Clojure libs]: [Twitter]: -[semantic]: +[SemVer]: +[Break Version]: [Leiningen]: [CDS]: [ClojureWerkz]: diff --git a/project.clj b/project.clj index 9ac9aa0..62e94cd 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject com.taoensso/timbre "3.2.1" +(defproject com.taoensso/timbre "3.3.0" :author "Peter Taoussanis " :description "Clojure logging & profiling library" :url "https://github.com/ptaoussanis/timbre" @@ -9,42 +9,39 @@ :min-lein-version "2.3.3" :global-vars {*warn-on-reflection* true *assert* true} + :dependencies [[org.clojure/clojure "1.4.0"] - [com.taoensso/encore "1.5.1"] - [io.aviso/pretty "0.1.10"]] + [com.taoensso/encore "1.7.3"] + [io.aviso/pretty "0.1.12"]] - :test-paths ["test" "src"] :profiles {;; :default [:base :system :user :provided :dev] + :server-jvm {:jvm-opts ^:replace ["-server"]} :1.5 {:dependencies [[org.clojure/clojure "1.5.1"]]} :1.6 {:dependencies [[org.clojure/clojure "1.6.0"]]} - :test {:dependencies [[expectations "1.4.56"] - [org.clojure/test.check "0.5.7"] - [com.taoensso/nippy "2.6.3"] - [com.taoensso/carmine "2.6.2"] - [com.draines/postal "1.11.1"] - [org.clojure/tools.logging "0.2.6"]] + :test {:dependencies [[expectations "2.0.9"] + [org.clojure/test.check "0.5.9"] + [com.taoensso/nippy "2.7.0-RC1"] + [com.taoensso/carmine "2.7.0"] + [com.draines/postal "1.11.1"] + [org.clojure/tools.logging "0.3.0"]] :plugins [[lein-expectations "0.0.8"] [lein-autoexpect "1.2.2"]]} - :dev* [:dev {:jvm-opts ^:replace ["-server"] - ;; :hooks [cljx.hooks leiningen.cljsbuild] ; cljx - }] :dev [:1.6 :test - {:dependencies [] + {:dependencies [[irclj "0.5.0-alpha4"]] :plugins [[lein-ancient "0.5.4"] - [codox "0.6.7"]]}]} + [codox "0.8.10"]]}]} + + :test-paths ["test" "src"] - ;; :codox {:sources ["target/classes"]} ; cljx :aliases {"test-all" ["with-profile" "default:+1.5:+1.6" "expectations"] ;; "test-all" ["with-profile" "default:+1.6" "expectations"] "test-auto" ["with-profile" "+test" "autoexpect"] - ;; "build-once" ["do" "cljx" "once," "cljsbuild" "once"] ; cljx - ;; "deploy-lib" ["do" "build-once," "deploy" "clojars," "install"] ; cljx "deploy-lib" ["do" "deploy" "clojars," "install"] - "start-dev" ["with-profile" "+dev*" "repl" ":headless"]} + "start-dev" ["with-profile" "+server-jvm" "repl" ":headless"]} :repositories {"sonatype" diff --git a/src/taoensso/timbre.clj b/src/taoensso/timbre.clj index f462adf..fccca49 100644 --- a/src/taoensso/timbre.clj +++ b/src/taoensso/timbre.clj @@ -283,6 +283,9 @@ (let [juxtfn-args (if-not msg-type juxtfn-args ; tools.logging (-> juxtfn-args (dissoc :msg-type) + ;; TODO Consider a breaking change here to + ;; swap assoc'd message with a delay, as + ;; per http://goo.gl/7YVSfj: (assoc :message (when-not (empty? args) (case msg-type @@ -403,6 +406,9 @@ })) nil)) +(defmacro get-compile-time-ns [] (str *ns*)) ; Nb need `str` to be readable +(comment (macroexpand '(get-compile-time-ns))) + (defmacro log* "Implementation detail." {:arglists '([base-appender-args msg-type level & log-args] [base-appender-args msg-type config level & log-args])} @@ -420,7 +426,7 @@ default-config?# (levels-scored s1#) config# (if default-config?# (get-default-config) s1#) level# (if default-config?# s1# ~s2) - compile-time-ns# ~(str *ns*)] + compile-time-ns# (get-compile-time-ns)] ;; (println "DEBUG: Runtime level check") (when (and (level-sufficient? level# config#) (ns-unfiltered? config# compile-time-ns#)) diff --git a/src/taoensso/timbre/appenders/irc.clj b/src/taoensso/timbre/appenders/irc.clj index d4b2993..4813e1a 100644 --- a/src/taoensso/timbre/appenders/irc.clj +++ b/src/taoensso/timbre/appenders/irc.clj @@ -2,45 +2,74 @@ "IRC appender. Depends on https://github.com/flatland/irclj." {:author "Emlyn Corrin"} (:require [clojure.string :as str] - [irclj.core :as irclj] + [irclj.core :as irc] [taoensso.timbre :as timbre])) -(def conn (atom nil)) +(defn default-fmt-output-fn + [{:keys [level throwable message]}] + (format "[%s] %s%s" + (-> level name (str/upper-case)) + (or message "") + (or (timbre/stacktrace throwable "\n") ""))) -(defn connect [{:keys [host port pass nick user name chan] - :or {:port 6667}}] - (let [conn (irclj/connect host port nick - :username user - :real-name name - :pass pass - :callbacks {})] - (irclj/join conn chan) +(def default-appender-opts + {:async? true + :enabled? true + :min-level :info}) + +(defn- connect [{:keys [host port pass nick user name chan] + :or {port 6667}}] + (let [conn (irc/connect host port nick + :username user + :real-name name + :pass pass + :callbacks {})] + (irc/join conn chan) conn)) -(defn ensure-conn [conf] - (swap! conn #(or % (connect conf)))) +(defn- ensure-conn [conn conf] + (if-not @conn + (reset! conn @(connect conf)))) -(defn send-message [{:keys [prefix throwable message chan] :as config}] - (let [conn (ensure-conn config) - lines (-> (str message (timbre/stacktrace throwable "\n")) - (str/split #"\n"))] - (irclj/message conn chan prefix (first lines)) - (doseq [line (rest lines)] - (irclj/message conn chan ">" line)))) +(defn- send-message [conn chan output] + (let [[fst & rst] (str/split output #"\n")] + (irc/message conn chan fst) + (doseq [line rst] + (irc/message conn chan ">" line)))) -(defn appender-fn [{:keys [ap-config prefix throwable message]}] - (when-let [irc-config (:irc ap-config)] - (send-message - (assoc irc-config - :prefix prefix - :throwable throwable - :message message)))) +(defn- make-appender-fn [irc-config conn] + (fn [{:keys [ap-config] :as args}] + (when-let [irc-config (or irc-config (:irc ap-config))] + (ensure-conn conn irc-config) + (let [fmt-fn (or (:fmt-output-fn irc-config) + default-fmt-output-fn)] + (send-message conn (:chan irc-config) (fmt-fn args)))))) -(def irc-appender - {:doc (str "Sends IRC messages using irclj.\n" - "Needs :irc config map in :shared-appender-config, e.g.: - {:host \"irc.example.org\" :port 6667 :nick \"logger\" - :name \"My Logger\" :chan \"#logs\"") - :min-level :info :enabled? true - :prefix-fn (fn [{:keys [level]}] (-> level name str/upper-case)) - :fn appender-fn}) +;;; Public + +(defn make-irc-appender + "Sends IRC messages using irc. + Needs :irc config map in :shared-appender-config, e.g.: + {:host \"irc.example.org\" :port 6667 :nick \"logger\" + :name \"My Logger\" :chan \"#logs\"}" + [& [appender-opts {:keys [irc-config]}]] + (let [conn (atom nil)] + (merge default-appender-opts + appender-opts + {:conn conn + :doc (:doc (meta #'make-irc-appender)) + :fn (make-appender-fn irc-config conn)}))) + +(def irc-appender "DEPRECATED: Use `make-irc-appender` instead." + (make-irc-appender)) + +(comment + (timbre/set-config! + [:shared-appender-config :irc] + {:host "127.0.0.1" + :nick "lazylog" + :user "lazare" + :name "Lazylus Logus" + :chan "bob"}) + (timbre/set-config! [:appenders :irc] (make-irc-appender)) + (timbre/log :error "A multiple\nline message\nfor you")) diff --git a/src/taoensso/timbre/appenders/zmq.clj b/src/taoensso/timbre/appenders/zmq.clj new file mode 100644 index 0000000..abfc628 --- /dev/null +++ b/src/taoensso/timbre/appenders/zmq.clj @@ -0,0 +1,34 @@ +(ns taoensso.timbre.appenders.zmq + "ØMQ appender. Requires https://github.com/zeromq/cljzmq" + {:author "Angus Fletcher"} + (:require [zeromq.zmq :as zmq] + [taoensso.timbre :as timbre])) + +(defn make-zmq-socket [context transport address port] + (doto (zmq/socket context :push) + (zmq/connect (format "%s://%s:%d" transport address port)))) + +(defn appender-fn [socket poller {:keys [ap-config output]}] + (loop [] + (zmq/poll poller 500) + (cond + (zmq/check-poller poller 0 :pollout) (zmq/send-str socket output) + (zmq/check-poller poller 0 :pollerr) (System/exit 1) + :else (recur)))) + +(defn make-zmq-appender + "Returns a ØMQ appender. Takes appender options and a map consisting of: + transport: a string representing transport type: tcp, ipc, inproc, pgm/epgm + address: a string containing an address to connect to. + port: a number representing the port to connect to." + [& [appender-opts {:keys [transport address port]}]] + (let [default-appender-opts {:enabled? true + :min-level :error + :async? true} + context (zmq/zcontext) + socket (make-zmq-socket context transport address port) + poller (doto (zmq/poller context) + (zmq/register socket :pollout :pollerr))] + (merge default-appender-opts + appender-opts + {:fn (partial appender-fn socket poller)}))) diff --git a/src/taoensso/timbre/profiling.clj b/src/taoensso/timbre/profiling.clj index 68a43da..9f30adf 100644 --- a/src/taoensso/timbre/profiling.clj +++ b/src/taoensso/timbre/profiling.clj @@ -9,7 +9,7 @@ (defmacro fq-keyword "Returns namespaced keyword for given id." [id] `(if (and (keyword? ~id) (namespace ~id)) ~id - (keyword (str *ns*) (name ~id)))) + (keyword (timbre/get-compile-time-ns) (name ~id)))) (comment (map #(fq-keyword %) ["foo" :foo :foo/bar])) @@ -56,7 +56,7 @@ (declare ^:private format-stats) (defmacro with-pdata [level & body] - `(if-not (timbre/logging-enabled? ~level ~(str *ns*)) + `(if-not (timbre/logging-enabled? ~level (timbre/get-compile-time-ns)) {:result (do ~@body)} (binding [*pdata* (atom {})] {:result (pspy ::clock-time ~@body) @@ -85,20 +85,6 @@ (if-not (< (rand) ~probability) (do ~@body) (profile ~level ~id ~@body)))) -(defmacro defnp "Like `defn` but wraps body in `p` macro." - {:arglists '([name ?doc-string ?attr-map [params] ?prepost-map body])} - [name & sigs] - (let [[name [params & sigs]] (encore/name-with-attrs name sigs) - prepost-map (when (and (map? (first sigs)) (next sigs)) (first sigs)) - body (if prepost-map (next sigs) sigs)] - `(defn ~name ~params ~prepost-map - (pspy ~(clojure.core/name name) - ~@body)))) - -(comment (defnp foo "Docstring "[x] "boo" (* x x)) - (macroexpand '(defnp foo "Docstring "[x] "boo" (* x x))) - (profile :info :defnp-test (foo 5))) - ;;;; Data capturing & aggregation (def ^:private ^:constant stats-gc-n 111111) @@ -193,7 +179,8 @@ s-pattern (str "%" max-id-width "s %11s %9s %10s %9s %9s %7s %1s%n") perc #(Math/round (/ %1 %2 0.01)) ft (fn [nanosecs] - (let [pow #(Math/pow 10 %) + (let [nanosecs (long nanosecs) ; Truncate any fractional nanosecs + pow #(Math/pow 10 %) ok-pow? #(>= nanosecs (pow %)) to-pow #(encore/round (/ nanosecs (pow %1)) :round %2)] (cond (ok-pow? 9) (str (to-pow 9 1) "s") @@ -213,19 +200,39 @@ (printf s-pattern "Accounted Time" "" "" "" "" "" (perc accounted clock-time) (ft accounted))))) -(defmacro defnp "Like `defn` but wraps body in `p` macro." - {:arglists '([name ?doc-string ?attr-map [params] ?prepost-map body])} - [name & sigs] - (let [[name [params & sigs]] (encore/name-with-attrs name sigs) - prepost-map (when (and (map? (first sigs)) (next sigs)) (first sigs)) - body (if prepost-map (next sigs) sigs)] - `(defn ~name ~params ~(or prepost-map {}) - (pspy ~(clojure.core/name name) - ~@body)))) +;;;; -(comment (defnp foo "Docstring "[x] "boo" (* x x)) - (macroexpand '(defnp foo "Docstring "[x] "boo" (* x x))) - (profile :info :defnp-test (foo 5))) +(defmacro defnp "Like `defn` but wraps fn bodies with `p` macro." + {:arglists + '([name doc-string? attr-map? [params*] prepost-map? body] + [name doc-string? attr-map? ([params*] prepost-map? body)+ attr-map?])} + [name' & sigs] + (let [[name' sigs] (encore/name-with-attrs name' sigs) + single-arity? (vector? (first sigs)) + [sigs func->str] + (if single-arity? + [(list sigs) (fn [name' _params] (name name'))] + [sigs (fn [name' params] (str (name name') \_ (count params)))]) + + new-sigs + (map (fn [[params & others]] + (let [has-prepost-map? (and (map? (first others)) (next others)) + [prepost-map & body] + (if has-prepost-map? + others + (cons {} others))] + `(~params ~prepost-map (pspy ~(func->str name' params) ~@body)))) + sigs)] + `(defn ~name' ~@new-sigs))) + +(comment + (defnp foo "Docstring "[x] "boo" (* x x)) + (macroexpand '(defnp foo "Docstring" [x] "boo" (* x x))) + (macroexpand '(defnp foo "Docstring" ([x] (* x x)) + ([x y] (* x y)))) + (profile :info :defnp-test (foo 5))) + +;;;; (comment (profile :info :sleepy-threads