diff 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
line wrap: on
line diff
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/src/clojure/contrib/logging.clj	Sat Aug 21 06:25:44 2010 -0400
     1.3 @@ -0,0 +1,343 @@
     1.4 +;;; logging.clj -- delegated logging for Clojure
     1.5 + 
     1.6 +;; by Alex Taggart
     1.7 +;; July 27, 2009
     1.8 + 
     1.9 +;; Copyright (c) Alex Taggart, July 2009. All rights reserved.  The use
    1.10 +;; and distribution terms for this software are covered by the Eclipse
    1.11 +;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
    1.12 +;; which can be found in the file epl-v10.html at the root of this
    1.13 +;; distribution.  By using this software in any fashion, you are
    1.14 +;; agreeing to be bound by the terms of this license.  You must not
    1.15 +;; remove this notice, or any other, from this software.
    1.16 +(ns 
    1.17 +  ^{:author "Alex Taggart, Timothy Pratley",
    1.18 +     :doc
    1.19 +  "Logging macros which delegate to a specific logging implementation. At
    1.20 +  runtime a specific implementation is selected from, in order, Apache
    1.21 +  commons-logging, log4j, and finally java.util.logging.
    1.22 +  
    1.23 +  Logging levels are specified by clojure keywords corresponding to the
    1.24 +  values used in log4j and commons-logging:
    1.25 +    :trace, :debug, :info, :warn, :error, :fatal
    1.26 +  
    1.27 +  Logging occurs with the log macro, or the level-specific convenience macros,
    1.28 +  which write either directly or via an agent.  For performance reasons, direct
    1.29 +  logging is enabled by default, but setting the *allow-direct-logging* boolean
    1.30 +  atom to false will disable it. If logging is invoked within a transaction it
    1.31 +  will always use an agent.
    1.32 +  
    1.33 +  The log macros will not evaluate their 'message' unless the specific logging
    1.34 +  level is in effect. Alternately, you can use the spy macro when you have code
    1.35 +  that needs to be evaluated, and also want to output the code and its result to
    1.36 +  the debug log.
    1.37 +  
    1.38 +  Unless otherwise specified, the current namespace (as identified by *ns*) will
    1.39 +  be used as the log-ns (similar to how the java class name is usually used).
    1.40 +  Note: your log configuration should display the name that was passed to the
    1.41 +  logging implementation, and not perform stack-inspection, otherwise you'll see
    1.42 +  something like \"fn__72$impl_write_BANG__39__auto____81\" in your logs.
    1.43 +  
    1.44 +  Use the enabled? macro to write conditional code against the logging level
    1.45 +  (beyond simply whether or not to call log, which is handled automatically).
    1.46 +  
    1.47 +  You can redirect all java writes of System.out and System.err to the log
    1.48 +  system by calling log-capture!.  To rebind *out* and *err* to the log system
    1.49 +  invoke with-logs.  In both cases a log-ns (e.g., \"com.example.captured\")
    1.50 +  needs to be specified to namespace the output."}
    1.51 +  clojure.contrib.logging)
    1.52 +
    1.53 +(declare *impl-name* impl-get-log impl-enabled? impl-write!)
    1.54 +
    1.55 +;; Macros used so that implementation-specific functions all have the same meta.
    1.56 +
    1.57 +(defmacro def-impl-name
    1.58 +  {:private true} [& body]
    1.59 +  `(def
    1.60 +    ^{:doc "The name of the logging implementation used."}
    1.61 +    *impl-name*
    1.62 +    ~@body))
    1.63 +
    1.64 +(defmacro def-impl-get-log
    1.65 +  {:private true} [& body]
    1.66 +  `(def
    1.67 +    ^{:doc
    1.68 +  "Returns an implementation-specific log by string namespace. End-users should
    1.69 +  not need to call this."
    1.70 +       :arglist '([~'log-ns])}
    1.71 +    impl-get-log
    1.72 +    (memoize ~@body)))
    1.73 +
    1.74 +(defmacro def-impl-enabled?
    1.75 +  {:private true} [& body]
    1.76 +  `(def
    1.77 +    ^{:doc
    1.78 +  "Implementation-specific check if a particular level is enabled. End-users
    1.79 +  should not need to call this."
    1.80 +       :arglist '([~'log ~'level])}
    1.81 +    impl-enabled?
    1.82 +    ~@body))
    1.83 +
    1.84 +(defmacro def-impl-write!
    1.85 +  {:private true} [& body]
    1.86 +  `(def
    1.87 +    ^{:doc
    1.88 +  "Implementation-specific write of a log message. End-users should not need to
    1.89 +  call this."
    1.90 +       :arglist '([~'log ~'level ~'message ~'throwable])}
    1.91 +    impl-write!
    1.92 +    ~@body))
    1.93 +
    1.94 +(defn- commons-logging
    1.95 +  "Defines the commons-logging-based implementations of the core logging
    1.96 +  functions. End-users should never need to call this."
    1.97 +  []
    1.98 +  (try
    1.99 +    (import (org.apache.commons.logging LogFactory Log))
   1.100 +    (eval
   1.101 +      `(do
   1.102 +        (def-impl-name "org.apache.commons.logging")
   1.103 +        (def-impl-get-log
   1.104 +          (fn [log-ns#]
   1.105 +            (org.apache.commons.logging.LogFactory/getLog ^String log-ns#)))
   1.106 +        (def-impl-enabled?
   1.107 +          (fn [^org.apache.commons.logging.Log log# level#]
   1.108 +            (condp = level#
   1.109 +              :trace (.isTraceEnabled log#)
   1.110 +              :debug (.isDebugEnabled log#)
   1.111 +              :info  (.isInfoEnabled  log#)
   1.112 +              :warn  (.isWarnEnabled  log#)
   1.113 +              :error (.isErrorEnabled log#)
   1.114 +              :fatal (.isFatalEnabled log#))))
   1.115 +        (def-impl-write!
   1.116 +          (fn [^org.apache.commons.logging.Log log# level# msg# e#]
   1.117 +            (condp = level#
   1.118 +              :trace (.trace log# msg# e#)
   1.119 +              :debug (.debug log# msg# e#)
   1.120 +              :info  (.info  log# msg# e#)
   1.121 +              :warn  (.warn  log# msg# e#)
   1.122 +              :error (.error log# msg# e#)
   1.123 +              :fatal (.fatal log# msg# e#))))
   1.124 +        true))
   1.125 +    (catch Exception e nil)))
   1.126 +
   1.127 +
   1.128 +(defn- log4j-logging
   1.129 +  "Defines the log4j-based implementations of the core logging functions.
   1.130 +   End-users should never need to call this."
   1.131 +  []
   1.132 +  (try
   1.133 +    (import (org.apache.log4j Logger Level))
   1.134 +    (eval
   1.135 +      '(do
   1.136 +        (def-impl-name "org.apache.log4j")
   1.137 +        (def-impl-get-log
   1.138 +          (fn [log-ns#]
   1.139 +            (org.apache.log4j.Logger/getLogger ^String log-ns#)))
   1.140 +        (let [levels# {:trace org.apache.log4j.Level/TRACE
   1.141 +                       :debug org.apache.log4j.Level/DEBUG
   1.142 +                       :info  org.apache.log4j.Level/INFO
   1.143 +                       :warn  org.apache.log4j.Level/WARN
   1.144 +                       :error org.apache.log4j.Level/ERROR
   1.145 +                       :fatal org.apache.log4j.Level/FATAL}]
   1.146 +          (def-impl-enabled?
   1.147 +            (fn [^org.apache.log4j.Logger log# level#]
   1.148 +              (.isEnabledFor log# (levels# level#))))
   1.149 +          (def-impl-write!
   1.150 +            (fn [^org.apache.log4j.Logger log# level# msg# e#]
   1.151 +              (if-not e#
   1.152 +                (.log log# (levels# level#) msg#)
   1.153 +                (.log log# (levels# level#) msg# e#)))))
   1.154 +        true))
   1.155 +    (catch Exception e nil)))
   1.156 +
   1.157 +
   1.158 +(defn- java-logging
   1.159 +  "Defines the java-logging-based implementations of the core logging
   1.160 +  functions. End-users should never need to call this."
   1.161 +  []
   1.162 +  (try
   1.163 +    (import (java.util.logging Logger Level))
   1.164 +    (eval
   1.165 +      `(do
   1.166 +        (def-impl-name "java.util.logging")
   1.167 +        (def-impl-get-log
   1.168 +          (fn [log-ns#]
   1.169 +            (java.util.logging.Logger/getLogger log-ns#)))
   1.170 +        (let [levels# {:trace java.util.logging.Level/FINEST
   1.171 +                       :debug java.util.logging.Level/FINE
   1.172 +                       :info  java.util.logging.Level/INFO
   1.173 +                       :warn  java.util.logging.Level/WARNING
   1.174 +                       :error java.util.logging.Level/SEVERE
   1.175 +                       :fatal java.util.logging.Level/SEVERE}]
   1.176 +          (def-impl-enabled?
   1.177 +            (fn [^java.util.logging.Logger log# level#]
   1.178 +              (.isLoggable log# (levels# level#))))
   1.179 +          (def-impl-write!
   1.180 +            (fn [^java.util.logging.Logger log# level# msg# e#]
   1.181 +              (if-not e#
   1.182 +                (.log log# ^java.util.logging.Level (levels# level#)
   1.183 +                           ^String (str msg#))
   1.184 +                (.log log# ^java.util.logging.Level (levels# level#)
   1.185 +                           ^String (str msg#) ^Throwable e#)))))
   1.186 +        true))
   1.187 +    (catch Exception e nil)))
   1.188 +
   1.189 +
   1.190 +;; Initialize implementation-specific functions
   1.191 +(or (commons-logging)
   1.192 +    (log4j-logging)
   1.193 +    (java-logging)
   1.194 +    (throw ; this should never happen in 1.5+
   1.195 +      (RuntimeException.
   1.196 +        "Valid logging implementation could not be found.")))
   1.197 +
   1.198 +
   1.199 +(def ^{:doc
   1.200 +  "The default agent used for performing logging durng a transaction or when
   1.201 +  direct logging is disabled."}
   1.202 +  *logging-agent* (agent nil))
   1.203 +
   1.204 +
   1.205 +(def ^{:doc
   1.206 +  "A boolean indicating whether direct logging (as opposed to via an agent) is
   1.207 +  allowed when not operating from within a transaction. Defaults to true."}
   1.208 +  *allow-direct-logging* (atom true))
   1.209 +
   1.210 +
   1.211 +(defmacro log
   1.212 +  "Logs a message, either directly or via an agent. Also see the level-specific
   1.213 +  convenience macros."
   1.214 +  ([level message]
   1.215 +    `(log ~level ~message nil))
   1.216 +  ([level message throwable]
   1.217 +    `(log ~level ~message ~throwable ~(str *ns*)))
   1.218 +  ([level message throwable log-ns]
   1.219 +    `(let [log# (impl-get-log ~log-ns)]
   1.220 +      (if (impl-enabled? log# ~level)
   1.221 +        (if (and @*allow-direct-logging*
   1.222 +                 (not (clojure.lang.LockingTransaction/isRunning)))
   1.223 +          (impl-write! log# ~level ~message ~throwable)
   1.224 +          (send-off *logging-agent*
   1.225 +            (fn [_# l# v# m# t#] (impl-write! l# v# m# t#))
   1.226 +            log# ~level ~message ~throwable))))))
   1.227 +
   1.228 +
   1.229 +(defmacro enabled?
   1.230 +  "Returns true if the specific logging level is enabled.  Use of this function
   1.231 +  should only be necessary if one needs to execute alternate code paths beyond
   1.232 +  whether the log should be written to."
   1.233 +  ([level]
   1.234 +    `(enabled? ~level ~(str *ns*)))
   1.235 +  ([level log-ns]
   1.236 +    `(impl-enabled? (impl-get-log ~log-ns) ~level)))
   1.237 +
   1.238 +
   1.239 +(defmacro spy
   1.240 +  "Evaluates expr and outputs the form and its result to the debug log; returns 
   1.241 +  the result of expr."
   1.242 +  [expr]
   1.243 +  `(let [a# ~expr] (log :debug (str '~expr " => " a#)) a#))
   1.244 +
   1.245 +
   1.246 +(defn log-stream
   1.247 +  "Creates a PrintStream that will output to the log. End-users should not need
   1.248 +  to invoke this."
   1.249 +  [level log-ns]
   1.250 +  (java.io.PrintStream.
   1.251 +    (proxy [java.io.ByteArrayOutputStream] []
   1.252 +      (flush []
   1.253 +        (proxy-super flush)
   1.254 +        (let [s (.trim (.toString ^java.io.ByteArrayOutputStream this))]
   1.255 +          (proxy-super reset)
   1.256 +          (if (> (.length s) 0)
   1.257 +            (log level s nil log-ns)))))
   1.258 +    true))
   1.259 +
   1.260 +
   1.261 +(def ^{:doc
   1.262 +  "A ref used by log-capture! to maintain a reference to the original System.out
   1.263 +  and System.err streams."
   1.264 +  :private true}
   1.265 +  *old-std-streams* (ref nil))
   1.266 +
   1.267 +
   1.268 +(defn log-capture!
   1.269 +  "Captures System.out and System.err, redirecting all writes of those streams
   1.270 +  to :info and :error logging, respectively. The specified log-ns value will
   1.271 +  be used to namespace all redirected logging. NOTE: this will not redirect
   1.272 +  output of *out* or *err*; for that, use with-logs."
   1.273 +  [log-ns]
   1.274 +  (dosync
   1.275 +    (let [new-out (log-stream :info log-ns)
   1.276 +          new-err (log-stream :error log-ns)]
   1.277 +      ; don't overwrite the original values
   1.278 +      (if (nil? @*old-std-streams*)
   1.279 +        (ref-set *old-std-streams* {:out System/out :err System/err})) 
   1.280 +      (System/setOut new-out)
   1.281 +      (System/setErr new-err))))
   1.282 +
   1.283 +
   1.284 +(defn log-uncapture!
   1.285 +  "Restores System.out and System.err to their original values."
   1.286 +  []
   1.287 +  (dosync
   1.288 +    (when-let [{old-out :out old-err :err} @*old-std-streams*]
   1.289 +      (ref-set *old-std-streams* nil)
   1.290 +      (System/setOut old-out)
   1.291 +      (System/setErr old-err))))
   1.292 +
   1.293 +
   1.294 +(defmacro with-logs
   1.295 +  "Evaluates exprs in a context in which *out* and *err* are bound to :info and
   1.296 +  :error logging, respectively. The specified log-ns value will be used to
   1.297 +  namespace all redirected logging."
   1.298 +  [log-ns & body]
   1.299 +  (if (and log-ns (seq body))
   1.300 +    `(binding [*out* (java.io.OutputStreamWriter.
   1.301 +                       (log-stream :info ~log-ns))
   1.302 +               *err* (java.io.OutputStreamWriter.
   1.303 +                       (log-stream :error ~log-ns))]
   1.304 +      ~@body)))
   1.305 +
   1.306 +(defmacro trace
   1.307 +  "Logs a message at the trace level."
   1.308 +  ([message]
   1.309 +    `(log :trace ~message))
   1.310 +  ([message throwable]
   1.311 +    `(log :trace ~message ~throwable)))
   1.312 +
   1.313 +(defmacro debug
   1.314 +  "Logs a message at the debug level."
   1.315 +  ([message]
   1.316 +    `(log :debug ~message))
   1.317 +  ([message throwable]
   1.318 +    `(log :debug ~message ~throwable)))
   1.319 +
   1.320 +(defmacro info
   1.321 +  "Logs a message at the info level."
   1.322 +  ([message]
   1.323 +    `(log :info ~message))
   1.324 +  ([message throwable]
   1.325 +    `(log :info ~message ~throwable)))
   1.326 +
   1.327 +(defmacro warn
   1.328 +  "Logs a message at the warn level."
   1.329 +  ([message]
   1.330 +    `(log :warn ~message))
   1.331 +  ([message throwable]
   1.332 +    `(log :warn ~message ~throwable)))
   1.333 +
   1.334 +(defmacro error
   1.335 +  "Logs a message at the error level."
   1.336 +  ([message]
   1.337 +    `(log :error ~message))
   1.338 +  ([message throwable]
   1.339 +    `(log :error ~message ~throwable)))
   1.340 +
   1.341 +(defmacro fatal
   1.342 +  "Logs a message at the fatal level."
   1.343 +  ([message]
   1.344 +    `(log :fatal ~message))
   1.345 +  ([message throwable]
   1.346 +    `(log :fatal ~message ~throwable)))