Improve backend and worker error handling

This commit is contained in:
Andrey Antukh 2022-04-01 15:55:28 +02:00 committed by Andrés Moya
parent c026d05bc3
commit 701a98fab6
7 changed files with 112 additions and 47 deletions

View file

@ -49,12 +49,20 @@
(defmethod handle-exception :validation (defmethod handle-exception :validation
[err _] [err _]
(let [data (ex-data err) (let [{:keys [code] :as data} (ex-data err)]
explain (us/pretty-explain data)] (cond
(yrs/response :status 400 (= code :spec-validation)
:body (-> data (let [explain (us/pretty-explain data)]
(dissoc ::s/problems ::s/value) (yrs/response :status 400
(cond-> explain (assoc :explain explain)))))) :body (-> data
(dissoc ::s/problems ::s/value)
(cond-> explain (assoc :explain explain)))))
(= code :request-body-too-large)
(yrs/response :status 413 :body data)
:else
(yrs/response :status 400 :body data))))
(defmethod handle-exception :assertion (defmethod handle-exception :assertion
[error request] [error request]

View file

@ -16,7 +16,10 @@
[yetti.middleware :as ymw] [yetti.middleware :as ymw]
[yetti.request :as yrq] [yetti.request :as yrq]
[yetti.response :as yrs]) [yetti.response :as yrs])
(:import java.io.OutputStream)) (:import
com.fasterxml.jackson.core.io.JsonEOFException
io.undertow.server.RequestTooBigException
java.io.OutputStream))
(def server-timing (def server-timing
{:name ::server-timing {:name ::server-timing
@ -46,16 +49,29 @@
(update :params merge params)))) (update :params merge params))))
:else :else
request)))] request)))
(handle-error [raise cause]
(cond
(instance? RequestTooBigException cause)
(raise (ex/error :type :validation
:code :request-body-too-large
:hint (ex-message cause)))
(instance? JsonEOFException cause)
(raise (ex/error :type :validation
:code :malformed-json
:hint (ex-message cause)))
:else
(raise cause)))]
(fn [request respond raise] (fn [request respond raise]
(when-let [request (try (when-let [request (try
(process-request request) (process-request request)
(catch Exception cause (catch RuntimeException cause
(raise (ex/error :type :validation (handle-error raise (or (.getCause cause) cause)))
:code :malformed-params (catch Throwable cause
:hint (ex-message cause) (handle-error raise cause)))]
:cause cause))))]
(handler request respond raise))))) (handler request respond raise)))))
(def parse-request (def parse-request

View file

@ -20,9 +20,9 @@
:read-only (cf/get :database-readonly false) :read-only (cf/get :database-readonly false)
:metrics (ig/ref :app.metrics/metrics) :metrics (ig/ref :app.metrics/metrics)
:migrations (ig/ref :app.migrations/all) :migrations (ig/ref :app.migrations/all)
:name :main :name :main
:min-size (cf/get :database-min-pool-size 0) :min-size (cf/get :database-min-pool-size 0)
:max-size (cf/get :database-max-pool-size 30)} :max-size (cf/get :database-max-pool-size 30)}
;; Default thread pool for IO operations ;; Default thread pool for IO operations
[::default :app.worker/executor] [::default :app.worker/executor]

View file

