Skip to content

Instantly share code, notes, and snippets.

@frankitox
Last active February 2, 2021 16:36
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save frankitox/f10dd85c4d2d5baea8dbfb0519a284a4 to your computer and use it in GitHub Desktop.
Save frankitox/f10dd85c4d2d5baea8dbfb0519a284a4 to your computer and use it in GitHub Desktop.
Babashka script to extract a raw stacktrace from a Sentry event
#!/usr/bin/env bb
(ns sentry-stacktrace
(:require
[babashka.curl :as curl]
[cheshire.core :as json]
[clojure.pprint :as pp]
[clojure.tools.cli :as cli]
[clojure.set :as set]
[clojure.string :as str]))
;; To run this script:
;; 1. Copy the file to your file system
;; 2. chmod u+x sentry_stacktrace.clj
;; 3. ./sentry_stacktrace --properties sentry.properties --event 5da81b523abd44ff9cdd68b5e884a7bc
(def options
[["-h" "--help"]
["-p" "--properties PROPS" "Path to sentry.properties file."]
["-o" "--org ORG" "The organization slug."]
["-p" "--project PROJECT" "The project slug."]
["-e" "--event EVENT" "The event id to find the stacktrace. REQUIRED."]
["-d" "--debug" "Pretty print the stacktrace data structure."]
[nil "--url URL" "Fully qualified URL to the Sentry server."
:default "https://sentry.io/"]
[nil "--auth-token AUTH_TOKEN" "Use the given Sentry auth token."]])
;; https://docs.sentry.io/api/events/retrieve-an-event-for-a-project/
(defn get-event [{:keys [org project auth-token event]}]
(-> (curl/get (str "https://sentry.io/api/0/projects/" org "/" project "/events/" event "/")
{:headers {"Authorization" (str "Bearer " auth-token)}})
(:body) (json/parse-string true)))
(defn parse-stacktrace [event {:keys [debug]}]
(let [stacktraces (->> event (:entries) (filter (fn [t] (= "exception" (:type t)))))
frames (->> stacktraces first :data :values first :rawStacktrace :frames
reverse (filter (fn [t] (and (:colNo t) (:lineNo t)))))
pp-frames (when debug (-> frames pp/pprint with-out-str (str "\n\n")))]
(cond
(not= 1 (count stacktraces))
{:msg (str pp-frames "The event has zero or more than one stacktraces")
:exit 1}
(empty? frames)
{:msg (str pp-frames "The stacktrace doesn't have specified columns and rows")
:exit 2}
:else
{:msg (str pp-frames
(->>
frames
(map #(str (:lineNo %) ":" (:colNo %)))
(str/join \newline)))
:exit 0})))
(defn usage [summary]
{:msg (->> ["Helper script to extract Sentry raw stacktraces."
""
"Usage: ./sentry-stacktrace.clj --properties FILE --event EVENT"
""
"You can also send the Sentry token with env var SENTRY_AUTH_TOKEN."
""
summary]
(str/join \newline))
:exit 0})
(defn file-props [path]
(let [props (->> (slurp path)
(str/split-lines)
(map #(str/split % #"=" 2))
(into {}))]
(-> props
(set/rename-keys {"defaults.url" :url
"defaults.org" :org
"defaults.project" :project
"auth.token" :auth-token})
(select-keys [:url :org :project :auth-token]))))
(defn process-opts [{:keys [options errors summary] :as parsed-args} env-token]
(let [{:keys [help debug] :as options} (merge
(when (:properties options)
(file-props (:properties options)))
options
(when (seq env-token)
{:auth-token env-token}))
required-opts #{:org :project :auth-token :event}
all-required-opts? (every? seq ((apply juxt required-opts) options))]
(cond
help (usage summary)
all-required-opts? (-> (get-event options)
(parse-stacktrace options))
:else {:msg (str "You are missing some of the required options: " required-opts)
:exit 3})))
(defn print-and-leave! [{:keys [msg exit]}]
(if (zero? exit)
(println msg)
(binding [*out* *err*]
(println msg)))
(System/exit exit))
(defn -main [& args]
(print-and-leave!
(process-opts (cli/parse-opts args options) (System/getenv "SENTRY_AUTH_TOKEN"))))
(when (= *file* (System/getProperty "babashka.file"))
(apply -main *command-line-args*))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment