diff --git a/backend/resources/error-report.tmpl b/backend/resources/error-report.tmpl new file mode 100644 index 000000000..32f796d6d --- /dev/null +++ b/backend/resources/error-report.tmpl @@ -0,0 +1,135 @@ + + + + + + penpot - error report {{id}} + + + + +
+
+
ERID:
+
{{id}}
+
+
+
VERS:
+
{{version}}
+
+
+
HOST:
+
{{host}}
+
+ {% if type %} +
+
TYPE:
+
{{type}}
+
+ {% endif %} + {% if code %} +
+
CODE:
+
{{code}}
+
+ {% endif %} +
+
CLASS:
+
{{class}}
+
+
+
HINT:
+
{{hint}}
+
+ +
+
PATH:
+
{{method|upper}} {{path}}
+
+ + {% if params %} +
+
PARAMS:
+
+
{{params}}
+
+
+ {% endif %} + + {% if explain %} +
+
EXPLAIN:
+
+
{{explain}}
+
+
+ {% endif %} + + {% if data %} +
+
EDATA:
+
+
{{data}}
+
+
+ {% endif %} + + +
+
TRACE:
+
+
{{message}}
+
+
+
+ + + diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 08c569622..3417b1b90 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -26,7 +26,7 @@ :secret-key "default" :asserts-enabled true - :public-uri "http://localhost:3449/" + :public-uri "http://localhost:3449" :redis-uri "redis://localhost/0" :storage-backend :fs diff --git a/backend/src/app/error_reporter.clj b/backend/src/app/error_reporter.clj index e9fd0a5cc..7b9ddead8 100644 --- a/backend/src/app/error_reporter.clj +++ b/backend/src/app/error_reporter.clj @@ -12,67 +12,94 @@ (:require [app.common.exceptions :as ex] [app.common.spec :as us] + [app.common.uuid :as uuid] [app.config :as cfg] [app.db :as db] [app.tasks :as tasks] [app.worker :as wrk] [app.util.json :as json] [app.util.http :as http] + [app.util.template :as tmpl] + [clojure.pprint :refer [pprint]] [clojure.core.async :as a] [clojure.spec.alpha :as s] [clojure.tools.logging :as log] + [clojure.java.io :as io] [cuerdas.core :as str] [integrant.core :as ig] - [promesa.exec :as px])) + [promesa.exec :as px]) + (:import + org.apache.logging.log4j.core.LogEvent + org.apache.logging.log4j.util.ReadOnlyStringMap)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Error Reporting +;; Error Listener ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(declare send-notification!) -(defonce queue-fn identity) +(declare handle-event) + +(defonce queue (a/chan (a/sliding-buffer 64))) +(defonce queue-fn (fn [event] (a/>!! queue event))) (s/def ::uri ::us/string) -(defmethod ig/pre-init-spec ::instance [_] - (s/keys :req-un [::wrk/executor] + +(defmethod ig/pre-init-spec ::reporter [_] + (s/keys :req-un [::wrk/executor ::db/pool] :opt-un [::uri])) -(defmethod ig/init-key ::instance +(defmethod ig/init-key ::reporter [_ {:keys [executor uri] :as cfg}] - (let [out (a/chan (a/sliding-buffer 64))] - (log/info "Intializing error reporter.") - (if uri - (do - (alter-var-root #'queue-fn (constantly (fn [x] (a/>!! out (str x))))) - (a/go-loop [] - (let [val (a/ (io/resource "error-report.tmpl") + (tmpl/render content)))] + + + (fn [request] + (let [result (some-> (parse-id request) + (retrieve-report) + (render-template))] + (if result + {:status 200 + :headers {"content-type" "text/html; charset=utf-8"} + :body result} + {:status 404 + :body "not found"}))))) diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 19fb909da..aeffb8020 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -9,17 +9,18 @@ (ns app.http (:require - [app.common.spec :as us] [app.common.data :as d] + [app.common.spec :as us] + [app.common.uuid :as uuid] [app.config :as cfg] + [app.http.assets :as assets] [app.http.auth :as auth] [app.http.errors :as errors] [app.http.middleware :as middleware] - [app.http.assets :as assets] [app.metrics :as mtx] + [clojure.spec.alpha :as s] [clojure.tools.logging :as log] [integrant.core :as ig] - [clojure.spec.alpha :as s] [reitit.ring :as rr] [ring.adapter.jetty9 :as jetty]) (:import @@ -101,12 +102,11 @@ (try (handler request) (catch Exception e - (log/errorf e - (str "Unhandled exception: " (ex-message e) "\n" - "=| uri: " (pr-str (:uri request)) "\n" - "=| method: " (pr-str (:request-method request)) "\n")) - {:status 500 - :body "internal server error"}))))) + (let [cdata (errors/get-error-context request e)] + (errors/update-thread-context! cdata) + (log/errorf e "Unhandled exception: %s (id: %s)" (ex-message e) (str (:id cdata))) + {:status 500 + :body "internal server error"})))))) (defn- create-router [{:keys [session rpc google-auth gitlab-auth github-auth metrics ldap-auth storage svgparse] :as cfg}] @@ -119,12 +119,15 @@ ["/by-file-media-id/:id" {:get #(assets/file-objects-handler storage %)}] ["/by-file-media-id/:id/thumbnail" {:get #(assets/file-thumbnails-handler storage %)}]] + ["/dbg" + ["/error-by-id/:id" {:get (:error-reporter-handler cfg)}]] + ["/api" {:middleware [[middleware/format-response-body] [middleware/parse-request-body] - [middleware/errors errors/handle] [middleware/params] [middleware/multipart-params] [middleware/keyword-params] + [middleware/errors errors/handle] [middleware/cookies]]} ["/svg" {:post svgparse}] diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index febc707de..7c16e965e 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -10,27 +10,50 @@ (ns app.http.errors "A errors handling for the http server." (:require - [clojure.pprint :refer [pprint]] [app.common.exceptions :as ex] + [app.common.uuid :as uuid] + [app.config :as cfg] + [clojure.pprint :refer [pprint]] [clojure.tools.logging :as log] [cuerdas.core :as str] - [expound.alpha :as expound])) + [expound.alpha :as expound]) + (:import + org.apache.logging.log4j.ThreadContext)) -(defn get-context-string - [request edata] - (str "=| uri: " (pr-str (:uri request)) "\n" - "=| method: " (pr-str (:request-method request)) "\n" - "=| params: \n" - (with-out-str - (pprint (:params request))) - "\n" +(defn update-thread-context! + [data] + (run! (fn [[key val]] + (ThreadContext/put + (name key) + (cond + (coll? val) (with-out-str (pprint val)) + (instance? clojure.lang.Named val) (name val) + :else (str val)))) + data)) - (when (map? edata) - (str "=| ex-data: \n" - (with-out-str - (pprint edata)))) +(defn- explain-error + [error] + (with-out-str + (expound/printer (:data error)))) - "\n")) +(defn get-error-context + [request error] + (let [edata (ex-data error)] + (merge + {:id (uuid/next) + :path (:uri request) + :method (:request-method request) + :params (:params request) + :version (:full cfg/version) + :host (:host cfg/config) + :class (.getCanonicalName ^java.lang.Class (class error)) + :hint (ex-message error)} + + (when (map? edata) + edata) + + (when (and (map? edata) (:data edata)) + {:explain (explain-error edata)})))) (defmulti handle-exception (fn [err & _rest] @@ -42,11 +65,6 @@ [err _] {:status 401 :body (ex-data err)}) -(defn- explain-error - [error] - (with-out-str - (expound/printer (:data error)))) - (defmethod handle-exception :validation [err req] (let [header (get-in req [:headers "accept"]) @@ -66,11 +84,10 @@ (defmethod handle-exception :assertion [error request] - (let [edata (ex-data error)] - (log/error error - (str "Internal error: assertion\n" - (get-context-string request edata) - (explain-error edata))) + (let [edata (ex-data error) + cdata (get-error-context request error)] + (update-thread-context! cdata) + (log/errorf error "Internal error: assertion (id: %s)" (str (:id cdata))) {:status 500 :body {:type :server-error :data (-> edata @@ -83,15 +100,15 @@ (defmethod handle-exception :default [error request] - (let [edata (ex-data error)] - (log/error error - (str "Internal Error: " - (ex-message error) - (get-context-string request edata))) + (let [cdata (get-error-context request error)] + (update-thread-context! cdata) + (log/errorf error "Internal error: %s (id: %s)" + (ex-message error) + (str (:id cdata))) {:status 500 :body {:type :server-error :hint (ex-message error) - :data edata}})) + :data (ex-data error)}})) (defn handle [error req] diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index 3caa6f858..4aab02774 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -14,7 +14,6 @@ [app.metrics :as mtx] [app.util.transit :as t] [app.util.json :as json] - ;; [clojure.data.json :as json] [clojure.java.io :as io] [ring.middleware.cookies :refer [wrap-cookies]] [ring.middleware.keyword-params :refer [wrap-keyword-params]] diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 18911e2dc..411a54525 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -84,6 +84,9 @@ :github-auth (ig/ref :app.http.auth/github) :ldap-auth (ig/ref :app.http.auth/ldap) :svgparse (ig/ref :app.svgparse/handler) + + :error-reporter-handler (ig/ref :app.error-reporter/handler) + :storage (ig/ref :app.storage/storage)} :app.svgparse/svgc @@ -240,10 +243,14 @@ :app.srepl/server {:port 6062} - :app.error-reporter/instance - {:uri (:error-report-webhook cfg/config) + :app.error-reporter/reporter + {:uri (:error-report-webhook cfg/config) + :pool (ig/ref :app.db/pool) :executor (ig/ref :app.worker/executor)} + :app.error-reporter/handler + {:pool (ig/ref :app.db/pool)} + :app.storage/storage {:pool (ig/ref :app.db/pool) :executor (ig/ref :app.worker/executor) diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index f74c9f2d8..769b01434 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -131,6 +131,9 @@ {:name "0039-fix-some-on-delete-triggers" :fn (mg/resource "app/migrations/sql/0039-fix-some-on-delete-triggers.sql")} + + {:name "0040-add-error-report-tables" + :fn (mg/resource "app/migrations/sql/0040-add-error-report-tables.sql")} ]) diff --git a/backend/src/app/migrations/sql/0040-add-error-report-tables.sql b/backend/src/app/migrations/sql/0040-add-error-report-tables.sql new file mode 100644 index 000000000..a5271a9c6 --- /dev/null +++ b/backend/src/app/migrations/sql/0040-add-error-report-tables.sql @@ -0,0 +1,10 @@ +CREATE TABLE server_error_report ( + id uuid NOT NULL, + created_at timestamptz NOT NULL DEFAULT clock_timestamp(), + content jsonb, + + PRIMARY KEY (id, created_at) +); + +ALTER TABLE server_error_report + ALTER COLUMN content SET STORAGE external; diff --git a/docker/devenv/files/nginx.conf b/docker/devenv/files/nginx.conf index 78a50b949..2227dc05a 100644 --- a/docker/devenv/files/nginx.conf +++ b/docker/devenv/files/nginx.conf @@ -99,6 +99,10 @@ http { proxy_pass http://127.0.0.1:6060/api; } + location /dbg { + proxy_pass http://127.0.0.1:6060/dbg; + } + location /export { proxy_pass http://127.0.0.1:6061; }