0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-01 01:21:21 -05:00

Improve error handling

This commit is contained in:
Andrey Antukh 2022-03-24 12:25:07 +01:00 committed by Alonso Torres
parent ec5a4d09b8
commit 9abf4b126c
5 changed files with 96 additions and 88 deletions

View file

@ -19,7 +19,7 @@
io.lettuce/lettuce-core {:mvn/version "6.1.6.RELEASE"} io.lettuce/lettuce-core {:mvn/version "6.1.6.RELEASE"}
java-http-clj/java-http-clj {:mvn/version "0.4.3"} java-http-clj/java-http-clj {:mvn/version "0.4.3"}
funcool/yetti {:git/tag "v8.0" :git/sha "ea7162d" funcool/yetti {:git/tag "v9.0" :git/sha "e09e46c"
:git/url "https://github.com/funcool/yetti.git" :git/url "https://github.com/funcool/yetti.git"
:exclusions [org.slf4j/slf4j-api]} :exclusions [org.slf4j/slf4j-api]}

View file

@ -8,6 +8,7 @@
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.logging :as l] [app.common.logging :as l]
[app.common.transit :as t]
[app.http.doc :as doc] [app.http.doc :as doc]
[app.http.errors :as errors] [app.http.errors :as errors]
[app.http.middleware :as middleware] [app.http.middleware :as middleware]
@ -67,7 +68,7 @@
:xnio/dispatch (:executor cfg) :xnio/dispatch (:executor cfg)
:ring/async true} :ring/async true}
handler (if (some? router) handler (if (some? router)
(wrap-router cfg router) (wrap-router router)
handler) handler)
server (yt/server handler (d/without-nils options))] server (yt/server handler (d/without-nils options))]
(assoc cfg :server (yt/start! server)))) (assoc cfg :server (yt/start! server))))
@ -81,30 +82,32 @@
[_ respond _] [_ respond _]
(respond (yrs/response 404))) (respond (yrs/response 404)))
(defn- ring-handler
[router]
(fn [request respond raise]
(if-let [match (r/match-by-path router (yrq/path request))]
(let [params (:path-params match)
result (:result match)
handler (or (:handler result) not-found-handler)
request (-> request
(assoc :path-params params)
(update :params merge params))]
(handler request respond raise))
(not-found-handler request respond raise))))
(defn- wrap-router (defn- wrap-router
[_ router] [router]
(let [handler (ring-handler router)] (letfn [(handler [request respond raise]
(if-let [match (r/match-by-path router (yrq/path request))]
(let [params (:path-params match)
result (:result match)
handler (or (:handler result) not-found-handler)
request (-> request
(assoc :path-params params)
(update :params merge params))]
(handler request respond raise))
(not-found-handler request respond raise)))
(on-error [cause request respond]
(let [{:keys [body] :as response} (errors/handle cause request)]
(respond
(cond-> response
(map? body)
(-> (update :headers assoc "content-type" "application/transit+json")
(assoc :body (t/encode-str body {:type :json-verbose})))))))]
(fn [request respond _] (fn [request respond _]
(handler request respond (try
(fn [cause] (handler request respond #(on-error % request respond))
(l/error :hint "unexpected error processing request" (catch Throwable cause
::l/context (errors/get-context request) (on-error cause request respond))))))
:query-string (yrq/query request)
:cause cause)
(respond (yrs/response 500 "internal server error")))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; HTTP ROUTER ;; HTTP ROUTER
@ -130,6 +133,8 @@
(rr/router (rr/router
[["" {:middleware [[middleware/server-timing] [["" {:middleware [[middleware/server-timing]
[middleware/format-response] [middleware/format-response]
[middleware/params]
[middleware/parse-request]
[middleware/errors errors/handle] [middleware/errors errors/handle]
[middleware/restrict-methods]]} [middleware/restrict-methods]]}
["/metrics" {:handler (:handler metrics)}] ["/metrics" {:handler (:handler metrics)}]
@ -138,9 +143,7 @@
["/by-file-media-id/:id" {:handler (:file-objects-handler assets)}] ["/by-file-media-id/:id" {:handler (:file-objects-handler assets)}]
["/by-file-media-id/:id/thumbnail" {:handler (:file-thumbnails-handler assets)}]] ["/by-file-media-id/:id/thumbnail" {:handler (:file-thumbnails-handler assets)}]]
["/dbg" {:middleware [[middleware/params] ["/dbg" {:middleware [(:middleware session)]}
[middleware/parse-request]
(:middleware session)]}
["" {:handler (:index debug)}] ["" {:handler (:index debug)}]
["/error-by-id/:id" {:handler (:retrieve-error debug)}] ["/error-by-id/:id" {:handler (:retrieve-error debug)}]
["/error/:id" {:handler (:retrieve-error debug)}] ["/error/:id" {:handler (:retrieve-error debug)}]
@ -152,15 +155,11 @@
["/sns" {:handler (:awsns-handler cfg) ["/sns" {:handler (:awsns-handler cfg)
:allowed-methods #{:post}}]] :allowed-methods #{:post}}]]
["/ws/notifications" {:middleware [[middleware/params] ["/ws/notifications" {:middleware [(:middleware session)]
[middleware/parse-request]
(:middleware session)]
:handler ws :handler ws
:allowed-methods #{:get}}] :allowed-methods #{:get}}]
["/api" {:middleware [[middleware/cors] ["/api" {:middleware [[middleware/cors]
[middleware/params]
[middleware/parse-request]
(:middleware session)]} (:middleware session)]}
["/health" {:handler (:health-check debug)}] ["/health" {:handler (:health-check debug)}]
["/_doc" {:handler (doc/handler rpc) ["/_doc" {:handler (doc/handler rpc)

View file

@ -24,8 +24,8 @@
(defn get-context (defn get-context
[request] [request]
(merge (merge
{:path (:uri request) {:path (:path request)
:method (:request-method request) :method (:method request)
:params (:params request) :params (:params request)
:ip-addr (parse-client-ip request) :ip-addr (parse-client-ip request)
:profile-id (:profile-id request)} :profile-id (:profile-id request)}
@ -51,9 +51,10 @@
[err _] [err _]
(let [data (ex-data err) (let [data (ex-data err)
explain (us/pretty-explain data)] explain (us/pretty-explain data)]
(yrs/response 400 (-> data (yrs/response :status 400
(dissoc ::s/problems ::s/value) :body (-> data
(cond-> explain (assoc :explain explain)))))) (dissoc ::s/problems ::s/value)
(cond-> explain (assoc :explain explain))))))
(defmethod handle-exception :assertion (defmethod handle-exception :assertion
[error request] [error request]
@ -73,26 +74,6 @@
[err _] [err _]
(yrs/response 404 (ex-data err))) (yrs/response 404 (ex-data err)))
(defmethod handle-exception :default
[error request]
(let [edata (ex-data error)]
;; NOTE: this is a special case for the idle-in-transaction error;
;; when it happens, the connection is automatically closed and
;; next-jdbc combines the two errors in a single ex-info. We only
;; need the :handling error, because the :rollback error will be
;; always "connection closed".
(if (and (ex/exception? (:rollback edata))
(ex/exception? (:handling edata)))
(handle-exception (:handling edata) request)
(do
(l/error ::l/raw (ex-message error)
::l/context (get-context request)
:cause error)
(yrs/response 500 {:type :server-error
:code :unexpected
:hint (ex-message error)
:data edata})))))
(defmethod handle-exception org.postgresql.util.PSQLException (defmethod handle-exception org.postgresql.util.PSQLException
[error request] [error request]
(let [state (.getSQLState ^java.sql.SQLException error)] (let [state (.getSQLState ^java.sql.SQLException error)]
@ -101,24 +82,56 @@
:cause error) :cause error)
(cond (cond
(= state "57014") (= state "57014")
(yrs/response 504 {:type :server-timeout (yrs/response 504 {:type :server-error
:code :statement-timeout :code :statement-timeout
:hint (ex-message error)}) :hint (ex-message error)})
(= state "25P03") (= state "25P03")
(yrs/response 504 {:type :server-timeout (yrs/response 504 {:type :server-error
:code :idle-in-transaction-timeout :code :idle-in-transaction-timeout
:hint (ex-message error)}) :hint (ex-message error)})
:else :else
(yrs/response 500 {:type :server-error (yrs/response 500 {:type :server-error
:code :psql-exception :code :unexpected
:hint (ex-message error) :hint (ex-message error)
:state state})))) :state state}))))
(defmethod handle-exception :default
[error request]
(let [edata (ex-data error)]
(cond
;; This means that exception is not a controlled exception.
(nil? edata)
(do
(l/error ::l/raw (ex-message error)
::l/context (get-context request)
:cause error)
(yrs/response 500 {:type :server-error
:code :unexpected
:hint (ex-message error)}))
;; This is a special case for the idle-in-transaction error;
;; when it happens, the connection is automatically closed and
;; next-jdbc combines the two errors in a single ex-info. We
;; only need the :handling error, because the :rollback error
;; will be always "connection closed".
(and (ex/exception? (:rollback edata))
(ex/exception? (:handling edata)))
(handle-exception (:handling edata) request)
:else
(do
(l/error ::l/raw (ex-message error)
::l/context (get-context request)
:cause error)
(yrs/response 500 {:type :server-error
:code :unhandled
:hint (ex-message error)
:data edata})))))
(defn handle (defn handle
[error req] [error request]
(if (or (instance? java.util.concurrent.CompletionException error) (if (or (instance? java.util.concurrent.CompletionException error)
(instance? java.util.concurrent.ExecutionException error)) (instance? java.util.concurrent.ExecutionException error))
(handle-exception (.getCause ^Throwable error) req) (handle-exception (.getCause ^Throwable error) request)
(handle-exception error req))) (handle-exception error request)))

View file

@ -6,6 +6,7 @@
(ns app.http.middleware (ns app.http.middleware
(:require (:require
[app.common.exceptions :as ex]
[app.common.logging :as l] [app.common.logging :as l]
[app.common.transit :as t] [app.common.transit :as t]
[app.config :as cf] [app.config :as cf]
@ -45,23 +46,17 @@
(update :params merge params)))) (update :params merge params))))
:else :else
request))) request)))]
(handle-exception [cause]
(let [data {:type :validation
:code :unable-to-parse-request-body
:hint "malformed params"}]
(l/error :hint (ex-message cause) :cause cause)
(yrs/response :status 400
:headers {"content-type" "application/transit+json"}
:body (t/encode-str data {:type :json-verbose}))))]
(fn [request respond raise] (fn [request respond raise]
(try (try
(let [request (process-request request)] (let [request (process-request request)]
(handler request respond raise)) (handler request respond raise))
(catch Exception cause (catch Exception cause
(respond (handle-exception cause))))))) (raise (ex/error :type :validation
:code :malformed-params
:hint (ex-message cause)
:cause cause)))))))
(def parse-request (def parse-request
{:name ::parse-request {:name ::parse-request

View file

@ -52,18 +52,6 @@
(st/emit! (du/logout {:capture-redirect true})) (st/emit! (du/logout {:capture-redirect true}))
(ts/schedule 500 (st/emitf (msg/warn msg))))) (ts/schedule 500 (st/emitf (msg/warn msg)))))
;; That are special case server-errors that should be treated
;; differently.
(derive :not-found ::exceptional-state)
(derive :bad-gateway ::exceptional-state)
(derive :service-unavailable ::exceptional-state)
(defmethod ptk/handle-error ::exceptional-state
[error]
(ts/schedule
(st/emitf (rt/assign-exception error))))
;; Error that happens on an active business model validation does not ;; Error that happens on an active business model validation does not
;; passes an validation (example: profile can't leave a team). From ;; passes an validation (example: profile can't leave a team). From
;; the user perspective a error flash message should be visualized but ;; the user perspective a error flash message should be visualized but
@ -134,9 +122,22 @@
(js/console.error (with-out-str (expound/printer error))) (js/console.error (with-out-str (expound/printer error)))
(js/console.groupEnd message))) (js/console.groupEnd message)))
;; That are special case server-errors that should be treated
;; differently.
(derive :not-found ::exceptional-state)
(derive :bad-gateway ::exceptional-state)
(derive :service-unavailable ::exceptional-state)
(defmethod ptk/handle-error ::exceptional-state
[error]
(ts/schedule
(st/emitf (rt/assign-exception error))))
;; This happens when the backed server fails to process the ;; This happens when the backed server fails to process the
;; request. This can be caused by an internal assertion or any other ;; request. This can be caused by an internal assertion or any other
;; uncontrolled error. ;; uncontrolled error.
(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))
@ -146,8 +147,8 @@
(ts/schedule (ts/schedule
#(st/emit! #(st/emit!
(msg/show {:content "Something wrong has happened (on backend)." (msg/show {:content "Something wrong has happened (on backend)."
:type :error :type :error
:timeout 3000}))) :timeout 3000})))
(js/console.group msg) (js/console.group msg)
(js/console.info info) (js/console.info info)