tags:

views:

376

answers:

1

just started using log4j in one of my home-projects and I was just about to break out the mouse and cut-and-paste (trace (str "entering: " function-name)) into every function in a large module. then the voice of reason caught up and said "there has simply got to be a better way"... I can think of making a macro that wraps a whole block of functions and adds the traces to them or something like that? Any advice from the wise Stack-overflowing-clojurians?

+9  A: 

No need for a macro:

(defn trace-ns
  "ns should be a namespace object or a symbol."
  [ns]
  (doseq [s (keys (ns-interns ns))
          :let [v (ns-resolve ns s)]
          :when (and (ifn? @v) (-> v meta :macro not))]
    (intern ns
            (with-meta s {:traced true :untraced @v})
            (let [f @v] (fn [& args]
                          (clojure.contrib.trace/trace (str "entering: " s))
                          (apply f args))))))

(defn untrace-ns [ns]
  (doseq [s (keys (ns-interns ns))
          :let [v (ns-resolve ns s)]
          :when (:traced (meta v))]
    (alter-meta! (intern ns s (:untraced (meta v)))
                 #(dissoc % :traced :untraced))))

...or something similar. The most likely extra requirement would be to use filter so as not to call trace on things which aren't ifn?s. Update: edited in a solution to that (also handling macros). Update 2: fixed some major bugs. Update 4: added untrace functionality.

Update 3: Here's an example from my REPL:

user> (ns foo)
nil
foo> (defn foo [x] x)
#'foo/foo
foo> (defmacro bar [x] x)
#'foo/bar
foo> (ns user)
nil
user> (trace-ns 'foo)
nil
user> (foo/foo :foo)
TRACE: "entering: foo"
:foo
user> (foo/bar :foo)
:foo
user> (untrace-ns 'foo)
nil
user> (foo/foo :foo)
:foo
Michał Marczyk
You're simply awesome! could you perhaps explain the ns-resolve part?
Arthur Ulfeldt
:-) `ns-resolve` takes a namespace or namespace-naming symbol and a symbol and attempts to find a Var interned in the given namespace under the name given by the (second) symbol. The important point here is that (1) it is possible to change the Var's root binding (there's a number of ways to do it, but `intern` is here particularly convenient because of how it also handles extra metadata; `trace-ns` uses rebinding to subsitute a wrapped function for the original) and (2) it is possible to change the Var's metadata (so `untraced-ns` is possible).
Michał Marczyk
See http://gist.github.com/492764 for the most recent version of the above (featuring a more useful docstring!).
Michał Marczyk