@ -7,8 +7,10 @@
(ns app.main.errors (ns app.main.errors
"Generic error handling" "Generic error handling"
(:require (:require
[app.common.data :as d]
[app.common.data.macros :as dm] [app.common.data.macros :as dm]
[app.common.exceptions :as ex] [app.common.exceptions :as ex]
[app.common.pprint :as pp]
[app.config :as cf] [app.config :as cf]
[app.main.data.messages :as msg] [app.main.data.messages :as msg]
[app.main.data.users :as du] [app.main.data.users :as du]
@ -17,7 +19,6 @@
[app.util.i18n :refer [tr]] [app.util.i18n :refer [tr]]
[app.util.router :as rt] [app.util.router :as rt]
[app.util.timers :as ts] [app.util.timers :as ts]
[fipp.edn :as fpp]
[potok.core :as ptk])) [potok.core :as ptk]))
(defn on-error (defn on-error
@ -59,16 +60,15 @@
(defmethod ptk/handle-error :validation (defmethod ptk/handle-error :validation
[error] [error]
(ts/schedule (ts/schedule
(st/emitf #(st/emit! (msg/show {:content "Validation error"
(msg/show {:content "Unexpected validation error." :type :error
:type :error :timeout 3000})))
:timeout 3000})))
;; Print to the console some debug info. ;; Print to the console some debug info.
(js/console.group "Validation Error:") (js/console.group "Validation Error:")
(ex/ignoring (ex/ignoring
(js/console.info (js/console.info
(with-out-str (fpp/pprint (dissoc error :explain))))) (pp/pprint-str (dissoc error :explain))))
(when-let [explain (:explain error)] (when-let [explain (:explain error)]
(js/console.group "Spec explain:") (js/console.group "Spec explain:")
@ -78,24 +78,46 @@
(js/console.groupEnd "Validation Error:")) (js/console.groupEnd "Validation Error:"))
;; All the errors that happens on worker are handled here.
(defmethod ptk/handle-error :worker-error
[{:keys [code data hint] :as error}]
(let [hint (or hint (:hint data) (:message data) (d/name code))
info (pp/pprint-str (dissoc data :explain))
msg (dm/str "Internal Worker Error: " hint)]
(ts/schedule
#(st/emit!
(msg/show {:content "Something wrong has happened (on worker)."
:type :error
:timeout 3000})))
(js/console.group msg)
(js/console.info info)
(when-let [explain (:explain data)]
(js/console.group "Spec explain:")
(js/console.log explain)
(js/console.groupEnd "Spec explain:"))
(js/console.groupEnd msg)))
;; Error on parsing an SVG ;; Error on parsing an SVG
;; TODO: looks unused and deprecated ;; TODO: looks unused and deprecated
(defmethod ptk/handle-error :svg-parser (defmethod ptk/handle-error :svg-parser
[_] [_]
(ts/schedule (ts/schedule
(st/emitf #(st/emit! (msg/show {:content "SVG is invalid or malformed"
(msg/show {:content "SVG is invalid or malformed" :type :error
:type :error :timeout 3000}))))
:timeout 3000}))))
;; TODO: should be handled in the event and not as general error handler ;; TODO: should be handled in the event and not as general error handler
(defmethod ptk/handle-error :comment-error (defmethod ptk/handle-error :comment-error
[_] [_]
(ts/schedule (ts/schedule
(st/emitf #(st/emit! (msg/show {:content "There was an error with the comment"
(msg/show {:content "There was an error with the comment" :type :error
:type :error :timeout 3000}))))
:timeout 3000}))))
;; This is a pure frontend error that can be caused by an active ;; This is a pure frontend error that can be caused by an active
;; assertion (assertion that is preserved on production builds). From ;; assertion (assertion that is preserved on production builds). From
@ -110,10 +132,9 @@
(dm/str cf/public-uri "js/cljs-runtime/" (:file error)) (dm/str cf/public-uri "js/cljs-runtime/" (:file error))
(:line error))] (:line error))]
(ts/schedule (ts/schedule
(st/emitf #(st/emit! (msg/show {:content "Internal error: assertion."
(msg/show {:content "Internal error: assertion." :type :error
:type :error :timeout 3000})))
:timeout 3000})))
;; Print to the console some debugging info ;; Print to the console some debugging info
(js/console.group message) (js/console.group message)
@ -139,7 +160,7 @@
(defmethod ptk/handle-error :server-error (defmethod ptk/handle-error :server-error
[{:keys [data hint] :as error}] [{:keys [data hint] :as error}]
(let [hint (or hint (:hint data) (:message data)) (let [hint (or hint (:hint data) (:message data))
info (with-out-str (fpp/pprint (dissoc data :explain))) info (pp/pprint-str (dissoc data :explain))
msg (dm/str "Internal Server Error: " hint)] msg (dm/str "Internal Server Error: " hint)]
(ts/schedule (ts/schedule

View file

@ -31,6 +31,10 @@
(= 200 status) (= 200 status)
(rx/of body) (rx/of body)
(= 413 status)
(rx/throw {:type :validation
:code :request-body-too-large})
(and (>= status 400) (and (>= status 400)
(map? body)) (map? body))
(rx/throw body) (rx/throw body)

View file

@ -54,9 +54,11 @@
(reply-error [cause] (reply-error [cause]
(if (map? cause) (if (map? cause)
(post {:error cause}) (post {:error {:type :worker-error
(post {:error {:type :unexpected :code (or (:type cause) :wrapped)
:code :unhandled-error-on-worker :data cause}})
(post {:error {:type :worker-error
:code :unhandled-error
:hint (ex-message cause) :hint (ex-message cause)
:data (ex-data cause)}}))) :data (ex-data cause)}})))

View file

@ -16,22 +16,35 @@
[beicon.core :as rx] [beicon.core :as rx]
[rumext.alpha :as mf])) [rumext.alpha :as mf]))
(defn- not-found?
[{:keys [type]}]
(= :not-found type))
(defn- handle-response (defn- handle-response
[response] [{:keys [body status] :as response}]
(cond (cond
(http/success? response) (http/success? response)
(rx/of (:body response)) (rx/of (:body response))
(http/client-error? response) (= status 413)
(rx/throw (:body response)) (rx/throw {:type :validation
:code :request-body-too-large
:hint "request body too large"})
(and (http/client-error? response)
(map? body))
(rx/throw body)
:else :else
(rx/throw {:type :unexpected (rx/throw {:type :unexpected-error
:code (:error response)}))) :code :unhandled-http-response
:http-status status
:http-body body})))
(defn- not-found?
[{:keys [type]}]
(= :not-found type))
(defn- body-too-large?
[{:keys [type code]}]
(and (= :validation type)
(= :request-body-too-large code)))
(defn- request-data-for-thumbnail (defn- request-data-for-thumbnail
[file-id revn] [file-id revn]
@ -88,6 +101,7 @@
(->> (http/send! request) (->> (http/send! request)
(rx/map http/conditional-decode-transit) (rx/map http/conditional-decode-transit)
(rx/mapcat handle-response) (rx/mapcat handle-response)
(rx/catch body-too-large? (constantly nil))
(rx/map (constantly params))))) (rx/map (constantly params)))))
(defmethod impl/handler :thumbnails/generate (defmethod impl/handler :thumbnails/generate