From c3fa4f4c6e9273a899a723dd74efa87c3a7768e5 Mon Sep 17 00:00:00 2001 From: arthura <58684963+arthuraliiev@users.noreply.github.com> Date: Mon, 18 Mar 2024 09:46:51 +0100 Subject: [PATCH] Migrate to sentry-clj. Getting rid of raven lib. (#65) - Migration to sentry-clj library. - Getting rid of unsupported raven library. --- README.md | 7 +- deps.edn | 2 +- project.clj | 2 +- src/spootnik/reporter.clj | 10 +-- src/spootnik/reporter/impl.clj | 51 ++++++------ src/spootnik/reporter/sentry.clj | 115 +++++++++++++++++++++++++++ test/spootnik/reporter/impl_test.clj | 2 +- 7 files changed, 151 insertions(+), 38 deletions(-) create mode 100644 src/spootnik/reporter/sentry.clj diff --git a/README.md b/README.md index b339a77..28537b0 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,14 @@ Reporter provides a [component](https://github.com/stuartsierra/component) in or ### Changelog +#### 1.0.0 + +- Migrating from `exoscale/raven` to `io.sentry/sentry-clj` +- `raven-options` renamed to `sentry-options` within `spootnik.reporter.impl/Reporter` signature + #### 0.2.0 -- Allow arbitray values for counter +- Allow arbitray values for counter #### 0.1.60 diff --git a/deps.edn b/deps.edn index e7de271..8b01a75 100644 --- a/deps.edn +++ b/deps.edn @@ -4,7 +4,7 @@ {org.clojure/clojure "1.9.0" org.clojure/tools.logging "0.4.0" com.stuartsierra/component "0.3.2" - exoscale/raven "0.4.2" + io.sentry/sentry-clj "7.4.213" spootnik/net "0.3.3-beta24" spootnik/uncaught "0.5.3" metrics-clojure "2.10.0" diff --git a/project.clj b/project.clj index f44ff49..5308002 100644 --- a/project.clj +++ b/project.clj @@ -14,7 +14,7 @@ [org.clojure/clojure "1.10.1"] [org.clojure/tools.logging "1.0.0"] [com.stuartsierra/component "1.0.0"] - [exoscale/raven "0.4.15"] + [io.sentry/sentry-clj "7.4.213"] [spootnik/uncaught "0.5.5"] [metrics-clojure "2.10.0"] [metrics-clojure-riemann "2.10.0"] diff --git a/src/spootnik/reporter.clj b/src/spootnik/reporter.clj index 4f6a7af..7e56496 100644 --- a/src/spootnik/reporter.clj +++ b/src/spootnik/reporter.clj @@ -1,7 +1,6 @@ (ns spootnik.reporter (:require [com.stuartsierra.component :as c] [clojure.tools.logging :as log] - [raven.client :as raven] [spootnik.reporter.impl :as rptr] [manifold.deferred :as d] spootnik.reporter.specs)) @@ -80,11 +79,10 @@ ([error extra] (log/error error) - (-> (capture! (-> (if (instance? Exception error) - (-> {:data (ex-data error)} - (raven/add-exception! error)) - {:message error}) - (raven/add-extra! extra))) + (-> (capture! (if (instance? Exception error) + {:extra extra + :throwable error} + {:message error})) (d/catch Throwable (fn [e] (log/error e "Sentry failure"))) diff --git a/src/spootnik/reporter/impl.clj b/src/spootnik/reporter/impl.clj index cd31f37..05c691a 100644 --- a/src/spootnik/reporter/impl.clj +++ b/src/spootnik/reporter/impl.clj @@ -1,9 +1,7 @@ (ns spootnik.reporter.impl (:require [aleph.http :as http] - [manifold.deferred :as d] [clojure.java.io :as io] [com.stuartsierra.component :as c] - [raven.client :as raven] [metrics.reporters.console :as console] [metrics.reporters.jmx :as jmx] [metrics.reporters.graphite :as graphite] @@ -19,7 +17,8 @@ [camel-snake-kebab.core :as csk] [clojure.string :as str] [clojure.tools.logging :refer [info error]] - [spootnik.uncaught :refer [with-uncaught]]) + [spootnik.uncaught :refer [with-uncaught]] + [spootnik.reporter.sentry :as rs]) (:import com.aphyr.riemann.client.RiemannClient com.aphyr.riemann.client.RiemannBatchClient com.aphyr.riemann.client.TcpTransport @@ -209,7 +208,7 @@ state (or (:state ev) (:state defaults)) time ^Long (or time (quot (System/currentTimeMillis) 1000))] (-> (.event client) - (.host (or host (:host defaults) (raven/localhost))) + (.host (or host (:host defaults) (rs/localhost))) (.service (or service (:service defaults) "")) (.time (long time)) (cond-> metric (.metric metric) @@ -372,16 +371,17 @@ (defn parse-pggrouping-keys [grouping-keys] (into {} (for [[k v] grouping-keys] [(csk/->snake_case_string k) v]))) -;; "sentry" is a sentry map like {:dsn "..."} -;; "raven-options" is the options map sent to raven http client -;; http://aleph.io/codox/aleph/aleph.http.html#var-request -(defrecord Reporter [rclient raven-options reporters registry sentry metrics riemann prevent-capture? prometheus +;; Reporter configuration specs: +;; https://github.com/exoscale/reporter/blob/master/src/spootnik/reporter/specs.clj + +(defrecord Reporter [rclient sentry-options reporters registry sentry + metrics riemann prevent-capture? prometheus started? pushgateway] c/Lifecycle (start [this] (if started? this - (let [prometheus-registry (CollectorRegistry/defaultRegistry) + (let [prometheus-registry (CollectorRegistry/defaultRegistry) [pgclient pgjob pgregistry pggrouping-keys] (when pushgateway [(build-pushgateway-client pushgateway) (name (:job pushgateway)) (CollectorRegistry.) @@ -389,7 +389,7 @@ pgmetrics (when pushgateway (build-collectors! pgregistry (get-in metrics [:reporters :pushgateway]))) rclient (when riemann (riemann-client riemann)) [reg reps] (build-metrics metrics rclient prometheus-registry pgregistry) - options (when sentry (or raven-options {})) + options (when sentry (or sentry-options {})) prometheus-server (when prometheus (let [tls (:tls prometheus) opts (cond-> {:port (:port prometheus)} @@ -405,14 +405,17 @@ prometheus prometheus-registry) opts)))] + + (rs/init! sentry) + (when-not prevent-capture? (with-uncaught e - (capture! (assoc this :raven-options options) e))) + (capture! (assoc this :sentry-options options) e))) (cond-> (assoc this :registry reg :reporters reps :rclient rclient - :raven-options options + :sentry-options options :started? true) prometheus (assoc :prometheus {:server prometheus-server :registry prometheus-registry}) @@ -436,9 +439,12 @@ (.close ^RiemannClient rclient) (catch Exception _))) (when prometheus - (.close ^java.io.Closeable (:server prometheus)))) + (.close ^java.io.Closeable (:server prometheus))) + + (rs/close! sentry)) + (assoc this - :raven-options nil + :sentry-options nil :reporters nil :registry nil :rclient nil @@ -516,21 +522,10 @@ SentrySink (capture! [this e] (capture! this e {})) - (capture! [this e tags] - (if (:dsn sentry) - (-> (try - (raven/capture! raven-options (:dsn sentry) e tags) - (catch Exception e - (d/error-deferred e))) - (d/chain - (fn [event-id] - (error e (str "captured exception as sentry event: " event-id)))) - (d/catch (fn [e'] - (error e "Failed to capture exception" {:tags tags :capture-exception e'}) - (capture! this e')))) - (error e))) + (capture! [_this e tags] + (rs/send-event! (:dsn sentry) sentry-options e tags)) RiemannSink - (send! [this ev] + (send! [_this ev] (when rclient (let [to-seq #(if-not (sequential? %) [%] %)] (->> ev diff --git a/src/spootnik/reporter/sentry.clj b/src/spootnik/reporter/sentry.clj new file mode 100644 index 0000000..ae98b5b --- /dev/null +++ b/src/spootnik/reporter/sentry.clj @@ -0,0 +1,115 @@ +(ns spootnik.reporter.sentry + (:require + [sentry-clj.core :as sentry-io] + [manifold.deferred :as d] + [clojure.string :as str] + [clojure.tools.logging :refer [error]] + [clojure.java.shell :as sh])) + +(def http-requests-payload-stub + "Storage for stubbed http sentry events " + (atom nil)) + +(defn in-memory? [dsn] + (or (= dsn ":memory:") + (nil? dsn))) + +(def hostname-refresh-interval + "How often to allow reading /etc/hostname, in seconds." + 60) + +(defn get-hostname + "Get the current hostname by shelling out to 'hostname'" + [] + (or + (try + (let [{:keys [exit out]} (sh/sh "hostname")] + (when (= exit 0) + (str/trim out))) + (catch Exception _)) + "")) + +(defn hostname + "Fetches the hostname by shelling to 'hostname', whenever the given age + is stale enough. If the given age is recent, as defined by + hostname-refresh-interval, returns age and val instead." + [[age val]] + (if (and val (<= (* 1000 hostname-refresh-interval) + (- (System/currentTimeMillis) age))) + [age val] + [(System/currentTimeMillis) (get-hostname)])) + +(let [cache (atom [nil nil])] + (defn localhost + "Returns the local host name." + [] + (if (re-find #"^Windows" (System/getProperty "os.name")) + (or (System/getenv "COMPUTERNAME") "localhost") + (or (System/getenv "HOSTNAME") + (second (swap! cache hostname)))))) + +(defn- payload->sentry-request [payload] + {:url (-> payload :uri) + :method (-> payload :request-method name clojure.string/upper-case) + :query-string (-> payload :query-string) + :headers (-> payload :headers)}) + +(defn e->sentry-event + " + Supported event keys: + https://github.com/getsentry/sentry-clj/tree/master?tab=readme-ov-file#supported-event-keys + " + + [e options tags] + (let [{:keys [message extra throwable]} e + message (or message (ex-message e)) + user (some-> extra :org/uuid str) + fingerprints (some-> options :fingerpint seq) + request (some-> extra :payload payload->sentry-request)] + + (cond-> {:message message + :level :error + :platform "java" + :server-name (localhost)} + + throwable (assoc :throwable throwable) + extra (assoc :extra extra) + (seq tags) (assoc :tags tags) + user (assoc :user user) + fingerprints (assoc :fingerprints fingerprints) + request (assoc :request request)))) + +(defn send-event! [dsn options e tags] + (let [event (e->sentry-event e options tags)] + (if-not (in-memory? dsn) + (-> (try + (sentry-io/send-event event) + (catch Exception e + (d/error-deferred e))) + + (d/chain + (fn [event-id] + (error e (str "captured exception as sentry event: " event-id)) + event-id)) + + (d/catch (fn [e'] + (error e "Failed to capture exception" {:tags tags :capture-exception e'}) + (send-event! dsn options e' tags)))) + + (swap! http-requests-payload-stub conj event)))) + +(defn init! + " + Additional options can be found here: + https://github.com/getsentry/sentry-clj/tree/master?tab=readme-ov-file#additional-initialisation-options + " + + [{:keys [dsn] :as sentry}] + (if-not (in-memory? dsn) + (sentry-io/init! dsn sentry) + (reset! http-requests-payload-stub []))) + +(defn close! [{:keys [dsn]}] + (if-not (in-memory? dsn) + (sentry-io/close!) + (reset! http-requests-payload-stub nil))) diff --git a/test/spootnik/reporter/impl_test.clj b/test/spootnik/reporter/impl_test.clj index fb36589..325f6ed 100644 --- a/test/spootnik/reporter/impl_test.clj +++ b/test/spootnik/reporter/impl_test.clj @@ -7,7 +7,7 @@ [prometheus.core :as prometheus] [spootnik.reporter.impl :refer :all] [com.stuartsierra.component :as component] - [raven.client :refer [http-requests-payload-stub]] + [spootnik.reporter.sentry :refer [http-requests-payload-stub]] [clojure.set :as cljset]) (:import io.prometheus.client.CollectorRegistry io.netty.handler.ssl.SslContextBuilder