diff options
-rw-r--r-- | src/clojure/contrib/logging.clj | 352 |
1 files changed, 352 insertions, 0 deletions
diff --git a/src/clojure/contrib/logging.clj b/src/clojure/contrib/logging.clj new file mode 100644 index 00000000..1df25ef5 --- /dev/null +++ b/src/clojure/contrib/logging.clj @@ -0,0 +1,352 @@ +;;; logging.clj -- delegated logging for Clojure + +;; by Alex Taggart +;; July 27, 2009 + +;; Copyright (c) Alex Taggart, July 2009. All rights reserved. The use +;; and distribution terms for this software are covered by the Eclipse +;; Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; which can be found in the file epl-v10.html at the root of this +;; distribution. By using this software in any fashion, you are +;; agreeing to be bound by the terms of this license. You must not +;; remove this notice, or any other, from this software. +(ns + #^{:author "Alex Taggart, Timothy Pratley" + :doc + "Logging macros which delegate to a specific logging implementation. At + macro-expansion-time a specific implementation is selected from, in order + Apache commons-logging, log4j, and finally java.util.logging. + + Logging levels are specified by clojure keywords corresponding to the + values used in log4j and commons-logging: + :trace, :debug, :info, :warn, :error, :fatal + + Logging occurs with the log macro, or the level-specific convenience macros + which write either directly or via an agent. For performance reasons, direct + logging is enabled by default, but setting the *allow-direct-logging* boolean + atom to false will disable it. If logging is invoked within a transaction it + will always use an agent. + + The log macros will not evaluate their 'message' unless the specific logging + level is in effect. Alternately, you can use the spy macro when you have code + that needs to be evaluated, and also want to output the code and its result to + the debug log. + + Unless otherwise specified, the current namespace (as identified by *ns*) will + be used as the log-ns (similar to how the java class name is usually used). + Note: your log configuration should display the name that was passed to the + logging implementation, and not perform stack-inspection, otherwise you'll see + something like \"fn__72$impl_write_BANG__39__auto____81\" in your logs. + + Use the enabled? macro to write conditional code against the logging level + (beyond simply whether or not to call log, which is handled automatically). + + You can redirect all java writes of System.out and System.err to the log + system by calling log-capture!. To rebind *out* and *err* to the log system + invoke with-logs. In both cases a log-ns (e.g., \"com.example.captured\") + needs to be specified to namespace the output."} + clojure.contrib.logging) + +(declare *impl-name* impl-get-log impl-enabled? impl-write!) + +;; Macros used so that implementation-specific functions all have the same meta. + +(defmacro def-impl-name + {:private true} [& body] + `(def + #^{:doc "The name of the logging implementation used."} + *impl-name* + ~@body)) + +(defmacro def-impl-get-log + {:private true} [& body] + `(def + #^{:doc + "Returns an implementation-specific log by string namespace. End-users should + not need to call this function." + :arglist '([~'log-ns])} + impl-get-log + (memoize ~@body))) + +(defmacro def-impl-enabled? + {:private true} [& body] + `(def + #^{:doc + "Implementation-specific check if a particular level is enabled. End-users + should not need to call this function." + :arglist '([~'log ~'level])} + impl-enabled? + ~@body)) + +(defmacro def-impl-write! + {:private true} [& body] + `(def + #^{:doc + "Implementation-specific write of a log message. End-users should not need to + call this function." + :arglist '([~'log ~'level ~'message ~'throwable])} + impl-write! + ~@body)) + +(defmacro commons-logging + "Defines the commons-logging-based implementations of the core logging + functions. End-users should never need to call this macro." + {:private true} + [] + (try + (import 'org.apache.commons.logging.LogFactory) + (import 'org.apache.commons.logging.Log) + `(do + (def-impl-name "org.apache.commons.logging") + (def-impl-get-log + (fn [log-ns#] + (org.apache.commons.logging.LogFactory/getLog #^String log-ns#))) + (def-impl-enabled? + (fn [#^org.apache.commons.logging.Log log# level#] + (condp = level# + :trace (.isTraceEnabled log#) + :debug (.isDebugEnabled log#) + :info (.isInfoEnabled log#) + :warn (.isWarnEnabled log#) + :error (.isErrorEnabled log#) + :fatal (.isFatalEnabled log#)))) + (def-impl-write! + (fn [#^org.apache.commons.logging.Log log# level# msg# e#] + (condp = level# + :trace (.trace log# msg# e#) + :debug (.debug log# msg# e#) + :info (.info log# msg# e#) + :warn (.warn log# msg# e#) + :error (.error log# msg# e#) + :fatal (.fatal log# msg# e#)))) + true) + (catch Exception e nil))) + + +(defmacro log4j-logging + "Defines the log4j-based implementations of the core logging functions. + End-users should never need to call this macro." + {:private true} + [] + (try + (import 'org.apache.log4j.Logger) + (import 'org.apache.log4j.Level) + `(do + (def-impl-name "org.apache.log4j") + (def-impl-get-log + (fn [log-ns#] + (org.apache.log4j.Logger/getLogger #^String log-ns#))) + (let [levels# {:trace org.apache.log4j.Level/TRACE + :debug org.apache.log4j.Level/DEBUG + :info org.apache.log4j.Level/INFO + :warn org.apache.log4j.Level/WARN + :error org.apache.log4j.Level/ERROR + :fatal org.apache.log4j.Level/FATAL}] + (def-impl-enabled? + (fn [#^org.apache.log4j.Logger log# level#] + (.isEnabledFor log# (levels# level#)))) + (def-impl-write! + (fn [#^org.apache.log4j.Logger log# level# msg# e#] + (if-not e# + (.log log# (levels# level#) msg#) + (.log log# (levels# level#) msg# e#))))) + true) + (catch Exception e nil))) + + +(defmacro java-logging + "Defines the java-logging-based implementations of the core logging + functions. End-users should never need to call this macro." + {:private true} + [] + (try + (import 'java.util.logging.Logger) + (import 'java.util.logging.Level) + `(do + (def-impl-name "java.util.logging") + (def-impl-get-log + (fn [log-ns#] + (java.util.logging.Logger/getLogger log-ns#))) + (let [levels# {:trace java.util.logging.Level/FINEST + :debug java.util.logging.Level/FINE + :info java.util.logging.Level/INFO + :warn java.util.logging.Level/WARNING + :error java.util.logging.Level/SEVERE + :fatal java.util.logging.Level/SEVERE}] + (def-impl-enabled? + (fn [#^java.util.logging.Logger log# level#] + (.isLoggable log# (levels# level#)))) + (def-impl-write! + (fn [#^java.util.logging.Logger log# level# msg# e#] + (if-not e# + (.log log# #^java.util.logging.Level (levels# level#) + #^String (str msg#)) + (.log log# #^java.util.logging.Level (levels# level#) + #^String (str msg#) #^Throwable e#))))) + true) + (catch Exception e nil))) + + +;; Initialize implementation-specific functions +(or (commons-logging) + (log4j-logging) + (java-logging) + (throw ; this should never happen in 1.5+ + (RuntimeException. + "Valid logging implementation could not be found."))) + + +(def #^{:doc + "The default agent used for performing logging durng a transaction or when + direct logging is disabled."} + *logging-agent* (agent nil)) + + +(def #^{:doc + "A boolean indicating whether direct logging (as opposed to via an agent) is + allowed when not operating from within a transaction. Defaults to true."} + *allow-direct-logging* (atom true)) + + +(defn agent-write! + "Writes the message immediately, and ignores the first argument. Used by the + logging agent. End-users should never need to call this function." + [_ log level message throwable] + (impl-write! log level message throwable)) + + +(defmacro log + "Logs a message, either directly or via an agent. Also see the level-specific + convenience macros." + ([level message] + `(log ~level ~message nil)) + ([level message throwable] + `(log ~level ~message ~throwable ~(str *ns*))) + ([level message throwable log-ns] + `(let [log# (impl-get-log ~log-ns)] + (if (impl-enabled? log# ~level) + (if (and @*allow-direct-logging* + (not (clojure.lang.LockingTransaction/isRunning))) + (impl-write! log# ~level ~message ~throwable) + (send-off *logging-agent* + agent-write! log# ~level ~message ~throwable)))))) + + +(defmacro enabled? + "Returns true if the specific logging level is enabled. Use of this function + should only be necessary if one needs to execute alternate code paths beyond + whether the log should be written to." + ([level] + `(enabled? ~level ~(str *ns*))) + ([level log-ns] + `(impl-enabled? (impl-get-log ~log-ns) ~level))) + + +(defmacro spy + "Evaluates expr and outputs the form and its result to the debug log; returns + the result of expr." + [expr] + `(let [a# ~expr] (log :debug (str '~expr " => " a#)) a#)) + + +(defn log-stream + "Creates a PrintStream that will output to the log. End-users should not need + to invoke this function." + [level log-ns] + (java.io.PrintStream. + (proxy [java.io.ByteArrayOutputStream] [] + (flush [] + (proxy-super flush) + (let [s (.trim (.toString #^java.io.ByteArrayOutputStream this))] + (proxy-super reset) + (if (> (.length s) 0) + (log level s nil log-ns))))) + true)) + + +(def #^{:doc + "A ref used by log-capture! to maintain a reference to the original System.out + and System.err streams." + :private true} + *old-std-streams* (ref nil)) + + +(defn log-capture! + "Captures System.out and System.err, redirecting all writes of those streams + to :info and :error logging, respectively. The specified log-ns value will + be used to namespace all redirected logging. NOTE: this will not redirect + output of *out* or *err*; for that, use with-logs." + [log-ns] + (dosync + (let [new-out (log-stream :info log-ns) + new-err (log-stream :error log-ns)] + ; don't overwrite the original values + (if (nil? @*old-std-streams*) + (ref-set *old-std-streams* {:out System/out :err System/err})) + (System/setOut new-out) + (System/setErr new-err)))) + + +(defn log-uncapture! + "Restores System.out and System.err to their original values." + [] + (dosync + (when-let [{old-out :out old-err :err} @*old-std-streams*] + (ref-set *old-std-streams* nil) + (System/setOut old-out) + (System/setErr old-err)))) + + +(defmacro with-logs + "Evaluates exprs in a context in which *out* and *err* are bound to :info and + :error logging, respectively. The specified log-ns value will be used to + namespace all redirected logging." + [log-ns & body] + (if (and log-ns (seq body)) + `(binding [*out* (java.io.OutputStreamWriter. + (log-stream :info ~log-ns)) + *err* (java.io.OutputStreamWriter. + (log-stream :error ~log-ns))] + ~@body))) + +(defmacro trace + "Logs a message at the trace level." + ([message] + `(log :trace ~message)) + ([message throwable] + `(log :trace ~message ~throwable))) + +(defmacro debug + "Logs a message at the debug level." + ([message] + `(log :debug ~message)) + ([message throwable] + `(log :debug ~message ~throwable))) + +(defmacro info + "Logs a message at the info level." + ([message] + `(log :info ~message)) + ([message throwable] + `(log :info ~message ~throwable))) + +(defmacro warn + "Logs a message at the warn level." + ([message] + `(log :warn ~message)) + ([message throwable] + `(log :warn ~message ~throwable))) + +(defmacro error + "Logs a message at the error level." + ([message] + `(log :error ~message)) + ([message throwable] + `(log :error ~message ~throwable))) + +(defmacro fatal + "Logs a message at the fatal level." + ([message] + `(log :fatal ~message)) + ([message throwable] + `(log :fatal ~message ~throwable))) |