Skip to content

Commit

Permalink
add direct call support to alertmanager
Browse files Browse the repository at this point in the history
  • Loading branch information
mpenet committed Jan 28, 2025
1 parent 40a9277 commit b9cab81
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 17 deletions.
33 changes: 17 additions & 16 deletions deps.edn
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,35 @@

:deps
{aleph/aleph {:mvn/version "0.4.7-alpha5"}
org.clojure/clojure {:mvn/version "1.12.0"}
org.clojure/tools.logging {:mvn/version "1.3.0"}
camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.2"}
com.stuartsierra/component {:mvn/version "1.1.0"}
exoscale/ex {:mvn/version "0.4.1"}
io.prometheus/simpleclient {:mvn/version "0.12.0"}
io.prometheus/simpleclient_common {:mvn/version "0.12.0"}
io.prometheus/simpleclient_dropwizard {:mvn/version "0.12.0"}
io.prometheus/simpleclient_hotspot {:mvn/version "0.12.0"}
io.prometheus/simpleclient_pushgateway {:mvn/version "0.12.0"}
io.sentry/sentry-clj {:mvn/version "7.20.226"}
spootnik/uncaught {:mvn/version "0.5.5"}
metrics-clojure/metrics-clojure {:mvn/version "2.10.0"}
metrics-clojure-riemann/metrics-clojure-riemann {:mvn/version "2.10.0"}
metrics-clojure-jvm/metrics-clojure-jvm {:mvn/version "2.10.0"}
javax.xml.bind/jaxb-api {:mvn/version "2.4.0-b180830.0359"}
metosin/jsonista {:mvn/version "0.3.13"}
metrics-clojure-graphite/metrics-clojure-graphite {:mvn/version "2.10.0"}
io.prometheus/simpleclient {:mvn/version "0.16.0"}
io.prometheus/simpleclient_common {:mvn/version "0.16.0"}
io.prometheus/simpleclient_hotspot {:mvn/version "0.16.0"}
io.prometheus/simpleclient_dropwizard {:mvn/version "0.16.0"}
io.prometheus/simpleclient_pushgateway {:mvn/version "0.16.0"}
camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.3"}
javax.xml.bind/jaxb-api {:mvn/version "2.4.0-b180830.0359"}}
metrics-clojure-jvm/metrics-clojure-jvm {:mvn/version "2.10.0"}
metrics-clojure-riemann/metrics-clojure-riemann {:mvn/version "2.10.0"}
metrics-clojure/metrics-clojure {:mvn/version "2.10.0"}
org.clojure/clojure {:mvn/version "1.12.0"}
org.clojure/tools.logging {:mvn/version "1.3.0"}
spootnik/uncaught {:mvn/version "0.5.5"}}

:aliases
{:build
{:deps {io.github.clojure/tools.build {:git/tag "v0.10.6" :git/sha "52cf7d6"}
io.github.slipset/deps-deploy {:git/sha "b87c9299761762984bd54a285ca8fa0aac81809f"}}
io.github.slipset/deps-deploy {:git/sha "07022b92d768590ab25b9ceb619ef17d2922da9a"}}
:ns-default build}

:test
{:extra-deps {exoscale/test-runner {:local/root "dev"}
ch.qos.logback/logback-classic {:mvn/version "1.5.16"}
com.soundcloud/prometheus-clj {:mvn/version "2.4.1"}
org.slf4j/slf4j-api {:mvn/version "1.7.26"}
org.slf4j/slf4j-log4j12 {:mvn/version "1.7.26"}
org.clojure/tools.logging {:mvn/version "1.3.0"}}
:jvm-opts ["-Dclojure.main.report=stderr"]
:exec-fn test-runner/run
Expand Down
2 changes: 1 addition & 1 deletion dev/deps.edn
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{:paths ["."]
:deps {com.exoscale/eftest {:mvn/version "1.0.0"}
org.clojure/test.check {:mvn/version "1.1.1"}}}
nubank/matcher-combinators {:mvn/version "3.9.1"}}}
97 changes: 97 additions & 0 deletions src/spootnik/reporter/alert.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
(ns spootnik.reporter.alert
(:require [aleph.http :as http]
[camel-snake-kebab.core :as csk]
[clojure.spec.alpha :as s]
[exoscale.ex :as ex]
[jsonista.core :as json])
(:import (java.time Instant)))

(defprotocol Alert
(-send! [client alert])
(-msend! [client alerts]))

(s/def ::named (s/or :k ident? :s string?))

