From 4708af3b9138c3b1abca4c2e4f50e681d08990e8 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 21 Sep 2021 16:08:42 +0200 Subject: [PATCH] :tada: Add sentry integration (on exporter). --- exporter/src/app/browser.cljs | 52 +++++++++++++++++---------------- exporter/src/app/config.cljs | 15 +++++++--- exporter/src/app/core.cljs | 2 ++ exporter/src/app/http.cljs | 6 +++- exporter/src/app/http/impl.cljs | 4 ++- exporter/src/app/sentry.cljs | 44 ++++++++++++++++++++++++++++ 6 files changed, 92 insertions(+), 31 deletions(-) create mode 100644 exporter/src/app/sentry.cljs diff --git a/exporter/src/app/browser.cljs b/exporter/src/app/browser.cljs index 44b4d93563..a3775a048c 100644 --- a/exporter/src/app/browser.cljs +++ b/exporter/src/app/browser.cljs @@ -108,7 +108,6 @@ (p/then (fn [browser] (let [id (deref pool-browser-id)] (log/info :origin "factory" :action "create" :browser-id id) - (unchecked-set browser "__num_use" 0) (unchecked-set browser "__id" id) (swap! pool-browser-id inc) browser)))))) @@ -118,16 +117,9 @@ (.close ^js obj))) (validate [obj] - (let [max-use (cf/get :browser-max-usage 10) - num-use (unchecked-get obj "__num_use") - id (unchecked-get obj "__id")] - - (log/info :origin "factory" :action "validate" :browser-id id :max-use max-use :num-use num-use :obj obj) - (if (> num-use max-use) - (p/resolved false) - (do - (unchecked-set obj "__num_use" (inc num-use)) - (p/resolved (.isConnected ^js obj))))))] + (let [id (unchecked-get obj "__id")] + (log/info :origin "factory" :action "validate" :browser-id id :obj obj) + (p/resolved (.isConnected ^js obj))))] #js {:create create :destroy destroy @@ -155,19 +147,29 @@ (p/then (fn [] (.clear ^js pool)))))) (defn exec! - [f] - (letfn [(on-acquire [pool browser] - (p/let [ctx (.createIncognitoBrowserContext ^js browser) - page (.newPage ^js ctx)] - (-> (p/do! (f page)) - (p/handle - (fn [result error] - (-> (p/do! (.close ^js ctx) - (.release ^js pool browser)) - (p/handle (fn [_ _] - (if result - (p/resolved result) - (p/rejected error))))))))))] + [callback] + (letfn [(on-release [pool browser ctx result error] + (-> (p/do! (.close ^js ctx)) + (p/handle + (fn [_ _] + (.release ^js pool browser))) + (p/handle + (fn [_ _] + (let [id (unchecked-get browser "__id")] + (log/info :origin "exec" :action "release" :browser-id id)) + (if result + (p/resolved result) + (p/rejected error)))))) + + (on-context [pool browser ctx] + (-> (p/do! (.newPage ^js ctx)) + (p/then callback) + (p/handle #(on-release pool browser ctx %1 %2)))) + + (on-acquire [pool browser] + (-> (.createIncognitoBrowserContext ^js browser) + (p/then #(on-context pool browser %))))] + (when-let [pool (deref pool)] - (-> (.acquire ^js pool) + (-> (p/do! (.acquire ^js pool)) (p/then (partial on-acquire pool)))))) diff --git a/exporter/src/app/config.cljs b/exporter/src/app/config.cljs index cb45784b01..307019171f 100644 --- a/exporter/src/app/config.cljs +++ b/exporter/src/app/config.cljs @@ -21,6 +21,8 @@ (def defaults {:public-uri "http://localhost:3449" + :tenant "dev" + :host "devenv" :http-server-port 6061 :browser-concurrency 5 :browser-strategy :incognito}) @@ -30,9 +32,15 @@ (s/def ::browser-strategy ::us/keyword) (s/def ::http-server-port ::us/integer) (s/def ::public-uri ::us/string) +(s/def ::sentry-dsn ::us/string) +(s/def ::tenant ::us/string) +(s/def ::host ::us/string) (s/def ::config (s/keys :opt-un [::public-uri + ::sentry-dsn + ::host + ::tenant ::http-server-port ::browser-concurrency ::browser-strategy @@ -62,11 +70,10 @@ (def config (atom (prepare-config))) - (def version - (v/parse (or (some-> (ex/ignoring (fs/readFileSync "version.txt")) - (str/trim)) - "%version%"))) + (atom (v/parse (or (some-> (ex/ignoring (fs/readFileSync "version.txt")) + (str/trim)) + "%version%")))) (defn get "A configuration getter." diff --git a/exporter/src/app/core.cljs b/exporter/src/app/core.cljs index 7fafd8393e..29eb8865d8 100644 --- a/exporter/src/app/core.cljs +++ b/exporter/src/app/core.cljs @@ -10,11 +10,13 @@ [lambdaisland.glogi.console :as glogi-console] [promesa.core :as p] [app.http :as http] + [app.sentry :as sentry] [app.config] [app.browser :as bwr])) (glogi-console/install!) (enable-console-print!) +(sentry/init!) (defonce state (atom nil)) diff --git a/exporter/src/app/http.cljs b/exporter/src/app/http.cljs index 001151316b..a36e030d9b 100644 --- a/exporter/src/app/http.cljs +++ b/exporter/src/app/http.cljs @@ -11,6 +11,7 @@ [app.http.export-frames :refer [export-frames-handler]] [app.http.impl :as impl] [app.util.transit :as t] + [app.sentry :as sentry] [cuerdas.core :as str] [lambdaisland.glogi :as log] [promesa.core :as p] @@ -25,6 +26,9 @@ (defn- on-error [error request] (let [{:keys [type message code] :as data} (ex-data error)] + (sentry/capture-exception error {::sentry/request request + :ex-data data}) + (cond (= :validation type) (let [header (get-in request [:headers "accept"])] @@ -60,7 +64,7 @@ (.listen server port) (log/info :msg "welcome to penpot" :module "exporter" - :version (:full cf/version)) + :version (:full @cf/version)) (log/info :msg "starting http server" :port port) (reset! instance server))) diff --git a/exporter/src/app/http/impl.cljs b/exporter/src/app/http/impl.cljs index 288aa021e8..67d54f6d89 100644 --- a/exporter/src/app/http/impl.cljs +++ b/exporter/src/app/http/impl.cljs @@ -63,7 +63,9 @@ :query (:query uri) :url uri :headers headers - :cookies cookies}] + :cookies cookies + :internal-request req + :internal-response res}] (-> (parse-body req) (p/then (fn [body] (let [request (assoc request :body body)] diff --git a/exporter/src/app/sentry.cljs b/exporter/src/app/sentry.cljs new file mode 100644 index 0000000000..4f7c9be5ca --- /dev/null +++ b/exporter/src/app/sentry.cljs @@ -0,0 +1,44 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.sentry + (:require + ["@sentry/node" :as sentry] + ["@sentry/tracing" :as sentry-t] + [app.common.data :as d] + [app.config :as cf])) + +(defn init! + [] + (when-let [dsn (cf/get :sentry-dsn)] + (sentry/init + #js {:dsn dsn + :release (str "frontend@" (:base @cf/version)) + :serverName (cf/get :host) + :environment (cf/get :tenant) + :autoSessionTracking false + :attachStacktrace false + :maxBreadcrumbs 20 + :tracesSampleRate 1.0}))) + +(def parse-request (unchecked-get sentry/Handlers "parseRequest")) + +(defn capture-exception + [error {:keys [::request ::tags] :as context}] + (let [context (-> (dissoc context ::request ::tags) + (d/without-nils))] + (sentry/withScope + (fn [scope] + (.addEventProcessor ^js scope (fn [event] + (let [node-request (:internal-request request)] + (parse-request event node-request)))) + (doseq [[k v] tags] + (.setTag ^js scope (if (keyword? k) (name k) (str k)) (str v))) + + (doseq [[k v] context] + (.setContext ^js scope (if (keyword? k) (name k) (str k)) (clj->js v))) + + (sentry/captureException error)))))