annotate src/clojure/contrib/logging.clj @ 10:ef7dbbd6452c

added clojure source goodness
author Robert McIntyre <rlm@mit.edu>
date Sat, 21 Aug 2010 06:25:44 -0400
parents
children
rev   line source
rlm@10 1 ;;; logging.clj -- delegated logging for Clojure
rlm@10 2
rlm@10 3 ;; by Alex Taggart
rlm@10 4 ;; July 27, 2009
rlm@10 5
rlm@10 6 ;; Copyright (c) Alex Taggart, July 2009. All rights reserved. The use
rlm@10 7 ;; and distribution terms for this software are covered by the Eclipse
rlm@10 8 ;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
rlm@10 9 ;; which can be found in the file epl-v10.html at the root of this
rlm@10 10 ;; distribution. By using this software in any fashion, you are
rlm@10 11 ;; agreeing to be bound by the terms of this license. You must not
rlm@10 12 ;; remove this notice, or any other, from this software.
rlm@10 13 (ns
rlm@10 14 ^{:author "Alex Taggart, Timothy Pratley",
rlm@10 15 :doc
rlm@10 16 "Logging macros which delegate to a specific logging implementation. At
rlm@10 17 runtime a specific implementation is selected from, in order, Apache
rlm@10 18 commons-logging, log4j, and finally java.util.logging.
rlm@10 19
rlm@10 20 Logging levels are specified by clojure keywords corresponding to the
rlm@10 21 values used in log4j and commons-logging:
rlm@10 22 :trace, :debug, :info, :warn, :error, :fatal
rlm@10 23
rlm@10 24 Logging occurs with the log macro, or the level-specific convenience macros,
rlm@10 25 which write either directly or via an agent. For performance reasons, direct
rlm@10 26 logging is enabled by default, but setting the *allow-direct-logging* boolean
rlm@10 27 atom to false will disable it. If logging is invoked within a transaction it
rlm@10 28 will always use an agent.
rlm@10 29
rlm@10 30 The log macros will not evaluate their 'message' unless the specific logging
rlm@10 31 level is in effect. Alternately, you can use the spy macro when you have code
rlm@10 32 that needs to be evaluated, and also want to output the code and its result to
rlm@10 33 the debug log.
rlm@10 34
rlm@10 35 Unless otherwise specified, the current namespace (as identified by *ns*) will
rlm@10 36 be used as the log-ns (similar to how the java class name is usually used).
rlm@10 37 Note: your log configuration should display the name that was passed to the
rlm@10 38 logging implementation, and not perform stack-inspection, otherwise you'll see
rlm@10 39 something like \"fn__72$impl_write_BANG__39__auto____81\" in your logs.
rlm@10 40
rlm@10 41 Use the enabled? macro to write conditional code against the logging level
rlm@10 42 (beyond simply whether or not to call log, which is handled automatically).
rlm@10 43
rlm@10 44 You can redirect all java writes of System.out and System.err to the log
rlm@10 45 system by calling log-capture!. To rebind *out* and *err* to the log system
rlm@10 46 invoke with-logs. In both cases a log-ns (e.g., \"com.example.captured\")
rlm@10 47 needs to be specified to namespace the output."}
rlm@10 48 clojure.contrib.logging)
rlm@10 49
rlm@10 50 (declare *impl-name* impl-get-log impl-enabled? impl-write!)
rlm@10 51
rlm@10 52 ;; Macros used so that implementation-specific functions all have the same meta.
rlm@10 53
rlm@10 54 (defmacro def-impl-name
rlm@10 55 {:private true} [& body]
rlm@10 56 `(def
rlm@10 57 ^{:doc "The name of the logging implementation used."}
rlm@10 58 *impl-name*
rlm@10 59 ~@body))
rlm@10 60
rlm@10 61 (defmacro def-impl-get-log
rlm@10 62 {:private true} [& body]
rlm@10 63 `(def
rlm@10 64 ^{:doc
rlm@10 65 "Returns an implementation-specific log by string namespace. End-users should
rlm@10 66 not need to call this."
rlm@10 67 :arglist '([~'log-ns])}
rlm@10 68 impl-get-log
rlm@10 69 (memoize ~@body)))
rlm@10 70
rlm@10 71 (defmacro def-impl-enabled?
rlm@10 72 {:private true} [& body]
rlm@10 73 `(def
rlm@10 74 ^{:doc
rlm@10 75 "Implementation-specific check if a particular level is enabled. End-users
rlm@10 76 should not need to call this."
rlm@10 77 :arglist '([~'log ~'level])}
rlm@10 78 impl-enabled?
rlm@10 79 ~@body))
rlm@10 80
rlm@10 81 (defmacro def-impl-write!
rlm@10 82 {:private true} [& body]
rlm@10 83 `(def
rlm@10 84 ^{:doc
rlm@10 85 "Implementation-specific write of a log message. End-users should not need to
rlm@10 86 call this."
rlm@10 87 :arglist '([~'log ~'level ~'message ~'throwable])}
rlm@10 88 impl-write!
rlm@10 89 ~@body))
rlm@10 90
rlm@10 91 (defn- commons-logging
rlm@10 92 "Defines the commons-logging-based implementations of the core logging
rlm@10 93 functions. End-users should never need to call this."
rlm@10 94 []
rlm@10 95 (try
rlm@10 96 (import (org.apache.commons.logging LogFactory Log))
rlm@10 97 (eval
rlm@10 98 `(do
rlm@10 99 (def-impl-name "org.apache.commons.logging")
rlm@10 100 (def-impl-get-log
rlm@10 101 (fn [log-ns#]
rlm@10 102 (org.apache.commons.logging.LogFactory/getLog ^String log-ns#)))
rlm@10 103 (def-impl-enabled?
rlm@10 104 (fn [^org.apache.commons.logging.Log log# level#]
rlm@10 105 (condp = level#
rlm@10 106 :trace (.isTraceEnabled log#)
rlm@10 107 :debug (.isDebugEnabled log#)
rlm@10 108 :info (.isInfoEnabled log#)
rlm@10 109 :warn (.isWarnEnabled log#)
rlm@10 110 :error (.isErrorEnabled log#)
rlm@10 111 :fatal (.isFatalEnabled log#))))
rlm@10 112 (def-impl-write!
rlm@10 113 (fn [^org.apache.commons.logging.Log log# level# msg# e#]
rlm@10 114 (condp = level#
rlm@10 115 :trace (.trace log# msg# e#)
rlm@10 116 :debug (.debug log# msg# e#)
rlm@10 117 :info (.info log# msg# e#)
rlm@10 118 :warn (.warn log# msg# e#)
rlm@10 119 :error (.error log# msg# e#)
rlm@10 120 :fatal (.fatal log# msg# e#))))
rlm@10 121 true))
rlm@10 122 (catch Exception e nil)))
rlm@10 123
rlm@10 124
rlm@10 125 (defn- log4j-logging
rlm@10 126 "Defines the log4j-based implementations of the core logging functions.
rlm@10 127 End-users should never need to call this."
rlm@10 128 []
rlm@10 129 (try
rlm@10 130 (import (org.apache.log4j Logger Level))
rlm@10 131 (eval
rlm@10 132 '(do
rlm@10 133 (def-impl-name "org.apache.log4j")
rlm@10 134 (def-impl-get-log
rlm@10 135 (fn [log-ns#]
rlm@10 136 (org.apache.log4j.Logger/getLogger ^String log-ns#)))
rlm@10 137 (let [levels# {:trace org.apache.log4j.Level/TRACE
rlm@10 138 :debug org.apache.log4j.Level/DEBUG
rlm@10 139 :info org.apache.log4j.Level/INFO
rlm@10 140 :warn org.apache.log4j.Level/WARN
rlm@10 141 :error org.apache.log4j.Level/ERROR
rlm@10 142 :fatal org.apache.log4j.Level/FATAL}]
rlm@10 143 (def-impl-enabled?
rlm@10 144 (fn [^org.apache.log4j.Logger log# level#]
rlm@10 145 (.isEnabledFor log# (levels# level#))))
rlm@10 146 (def-impl-write!
rlm@10 147 (fn [^org.apache.log4j.Logger log# level# msg# e#]
rlm@10 148 (if-not e#
rlm@10 149 (.log log# (levels# level#) msg#)
rlm@10 150 (.log log# (levels# level#) msg# e#)))))
rlm@10 151 true))
rlm@10 152 (catch Exception e nil)))
rlm@10 153
rlm@10 154
rlm@10 155 (defn- java-logging
rlm@10 156 "Defines the java-logging-based implementations of the core logging
rlm@10 157 functions. End-users should never need to call this."
rlm@10 158 []
rlm@10 159 (try
rlm@10 160 (import (java.util.logging Logger Level))
rlm@10 161 (eval
rlm@10 162 `(do
rlm@10 163 (def-impl-name "java.util.logging")
rlm@10 164 (def-impl-get-log
rlm@10 165 (fn [log-ns#]
rlm@10 166 (java.util.logging.Logger/getLogger log-ns#)))
rlm@10 167 (let [levels# {:trace java.util.logging.Level/FINEST
rlm@10 168 :debug java.util.logging.Level/FINE
rlm@10 169 :info java.util.logging.Level/INFO
rlm@10 170 :warn java.util.logging.Level/WARNING
rlm@10 171 :error java.util.logging.Level/SEVERE
rlm@10 172 :fatal java.util.logging.Level/SEVERE}]
rlm@10 173 (def-impl-enabled?
rlm@10 174 (fn [^java.util.logging.Logger log# level#]
rlm@10 175 (.isLoggable log# (levels# level#))))
rlm@10 176 (def-impl-write!
rlm@10 177 (fn [^java.util.logging.Logger log# level# msg# e#]
rlm@10 178 (if-not e#
rlm@10 179 (.log log# ^java.util.logging.Level (levels# level#)
rlm@10 180 ^String (str msg#))
rlm@10 181 (.log log# ^java.util.logging.Level (levels# level#)
rlm@10 182 ^String (str msg#) ^Throwable e#)))))
rlm@10 183 true))
rlm@10 184 (catch Exception e nil)))
rlm@10 185
rlm@10 186
rlm@10 187 ;; Initialize implementation-specific functions
rlm@10 188 (or (commons-logging)
rlm@10 189 (log4j-logging)
rlm@10 190 (java-logging)
rlm@10 191 (throw ; this should never happen in 1.5+
rlm@10 192 (RuntimeException.
rlm@10 193 "Valid logging implementation could not be found.")))
rlm@10 194
rlm@10 195
rlm@10 196 (def ^{:doc
rlm@10 197 "The default agent used for performing logging durng a transaction or when
rlm@10 198 direct logging is disabled."}
rlm@10 199 *logging-agent* (agent nil))
rlm@10 200
rlm@10 201
rlm@10 202 (def ^{:doc
rlm@10 203 "A boolean indicating whether direct logging (as opposed to via an agent) is
rlm@10 204 allowed when not operating from within a transaction. Defaults to true."}
rlm@10 205 *allow-direct-logging* (atom true))
rlm@10 206
rlm@10 207
rlm@10 208 (defmacro log
rlm@10 209 "Logs a message, either directly or via an agent. Also see the level-specific
rlm@10 210 convenience macros."
rlm@10 211 ([level message]
rlm@10 212 `(log ~level ~message nil))
rlm@10 213 ([level message throwable]
rlm@10 214 `(log ~level ~message ~throwable ~(str *ns*)))
rlm@10 215 ([level message throwable log-ns]
rlm@10 216 `(let [log# (impl-get-log ~log-ns)]
rlm@10 217 (if (impl-enabled? log# ~level)
rlm@10 218 (if (and @*allow-direct-logging*
rlm@10 219 (not (clojure.lang.LockingTransaction/isRunning)))
rlm@10 220 (impl-write! log# ~level ~message ~throwable)
rlm@10 221 (send-off *logging-agent*
rlm@10 222 (fn [_# l# v# m# t#] (impl-write! l# v# m# t#))
rlm@10 223 log# ~level ~message ~throwable))))))
rlm@10 224
rlm@10 225
rlm@10 226 (defmacro enabled?
rlm@10 227 "Returns true if the specific logging level is enabled. Use of this function
rlm@10 228 should only be necessary if one needs to execute alternate code paths beyond
rlm@10 229 whether the log should be written to."
rlm@10 230 ([level]
rlm@10 231 `(enabled? ~level ~(str *ns*)))
rlm@10 232 ([level log-ns]
rlm@10 233 `(impl-enabled? (impl-get-log ~log-ns) ~level)))
rlm@10 234
rlm@10 235
rlm@10 236 (defmacro spy
rlm@10 237 "Evaluates expr and outputs the form and its result to the debug log; returns
rlm@10 238 the result of expr."
rlm@10 239 [expr]
rlm@10 240 `(let [a# ~expr] (log :debug (str '~expr " => " a#)) a#))
rlm@10 241
rlm@10 242
rlm@10 243 (defn log-stream
rlm@10 244 "Creates a PrintStream that will output to the log. End-users should not need
rlm@10 245 to invoke this."
rlm@10 246 [level log-ns]
rlm@10 247 (java.io.PrintStream.
rlm@10 248 (proxy [java.io.ByteArrayOutputStream] []
rlm@10 249 (flush []
rlm@10 250 (proxy-super flush)
rlm@10 251 (let [s (.trim (.toString ^java.io.ByteArrayOutputStream this))]
rlm@10 252 (proxy-super reset)
rlm@10 253 (if (> (.length s) 0)
rlm@10 254 (log level s nil log-ns)))))
rlm@10 255 true))
rlm@10 256
rlm@10 257
rlm@10 258 (def ^{:doc
rlm@10 259 "A ref used by log-capture! to maintain a reference to the original System.out
rlm@10 260 and System.err streams."
rlm@10 261 :private true}
rlm@10 262 *old-std-streams* (ref nil))
rlm@10 263
rlm@10 264
rlm@10 265 (defn log-capture!
rlm@10 266 "Captures System.out and System.err, redirecting all writes of those streams
rlm@10 267 to :info and :error logging, respectively. The specified log-ns value will
rlm@10 268 be used to namespace all redirected logging. NOTE: this will not redirect
rlm@10 269 output of *out* or *err*; for that, use with-logs."
rlm@10 270 [log-ns]
rlm@10 271 (dosync
rlm@10 272 (let [new-out (log-stream :info log-ns)
rlm@10 273 new-err (log-stream :error log-ns)]
rlm@10 274 ; don't overwrite the original values
rlm@10 275 (if (nil? @*old-std-streams*)
rlm@10 276 (ref-set *old-std-streams* {:out System/out :err System/err}))
rlm@10 277 (System/setOut new-out)
rlm@10 278 (System/setErr new-err))))
rlm@10 279
rlm@10 280
rlm@10 281 (defn log-uncapture!
rlm@10 282 "Restores System.out and System.err to their original values."
rlm@10 283 []
rlm@10 284 (dosync
rlm@10 285 (when-let [{old-out :out old-err :err} @*old-std-streams*]
rlm@10 286 (ref-set *old-std-streams* nil)
rlm@10 287 (System/setOut old-out)
rlm@10 288 (System/setErr old-err))))
rlm@10 289
rlm@10 290
rlm@10 291 (defmacro with-logs
rlm@10 292 "Evaluates exprs in a context in which *out* and *err* are bound to :info and
rlm@10 293 :error logging, respectively. The specified log-ns value will be used to
rlm@10 294 namespace all redirected logging."
rlm@10 295 [log-ns & body]
rlm@10 296 (if (and log-ns (seq body))
rlm@10 297 `(binding [*out* (java.io.OutputStreamWriter.
rlm@10 298 (log-stream :info ~log-ns))
rlm@10 299 *err* (java.io.OutputStreamWriter.
rlm@10 300 (log-stream :error ~log-ns))]
rlm@10 301 ~@body)))
rlm@10 302
rlm@10 303 (defmacro trace
rlm@10 304 "Logs a message at the trace level."
rlm@10 305 ([message]
rlm@10 306 `(log :trace ~message))
rlm@10 307 ([message throwable]
rlm@10 308 `(log :trace ~message ~throwable)))
rlm@10 309
rlm@10 310 (defmacro debug
rlm@10 311 "Logs a message at the debug level."
rlm@10 312 ([message]
rlm@10 313 `(log :debug ~message))
rlm@10 314 ([message throwable]
rlm@10 315 `(log :debug ~message ~throwable)))
rlm@10 316
rlm@10 317 (defmacro info
rlm@10 318 "Logs a message at the info level."
rlm@10 319 ([message]
rlm@10 320 `(log :info ~message))
rlm@10 321 ([message throwable]
rlm@10 322 `(log :info ~message ~throwable)))
rlm@10 323
rlm@10 324 (defmacro warn
rlm@10 325 "Logs a message at the warn level."
rlm@10 326 ([message]
rlm@10 327 `(log :warn ~message))
rlm@10 328 ([message throwable]
rlm@10 329 `(log :warn ~message ~throwable)))
rlm@10 330
rlm@10 331 (defmacro error
rlm@10 332 "Logs a message at the error level."
rlm@10 333 ([message]
rlm@10 334 `(log :error ~message))
rlm@10 335 ([message throwable]
rlm@10 336 `(log :error ~message ~throwable)))
rlm@10 337
rlm@10 338 (defmacro fatal
rlm@10 339 "Logs a message at the fatal level."
rlm@10 340 ([message]
rlm@10 341 `(log :fatal ~message))
rlm@10 342 ([message throwable]
rlm@10 343 `(log :fatal ~message ~throwable)))