 (ns io.aviso.tracker
-  "Code for tracking operations so that a history can be presented when an exception is thrown."
+  "Code for tracking operations so that a history can be presented when an exception is thrown.
+
+  The main entrypoint is the [[track]] macro."
   (:require [clojure.string :as str]
             [clojure.tools.logging :as l]
             [clojure.tools.logging.impl :as impl]
             [io.aviso.writer :as writer]
             [io.aviso.exception :as exception]
             [io.aviso.toolchest.exceptions :refer [to-message]])
-  (:import (java.util WeakHashMap Map)))
+  (:import [java.util WeakHashMap Map]))
 
 ;;; Contains a vector of messages (or functions that return messages) used to log the route to the exception.
 (def ^:private ^:dynamic *operation-labels*

                 (label-to-string label)
                 label)]
    (binding [*operation-labels* (conj *operation-labels* label')]
      (set-should-log-operations! true)
      (try
        (when level'
          (l/log* logger *log-trace-level* nil label'))
        (f)
        (catch Throwable t
          (if (should-log-operations?)
            (let [trace-label-strings (operation-labels)]
              (log-trace-label-stack logger trace-label-strings t)
              (set-should-log-operations! false)
              (throw (ex-info (to-message t)
                              {:operation-trace trace-label-strings}
                              t)))
            (throw t)))))))

(defn get-logger
  "Determines the logger instance for a namespace (by leveraging some clojure.tools.logging internals)."
  [namespace]
  (impl/get-logger l/*logger-factory* namespace))

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

  If an exception occurs inside the body, then all labels 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 label. The exception thrown is formatted and logged as well.  The actual exception will be rethrown, wrapped in a
  new `ex-info` exception, with key `:operation-trace` set to the vector of operation trace strings.

  label may be a string, an object, or a function that returns a string; execution of the function is deferred until
  an operation trace label is needed.

  The operation trace is stored in a dynamic var; if you make use of Clojure's bound-fn macro to execute code in a new thread
  with the same bindings, then operations in a new thread will log as successors to operations in the current thread.

  Note that the clojure.core.async thead and go macros *do* propogate bindings to new threads."
  [label & body]
  `(track* (get-logger ~*ns*) ~label #(do ~@body)))


  "Controls whether operation labels are logged on entry to the [[track]] macro. By default (nil), no
  logging takes places.  Otherwise, this is a logging level (such as :debug or :info) to be used."
  nil)

(defn get-logger
  "Determines the logger instance for a namespace (by leveraging some clojure.tools.logging internals)."
  [namespace]
  (impl/get-logger l/*logger-factory* namespace))

(def ^:private ^Map log-exceptions?
  (WeakHashMap.))
