From c883ff6794561719b39ade96f8b3ca7ee57ff313 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 27 Jan 2026 13:02:53 +0100 Subject: [PATCH] :sparkles: Improve unhandled exception handling --- frontend/src/app/main/errors.cljs | 33 +++++++++++++++++--- frontend/src/app/main/ui/error_boundary.cljs | 4 ++- frontend/src/debug.cljs | 6 ++++ frontend/translations/en.po | 3 ++ frontend/translations/es.po | 3 ++ 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index 9c0d050a7a..74cd9444f3 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -29,6 +29,9 @@ ;; Will contain the latest error report assigned (def last-report nil) +;; Will contain last uncaught exception +(def last-exception nil) + (defn- print-data! [data] (-> data @@ -338,7 +341,6 @@ (print-data! werror) (print-explain! werror)))))))) - (defonce uncaught-error-handler (letfn [(is-ignorable-exception? [cause] (let [message (ex-message cause)] @@ -349,10 +351,31 @@ (on-unhandled-error [event] (.preventDefault ^js event) - (when-let [error (unchecked-get event "error")] - (when-not (is-ignorable-exception? error) - (on-error error))))] + (when-let [cause (unchecked-get event "error")] + (set! last-exception cause) + (when-not (is-ignorable-exception? cause) + (ex/print-throwable cause :prefix "uncaught exception") + (st/async-emit! + (ntf/show {:content (tr "errors.unexpected-exception" (ex-message cause)) + :type :toast + :level :error + :timeout 3000}))))) + + + (on-unhandled-rejection [event] + (.preventDefault ^js event) + (when-let [cause (unchecked-get event "reason")] + (set! last-exception cause) + (ex/print-throwable cause :prefix "uncaught rejection") + (st/async-emit! + (ntf/show {:content (tr "errors.unexpected-exception" (ex-message cause)) + :type :toast + :level :error + :timeout 3000}))))] (.addEventListener glob/window "error" on-unhandled-error) + (.addEventListener glob/window "unhandledrejection" on-unhandled-rejection) (fn [] - (.removeEventListener glob/window "error" on-unhandled-error)))) + (.removeEventListener glob/window "error" on-unhandled-error) + (.removeEventListener glob/window "unhandledrejection" on-unhandled-rejection)))) + diff --git a/frontend/src/app/main/ui/error_boundary.cljs b/frontend/src/app/main/ui/error_boundary.cljs index 3ccec2101a..c3e9ec18b6 100644 --- a/frontend/src/app/main/ui/error_boundary.cljs +++ b/frontend/src/app/main/ui/error_boundary.cljs @@ -8,6 +8,7 @@ "React error boundary components" (:require ["react-error-boundary" :as reb] + [app.common.exceptions :as ex] [app.main.errors :as errors] [app.main.refs :as refs] [goog.functions :as gfn] @@ -34,7 +35,8 @@ ;; very small amount of time, so we debounce for 100ms for ;; avoid duplicate and redundant reports (gfn/debounce (fn [error info] - (js/console.log "Cause stack: \n" (.-stack error)) + (set! errors/last-exception error) + (ex/print-throwable error) (js/console.error "Component trace: \n" (unchecked-get info "componentStack") diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 7f55f10a46..6da1b7e27f 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.exceptions :as ex] [app.common.files.repair :as cfr] [app.common.files.validate :as cfv] [app.common.json :as json] @@ -456,3 +457,8 @@ (defn ^:export network-averages [] (.log js/console (clj->js @http/network-averages))) + + +(defn print-last-exception + [] + (some-> errors/last-exception ex/print-throwable)) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index fedd5e7e82..fba9f1dec9 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1343,6 +1343,9 @@ msgstr "" msgid "errors.generic" msgstr "Something wrong has happened." +msgid "errors.unexpected-exception" +msgstr "Unexpected exception: %s" + #: src/app/main/errors.cljs:200 msgid "errors.internal-assertion-error" msgstr "Internal Assertion Error" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 40fd3fd30f..dfe9748dbe 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1349,6 +1349,9 @@ msgstr "" msgid "errors.generic" msgstr "Ha ocurrido algún error." +msgid "errors.unexpected-exception" +msgstr "Error inesperado: %s" + #: src/app/main/errors.cljs:200 msgid "errors.internal-assertion-error" msgstr "Error interno de aserción"