 (defn- error [logger message]
   (l/log* logger :error nil message))
 
-(defn- log-trace-label-stack [logger trace-labels message e]
-  (error logger "An exception has occurred:")
-  (doseq [[trace-message i] (map vector trace-labels (iterate inc 1))]
-    (error logger (format "[%3d] - %s" i trace-message)))
-  (error logger (format "%s%n%s" message (exception/format-exception e))))
+(defn- log-trace-label-stack [logger trace-labels e]
+  (let [lines (concat ["An exception has occurred."]
+                      (map-indexed (fn [i label]
+                                     (format "[%3d] - %s" (inc i) label))
+                                   trace-labels)
+                      [(exception/format-exception e)])]
+    (->> lines
+         (str/join writer/eol)
+         (error logger))))
 
 (defn track*
   "Tracks the execution of a function of no arguments. The trace macro converts into a call to track*.

(ns io.aviso.tracker
  "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 *innermost-exception*)

(def ^:dynamic *log-traces*
  "A boolean to control the whether traces are logged at :trace level as they are created."
  false)

(defn- label-to-string
  "Converts a trace to a string; a trace may be a function which is invoked."

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

