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)))
|