diff --git a/src/circleci/rollcage/core.clj b/src/circleci/rollcage/core.clj index 02a420a..07d9f19 100644 --- a/src/circleci/rollcage/core.clj +++ b/src/circleci/rollcage/core.clj @@ -22,6 +22,7 @@ (def ^:private Client {:access-token (s/maybe String) :block-fields (s/maybe [s/Keyword]) + :indexed-ex-data (s/maybe (s/cond-pre s/Bool s/Str s/Keyword s/Num)) :result-fn clojure.lang.IFn :send-fn clojure.lang.IFn :data {:environment (s/maybe String) @@ -212,7 +213,7 @@ (s/defn ^:private client* :- Client [access-token :- (s/maybe String) - {:keys [os hostname environment code-version file-root result-fn block-fields] + {:keys [os hostname environment code-version file-root result-fn block-fields indexed-ex-data] :or {environment "production"}}] (let [os (or os (guess-os)) hostname (or hostname (guess-hostname)) @@ -221,6 +222,7 @@ {:access-token access-token :result-fn result-fn :block-fields block-fields + :indexed-ex-data indexed-ex-data :send-fn (if (string/blank? access-token) send-item-null send-item-http) @@ -253,6 +255,24 @@ -- git SHA (i.e. '3da541559918a808c2402bba5012f6c60b27661c') There is no default value. + `:indexed-ex-data` Tells rollcage to send nested exception's + `ex-data` under an indexed numeric level. It can be also a + string used instead of the \"__exception\" keys. + + ```clojure + (ex-info \"Second error\" {:bar 3} (ex-info \"First error\" {:foo 1 :bar 2})) + ``` + + Sending the previous exception will generate the following params: + + ``` + custom.0.__exception First error + custom.0.foo 1 + custom.0.bar 2 + custom.1.__exception Second error + custom.1.bar 3 + ``` + `:result-fn` (for advanced usage) a function that will be called after each exception is sent to Rollbar. The function will be passed 2 parameters: - The Throwable that was being reported @@ -315,7 +335,7 @@ ([^String level client ^Throwable exception] (notify level client exception {})) ([^String level {:keys [result-fn send-fn block-fields] :as client} ^Throwable exception {:keys [url params]}] - (let [params (merge params (throwables/merged-ex-data exception)) + (let [params (merge params (throwables/merged-ex-data client exception)) scrubbed (scrub params block-fields) item (make-rollbar client level exception url scrubbed) result (try diff --git a/src/circleci/rollcage/throwables.clj b/src/circleci/rollcage/throwables.clj index 1c8a529..1df4612 100644 --- a/src/circleci/rollcage/throwables.clj +++ b/src/circleci/rollcage/throwables.clj @@ -1,6 +1,8 @@ (ns circleci.rollcage.throwables {:no-doc true}) +(def default-exception-key "__exception") + (defn- cause-seq [^Throwable t] (take-while some? @@ -8,6 +10,40 @@ (and e (.getCause e))) t))) -(defn merged-ex-data +(defn merge-ex-data [^Throwable ex] (reduce merge {} (map ex-data (cause-seq ex)))) + +(defn enriched-ex-data + [exception-name-field ^Throwable ex] + (assoc (ex-data ex) + exception-name-field + (ex-message ex))) + +(defn index-ex-data + [exception-name-field ^Throwable ex] + (let [exes (reverse (cause-seq ex)) + quantity (count exes)] + (if (= 1 quantity) + (ex-data (first exes)) + (let [indexes (range 0 quantity) + rich-ex-data (map (partial enriched-ex-data exception-name-field) + exes)] + (zipmap indexes rich-ex-data))))) + +(defn select-exception-key + [indexed-ex-data] + (let [ekey (if-not (boolean? indexed-ex-data) + (str indexed-ex-data) + default-exception-key)] + (if-not (seq ekey) + default-exception-key + ekey))) + +(defn merged-ex-data + [{:keys [indexed-ex-data] :as client} + ^Throwable ex] + (if-not indexed-ex-data + (merge-ex-data ex) + (index-ex-data (select-exception-key indexed-ex-data) + ex))) diff --git a/test/circleci/rollcage/test_throwables.clj b/test/circleci/rollcage/test_throwables.clj index 1d906e0..fae8715 100644 --- a/test/circleci/rollcage/test_throwables.clj +++ b/test/circleci/rollcage/test_throwables.clj @@ -1,6 +1,6 @@ (ns circleci.rollcage.test-throwables (:require [circleci.rollcage.throwables :as throwables] - [clojure.test :refer (deftest is)] + [clojure.test :refer (deftest is are)] [speculative.instrument])) (deftest cause-seq-works @@ -11,8 +11,49 @@ :c-added "this"} b)] (is (= [a] (#'throwables/cause-seq a))) (is (= [c b a] (#'throwables/cause-seq c))) - (is (= {:name "a"} (throwables/merged-ex-data a))) + (is (= {:name "a"} (throwables/merge-ex-data a))) (is (= {:name "a" :b-added "this" :c-added "this"} - (throwables/merged-ex-data c))))) + (throwables/merge-ex-data c))))) + +(deftest select-exception-key-works + (are [input expected] (= expected (throwables/select-exception-key input)) + nil throwables/default-exception-key + "" throwables/default-exception-key + 123 "123" + "some-key" "some-key" + :some-key ":some-key" + identity (str identity))) + +(deftest merged-ex-data-works + (let [simple-error-data {:a 1 :b 2} + simple-error (ex-info "An error" simple-error-data) + nested-error (ex-info "outer" {:foo 2 :bar 3} (ex-info "inner" {:foo 1}))] + (are [args expected] (= expected + (apply throwables/merged-ex-data args)) + [{} simple-error] + simple-error-data + + [{} nested-error] + {:foo 1 :bar 3} + + [{:indexed-ex-data false} nested-error] + {:foo 1 :bar 3} + + [{:indexed-ex-data true} simple-error] + simple-error-data + + [{:indexed-ex-data true} nested-error] + {0 {"__exception" "inner" + :foo 1} + 1 {"__exception" "outer" + :foo 2 + :bar 3}} + + [{:indexed-ex-data "KEY"} nested-error] + {0 {"KEY" "inner" + :foo 1} + 1 {"KEY" "outer" + :foo 2 + :bar 3}})))