mirror of
https://github.com/penpot/penpot.git
synced 2026-09-12 23:19:28 +00:00
✨ Add several improvements to frontend error reporting
* ✨ Add major improvement on error handling * ✨ Add the ability to store frontend reports * 📎 Add PR feedback changes
This commit is contained in:
parent
ebb7d01bc9
commit
d80ba1856a
@ -5,7 +5,6 @@
|
|||||||
<meta name="robots" content="noindex,nofollow">
|
<meta name="robots" content="noindex,nofollow">
|
||||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||||
<title>{% block title %}{% endblock %}</title>
|
<title>{% block title %}{% endblock %}</title>
|
||||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono">
|
|
||||||
<style>
|
<style>
|
||||||
{% include "app/templates/styles.css" %}
|
{% include "app/templates/styles.css" %}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -5,23 +5,25 @@ penpot - error list
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<nav>
|
<nav>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<h1>Error reports (last 200)
|
<a href="/dbg"> [BACK]</a>
|
||||||
<a href="/dbg">[GO BACK]</a>
|
<h1>Error reports (last 300)</h1>
|
||||||
</h1>
|
|
||||||
</div>
|
<a class="{% if version = 3 %}strong{% endif %}" href="?version=3">[BACKEND ERRORS]</a>
|
||||||
</nav>
|
<a class="{% if version = 4 %}strong{% endif %}" href="?version=4">[FRONTEND ERRORS]</a>
|
||||||
<main class="horizontal-list">
|
</div>
|
||||||
<ul>
|
</nav>
|
||||||
{% for item in items %}
|
<main class="horizontal-list">
|
||||||
<li>
|
<ul>
|
||||||
<a class="date" href="/dbg/error/{{item.id}}">{{item.created-at}}</a>
|
{% for item in items %}
|
||||||
<a class="hint" href="/dbg/error/{{item.id}}">
|
<li>
|
||||||
<span class="title">{{item.hint|abbreviate:150}}</span>
|
<a class="date" href="/dbg/error/{{item.id}}">{{item.created-at}}</a>
|
||||||
</a>
|
<a class="hint" href="/dbg/error/{{item.id}}">
|
||||||
</li>
|
<span class="title">{{item.hint|abbreviate:150}}</span>
|
||||||
{% endfor %}
|
</a>
|
||||||
</ul>
|
</li>
|
||||||
</main>
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@ -6,7 +6,7 @@ Report: {{hint|abbreviate:150}} - {{id}} - Penpot Error Report (v3)
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<nav>
|
<nav>
|
||||||
<div>[<a href="/dbg/error">⮜</a>]</div>
|
<div>[<a href="/dbg/error?version={{version}}">⮜</a>]</div>
|
||||||
<div>[<a href="#head">head</a>]</div>
|
<div>[<a href="#head">head</a>]</div>
|
||||||
<div>[<a href="#props">props</a>]</div>
|
<div>[<a href="#props">props</a>]</div>
|
||||||
<div>[<a href="#context">context</a>]</div>
|
<div>[<a href="#context">context</a>]</div>
|
||||||
|
|||||||
46
backend/resources/app/templates/error-report.v4.tmpl
Normal file
46
backend/resources/app/templates/error-report.v4.tmpl
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
{% extends "app/templates/base.tmpl" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
Report: {{hint|abbreviate:150}} - {{id}} - Penpot Error Report (v4)
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<nav>
|
||||||
|
<div>[<a href="/dbg/error?version={{version}}">⮜</a>]</div>
|
||||||
|
<div>[<a href="#head">head</a>]</div>
|
||||||
|
<!-- <div>[<a href="#props">props</a>]</div> -->
|
||||||
|
<div>[<a href="#context">context</a>]</div>
|
||||||
|
{% if report %}
|
||||||
|
<div>[<a href="#report">report</a>]</div>
|
||||||
|
{% endif %}
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
<div class="table">
|
||||||
|
<div class="table-row multiline">
|
||||||
|
<div id="head" class="table-key">HEAD</div>
|
||||||
|
<div class="table-val">
|
||||||
|
<h1><span class="not-important">Hint:</span> <br/> {{hint}}</h1>
|
||||||
|
<h2><span class="not-important">Reported at:</span> <br/> {{created-at}}</h2>
|
||||||
|
<h2><span class="not-important">Report ID:</span> <br/> {{id}}</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="table-row multiline">
|
||||||
|
<div id="context" class="table-key">CONTEXT: </div>
|
||||||
|
|
||||||
|
<div class="table-val">
|
||||||
|
<pre>{{context}}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if report %}
|
||||||
|
<div class="table-row multiline">
|
||||||
|
<div id="report" class="table-key">REPORT:</div>
|
||||||
|
<div class="table-val">
|
||||||
|
<pre>{{report}}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
{% endblock %}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
* {
|
* {
|
||||||
font-family: "JetBrains Mono", monospace;
|
font-family: monospace;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,6 +36,10 @@ small {
|
|||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.strong {
|
||||||
|
font-weight: 900;
|
||||||
|
}
|
||||||
|
|
||||||
.not-important {
|
.not-important {
|
||||||
color: #888;
|
color: #888;
|
||||||
font-weight: 200;
|
font-weight: 200;
|
||||||
@ -57,14 +61,26 @@ nav {
|
|||||||
|
|
||||||
nav > .title {
|
nav > .title {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nav > .title > a {
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav > .title > a.strong {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
nav > .title > h1 {
|
nav > .title > h1 {
|
||||||
padding: 0px;
|
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav > .title > * {
|
||||||
|
padding: 0px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav > div {
|
nav > div {
|
||||||
|
|||||||
@ -232,13 +232,22 @@
|
|||||||
(-> (io/resource "app/templates/error-report.v3.tmpl")
|
(-> (io/resource "app/templates/error-report.v3.tmpl")
|
||||||
(tmpl/render (-> content
|
(tmpl/render (-> content
|
||||||
(assoc :id id)
|
(assoc :id id)
|
||||||
|
(assoc :version 3)
|
||||||
|
(assoc :created-at (ct/format-inst created-at :rfc1123))))))
|
||||||
|
|
||||||
|
(render-template-v4 [{:keys [content id created-at]}]
|
||||||
|
(-> (io/resource "app/templates/error-report.v4.tmpl")
|
||||||
|
(tmpl/render (-> content
|
||||||
|
(assoc :id id)
|
||||||
|
(assoc :version 4)
|
||||||
(assoc :created-at (ct/format-inst created-at :rfc1123))))))]
|
(assoc :created-at (ct/format-inst created-at :rfc1123))))))]
|
||||||
|
|
||||||
(if-let [report (get-report request)]
|
(if-let [report (get-report request)]
|
||||||
(let [result (case (:version report)
|
(let [result (case (:version report)
|
||||||
1 (render-template-v1 report)
|
1 (render-template-v1 report)
|
||||||
2 (render-template-v2 report)
|
2 (render-template-v2 report)
|
||||||
3 (render-template-v3 report))]
|
3 (render-template-v3 report)
|
||||||
|
4 (render-template-v4 report))]
|
||||||
{::yres/status 200
|
{::yres/status 200
|
||||||
::yres/body result
|
::yres/body result
|
||||||
::yres/headers {"content-type" "text/html; charset=utf-8"
|
::yres/headers {"content-type" "text/html; charset=utf-8"
|
||||||
@ -246,20 +255,22 @@
|
|||||||
{::yres/status 404
|
{::yres/status 404
|
||||||
::yres/body "not found"})))
|
::yres/body "not found"})))
|
||||||
|
|
||||||
(def sql:error-reports
|
(def ^:private sql:error-reports
|
||||||
"SELECT id, created_at,
|
"SELECT id, created_at,
|
||||||
content->>'~:hint' AS hint
|
content->>'~:hint' AS hint
|
||||||
FROM server_error_report
|
FROM server_error_report
|
||||||
|
WHERE version = ?
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
LIMIT 200")
|
LIMIT 300")
|
||||||
|
|
||||||
(defn error-list-handler
|
(defn- error-list-handler
|
||||||
[{:keys [::db/pool]} _request]
|
[{:keys [::db/pool]} {:keys [params]}]
|
||||||
(let [items (->> (db/exec! pool [sql:error-reports])
|
(let [version (or (some-> (get params :version) parse-long) 3)
|
||||||
(map #(update % :created-at ct/format-inst :rfc1123)))]
|
items (->> (db/exec! pool [sql:error-reports version])
|
||||||
|
(map #(update % :created-at ct/format-inst :rfc1123)))]
|
||||||
{::yres/status 200
|
{::yres/status 200
|
||||||
::yres/body (-> (io/resource "app/templates/error-list.tmpl")
|
::yres/body (-> (io/resource "app/templates/error-list.tmpl")
|
||||||
(tmpl/render {:items items}))
|
(tmpl/render {:items items :version version}))
|
||||||
::yres/headers {"content-type" "text/html; charset=utf-8"
|
::yres/headers {"content-type" "text/html; charset=utf-8"
|
||||||
"x-robots-tag" "noindex"}}))
|
"x-robots-tag" "noindex"}}))
|
||||||
|
|
||||||
|
|||||||
@ -32,7 +32,7 @@
|
|||||||
(assoc :request/ip-addr (inet/parse-request request))
|
(assoc :request/ip-addr (inet/parse-request request))
|
||||||
(assoc :request/profile-id (get claims :uid))
|
(assoc :request/profile-id (get claims :uid))
|
||||||
(assoc :request/auth-data auth)
|
(assoc :request/auth-data auth)
|
||||||
(assoc :version/frontend (or (yreq/get-header request "x-frontend-version") "unknown")))))
|
(assoc :frontend/version (or (yreq/get-header request "x-frontend-version") "unknown")))))
|
||||||
|
|
||||||
(defmulti handle-error
|
(defmulti handle-error
|
||||||
(fn [cause _ _]
|
(fn [cause _ _]
|
||||||
|
|||||||
@ -113,6 +113,8 @@
|
|||||||
;; COLLECTOR API
|
;; COLLECTOR API
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(declare ^:private prepare-context-from-request)
|
||||||
|
|
||||||
;; Defines a service that collects the audit/activity log using
|
;; Defines a service that collects the audit/activity log using
|
||||||
;; internal database. Later this audit log can be transferred to
|
;; internal database. Later this audit log can be transferred to
|
||||||
;; an external storage and data cleared.
|
;; an external storage and data cleared.
|
||||||
@ -126,6 +128,8 @@
|
|||||||
[::props {:optional true} [:map-of :keyword :any]]
|
[::props {:optional true} [:map-of :keyword :any]]
|
||||||
[::context {:optional true} [:map-of :keyword :any]]
|
[::context {:optional true} [:map-of :keyword :any]]
|
||||||
[::tracked-at {:optional true} ::ct/inst]
|
[::tracked-at {:optional true} ::ct/inst]
|
||||||
|
[::created-at {:optional true} ::ct/inst]
|
||||||
|
[::source {:optional true} ::sm/text]
|
||||||
[::webhooks/event? {:optional true} ::sm/boolean]
|
[::webhooks/event? {:optional true} ::sm/boolean]
|
||||||
[::webhooks/batch-timeout {:optional true} ::ct/duration]
|
[::webhooks/batch-timeout {:optional true} ::ct/duration]
|
||||||
[::webhooks/batch-key {:optional true}
|
[::webhooks/batch-key {:optional true}
|
||||||
@ -134,36 +138,8 @@
|
|||||||
(def ^:private check-event
|
(def ^:private check-event
|
||||||
(sm/check-fn schema:event))
|
(sm/check-fn schema:event))
|
||||||
|
|
||||||
(defn- prepare-context-from-request
|
(def valid-event?
|
||||||
[request]
|
(sm/validator schema:event))
|
||||||
(let [client-event-origin (get-client-event-origin request)
|
|
||||||
client-version (get-client-version request)
|
|
||||||
client-user-agent (get-client-user-agent request)
|
|
||||||
session-id (get-external-session-id request)
|
|
||||||
key-id (::http/auth-key-id request)
|
|
||||||
token-id (::actoken/id request)
|
|
||||||
token-type (::actoken/type request)]
|
|
||||||
(d/without-nils
|
|
||||||
{:external-session-id session-id
|
|
||||||
:initiator (or key-id "app")
|
|
||||||
:access-token-id (some-> token-id str)
|
|
||||||
:access-token-type (some-> token-type str)
|
|
||||||
:client-event-origin client-event-origin
|
|
||||||
:client-user-agent client-user-agent
|
|
||||||
:client-version client-version
|
|
||||||
:version (:full cf/version)})))
|
|
||||||
|
|
||||||
(defn event-from-rpc-params
|
|
||||||
"Create a base event skeleton with pre-filled some important
|
|
||||||
data that can be extracted from RPC params object"
|
|
||||||
[params]
|
|
||||||
(let [context (some-> params meta ::http/request prepare-context-from-request)
|
|
||||||
event {::type "action"
|
|
||||||
::profile-id (or (::rpc/profile-id params) uuid/zero)
|
|
||||||
::ip-addr (::rpc/ip-addr params)}]
|
|
||||||
(cond-> event
|
|
||||||
(some? context)
|
|
||||||
(assoc ::context context))))
|
|
||||||
|
|
||||||
(defn prepare-event
|
(defn prepare-event
|
||||||
[cfg mdata params result]
|
[cfg mdata params result]
|
||||||
@ -212,6 +188,38 @@
|
|||||||
(::webhooks/event? resultm)
|
(::webhooks/event? resultm)
|
||||||
false)}))
|
false)}))
|
||||||
|
|
||||||
|
(defn- prepare-context-from-request
|
||||||
|
"Prepare backend event context from request"
|
||||||
|
[request]
|
||||||
|
(let [client-event-origin (get-client-event-origin request)
|
||||||
|
client-version (get-client-version request)
|
||||||
|
client-user-agent (get-client-user-agent request)
|
||||||
|
session-id (get-external-session-id request)
|
||||||
|
key-id (::http/auth-key-id request)
|
||||||
|
token-id (::actoken/id request)
|
||||||
|
token-type (::actoken/type request)]
|
||||||
|
(d/without-nils
|
||||||
|
{:external-session-id session-id
|
||||||
|
:initiator (or key-id "app")
|
||||||
|
:access-token-id (some-> token-id str)
|
||||||
|
:access-token-type (some-> token-type str)
|
||||||
|
:client-event-origin client-event-origin
|
||||||
|
:client-user-agent client-user-agent
|
||||||
|
:client-version client-version
|
||||||
|
:version (:full cf/version)})))
|
||||||
|
|
||||||
|
(defn event-from-rpc-params
|
||||||
|
"Create a base event skeleton with pre-filled some important
|
||||||
|
data that can be extracted from RPC params object"
|
||||||
|
[params]
|
||||||
|
(let [context (some-> params meta ::http/request prepare-context-from-request)
|
||||||
|
event {::type "action"
|
||||||
|
::profile-id (or (::rpc/profile-id params) uuid/zero)
|
||||||
|
::ip-addr (::rpc/ip-addr params)}]
|
||||||
|
(cond-> event
|
||||||
|
(some? context)
|
||||||
|
(assoc ::context context))))
|
||||||
|
|
||||||
(defn- event->params
|
(defn- event->params
|
||||||
[event]
|
[event]
|
||||||
(let [params {:id (uuid/next)
|
(let [params {:id (uuid/next)
|
||||||
@ -238,8 +246,10 @@
|
|||||||
|
|
||||||
(defn- handle-event!
|
(defn- handle-event!
|
||||||
[cfg event]
|
[cfg event]
|
||||||
(let [params (event->params event)
|
(let [tnow (ct/now)
|
||||||
tnow (ct/now)]
|
params (-> (event->params event)
|
||||||
|
(assoc :created-at tnow)
|
||||||
|
(update :tracked-at #(or % tnow)))]
|
||||||
|
|
||||||
(when (contains? cf/flags :audit-log-logger)
|
(when (contains? cf/flags :audit-log-logger)
|
||||||
(l/log! ::l/logger "app.audit"
|
(l/log! ::l/logger "app.audit"
|
||||||
@ -255,10 +265,7 @@
|
|||||||
;; NOTE: this operation may cause primary key conflicts on inserts
|
;; NOTE: this operation may cause primary key conflicts on inserts
|
||||||
;; because of the timestamp precission (two concurrent requests), in
|
;; because of the timestamp precission (two concurrent requests), in
|
||||||
;; this case we just retry the operation.
|
;; this case we just retry the operation.
|
||||||
(let [params (-> params
|
(append-audit-entry cfg params))
|
||||||
(assoc :created-at tnow)
|
|
||||||
(update :tracked-at #(or % tnow)))]
|
|
||||||
(append-audit-entry cfg params)))
|
|
||||||
|
|
||||||
(when (and (or (contains? cf/flags :telemetry)
|
(when (and (or (contains? cf/flags :telemetry)
|
||||||
(cf/get :telemetry-enabled))
|
(cf/get :telemetry-enabled))
|
||||||
@ -269,8 +276,6 @@
|
|||||||
;;
|
;;
|
||||||
;; NOTE: this is only executed when general audit log is disabled
|
;; NOTE: this is only executed when general audit log is disabled
|
||||||
(let [params (-> params
|
(let [params (-> params
|
||||||
(assoc :created-at tnow)
|
|
||||||
(update :tracked-at #(or % tnow))
|
|
||||||
(assoc :props {})
|
(assoc :props {})
|
||||||
(assoc :context {}))]
|
(assoc :context {}))]
|
||||||
(append-audit-entry cfg params)))
|
(append-audit-entry cfg params)))
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
|
[app.loggers.audit :as audit]
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[integrant.core :as ig]
|
[integrant.core :as ig]
|
||||||
[promesa.exec :as px]
|
[promesa.exec :as px]
|
||||||
@ -28,69 +29,108 @@
|
|||||||
(defonce enabled (atom true))
|
(defonce enabled (atom true))
|
||||||
|
|
||||||
(defn- persist-on-database!
|
(defn- persist-on-database!
|
||||||
[pool id report]
|
[pool id version report]
|
||||||
(when-not (db/read-only? pool)
|
(when-not (db/read-only? pool)
|
||||||
(db/insert! pool :server-error-report
|
(db/insert! pool :server-error-report
|
||||||
{:id id
|
{:id id
|
||||||
:version 3
|
:version version
|
||||||
:content (db/tjson report)})))
|
:content (db/tjson report)})))
|
||||||
|
|
||||||
|
(defn- concurrent-exception?
|
||||||
|
[cause]
|
||||||
|
(or (instance? java.util.concurrent.CompletionException cause)
|
||||||
|
(instance? java.util.concurrent.ExecutionException cause)))
|
||||||
|
|
||||||
(defn record->report
|
(defn record->report
|
||||||
[{:keys [::l/context ::l/message ::l/props ::l/logger ::l/level ::l/cause] :as record}]
|
[{:keys [::l/context ::l/message ::l/props ::l/logger ::l/level ::l/cause] :as record}]
|
||||||
(assert (l/valid-record? record) "expectd valid log record")
|
(assert (l/valid-record? record) "expectd valid log record")
|
||||||
(if (or (instance? java.util.concurrent.CompletionException cause)
|
(let [data (if (concurrent-exception? cause)
|
||||||
(instance? java.util.concurrent.ExecutionException cause))
|
(ex-data (ex-cause cause))
|
||||||
(-> record
|
(ex-data cause))
|
||||||
(assoc ::trace (ex/format-throwable cause :data? true :explain? false :header? false :summary? false))
|
|
||||||
(assoc ::l/cause (ex-cause cause))
|
|
||||||
(record->report))
|
|
||||||
|
|
||||||
(let [data (ex-data cause)
|
ctx (-> context
|
||||||
ctx (-> context
|
(assoc :service/tenant (cf/get :tenant))
|
||||||
(assoc :tenant (cf/get :tenant))
|
(assoc :service/host (cf/get :host))
|
||||||
(assoc :host (cf/get :host))
|
(assoc :service/public-uri (str (cf/get :public-uri)))
|
||||||
(assoc :public-uri (str (cf/get :public-uri)))
|
(assoc :backend/version (:full cf/version))
|
||||||
(assoc :logger/name logger)
|
(assoc :logger/name logger)
|
||||||
(assoc :logger/level level)
|
(assoc :logger/level level)
|
||||||
(dissoc :request/params :value :params :data))]
|
(dissoc :request/params :value :params :data))]
|
||||||
|
|
||||||
(merge
|
(merge
|
||||||
{:context (-> (into (sorted-map) ctx)
|
{:context (-> (into (sorted-map) ctx)
|
||||||
(pp/pprint-str :length 50))
|
(pp/pprint-str :length 50))
|
||||||
:props (pp/pprint-str props :length 50)
|
:props (pp/pprint-str props :length 50)
|
||||||
:hint (or (when-let [message (ex-message cause)]
|
:hint (or (when-let [message (ex-message cause)]
|
||||||
(if-let [props-hint (:hint props)]
|
(if-let [props-hint (:hint props)]
|
||||||
(str props-hint ": " message)
|
(str props-hint ": " message)
|
||||||
message))
|
message))
|
||||||
@message)
|
@message)
|
||||||
:trace (or (::trace record)
|
:trace (or (::trace record)
|
||||||
(some-> cause (ex/format-throwable :data? true :explain? false :header? false :summary? false)))}
|
(some-> cause (ex/format-throwable :data? true :explain? false :header? false :summary? false)))}
|
||||||
|
|
||||||
(when-let [params (or (:request/params context) (:params context))]
|
(when-let [params (or (:request/params context) (:params context))]
|
||||||
{:params (pp/pprint-str params :length 20 :level 20)})
|
{:params (pp/pprint-str params :length 20 :level 20)})
|
||||||
|
|
||||||
(when-let [value (:value context)]
|
(when-let [value (:value context)]
|
||||||
{:value (pp/pprint-str value :length 30 :level 13)})
|
{:value (pp/pprint-str value :length 30 :level 13)})
|
||||||
|
|
||||||
(when-let [data (some-> data (dissoc ::s/problems ::s/value ::s/spec ::sm/explain :hint))]
|
(when-let [data (some-> data (dissoc ::s/problems ::s/value ::s/spec ::sm/explain :hint))]
|
||||||
{:data (pp/pprint-str data :length 30 :level 13)})
|
{:data (pp/pprint-str data :length 30 :level 13)})
|
||||||
|
|
||||||
(when-let [explain (ex/explain data :length 30 :level 13)]
|
(when-let [explain (ex/explain data :length 30 :level 13)]
|
||||||
{:explain explain})))))
|
{:explain explain}))))
|
||||||
|
|
||||||
(defn error-record?
|
(defn- handle-log-record
|
||||||
[{:keys [::l/level]}]
|
"Convert the log record into a report object and persist it on the database"
|
||||||
(= :error level))
|
|
||||||
|
|
||||||
(defn- handle-event
|
|
||||||
[{:keys [::db/pool]} {:keys [::l/id] :as record}]
|
[{:keys [::db/pool]} {:keys [::l/id] :as record}]
|
||||||
(try
|
(try
|
||||||
(let [uri (cf/get :public-uri)
|
(let [uri (cf/get :public-uri)
|
||||||
report (-> record record->report d/without-nils)]
|
report (-> record record->report d/without-nils)]
|
||||||
(l/debug :hint "registering error on database" :id id
|
(l/dbg :hint "registering error on database"
|
||||||
:uri (str uri "/dbg/error/" id))
|
:id id
|
||||||
|
:src "logging"
|
||||||
|
:uri (str uri "/dbg/error/" id))
|
||||||
|
(persist-on-database! pool id 3 report))
|
||||||
|
(catch Throwable cause
|
||||||
|
(l/warn :hint "unexpected exception on database error logger" :cause cause))))
|
||||||
|
|
||||||
(persist-on-database! pool id report))
|
(defn- event->report
|
||||||
|
[{:keys [::audit/context ::audit/props ::audit/ip-addr] :as record}]
|
||||||
|
(let [context
|
||||||
|
(reduce-kv (fn [context k v]
|
||||||
|
(let [k' (keyword "frontend" (name k))]
|
||||||
|
(-> context
|
||||||
|
(dissoc k)
|
||||||
|
(assoc k' v))))
|
||||||
|
context
|
||||||
|
context)
|
||||||
|
|
||||||
|
context
|
||||||
|
(-> context
|
||||||
|
(assoc :backend/tenant (cf/get :tenant))
|
||||||
|
(assoc :backend/host (cf/get :host))
|
||||||
|
(assoc :backend/public-uri (str (cf/get :public-uri)))
|
||||||
|
(assoc :backend/version (:full cf/version))
|
||||||
|
(assoc :frontend/ip-addr ip-addr))]
|
||||||
|
|
||||||
|
{:context (-> (into (sorted-map) context)
|
||||||
|
(pp/pprint-str :length 50))
|
||||||
|
:props (pp/pprint-str props :length 50)
|
||||||
|
:hint (get props :hint)
|
||||||
|
:report (get props :report)}))
|
||||||
|
|
||||||
|
(defn- handle-audit-event
|
||||||
|
"Convert the log record into a report object and persist it on the database"
|
||||||
|
[{:keys [::db/pool]} {:keys [::audit/id] :as event}]
|
||||||
|
(try
|
||||||
|
(let [uri (cf/get :public-uri)
|
||||||
|
report (-> event event->report d/without-nils)]
|
||||||
|
(l/dbg :hint "registering error on database"
|
||||||
|
:id id
|
||||||
|
:src "audit-log"
|
||||||
|
:uri (str uri "/dbg/error/" id))
|
||||||
|
(persist-on-database! pool id 4 report))
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(l/warn :hint "unexpected exception on database error logger" :cause cause))))
|
(l/warn :hint "unexpected exception on database error logger" :cause cause))))
|
||||||
|
|
||||||
@ -100,26 +140,49 @@
|
|||||||
|
|
||||||
(defmethod ig/init-key ::reporter
|
(defmethod ig/init-key ::reporter
|
||||||
[_ cfg]
|
[_ cfg]
|
||||||
(let [input (sp/chan :buf (sp/sliding-buffer 64)
|
(let [input (sp/chan :buf (sp/sliding-buffer 256))
|
||||||
:xf (filter error-record?))]
|
thread (px/thread
|
||||||
(add-watch l/log-record ::reporter #(sp/put! input %4))
|
{:name "penpot/reporter/database"}
|
||||||
|
(l/info :hint "initializing database error persistence")
|
||||||
|
(try
|
||||||
|
(loop []
|
||||||
|
(when-let [item (sp/take! input)]
|
||||||
|
(cond
|
||||||
|
(::l/id item)
|
||||||
|
(handle-log-record cfg item)
|
||||||
|
|
||||||
(px/thread {:name "penpot/database-reporter"}
|
(::audit/id item)
|
||||||
(l/info :hint "initializing database error persistence")
|
(handle-audit-event cfg item)
|
||||||
(try
|
|
||||||
(loop []
|
:else
|
||||||
(when-let [record (sp/take! input)]
|
(l/warn :hint "received unexpected item" :item item))
|
||||||
(handle-event cfg record)
|
|
||||||
(recur)))
|
(recur)))
|
||||||
(catch InterruptedException _
|
|
||||||
(l/debug :hint "reporter interrupted"))
|
(catch InterruptedException _
|
||||||
(catch Throwable cause
|
(l/debug :hint "reporter interrupted"))
|
||||||
(l/error :hint "unexpected error" :cause cause))
|
(catch Throwable cause
|
||||||
(finally
|
(l/error :hint "unexpected error" :cause cause))
|
||||||
(sp/close! input)
|
(finally
|
||||||
(remove-watch l/log-record ::reporter)
|
(l/info :hint "reporter terminated"))))]
|
||||||
(l/info :hint "reporter terminated"))))))
|
|
||||||
|
(add-watch l/log-record ::reporter
|
||||||
|
(fn [_ _ _ record]
|
||||||
|
(when (= :error (::l/level record))
|
||||||
|
(sp/put! input record))))
|
||||||
|
|
||||||
|
{::input input
|
||||||
|
::thread thread}))
|
||||||
|
|
||||||
(defmethod ig/halt-key! ::reporter
|
(defmethod ig/halt-key! ::reporter
|
||||||
[_ thread]
|
[_ {:keys [::input ::thread]}]
|
||||||
(some-> thread px/interrupt!))
|
(remove-watch l/log-record ::reporter)
|
||||||
|
(sp/close! input)
|
||||||
|
(px/interrupt! thread))
|
||||||
|
|
||||||
|
(defn emit
|
||||||
|
"Emit an event/report into the database reporter"
|
||||||
|
[cfg event]
|
||||||
|
(when-let [{:keys [::input]} (get cfg ::reporter)]
|
||||||
|
(sp/put! input event)))
|
||||||
|
|
||||||
|
|||||||
@ -9,9 +9,10 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
|
[app.common.uri :as u]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.http.client :as http]
|
[app.http.client :as http]
|
||||||
[app.loggers.database :as ldb]
|
[app.loggers.audit :as audit]
|
||||||
[app.util.json :as json]
|
[app.util.json :as json]
|
||||||
[integrant.core :as ig]
|
[integrant.core :as ig]
|
||||||
[promesa.exec :as px]
|
[promesa.exec :as px]
|
||||||
@ -20,24 +21,27 @@
|
|||||||
(defonce enabled (atom true))
|
(defonce enabled (atom true))
|
||||||
|
|
||||||
(defn- send-mattermost-notification!
|
(defn- send-mattermost-notification!
|
||||||
[cfg {:keys [id public-uri] :as report}]
|
[cfg {:keys [id] :as report}]
|
||||||
|
|
||||||
|
|
||||||
(let [text (str "Exception: " public-uri "/dbg/error/" id " "
|
(let [url (u/join (cf/get :public-uri) "/dbg/error/" id)
|
||||||
|
|
||||||
|
text (str "Exception: " url " "
|
||||||
(when-let [pid (:profile-id report)]
|
(when-let [pid (:profile-id report)]
|
||||||
(str "(pid: #uuid-" pid ")"))
|
(str "(pid: #uuid-" pid ")"))
|
||||||
"\n"
|
"\n"
|
||||||
"- host: #" (:host report) "\n"
|
"- host: #" (:host report) "\n"
|
||||||
"- tenant: #" (:tenant report) "\n"
|
"- tenant: #" (:tenant report) "\n"
|
||||||
"- logger: #" (:logger report) "\n"
|
"- origin: #" (:origin report) "\n"
|
||||||
"- request-path: `" (:request-path report) "`\n"
|
"- href: `" (:href report) "`\n"
|
||||||
"- frontend-version: `" (:frontend-version report) "`\n"
|
"- frontend-version: `" (:frontend-version report) "`\n"
|
||||||
"- backend-version: `" (:backend-version report) "`\n"
|
"- backend-version: `" (:backend-version report) "`\n"
|
||||||
"\n"
|
"\n"
|
||||||
"```\n"
|
(when-let [trace (:trace report)]
|
||||||
"Trace:\n"
|
(str "```\n"
|
||||||
(:trace report)
|
"Trace:\n"
|
||||||
"```")
|
trace
|
||||||
|
"```")))
|
||||||
|
|
||||||
resp (http/req! cfg
|
resp (http/req! cfg
|
||||||
{:uri (cf/get :error-report-webhook)
|
{:uri (cf/get :error-report-webhook)
|
||||||
@ -50,28 +54,49 @@
|
|||||||
(l/warn :hint "error on sending data"
|
(l/warn :hint "error on sending data"
|
||||||
:response (pr-str resp)))))
|
:response (pr-str resp)))))
|
||||||
|
|
||||||
(defn record->report
|
(defn- record->report
|
||||||
[{:keys [::l/context ::l/id ::l/cause] :as record}]
|
[{:keys [::l/context ::l/id ::l/cause] :as record}]
|
||||||
(assert (l/valid-record? record) "expectd valid log record")
|
(assert (l/valid-record? record) "expectd valid log record")
|
||||||
|
|
||||||
|
(let [public-uri (cf/get :public-uri)]
|
||||||
|
{:id id
|
||||||
|
:origin "logging"
|
||||||
|
:tenant (cf/get :tenant)
|
||||||
|
:host (cf/get :host)
|
||||||
|
:backend-version (:full cf/version)
|
||||||
|
:frontend-version (:frontend/version context)
|
||||||
|
:profile-id (:request/profile-id context)
|
||||||
|
:href (-> public-uri
|
||||||
|
(assoc :path (:request/path context))
|
||||||
|
(str))
|
||||||
|
:trace (ex/format-throwable cause :detail? false :header? false)}))
|
||||||
|
|
||||||
|
(defn- audit-event->report
|
||||||
|
[{:keys [::audit/context ::audit/props ::audit/id] :as event}]
|
||||||
{:id id
|
{:id id
|
||||||
|
:origin "audit-log"
|
||||||
:tenant (cf/get :tenant)
|
:tenant (cf/get :tenant)
|
||||||
:host (cf/get :host)
|
:host (cf/get :host)
|
||||||
:public-uri (cf/get :public-uri)
|
:backend-version (:full cf/version)
|
||||||
:backend-version (or (:version/backend context) (:full cf/version))
|
:frontend-version (:version context)
|
||||||
:frontend-version (:version/frontend context)
|
:profile-id (:audit/profile-id event)
|
||||||
:profile-id (:request/profile-id context)
|
:href (get props :href)})
|
||||||
:request-path (:request/path context)
|
|
||||||
:logger (::l/logger record)
|
|
||||||
:trace (ex/format-throwable cause :detail? false :header? false)})
|
|
||||||
|
|
||||||
(defn handle-event
|
(defn- handle-log-record
|
||||||
[cfg record]
|
[cfg record]
|
||||||
(when @enabled
|
(try
|
||||||
(try
|
(let [report (record->report record)]
|
||||||
(let [report (record->report record)]
|
(send-mattermost-notification! cfg report))
|
||||||
(send-mattermost-notification! cfg report))
|
(catch Throwable cause
|
||||||
(catch Throwable cause
|
(l/warn :hint "unhandled error" :cause cause))))
|
||||||
(l/warn :hint "unhandled error" :cause cause)))))
|
|
||||||
|
(defn- handle-audit-event
|
||||||
|
[cfg record]
|
||||||
|
(try
|
||||||
|
(let [report (audit-event->report record)]
|
||||||
|
(send-mattermost-notification! cfg report))
|
||||||
|
(catch Throwable cause
|
||||||
|
(l/warn :hint "unhandled error" :cause cause))))
|
||||||
|
|
||||||
(defmethod ig/assert-key ::reporter
|
(defmethod ig/assert-key ::reporter
|
||||||
[_ params]
|
[_ params]
|
||||||
@ -80,27 +105,49 @@
|
|||||||
(defmethod ig/init-key ::reporter
|
(defmethod ig/init-key ::reporter
|
||||||
[_ cfg]
|
[_ cfg]
|
||||||
(when-let [uri (cf/get :error-report-webhook)]
|
(when-let [uri (cf/get :error-report-webhook)]
|
||||||
(px/thread
|
(let [input (sp/chan :buf (sp/sliding-buffer 256))
|
||||||
{:name "penpot/mattermost-reporter"
|
thread (px/thread
|
||||||
:virtual true}
|
{:name "penpot/reporter/mattermost"}
|
||||||
(l/info :hint "initializing error reporter" :uri uri)
|
(l/info :hint "initializing error reporter" :uri uri)
|
||||||
(let [input (sp/chan :buf (sp/sliding-buffer 128)
|
|
||||||
:xf (filter ldb/error-record?))]
|
(try
|
||||||
(add-watch l/log-record ::reporter #(sp/put! input %4))
|
(loop []
|
||||||
(try
|
(when-let [item (sp/take! input)]
|
||||||
(loop []
|
(when @enabled
|
||||||
(when-let [msg (sp/take! input)]
|
(cond
|
||||||
(handle-event cfg msg)
|
(::l/id item)
|
||||||
(recur)))
|
(handle-log-record cfg item)
|
||||||
(catch InterruptedException _
|
|
||||||
(l/debug :hint "reporter interrupted"))
|
(::audit/id item)
|
||||||
(catch Throwable cause
|
(handle-audit-event cfg item)
|
||||||
(l/error :hint "unexpected error" :cause cause))
|
|
||||||
(finally
|
:else
|
||||||
(sp/close! input)
|
(l/warn :hint "received unexpected item" :item item)))
|
||||||
(remove-watch l/log-record ::reporter)
|
|
||||||
(l/info :hint "reporter terminated")))))))
|
(recur)))
|
||||||
|
(catch InterruptedException _
|
||||||
|
(l/debug :hint "reporter interrupted"))
|
||||||
|
(catch Throwable cause
|
||||||
|
(l/error :hint "unexpected error" :cause cause))
|
||||||
|
(finally
|
||||||
|
(l/info :hint "reporter terminated"))))]
|
||||||
|
|
||||||
|
(add-watch l/log-record ::reporter
|
||||||
|
(fn [_ _ _ record]
|
||||||
|
(when (= :error (::l/level record))
|
||||||
|
(sp/put! input record))))
|
||||||
|
|
||||||
|
{::input input
|
||||||
|
::thread thread})))
|
||||||
|
|
||||||
(defmethod ig/halt-key! ::reporter
|
(defmethod ig/halt-key! ::reporter
|
||||||
[_ thread]
|
[_ {:keys [::input ::thread]}]
|
||||||
|
(remove-watch l/log-record ::reporter)
|
||||||
|
(some-> input sp/close!)
|
||||||
(some-> thread px/interrupt!))
|
(some-> thread px/interrupt!))
|
||||||
|
|
||||||
|
(defn emit
|
||||||
|
"Emit an event/report into the mattermost reporter"
|
||||||
|
[cfg event]
|
||||||
|
(when-let [{:keys [::input]} (get cfg ::reporter)]
|
||||||
|
(sp/put! input event)))
|
||||||
|
|||||||
@ -337,7 +337,13 @@
|
|||||||
::setup/props (ig/ref ::setup/props)
|
::setup/props (ig/ref ::setup/props)
|
||||||
|
|
||||||
::email/blacklist (ig/ref ::email/blacklist)
|
::email/blacklist (ig/ref ::email/blacklist)
|
||||||
::email/whitelist (ig/ref ::email/whitelist)}
|
::email/whitelist (ig/ref ::email/whitelist)
|
||||||
|
|
||||||
|
:app.loggers.database/reporter
|
||||||
|
(ig/ref :app.loggers.database/reporter)
|
||||||
|
|
||||||
|
:app.loggers.mattermost/reporter
|
||||||
|
(ig/ref :app.loggers.mattermost/reporter)}
|
||||||
|
|
||||||
:app.nitrate/client
|
:app.nitrate/client
|
||||||
{::http.client/client (ig/ref ::http.client/client)
|
{::http.client/client (ig/ref ::http.client/client)
|
||||||
|
|||||||
@ -456,7 +456,10 @@
|
|||||||
:fn (mg/resource "app/migrations/sql/0142-add-sso-provider-table.sql")}
|
:fn (mg/resource "app/migrations/sql/0142-add-sso-provider-table.sql")}
|
||||||
|
|
||||||
{:name "0143-http-session-v2-table"
|
{:name "0143-http-session-v2-table"
|
||||||
:fn (mg/resource "app/migrations/sql/0143-add-http-session-v2-table.sql")}])
|
:fn (mg/resource "app/migrations/sql/0143-add-http-session-v2-table.sql")}
|
||||||
|
|
||||||
|
{:name "0144-mod-server-error-report-table"
|
||||||
|
:fn (mg/resource "app/migrations/sql/0144-mod-server-error-report-table.sql")}])
|
||||||
|
|
||||||
(defn apply-migrations!
|
(defn apply-migrations!
|
||||||
[pool name migrations]
|
[pool name migrations]
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
ALTER TABLE server_error_report DROP CONSTRAINT server_error_report_pkey;
|
||||||
|
ALTER TABLE server_error_report ADD PRIMARY KEY (id);
|
||||||
|
CREATE INDEX server_error_report__version__idx
|
||||||
|
ON server_error_report ( version );
|
||||||
@ -16,6 +16,8 @@
|
|||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.http :as-alias http]
|
[app.http :as-alias http]
|
||||||
[app.loggers.audit :as-alias audit]
|
[app.loggers.audit :as-alias audit]
|
||||||
|
[app.loggers.database :as loggers.db]
|
||||||
|
[app.loggers.mattermost :as loggers.mm]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
[app.rpc.climit :as-alias climit]
|
[app.rpc.climit :as-alias climit]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
@ -36,52 +38,79 @@
|
|||||||
:context])
|
:context])
|
||||||
|
|
||||||
(defn- event->row [event]
|
(defn- event->row [event]
|
||||||
[(uuid/next)
|
[(::audit/id event)
|
||||||
(:name event)
|
(::audit/name event)
|
||||||
(:source event)
|
(::audit/source event)
|
||||||
(:type event)
|
(::audit/type event)
|
||||||
(:timestamp event)
|
(::audit/tracked-at event)
|
||||||
(:created-at event)
|
(::audit/created-at event)
|
||||||
(:profile-id event)
|
(::audit/profile-id event)
|
||||||
(db/inet (:ip-addr event))
|
(db/inet (::audit/ip-addr event))
|
||||||
(db/tjson (:props event))
|
(db/tjson (::audit/props event))
|
||||||
(db/tjson (d/without-nils (:context event)))])
|
(db/tjson (d/without-nils (::audit/context event)))])
|
||||||
|
|
||||||
(defn- adjust-timestamp
|
(defn- adjust-timestamp
|
||||||
[{:keys [timestamp created-at] :as event}]
|
[{:keys [::audit/tracked-at ::audit/created-at] :as event}]
|
||||||
(let [margin (inst-ms (ct/diff timestamp created-at))]
|
(let [margin (inst-ms (ct/diff tracked-at created-at))]
|
||||||
(if (or (neg? margin)
|
(if (or (neg? margin)
|
||||||
(> margin 3600000))
|
(> margin 3600000))
|
||||||
;; If event is in future or lags more than 1 hour, we reasign
|
;; If event is in future or lags more than 1 hour, we reasign
|
||||||
;; timestamp to the server creation date
|
;; tracked-at to the server creation date
|
||||||
(-> event
|
(-> event
|
||||||
(assoc :timestamp created-at)
|
(assoc ::audit/tracked-at created-at)
|
||||||
(update :context assoc :original-timestamp timestamp))
|
(update ::audit/context assoc :original-tracked-at tracked-at))
|
||||||
event)))
|
event)))
|
||||||
|
|
||||||
(defn- handle-events
|
(defn- exception-event?
|
||||||
[{:keys [::db/pool]} {:keys [::rpc/profile-id events] :as params}]
|
[{:keys [::audit/type ::audit/name] :as ev}]
|
||||||
|
(and (= "action" type)
|
||||||
|
(or (= "unhandled-exception" name)
|
||||||
|
(= "exception-page" name))))
|
||||||
|
|
||||||
|
(def ^:private xf:map-event-row
|
||||||
|
(comp
|
||||||
|
(map adjust-timestamp)
|
||||||
|
(map event->row)))
|
||||||
|
|
||||||
|
(defn- get-events
|
||||||
|
[{:keys [::rpc/request-at ::rpc/profile-id events] :as params}]
|
||||||
(let [request (-> params meta ::http/request)
|
(let [request (-> params meta ::http/request)
|
||||||
ip-addr (inet/parse-request request)
|
ip-addr (inet/parse-request request)
|
||||||
tnow (ct/now)
|
|
||||||
xform (comp
|
|
||||||
(map (fn [event]
|
|
||||||
(-> event
|
|
||||||
(assoc :created-at tnow)
|
|
||||||
(assoc :profile-id profile-id)
|
|
||||||
(assoc :ip-addr ip-addr)
|
|
||||||
(assoc :source "frontend"))))
|
|
||||||
(filter :profile-id)
|
|
||||||
(map adjust-timestamp)
|
|
||||||
(map event->row))
|
|
||||||
events (sequence xform events)]
|
|
||||||
(when (seq events)
|
|
||||||
(db/insert-many! pool :audit-log event-columns events))))
|
|
||||||
|
|
||||||
(def valid-event-types
|
xform (map (fn [event]
|
||||||
|
{::audit/id (uuid/next)
|
||||||
|
::audit/type (:type event)
|
||||||
|
::audit/name (:name event)
|
||||||
|
::audit/props (:props event)
|
||||||
|
::audit/context (:context event)
|
||||||
|
::audit/profile-id profile-id
|
||||||
|
::audit/ip-addr ip-addr
|
||||||
|
::audit/source "frontend"
|
||||||
|
::audit/tracked-at (:timestamp event)
|
||||||
|
::audit/created-at request-at}))]
|
||||||
|
|
||||||
|
(sequence xform events)))
|
||||||
|
|
||||||
|
(defn- handle-events
|
||||||
|
[{:keys [::db/pool] :as cfg} params]
|
||||||
|
(let [events (get-events params)]
|
||||||
|
|
||||||
|
;; Look for error reports and save them on internal reports table
|
||||||
|
(when-let [events (->> events
|
||||||
|
(sequence (filter exception-event?))
|
||||||
|
(not-empty))]
|
||||||
|
(run! (partial loggers.db/emit cfg) events)
|
||||||
|
(run! (partial loggers.mm/emit cfg) events))
|
||||||
|
|
||||||
|
;; Process and save events
|
||||||
|
(when (seq events)
|
||||||
|
(let [rows (sequence xf:map-event-row events)]
|
||||||
|
(db/insert-many! pool :audit-log event-columns rows)))))
|
||||||
|
|
||||||
|
(def ^:private valid-event-types
|
||||||
#{"action" "identify" "trigger"})
|
#{"action" "identify" "trigger"})
|
||||||
|
|
||||||
(def schema:event
|
(def ^:private schema:frontend-event
|
||||||
[:map {:title "Event"}
|
[:map {:title "Event"}
|
||||||
[:name
|
[:name
|
||||||
[:and {:gen/elements ["update-file", "get-profile"]}
|
[:and {:gen/elements ["update-file", "get-profile"]}
|
||||||
@ -93,12 +122,13 @@
|
|||||||
[::sm/one-of {:format "string"} valid-event-types]]]
|
[::sm/one-of {:format "string"} valid-event-types]]]
|
||||||
[:props
|
[:props
|
||||||
[:map-of :keyword ::sm/any]]
|
[:map-of :keyword ::sm/any]]
|
||||||
|
[:timestamp ::ct/inst]
|
||||||
[:context {:optional true}
|
[:context {:optional true}
|
||||||
[:map-of :keyword ::sm/any]]])
|
[:map-of :keyword ::sm/any]]])
|
||||||
|
|
||||||
(def schema:push-audit-events
|
(def ^:private schema:push-audit-events
|
||||||
[:map {:title "push-audit-events"}
|
[:map {:title "push-audit-events"}
|
||||||
[:events [:vector schema:event]]])
|
[:events [:vector schema:frontend-event]]])
|
||||||
|
|
||||||
(sv/defmethod ::push-audit-events
|
(sv/defmethod ::push-audit-events
|
||||||
{::climit/id :submit-audit-events/by-profile
|
{::climit/id :submit-audit-events/by-profile
|
||||||
|
|||||||
@ -103,6 +103,14 @@
|
|||||||
(assoc-in [::db/pool ::db/username] (:database-username config))
|
(assoc-in [::db/pool ::db/username] (:database-username config))
|
||||||
(assoc-in [::db/pool ::db/password] (:database-password config))
|
(assoc-in [::db/pool ::db/password] (:database-password config))
|
||||||
(assoc-in [:app.rpc/methods :app.setup/templates] templates)
|
(assoc-in [:app.rpc/methods :app.setup/templates] templates)
|
||||||
|
(assoc-in [:app.rpc/methods :app.setup/templates] templates)
|
||||||
|
(update :app.rpc/methods
|
||||||
|
(fn [state]
|
||||||
|
(-> state
|
||||||
|
(assoc :app.setup/templates templates)
|
||||||
|
(assoc :app.loggers.mattermost/reporter nil)
|
||||||
|
(assoc :app.loggers.database/reporter nil))))
|
||||||
|
|
||||||
(dissoc :app.srepl/server
|
(dissoc :app.srepl/server
|
||||||
:app.http/server
|
:app.http/server
|
||||||
:app.http/route
|
:app.http/route
|
||||||
|
|||||||
@ -239,22 +239,63 @@
|
|||||||
(recur cause))))))]
|
(recur cause))))))]
|
||||||
|
|
||||||
(with-out-str
|
(with-out-str
|
||||||
(print-all cause)))))
|
(print-all cause))))
|
||||||
|
|
||||||
|
:cljs
|
||||||
|
(defn format-throwable
|
||||||
|
[cause & {:as opts}]
|
||||||
|
(with-out-str
|
||||||
|
(when-let [exdata (ex-data cause)]
|
||||||
|
(when-let [hint (get exdata :hint)]
|
||||||
|
(when (str/index-of hint "\n")
|
||||||
|
(println "Hint:")
|
||||||
|
(println "--------------------")
|
||||||
|
(println hint)
|
||||||
|
(println)))
|
||||||
|
|
||||||
|
(when-let [explain (get exdata ::sm/explain)]
|
||||||
|
(println "Explain:")
|
||||||
|
(println "--------------------")
|
||||||
|
|
||||||
|
(println (sm/humanize-explain explain))
|
||||||
|
(println))
|
||||||
|
|
||||||
|
(when-let [explain (get exdata :explain)]
|
||||||
|
(println "Server Explain:")
|
||||||
|
(println "--------------------")
|
||||||
|
(println explain))
|
||||||
|
|
||||||
|
(println "Data:")
|
||||||
|
(println "--------------------")
|
||||||
|
(pp/pprint (dissoc exdata ::sm/explain :explain))
|
||||||
|
(println))
|
||||||
|
|
||||||
|
(when-let [trace (.-stack cause)]
|
||||||
|
(println "Trace:")
|
||||||
|
(println "--------------------")
|
||||||
|
(println (.-stack cause))))))
|
||||||
|
|
||||||
|
(defn first-line
|
||||||
|
[s]
|
||||||
|
(let [break-index (str/index-of s "\n")]
|
||||||
|
(if (pos? break-index)
|
||||||
|
(subs s 0 break-index)
|
||||||
|
s)))
|
||||||
|
|
||||||
(defn print-throwable
|
(defn print-throwable
|
||||||
[cause & {:as opts}]
|
[cause & {:as opts}]
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(println (format-throwable cause opts))
|
(println (format-throwable cause opts))
|
||||||
:cljs
|
:cljs
|
||||||
(let [prefix (get opts :prefix "exception")
|
(let [prefix (get opts :prefix)
|
||||||
title (str prefix ": " (ex-message cause))
|
data (ex-data cause)
|
||||||
exdata (ex-data cause)]
|
title (cond->> (or (some-> (:hint data) first-line)
|
||||||
|
(ex-message cause))
|
||||||
|
(string? prefix)
|
||||||
|
(str prefix ": "))]
|
||||||
|
|
||||||
(js/console.group title)
|
(js/console.group title)
|
||||||
(when-let [explain (get exdata ::sm/explain)]
|
(try
|
||||||
(println (sm/humanize-explain explain)))
|
(js/console.log (format-throwable cause))
|
||||||
|
(finally
|
||||||
(js/console.log "\nData:")
|
(js/console.groupEnd))))))
|
||||||
(pp/pprint (dissoc exdata ::sm/explain))
|
|
||||||
|
|
||||||
(js/console.log "\nTrace:")
|
|
||||||
(js/console.error (.-stack cause)))))
|
|
||||||
|
|||||||
@ -9,15 +9,16 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.pprint :as pp]
|
[app.common.pprint :as pp]
|
||||||
[app.common.schema :as sm]
|
[app.config :as cf]
|
||||||
[app.main.data.auth :as da]
|
[app.main.data.auth :as da]
|
||||||
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.notifications :as ntf]
|
[app.main.data.notifications :as ntf]
|
||||||
[app.main.data.workspace :as-alias dw]
|
[app.main.data.workspace :as-alias dw]
|
||||||
[app.main.router :as rt]
|
[app.main.router :as rt]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.worker]
|
[app.main.worker]
|
||||||
[app.util.globals :as glob]
|
[app.util.globals :as g]
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
@ -32,44 +33,6 @@
|
|||||||
;; Will contain last uncaught exception
|
;; Will contain last uncaught exception
|
||||||
(def last-exception nil)
|
(def last-exception nil)
|
||||||
|
|
||||||
(defn- print-data!
|
|
||||||
[data]
|
|
||||||
(-> data
|
|
||||||
(dissoc ::sm/explain)
|
|
||||||
(dissoc :explain)
|
|
||||||
(dissoc ::trace)
|
|
||||||
(dissoc ::instance)
|
|
||||||
(pp/pprint {:width 70})))
|
|
||||||
|
|
||||||
(defn- print-explain!
|
|
||||||
[data]
|
|
||||||
(when-let [{:keys [errors] :as explain} (::sm/explain data)]
|
|
||||||
(let [errors (mapv #(update % :schema sm/form) errors)]
|
|
||||||
(pp/pprint errors {:width 100 :level 15 :length 20})))
|
|
||||||
|
|
||||||
(when-let [explain (:explain data)]
|
|
||||||
(js/console.log explain)))
|
|
||||||
|
|
||||||
(defn- print-trace!
|
|
||||||
[data]
|
|
||||||
(some-> data ::trace js/console.log))
|
|
||||||
|
|
||||||
(defn- print-group!
|
|
||||||
[message f]
|
|
||||||
(try
|
|
||||||
(js/console.group message)
|
|
||||||
(f)
|
|
||||||
(catch :default _ nil)
|
|
||||||
(finally
|
|
||||||
(js/console.groupEnd message))))
|
|
||||||
|
|
||||||
(defn print-cause!
|
|
||||||
[message cause]
|
|
||||||
(print-group! message (fn []
|
|
||||||
(print-data! cause)
|
|
||||||
(print-explain! cause)
|
|
||||||
(print-trace! cause))))
|
|
||||||
|
|
||||||
(defn exception->error-data
|
(defn exception->error-data
|
||||||
[cause]
|
[cause]
|
||||||
(let [data (ex-data cause)]
|
(let [data (ex-data cause)]
|
||||||
@ -78,18 +41,6 @@
|
|||||||
(assoc ::instance cause)
|
(assoc ::instance cause)
|
||||||
(assoc ::trace (.-stack cause)))))
|
(assoc ::trace (.-stack cause)))))
|
||||||
|
|
||||||
(defn print-error!
|
|
||||||
[cause]
|
|
||||||
(cond
|
|
||||||
(map? cause)
|
|
||||||
(print-cause! (:hint cause "Unexpected Error") cause)
|
|
||||||
|
|
||||||
(ex/error? cause)
|
|
||||||
(print-cause! (ex-message cause) (ex-data cause))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(print-cause! (ex-message cause) (exception->error-data cause))))
|
|
||||||
|
|
||||||
(defn on-error
|
(defn on-error
|
||||||
"A general purpose error handler."
|
"A general purpose error handler."
|
||||||
[error]
|
[error]
|
||||||
@ -104,13 +55,66 @@
|
|||||||
;; Set the main potok error handler
|
;; Set the main potok error handler
|
||||||
(reset! st/on-error on-error)
|
(reset! st/on-error on-error)
|
||||||
|
|
||||||
|
(defn generate-report
|
||||||
|
[cause]
|
||||||
|
(try
|
||||||
|
(let [team-id (:current-team-id @st/state)
|
||||||
|
file-id (:current-file-id @st/state)
|
||||||
|
profile-id (:profile-id @st/state)
|
||||||
|
data (ex-data cause)]
|
||||||
|
|
||||||
|
(with-out-str
|
||||||
|
(println "Context:")
|
||||||
|
(println "--------------------")
|
||||||
|
(println "Hint: " (or (:hint data) (ex-message cause) "--"))
|
||||||
|
(println "Prof ID: " (str (or profile-id "--")))
|
||||||
|
(println "Team ID: " (str (or team-id "--")))
|
||||||
|
(when-let [file-id (or (:file-id data) file-id)]
|
||||||
|
(println "File ID: " (str file-id)))
|
||||||
|
(println "Version: " (:full cf/version))
|
||||||
|
(println "URI: " (str cf/public-uri))
|
||||||
|
(println "HREF: " (rt/get-current-href))
|
||||||
|
(println)
|
||||||
|
|
||||||
|
(println
|
||||||
|
(ex/format-throwable cause))
|
||||||
|
(println)
|
||||||
|
|
||||||
|
(println "Last events:")
|
||||||
|
(println "--------------------")
|
||||||
|
(pp/pprint @st/last-events {:length 200})
|
||||||
|
(println)))
|
||||||
|
(catch :default cause
|
||||||
|
(.error js/console "error on generating report" cause)
|
||||||
|
nil)))
|
||||||
|
|
||||||
|
(defn- show-not-blocking-error
|
||||||
|
"Show a non user blocking error notification"
|
||||||
|
[cause]
|
||||||
|
(let [data (ex-data cause)
|
||||||
|
hint (or (some-> (:hint data) ex/first-line)
|
||||||
|
(ex-message cause))]
|
||||||
|
|
||||||
|
(st/emit!
|
||||||
|
(ev/event {::ev/name "unhandled-exception"
|
||||||
|
:hint hint
|
||||||
|
:href (rt/get-current-href)
|
||||||
|
:type (get data :type :unknown)
|
||||||
|
:report (generate-report cause)})
|
||||||
|
|
||||||
|
(ntf/show {:content (tr "errors.unexpected-exception" hint)
|
||||||
|
:type :toast
|
||||||
|
:level :error
|
||||||
|
:timeout 3000}))))
|
||||||
|
|
||||||
(defmethod ptk/handle-error :default
|
(defmethod ptk/handle-error :default
|
||||||
[error]
|
[error]
|
||||||
(st/async-emit! (rt/assign-exception error))
|
(if (and (string? (:hint error))
|
||||||
(print-group! "Unhandled Error"
|
(str/starts-with? (:hint error) "Assert failed:"))
|
||||||
(fn []
|
(ptk/handle-error (assoc error :type :assertion))
|
||||||
(print-trace! error)
|
(when-let [cause (::instance error)]
|
||||||
(print-data! error))))
|
(ex/print-throwable cause :prefix "Unexpected Error")
|
||||||
|
(show-not-blocking-error cause))))
|
||||||
|
|
||||||
;; We receive a explicit authentication error; If the uri is for
|
;; We receive a explicit authentication error; If the uri is for
|
||||||
;; workspace, dashboard, viewer or settings, then assign the exception
|
;; workspace, dashboard, viewer or settings, then assign the exception
|
||||||
@ -142,10 +146,10 @@
|
|||||||
|
|
||||||
(defmethod ptk/handle-error :validation
|
(defmethod ptk/handle-error :validation
|
||||||
[{:keys [code] :as error}]
|
[{:keys [code] :as error}]
|
||||||
(print-group! "Validation Error"
|
|
||||||
(fn []
|
(when-let [instance (get error ::instance)]
|
||||||
(print-data! error)
|
(ex/print-throwable instance :prefix "Validation Error"))
|
||||||
(print-explain! error)))
|
|
||||||
(cond
|
(cond
|
||||||
(= code :invalid-paste-data)
|
(= code :invalid-paste-data)
|
||||||
(let [message (tr "errors.paste-data-validation")]
|
(let [message (tr "errors.paste-data-validation")]
|
||||||
@ -193,23 +197,14 @@
|
|||||||
:else
|
:else
|
||||||
(st/async-emit! (rt/assign-exception error))))
|
(st/async-emit! (rt/assign-exception error))))
|
||||||
|
|
||||||
|
|
||||||
;; 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
|
||||||
;; the user perspective this should be treated as internal error.
|
;; the user perspective this should be treated as internal error.
|
||||||
(defmethod ptk/handle-error :assertion
|
(defmethod ptk/handle-error :assertion
|
||||||
[error]
|
[error]
|
||||||
(ts/schedule
|
(when-let [cause (::instance error)]
|
||||||
#(st/emit! (ntf/show {:content (tr "errors.internal-assertion-error")
|
(show-not-blocking-error cause)
|
||||||
:type :toast
|
(ex/print-throwable cause :prefix "Assertion Error")))
|
||||||
:level :error
|
|
||||||
:timeout 3000})))
|
|
||||||
|
|
||||||
(print-group! "Internal Assertion Error"
|
|
||||||
(fn []
|
|
||||||
(print-trace! error)
|
|
||||||
(print-data! error)
|
|
||||||
(print-explain! error))))
|
|
||||||
|
|
||||||
;; ;; All the errors that happens on worker are handled here.
|
;; ;; All the errors that happens on worker are handled here.
|
||||||
(defmethod ptk/handle-error :worker-error
|
(defmethod ptk/handle-error :worker-error
|
||||||
@ -221,9 +216,8 @@
|
|||||||
:level :error
|
:level :error
|
||||||
:timeout 3000})))
|
:timeout 3000})))
|
||||||
|
|
||||||
(print-group! "Internal Worker Error"
|
(some-> (::instance error)
|
||||||
(fn []
|
(ex/print-throwable :prefix "Web Worker Error")))
|
||||||
(print-data! error))))
|
|
||||||
|
|
||||||
;; Error on parsing an SVG
|
;; Error on parsing an SVG
|
||||||
(defmethod ptk/handle-error :svg-parser
|
(defmethod ptk/handle-error :svg-parser
|
||||||
@ -252,11 +246,9 @@
|
|||||||
|
|
||||||
(defmethod ptk/handle-error ::exceptional-state
|
(defmethod ptk/handle-error ::exceptional-state
|
||||||
[error]
|
[error]
|
||||||
(when-let [cause (::instance error)]
|
(when-let [instance (get error ::instance)]
|
||||||
(js/console.log (.-stack cause)))
|
(ex/print-throwable instance :prefix "Exceptional State"))
|
||||||
|
(ts/schedule #(st/emit! (rt/assign-exception error))))
|
||||||
(ts/schedule
|
|
||||||
#(st/emit! (rt/assign-exception error))))
|
|
||||||
|
|
||||||
(defn- redirect-to-dashboard
|
(defn- redirect-to-dashboard
|
||||||
[]
|
[]
|
||||||
@ -264,7 +256,7 @@
|
|||||||
project-id (:current-project-id @st/state)]
|
project-id (:current-project-id @st/state)]
|
||||||
(if (and project-id team-id)
|
(if (and project-id team-id)
|
||||||
(st/emit! (rt/nav :dashboard-files {:team-id team-id :project-id project-id}))
|
(st/emit! (rt/nav :dashboard-files {:team-id team-id :project-id project-id}))
|
||||||
(set! (.-href glob/location) ""))))
|
(set! (.-href g/location) ""))))
|
||||||
|
|
||||||
(defmethod ptk/handle-error :restriction
|
(defmethod ptk/handle-error :restriction
|
||||||
[{:keys [code] :as error}]
|
[{:keys [code] :as error}]
|
||||||
@ -312,9 +304,10 @@
|
|||||||
:text (tr "errors.deprecated.contact.text")
|
:text (tr "errors.deprecated.contact.text")
|
||||||
:after (tr "errors.deprecated.contact.after")
|
:after (tr "errors.deprecated.contact.after")
|
||||||
:on-click #(st/emit! (rt/nav :settings-feedback))}}))
|
:on-click #(st/emit! (rt/nav :settings-feedback))}}))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(print-cause! "Restriction Error" error)))
|
(when-let [cause (::instance error)]
|
||||||
|
(ex/print-throwable cause :prefix "Restriction Error")
|
||||||
|
(show-not-blocking-error cause))))
|
||||||
|
|
||||||
;; 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
|
||||||
@ -322,24 +315,9 @@
|
|||||||
|
|
||||||
(defmethod ptk/handle-error :server-error
|
(defmethod ptk/handle-error :server-error
|
||||||
[error]
|
[error]
|
||||||
(st/async-emit! (rt/assign-exception error))
|
(when-let [instance (get error ::instance)]
|
||||||
(print-group! "Server Error"
|
(ex/print-throwable instance :prefix "Server Error"))
|
||||||
(fn []
|
(st/async-emit! (rt/assign-exception error)))
|
||||||
(print-data! (dissoc error :data))
|
|
||||||
|
|
||||||
(when-let [werror (:data error)]
|
|
||||||
(cond
|
|
||||||
(= :assertion (:type werror))
|
|
||||||
(print-group! "Assertion Error"
|
|
||||||
(fn []
|
|
||||||
(print-data! werror)
|
|
||||||
(print-explain! werror)))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(print-group! "Unexpected"
|
|
||||||
(fn []
|
|
||||||
(print-data! werror)
|
|
||||||
(print-explain! werror))))))))
|
|
||||||
|
|
||||||
(defonce uncaught-error-handler
|
(defonce uncaught-error-handler
|
||||||
(letfn [(is-ignorable-exception? [cause]
|
(letfn [(is-ignorable-exception? [cause]
|
||||||
@ -354,28 +332,19 @@
|
|||||||
(when-let [cause (unchecked-get event "error")]
|
(when-let [cause (unchecked-get event "error")]
|
||||||
(set! last-exception cause)
|
(set! last-exception cause)
|
||||||
(when-not (is-ignorable-exception? cause)
|
(when-not (is-ignorable-exception? cause)
|
||||||
(ex/print-throwable cause :prefix "uncaught exception")
|
(ex/print-throwable cause :prefix "Uncaught Exception")
|
||||||
(st/async-emit!
|
(ts/schedule #(show-not-blocking-error cause)))))
|
||||||
(ntf/show {:content (tr "errors.unexpected-exception" (ex-message cause))
|
|
||||||
:type :toast
|
|
||||||
:level :error
|
|
||||||
:timeout 3000})))))
|
|
||||||
|
|
||||||
|
|
||||||
(on-unhandled-rejection [event]
|
(on-unhandled-rejection [event]
|
||||||
(.preventDefault ^js event)
|
(.preventDefault ^js event)
|
||||||
(when-let [cause (unchecked-get event "reason")]
|
(when-let [cause (unchecked-get event "reason")]
|
||||||
(set! last-exception cause)
|
(set! last-exception cause)
|
||||||
(ex/print-throwable cause :prefix "uncaught rejection")
|
(ex/print-throwable cause :prefix "Uncaught Rejection")
|
||||||
(st/async-emit!
|
(ts/schedule #(show-not-blocking-error cause))))]
|
||||||
(ntf/show {:content (tr "errors.unexpected-exception" (ex-message cause))
|
|
||||||
:type :toast
|
|
||||||
:level :error
|
|
||||||
:timeout 3000}))))]
|
|
||||||
|
|
||||||
(.addEventListener glob/window "error" on-unhandled-error)
|
(.addEventListener g/window "error" on-unhandled-error)
|
||||||
(.addEventListener glob/window "unhandledrejection" on-unhandled-rejection)
|
(.addEventListener g/window "unhandledrejection" on-unhandled-rejection)
|
||||||
(fn []
|
(fn []
|
||||||
(.removeEventListener glob/window "error" on-unhandled-error)
|
(.removeEventListener g/window "error" on-unhandled-error)
|
||||||
(.removeEventListener glob/window "unhandledrejection" on-unhandled-rejection))))
|
(.removeEventListener g/window "unhandledrejection" on-unhandled-rejection))))
|
||||||
|
|
||||||
|
|||||||
@ -9,12 +9,12 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.exceptions :as ex]
|
||||||
[app.common.logging :as log]
|
[app.common.logging :as log]
|
||||||
[app.main.data.dashboard :as dd]
|
[app.main.data.dashboard :as dd]
|
||||||
[app.main.data.event :as ev]
|
[app.main.data.event :as ev]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.notifications :as ntf]
|
[app.main.data.notifications :as ntf]
|
||||||
[app.main.errors :as errors]
|
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.components.file-uploader :refer [file-uploader*]]
|
[app.main.ui.components.file-uploader :refer [file-uploader*]]
|
||||||
[app.main.ui.ds.product.loader :refer [loader*]]
|
[app.main.ui.ds.product.loader :refer [loader*]]
|
||||||
@ -360,7 +360,7 @@
|
|||||||
on-error
|
on-error
|
||||||
(fn [cause]
|
(fn [cause]
|
||||||
(reset! status* :error)
|
(reset! status* :error)
|
||||||
(errors/print-error! cause)
|
(ex/print-throwable cause)
|
||||||
(rx/of (modal/hide)
|
(rx/of (modal/hide)
|
||||||
(ntf/error (tr "dashboard.libraries-and-templates.import-error"))))
|
(ntf/error (tr "dashboard.libraries-and-templates.import-error"))))
|
||||||
|
|
||||||
|
|||||||
@ -36,7 +36,6 @@
|
|||||||
[app.util.webapi :as wapi]
|
[app.util.webapi :as wapi]
|
||||||
[beicon.v2.core :as rx]
|
[beicon.v2.core :as rx]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[potok.v2.core :as ptk]
|
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
;; FIXME: this is a workaround until we export this class on beicon library
|
;; FIXME: this is a workaround until we export this class on beicon library
|
||||||
@ -447,25 +446,24 @@
|
|||||||
(rx/of default)
|
(rx/of default)
|
||||||
(rx/throw cause)))))))
|
(rx/throw cause)))))))
|
||||||
|
|
||||||
|
|
||||||
(mf/defc exception-section*
|
(mf/defc exception-section*
|
||||||
{::mf/private true}
|
{::mf/private true}
|
||||||
[{:keys [data route] :as props}]
|
[{:keys [data route] :as props}]
|
||||||
(let [type (get data :type)
|
(let [type (get data :type)
|
||||||
report (mf/with-memo [data]
|
report (mf/with-memo [data]
|
||||||
(generate-report data))
|
(some-> data ::errors/instance errors/generate-report))
|
||||||
props (mf/spread-props props {:report report})]
|
props (mf/spread-props props {:report report})]
|
||||||
|
|
||||||
(mf/with-effect [data route report]
|
(mf/with-effect [data route report]
|
||||||
(let [params (:query-params route)
|
(let [params (:query-params route)
|
||||||
params (u/map->query-string params)]
|
params (u/map->query-string params)]
|
||||||
(st/emit! (ptk/data-event ::ev/event
|
(st/emit! (ev/event {::ev/name "exception-page"
|
||||||
{::ev/name "exception-page"
|
:type (get data :type :unknown)
|
||||||
:type (get data :type :unknown)
|
:href (rt/get-current-href)
|
||||||
:hint (get data :hint)
|
:hint (get data :hint)
|
||||||
:path (get route :path)
|
:path (get route :path)
|
||||||
:report report
|
:report report
|
||||||
:params params}))))
|
:params params}))))
|
||||||
|
|
||||||
(case type
|
(case type
|
||||||
:not-found
|
:not-found
|
||||||
|
|||||||
@ -391,7 +391,7 @@
|
|||||||
(group-by :code)
|
(group-by :code)
|
||||||
(clj->js))
|
(clj->js))
|
||||||
(catch :default cause
|
(catch :default cause
|
||||||
(errors/print-error! cause))))))
|
(ex/print-throwable cause))))))
|
||||||
|
|
||||||
(defn ^:export validate-schema
|
(defn ^:export validate-schema
|
||||||
[]
|
[]
|
||||||
@ -399,7 +399,7 @@
|
|||||||
(let [file (dsh/lookup-file @st/state)]
|
(let [file (dsh/lookup-file @st/state)]
|
||||||
(cfv/validate-file-schema! file))
|
(cfv/validate-file-schema! file))
|
||||||
(catch :default cause
|
(catch :default cause
|
||||||
(errors/print-error! cause))))
|
(ex/print-throwable cause))))
|
||||||
|
|
||||||
(defn ^:export repair
|
(defn ^:export repair
|
||||||
[reload?]
|
[reload?]
|
||||||
@ -431,7 +431,7 @@
|
|||||||
(when reload?
|
(when reload?
|
||||||
(dom/reload-current-window)))
|
(dom/reload-current-window)))
|
||||||
(fn [cause]
|
(fn [cause]
|
||||||
(errors/print-error! cause)))))))))
|
(ex/print-throwable cause)))))))))
|
||||||
|
|
||||||
(defn ^:export fix-orphan-shapes
|
(defn ^:export fix-orphan-shapes
|
||||||
[]
|
[]
|
||||||
|
|||||||
@ -1467,7 +1467,7 @@ msgid "errors.generic"
|
|||||||
msgstr "Something wrong has happened."
|
msgstr "Something wrong has happened."
|
||||||
|
|
||||||
msgid "errors.unexpected-exception"
|
msgid "errors.unexpected-exception"
|
||||||
msgstr "Unexpected exception: %s"
|
msgstr "Unexpected error: %s"
|
||||||
|
|
||||||
#: src/app/main/errors.cljs:200
|
#: src/app/main/errors.cljs:200
|
||||||
msgid "errors.internal-assertion-error"
|
msgid "errors.internal-assertion-error"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user