 (ns io.aviso.tracker
-  "Code for tracing behavior so that it can be logged when there's an exception."
+  "Code for tracking operations so that a history can be presented when an exception is thrown."
   (:require
     [clojure.tools.logging :as l]
     [clojure.tools.logging.impl :as impl]
     [io.aviso.exception :as exception]))
 
 ;;; Contains a vector of messages (or functions that return messages) used to log the route to the exception.
-(def ^:dynamic ^:private operation-traces)
+(def ^:dynamic ^:private *operation-traces*)
 
-(def ^:dynamic ^:private innermost-exception)
+(def ^:dynamic ^:private *innermost-exception*)
 
 (def ^:dynamic *log-traces*
   "A boolean to control the whether traces are logged at :trace level as they are created."
   false)
 
-(defn- trace-to-string
+(defn- label-to-string
   "Converts a trace to a string; a trace may be a function which is invoked."
   [message]
   (str (if (fn? message) (message) message)))

  (impl/get-logger l/*logger-factory* namespace))

(defmacro track
  "Tracks the execution of an operation, associating the trace (which describes the operation) with the execution of the body.

  If an exception occurs inside the body, then all trace messages leading up to the
  point of the exception will be logged (the logger is determined from the current namespace); thus logging only occurs at the
  most deeply nested trace. The exception thrown is formatted and logged as well.

  trace may be a string, an object, or a function that returns a string; execution of the function is deferred until needed."
  [trace & body]
  `(track* (get-logger ~*ns*) ~trace #(do ~@body)))

(defn timer*
  "Executes a function, timing the duration. It then calculates the elapsed time in milliseconds (as a double) and passes that to a second function.

  "Tracks the execution of a function of no arguments. The trace macro converts into a call to track*.
  
  logger
  : Logger where logging should occur (as defined by the clojure.tools.logging Logger protocol)

  label
  : String, object, or function. If a function, the function will only be evaluated if the message
    needs to be printed (when an exception occurs, or when logging traces via [[*log-traces*]]).

  f
  : function to invoke. track* returns the result from this function."
  [logger label f]
  (if (bound? #'*operation-traces*)
    (try
      (swap! *operation-traces* conj label)
      (when *log-traces* (l/log* logger :trace nil (label-to-string label)))
      (f)
      (catch Throwable e
        (if @*innermost-exception*
          (let [trace-label-strings (mapv label-to-string @*operation-traces*)
                message (or (.getMessage e) (-> e .getClass .getName))]
            (log-trace-label-stack logger trace-label-strings message e)
            (reset! *innermost-exception* false)
            (throw (ex-info message
                            {:operation-trace trace-label-strings}
                            e)))
          (throw e)))
      (finally
        (swap! *operation-traces* pop)))
    ;; Else, if not yet bound..
    (binding [*operation-traces* (atom [])
              *innermost-exception* (atom true)]
      (track* logger label f))))

(defn get-logger
  "Determines the logger instance for a namespace (by leveraging some clojure.tools.logging internals)."