(s/def :spootnik.reporter.alert.annotation/key ::named)
(s/def :spootnik.reporter.alert.annotation/value (s/or :named ::named :num number?))
(s/def :spootnik.reporter.alert/annotations
(s/map-of :spootnik.reporter.alert.annotation/key
:spootnik.reporter.alert.annotation/value))

(s/def :spootnik.reporter.alert.label/key
(s/and ::named
(fn [[_ val]] (re-matches #"[a-zA-Z_][a-zA-Z0-9_-]*"
(name val)))))
(s/def :spootnik.reporter.alert.label/value (s/or :named ::named :num number?))
(s/def :spootnik.reporter.alert/labels
(s/map-of :spootnik.reporter.alert.label/key
:spootnik.reporter.alert.label/value))

(s/def :spootnik.reporter.alert/name :spootnik.reporter.alert.label/key)
(s/def :spootnik.reporter.alert/generator-url string?)
(s/def :spootnik.reporter.alert.options/delay-after-ms pos-int?)

(s/def :spootnik.reporter/alert
(s/keys :req-un [:spootnik.reporter.alert/name]
:opt-un [:spootnik.reporter.alert/labels
:spootnik.reporter.alert/annotations
:spootnik.reporter.alert/generator-url
:spootnik.reporter.alert.options/delay-after-ms]))

(s/def :spootnik.reporter/alerts (s/coll-of :spootnik.reporter/alert :min-count 1))

(defn alert->payload
[{:as _alert :keys [name labels annotations generator-url delay-after-ms]
:or {delay-after-ms (* 60 1000 15)}}]
(let [t (Instant/now)]
(cond-> {:startsAt (str t)
:endsAt (str (.plusMillis t (* delay-after-ms)))
:labels (into {:alertname (csk/->snake_case name)}
(map (fn [[k v]] [(csk/->snake_case k) v]))
labels)}

(seq annotations)
(assoc :annotations
(into {}
(map (fn [[k v]] [(csk/->snake_case k) v]))
annotations))

generator-url
(assoc :generatorURL generator-url))))

(defn- alerts-body
[alerts]
(json/write-value-as-string
(map (fn [alert] (alert->payload alert)) alerts)))

(defrecord AlephClient [server])

(s/def :spootnik.reporter.alert.client/server string?)
(s/def :spootnik.reporter.alert/client
(s/keys :req-un [:spootnik.reporter.alert.client/server]))

(extend-protocol Alert
AlephClient
(-msend! [client alerts]
@(http/post (format "%s/api/v2/alerts" (:server client))
(into {:body (alerts-body alerts)
:headers {"Content-Type" "application/json; charset=utf-8"}}
client)))
(-send! [client alert]
(-msend! client [alert])))

(defn send!
"Sends `alert`, conforming to `:spootnik.reporter/alert`, via `client` to
alertmanager instance conforming to `:spootnik.reporter.alert/client`"
[client alert]
(ex/assert-spec-valid :spootnik.reporter.alert/client client)
(ex/assert-spec-valid :spootnik.reporter/alert alert)
(-send! client alert))

(defn msend!
"Sends `alerts` in bulk, conforming to `:spootnik.reporter/alerts`, via `client`
to alertmanager instance conforming to `:spootnik.reporter.alert/client`"
[client alerts]
(ex/assert-spec-valid :spootnik.reporter.alert/client client)
(ex/assert-spec-valid :spootnik.reporter/alerts alerts)
(-msend! client alerts))
21 changes: 21 additions & 0 deletions test/spootnik/reporter/alert_test.clj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
(ns spootnik.reporter.alert-test
(:require [clojure.test :as _t :refer [deftest is]]
[matcher-combinators.test]
[spootnik.reporter.alert :as a]))

(deftest test-alert-format
(is (match? {:labels {:alertname "yolo"}}
(a/alert->payload {:name "yolo"})))
(is (match? {:labels {:alertname "yolo", :baz "bazz"
:key_format "format"}}
(a/alert->payload {:name "yolo"
:labels {:baz "bazz" :key-format "format"}})))
(is (match? {:labels {:alertname "yolo", :baz "bazz"},
:annotations {:foo "bar"}}
(a/alert->payload {:name "yolo"
:annotations {:foo "bar"}
:labels {:baz "bazz"}})))
(is (match? {:labels {:alertname "yolo"}
:generatorURL "http://google.com"}
(a/alert->payload {:name "yolo"
:generator-url "http://google.com"}))))

0 comments on commit b9cab81

Please sign in to comment.