diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index d1e91f24b..4ba5b0442 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -3,7 +3,8 @@ rumext.alpha/defc clojure.core/defn rumext.alpha/fnc clojure.core/fn app.common.data/export clojure.core/def - app.db/with-atomic clojure.core/with-open} + app.db/with-atomic clojure.core/with-open + app.common.logging/with-context clojure.core/do} :hooks {:analyze-call diff --git a/.clj-kondo/hooks/export.clj b/.clj-kondo/hooks/export.clj index f66d02783..16ab4e76a 100644 --- a/.clj-kondo/hooks/export.clj +++ b/.clj-kondo/hooks/export.clj @@ -74,5 +74,3 @@ ;; (prn "==============" rtype (into {} ?meta)) ;; (prn (api/sexpr result)) {:node result})) - - diff --git a/backend/deps.edn b/backend/deps.edn index 5a18c8c8c..eeae91dde 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -33,7 +33,6 @@ metosin/reitit-ring {:mvn/version "0.5.15"} org.postgresql/postgresql {:mvn/version "42.2.23"} com.zaxxer/HikariCP {:mvn/version "5.0.0"} - funcool/datoteka {:mvn/version "2.0.0"} buddy/buddy-core {:mvn/version "1.10.1"} @@ -50,9 +49,7 @@ io.sentry/sentry {:mvn/version "5.1.2"} ;; Pretty Print specs - fipp/fipp {:mvn/version "0.6.24"} pretty-spec/pretty-spec {:mvn/version "0.1.4"} - software.amazon.awssdk/s3 {:mvn/version "2.17.40"}} :paths ["src" "resources"] diff --git a/backend/dev/user.clj b/backend/dev/user.clj index ad51f5771..e9402a639 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -104,7 +104,8 @@ :v4 (humanize (alength (blob/encode data {:version 4}))) }]))) - -;; ;; (def contents (read-string (slurp (io/resource "bool-contents-1.edn")))) -;; (def pre-data (datoteka.core/slurp-bytes (io/resource "file-data-sample"))) -;; (def data (blob/decode pre-data)) +(defonce debug-tap + (do + (add-tap #(locking debug-tap + (prn "tap debug:" %))) + 1)) diff --git a/backend/resources/error-report.tmpl b/backend/resources/error-report.tmpl index 452036997..d6b3a4dbb 100644 --- a/backend/resources/error-report.tmpl +++ b/backend/resources/error-report.tmpl @@ -130,10 +130,10 @@ {% endif %} - {% if error %} + {% if hint %}
HINT:
-
{{error.message}}
+
{{hint}}
{% endif %} @@ -163,25 +163,39 @@ {% endif %} - {% if explain %} -
-
EXPLAIN:
-
-
{{explain}}
-
-
- {% endif %} - {% if data %}
-
EDATA:
+
ERROR DATA:
{{data}}
{% endif %} - {% if error %} + {% if spec-problems %} +
+
SPEC PROBLEMS:
+
+
{{spec-problems}}
+
+
+ {% endif %} + + {% if cause %} +
+
TRACE:
+
+
{{cause}}
+
+
+ {% elif trace %} +
+
TRACE:
+
+
{{trace}}
+
+
+ {% elif error %}
TRACE:
diff --git a/backend/resources/log4j2-devenv.xml b/backend/resources/log4j2-devenv.xml index 1b9dba567..d07a33d7d 100644 --- a/backend/resources/log4j2-devenv.xml +++ b/backend/resources/log4j2-devenv.xml @@ -2,7 +2,7 @@ - + diff --git a/backend/scripts/repl b/backend/scripts/repl index f2377e23d..3ca39aa9c 100755 --- a/backend/scripts/repl +++ b/backend/scripts/repl @@ -2,7 +2,6 @@ export PENPOT_FLAGS="enable-asserts enable-audit-log $PENPOT_FLAGS" -#-J-Dlog4j2.contextSelector=org.apache.logging.log4j.core.async.AsyncLoggerContextSelector export OPTIONS=" -A:jmx-remote:dev \ diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 0f8c016f2..b15a1e3b6 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -90,20 +90,9 @@ (try (handler request) (catch Throwable e - (try - (let [cdata (errors/get-error-context request e)] - (l/update-thread-context! cdata) - (l/error :hint "unhandled exception" - :message (ex-message e) - :error-id (str (:id cdata)) - :cause e)) - {:status 500 :body "internal server error"} - (catch Throwable e - (l/error :hint "unhandled exception" - :message (ex-message e) - :cause e) - {:status 500 :body "internal server error"}))))))) - + (l/with-context (errors/get-error-context request e) + (l/error :hint (ex-message e) :cause e) + {:status 500 :body "internal server error"})))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Http Main Handler (Router) diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index ec489054e..fac709447 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -7,11 +7,11 @@ (ns app.http.errors "A errors handling for the http server." (:require - [app.common.data :as d] [app.common.exceptions :as ex] [app.common.logging :as l] [app.common.uuid :as uuid] [clojure.pprint] + [clojure.spec.alpha :as s] [cuerdas.core :as str])) (defn- parse-client-ip @@ -20,44 +20,24 @@ (get headers "x-real-ip") (get request :remote-addr))) - -(defn- simple-prune - ([s] (simple-prune s (* 1024 1024))) - ([s max-length] - (if (> (count s) max-length) - (str (subs s 0 max-length) " [...]") - s))) - -(defn- stringify-data - [data] - (binding [clojure.pprint/*print-right-margin* 200] - (let [result (with-out-str (clojure.pprint/pprint data))] - (simple-prune result (* 1024 1024))))) - (defn get-error-context [request error] (let [data (ex-data error)] - (d/without-nils - (merge - {:id (str (uuid/next)) - :path (str (:uri request)) - :method (name (:request-method request)) - :hint (or (:hint data) (ex-message error)) - :params (stringify-data (:params request)) - :data (stringify-data (dissoc data :explain)) - :ip-addr (parse-client-ip request) - :explain (str/prune (:explain data) (* 1024 1024) "[...]")} - - (when-let [id (:profile-id request)] - {:profile-id id}) + (merge + {:id (uuid/next) + :path (:uri request) + :method (:request-method request) + :hint (or (:hint data) (ex-message error)) + :params (l/stringify-data (:params request)) + :spec-problems (some-> data ::s/problems) + :ip-addr (parse-client-ip request) + :profile-id (:profile-id request)} (let [headers (:headers request)] {:user-agent (get headers "user-agent") :frontend-version (get headers "x-frontend-version" "unknown")}) - (when (map? data) - {:error-type (:type data) - :error-code (:code data)}))))) + (dissoc data ::s/problems)))) (defmulti handle-exception (fn [err & _rest] @@ -85,21 +65,17 @@ (:explain edata) "\n")} {:status 400 - :body (dissoc edata :data)}))) + :body (dissoc edata ::s/problems)}))) (defmethod handle-exception :assertion [error request] - (let [edata (ex-data error) - cdata (get-error-context request error)] - (l/update-thread-context! cdata) - (l/error :hint "internal error: assertion" - :error-id (str (:id cdata)) - :cause error) - + (let [edata (ex-data error)] + (l/with-context (get-error-context request error) + (l/error :hint (ex-message error) :cause error)) {:status 500 :body {:type :server-error :code :assertion - :data (dissoc edata :data)}})) + :data (dissoc edata ::s/problems)}})) (defmethod handle-exception :not-found [err _] @@ -116,12 +92,10 @@ (if (and (ex/exception? (:rollback edata)) (ex/exception? (:handling edata))) (handle-exception (:handling edata) request) - (let [cdata (get-error-context request error)] - (l/update-thread-context! cdata) - (l/error :hint "internal error" - :error-message (ex-message error) - :error-id (str (:id cdata)) - :cause error) + (do + (l/with-context (get-error-context request error) + (l/error :hint (ex-message error) :cause error)) + {:status 500 :body {:type :server-error :code :unexpected @@ -130,15 +104,13 @@ (defmethod handle-exception org.postgresql.util.PSQLException [error request] - (let [cdata (get-error-context request error) - state (.getSQLState ^java.sql.SQLException error)] + (let [state (.getSQLState ^java.sql.SQLException error)] - (l/update-thread-context! cdata) - (l/error :hint "psql exception" - :error-message (ex-message error) - :error-id (str (:id cdata)) - :sql-state state - :cause error) + (l/with-context (get-error-context request error) + (l/error :hint "psql exception" + :error-message (ex-message error) + :state state + :cause error)) (cond (= state "57014") diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index 11e62b571..288896140 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -73,11 +73,7 @@ (if-let [{:keys [id profile-id] :as session} (retrieve-from-request cfg request)] (do (a/>!! (::events-ch cfg) id) - (l/update-thread-context! {:profile-id profile-id}) - (-> request - (assoc :profile-id profile-id) - (assoc :session-id id) - (handler))) + (handler (assoc request :profile-id profile-id))) (handler request)))) ;; --- STATE INIT: SESSION diff --git a/backend/src/app/loggers/database.clj b/backend/src/app/loggers/database.clj index 2685661d8..ca0fb5d6e 100644 --- a/backend/src/app/loggers/database.clj +++ b/backend/src/app/loggers/database.clj @@ -36,7 +36,7 @@ (db/insert! conn :server-error-report {:id id :content (db/tjson event)}))) -(defn- parse-context +(defn- parse-event-data [event] (reduce-kv (fn [acc k v] @@ -46,12 +46,11 @@ (str/blank? v) acc :else (assoc acc k v))) {} - (:context event))) + event)) (defn parse-event [event] - (-> (parse-context event) - (merge (dissoc event :context)) + (-> (parse-event-data event) (assoc :tenant (cf/get :tenant)) (assoc :host (cf/get :host)) (assoc :public-uri (cf/get :public-uri)) @@ -62,6 +61,7 @@ (aa/with-thread executor (try (let [event (parse-event event)] + (l/debug :hint "registering error on database" :id (:id event)) (persist-on-database! cfg event)) (catch Exception e (l/warn :hint "unexpected exception on database error logger" @@ -74,7 +74,8 @@ [_ {:keys [receiver] :as cfg}] (l/info :msg "initializing database error persistence") (let [output (a/chan (a/sliding-buffer 128) - (filter #(= (:level %) "error")))] + (filter (fn [event] + (= (:logger/level event) "error"))))] (receiver :sub output) (a/go-loop [] (let [msg (a/= (:retry-num item) (:max-retries item)) {:status :failed :task item :error error} {:status :retry :task item :error error}))))) diff --git a/common/deps.edn b/common/deps.edn index 71ff58a4c..005d0ce6a 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -33,7 +33,8 @@ com.sun.mail/jakarta.mail {:mvn/version "2.0.1"} ;; exception printing - io.aviso/pretty {:mvn/version "1.1"} + fipp/fipp {:mvn/version "0.6.24"} + io.aviso/pretty {:mvn/version "0.1.37"} environ/environ {:mvn/version "1.2.0"}} :paths ["src"] :aliases diff --git a/common/src/app/common/logging.cljc b/common/src/app/common/logging.cljc index a861bcc27..f9356e588 100644 --- a/common/src/app/common/logging.cljc +++ b/common/src/app/common/logging.cljc @@ -9,16 +9,23 @@ [app.common.exceptions :as ex] [clojure.pprint :refer [pprint]] [cuerdas.core :as str] + #?(:clj [io.aviso.exception :as ie]) #?(:cljs [goog.log :as glog])) - #?(:cljs (:require-macros [app.common.logging])) - #?(:clj - (:import - org.apache.logging.log4j.Level - org.apache.logging.log4j.LogManager - org.apache.logging.log4j.Logger - org.apache.logging.log4j.ThreadContext - org.apache.logging.log4j.message.MapMessage - org.apache.logging.log4j.spi.LoggerContext))) + #?(:cljs (:require-macros [app.common.logging]) + :clj (:import + org.apache.logging.log4j.Level + org.apache.logging.log4j.LogManager + org.apache.logging.log4j.Logger + org.apache.logging.log4j.ThreadContext + org.apache.logging.log4j.CloseableThreadContext + org.apache.logging.log4j.message.MapMessage + org.apache.logging.log4j.spi.LoggerContext))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; CLJ Specific +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +#?(:clj (set! *warn-on-reflection* true)) #?(:clj (defn build-map-message @@ -34,17 +41,69 @@ (def logging-agent (agent nil :error-mode :continue))) +(defn- simple-prune + ([s] (simple-prune s (* 1024 1024))) + ([s max-length] + (if (> (count s) max-length) + (str (subs s 0 max-length) " [...]") + s))) + +#?(:clj + (defn stringify-data + [val] + (cond + (instance? clojure.lang.Named val) + (name val) + + (instance? Throwable val) + (binding [ie/*app-frame-names* [#"app.*"] + ie/*fonts* nil + ie/*traditional* true] + (ie/format-exception val nil)) + + (string? val) + val + + (coll? val) + (binding [clojure.pprint/*print-right-margin* 120] + (-> (with-out-str (pprint val)) + (simple-prune (* 1024 1024 3)))) + + :else + (str val)))) + +#?(:clj + (defn data->context-map + ^java.util.Map + [data] + (into {} + (comp (filter second) + (map (fn [[key val]] + [(stringify-data key) + (stringify-data val)]))) + data))) + +#?(:clj + (defmacro with-context + [data & body] + `(let [data# (data->context-map ~data)] + (with-open [closeable# (CloseableThreadContext/putAll data#)] + ~@body)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Common +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn get-logger [lname] #?(:clj (.getLogger ^LoggerContext logger-context ^String lname) - :cljs - (glog/getLogger - (cond - (string? lname) lname - (= lname :root) "" - (simple-ident? lname) (name lname) - (qualified-ident? lname) (str (namespace lname) "." (name lname)) - :else (str lname))))) + :cljs (glog/getLogger + (cond + (string? lname) lname + (= lname :root) "" + (simple-ident? lname) (name lname) + (qualified-ident? lname) (str (namespace lname) "." (name lname)) + :else (str lname))))) (defn get-level [level] @@ -87,7 +146,7 @@ :cljs (when glog/ENABLED (when-let [l (get-logger logger)] - (let [level (get-level level) + (let [level (get-level level) record (glog/LogRecord. level message (.getName ^js l))] (when exception (.setException record exception)) (glog/publishLogRecord l record)))))) @@ -98,7 +157,7 @@ (.isEnabled ^Logger logger ^Level level))) (defmacro log - [& {:keys [level cause ::logger ::async ::raw] :as props}] + [& {:keys [level cause ::logger ::async ::raw] :or {async true} :as props}] (if (:ns &env) ; CLJS `(write-log! ~(or logger (str *ns*)) ~level @@ -112,10 +171,12 @@ ~level-sym (get-level ~level)] (if (enabled? ~logger-sym ~level-sym) ~(if async - `(send-off logging-agent - (fn [_#] - (let [message# (or ~raw (build-map-message ~props))] - (write-log! ~logger-sym ~level-sym ~cause message#)))) + `(let [cdata# (ThreadContext/getImmutableContext)] + (send-off logging-agent + (fn [_#] + (with-context (into {:cause ~cause} cdata#) + (->> (or ~raw (build-map-message ~props)) + (write-log! ~logger-sym ~level-sym ~cause)))))) `(let [message# (or ~raw (build-map-message ~props))] (write-log! ~logger-sym ~level-sym ~cause message#)))))))) @@ -147,24 +208,6 @@ (when (:ns &env) `(set-level* ~n ~level)))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; CLJ Specific -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -#?(:clj - (defn update-thread-context! - [data] - (run! (fn [[key val]] - (ThreadContext/put - (name key) - (cond - (coll? val) - (binding [clojure.pprint/*print-right-margin* 120] - (with-out-str (pprint val))) - (instance? clojure.lang.Named val) (name val) - :else (str val)))) - data))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; CLJS Specific ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -213,7 +256,6 @@ (some-> (get-logger name) (glog/setLevel (get-level lvl))))) - #?(:cljs (defn set-levels! [lvls] diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index 0a6f4a4c6..b8fbd7986 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -208,30 +208,30 @@ ;; --- Macros (defn spec-assert* - [spec x message context] - (if (s/valid? spec x) - x - (let [data (s/explain-data spec x) - explain (with-out-str (s/explain-out data))] + [spec val hint ctx] + (if (s/valid? spec val) + val + (let [data (s/explain-data spec val)] (ex/raise :type :assertion :code :spec-validation - :hint message - :data data - :explain explain - :context context - #?@(:cljs [:stack (.-stack (ex-info message {}))]))))) - + :hint hint + :ctx ctx + ::s/problems (::s/problems data))))) (defmacro assert "Development only assertion macro." [spec x] (when *assert* (let [nsdata (:ns &env) - context (when nsdata + context (if nsdata {:ns (str (:name nsdata)) :name (pr-str spec) :line (:line &env) - :file (:file (:meta nsdata))}) + :file (:file (:meta nsdata))} + (let [mdata (meta &form)] + {:ns (str (ns-name *ns*)) + :name (pr-str spec) + :line (:line mdata)})) message (str "spec assert: '" (pr-str spec) "'")] `(spec-assert* ~spec ~x ~message ~context)))) @@ -253,12 +253,9 @@ [spec data] (let [result (s/conform spec data)] (when (= result ::s/invalid) - (let [data (s/explain-data spec data) - explain (with-out-str - (s/explain-out data))] + (let [data (s/explain-data spec data)] (throw (ex/error :type :validation :code :spec-validation - :explain explain :data data)))) result)) diff --git a/frontend/deps.edn b/frontend/deps.edn index f9c592b8c..514830050 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -22,7 +22,8 @@ :main-opts ["-m" "antq.core"]} :dev - {:extra-deps + {:extra-paths ["dev"] + :extra-deps {thheller/shadow-cljs {:mvn/version "2.16.6"} cider/cider-nrepl {:mvn/version "0.27.2"}}} diff --git a/frontend/dev/bench/core.cljs b/frontend/dev/bench/core.cljs deleted file mode 100644 index 8b8f1ba94..000000000 --- a/frontend/dev/bench/core.cljs +++ /dev/null @@ -1,112 +0,0 @@ -(ns bench.core - (:require [kdtree.core :as k] - [intervaltree.core :as it] - [cljs.pprint :refer (pprint)] - [cljs.nodejs :as node])) - -(enable-console-print!) - -;; --- Index Initialization Benchmark - -(defn- bench-init-10000 - [] - (println "1000x1000,10 -> 10000 points") - (time - (k/generate 1000 1000 10 10))) - -(defn- bench-init-250000 - [] - (time - (k/generate 5000 5000 10 10))) - -(defn bench-init - [] - (bench-init-10000) - (bench-init-10000) - (bench-init-250000) - (bench-init-250000) - (bench-init-10000) - (bench-init-10000) - (bench-init-250000) - (bench-init-250000)) - -;; --- Nearest Search Benchmark - -(defn- bench-knn-160000 - [] - (let [tree (k/create)] - (k/setup tree 4000 4000 10 10) - (println "KNN Search (160000 points) 1000 times") - (time - (dotimes [i 1000] - (let [pt #js [(rand-int 400) - (rand-int 400)]] - (k/nearest tree pt 2)))))) - - -(defn- bench-knn-360000 - [] - (let [tree (k/create)] - (k/initialize tree 6000 6000 10 10) - (println "KNN Search (360000 points) 1000 times") - (time - (dotimes [i 1000] - (let [pt #js [(rand-int 600) - (rand-int 600)]] - (k/nearest tree pt 2)))))) - -(defn bench-knn - [] - (bench-knn-160000) - (bench-knn-360000)) - -;; --- Accuracy tests - -(defn test-accuracy - [] - (let [tree (k/create)] - (k/setup tree 4000 4000 20 20) - (print "[1742 1419]") - (pprint (js->clj (k/nearest tree #js [1742 1419] 6))) - (print "[1742 1420]") - (pprint (js->clj (k/nearest tree #js [1742 1420] 6))) - )) - -(defn test-interval - [] - (let [tree (it/create)] - (it/add tree #js [1 5]) - (it/add tree #js [5 7]) - (it/add tree #js [-4 -1]) - (it/add tree #js [-10 -3]) - (it/add tree #js [-20 -10]) - (it/add tree #js [20 30]) - (it/add tree #js [3 9]) - (it/add tree #js [100 200]) - (it/add tree #js [1000 2000]) - (it/add tree #js [6 9]) - - (js/console.dir tree #js {"depth" nil}) - (js/console.log "contains", 4, (it/contains tree 4)) - (js/console.log "contains", 0, (it/contains tree 0)) - )) - -(defn main - [& [type]] - (cond - (= type "kd-init") - (bench-init) - - (= type "kd-search") - (bench-knn) - - (= type "kd-test") - (test-accuracy) - - (= type "interval") - (test-interval) - - :else - (println "not implemented"))) - -(set! *main-cli-fn* main) diff --git a/frontend/dev/cljs/user.cljs b/frontend/dev/cljs/user.cljs new file mode 100644 index 000000000..3a927046b --- /dev/null +++ b/frontend/dev/cljs/user.cljs @@ -0,0 +1,5 @@ +(ns cljs.user) + +(defn hello + [] + (js/console.log "hello")) diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index b46f8e164..45f46de0f 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -81,10 +81,7 @@ (js/console.group "Validation Error:") (ex/ignoring (js/console.info - (with-out-str - (pprint (dissoc error :explain)))) - (when-let [explain (:explain error)] - (js/console.error explain))) + (with-out-str (pprint error)))) (js/console.groupEnd "Validation Error:")) @@ -138,8 +135,7 @@ (defmethod ptk/handle-error :server-error [{:keys [data hint] :as error}] (let [hint (or hint (:hint data) (:message data)) - info (with-out-str (pprint (dissoc data :explain))) - expl (:explain data) + info (with-out-str (pprint data)) msg (str "Internal Server Error: " hint)] (ts/schedule @@ -150,7 +146,6 @@ (js/console.group msg) (js/console.info info) - (when expl (js/console.error expl)) (js/console.groupEnd msg))) (defn on-unhandled-error