diff --git a/src/iapetos/collector/ring.clj b/src/iapetos/collector/ring.clj index f3c916d..fa4a58a 100644 --- a/src/iapetos/collector/ring.clj +++ b/src/iapetos/collector/ring.clj @@ -28,25 +28,28 @@ ;; ## Initialization (defn- make-latency-collector - [buckets] - (prometheus/histogram - :http/request-latency-seconds - {:description "the response latency for HTTP requests." - :labels [:method :status :statusClass :path]})) + ([buckets extra-labels] + (prometheus/histogram + :http/request-latency-seconds + {:description "the response latency for HTTP requests." + :labels (concat [:method :status :statusClass :path] extra-labels)})) + ([buckets] + (make-latency-collector buckets []))) (defn- make-count-collector - [] - (prometheus/counter - :http/requests-total - {:description "the total number of HTTP requests processed." - :labels [:method :status :statusClass :path]})) + ([extra-labels] + (prometheus/counter + :http/requests-total + {:description "the total number of HTTP requests processed." + :labels (concat [:method :status :statusClass :path] extra-labels)})) + ([] (make-count-collector []))) (defn- make-exception-collector - [] - (ex/exception-counter - :http/exceptions-total - {:description "the total number of exceptions encountered during HTTP processing." - :labels [:method :path]})) + ([] + (ex/exception-counter + :http/exceptions-total + {:description "the total number of exceptions encountered during HTTP processing." + :labels [:method :path]}))) (defn initialize "Initialize all collectors for Ring handler instrumentation. This includes: @@ -56,12 +59,13 @@ - `http_exceptions_total` " [registry - & [{:keys [latency-histogram-buckets] + & [{:keys [latency-histogram-buckets + extra-labels] :or {latency-histogram-buckets [0.001 0.005 0.01 0.02 0.05 0.1 0.2 0.3 0.5 0.75 1 5]}}]] (prometheus/register registry - (make-latency-collector latency-histogram-buckets) - (make-count-collector) + (make-latency-collector latency-histogram-buckets extra-labels) + (make-count-collector extra-labels) (make-exception-collector))) ;; ## Response @@ -94,30 +98,34 @@ (str status)) (defn- record-metrics! - [registry delta {:keys [request-method ::path]} response] - (let [labels {:method (-> request-method name string/upper-case) - :status (status response) - :statusClass (status-class response) - :path path} + [registry delta {:keys [request-method ::path] :as request} extra-labels-request extra-labels-response response] + (let [labels-request (select-keys request extra-labels-request) + labels-response (select-keys response extra-labels-response) + default-labels {:method (-> request-method name string/upper-case) + :status (status response) + :statusClass (status-class response) + :path path} + labels (merge default-labels labels-request labels-response) delta-in-seconds (/ delta 1e9)] (-> registry (prometheus/inc :http/requests-total labels) (prometheus/observe :http/request-latency-seconds labels delta-in-seconds)))) (defn- exception-counter-for - [registry {:keys [request-method ::path]}] - (let [labels {:method (-> request-method name string/upper-case) + [registry {:keys [request-method ::path] :as request} extra-labels-request] + (let [labels-request (select-keys request extra-labels-request) + labels {:method (-> request-method name string/upper-case) :path path}] (registry :http/exceptions-total labels))) (defn- run-instrumented - [registry handler request] - (ex/with-exceptions (exception-counter-for registry request) + [registry handler extra-labels-request extra-labels-response request] + (ex/with-exceptions (exception-counter-for registry request extra-labels-request) (let [start-time (System/nanoTime) response (handler request) delta (- (System/nanoTime) start-time)] (->> (ensure-response-map response) - (record-metrics! registry delta request)) + (record-metrics! registry delta request extra-labels-request extra-labels-response)) response))) (defn wrap-instrumentation @@ -134,10 +142,10 @@ the `:path` label) if you have any kind of ID in your URIs – since otherwise there will be one timeseries created for each observed ID." [handler registry - & [{:keys [path-fn] :or {path-fn :uri}}]] + & [{:keys [path-fn extra-labels-request extra-labels-response] :or {path-fn :uri}}]] (fn [request] (->> (assoc request ::path (path-fn request)) - (run-instrumented registry handler)))) + (run-instrumented registry handler extra-labels-request extra-labels-response)))) ;; ### Metrics Endpoint @@ -172,7 +180,7 @@ the `:path` label) if you have any kind of ID in your URIs – since otherwise there will be one timeseries created for each observed ID." [handler registry - & [{:keys [path path-fn on-request] + & [{:keys [path path-fn on-request extra-labels-request extra-labels-response] :or {path "/metrics" path-fn :uri} :as options}]] diff --git a/test/iapetos/collector/ring_test.clj b/test/iapetos/collector/ring_test.clj index 12bebee..d717426 100644 --- a/test/iapetos/collector/ring_test.clj +++ b/test/iapetos/collector/ring_test.clj @@ -13,41 +13,47 @@ (def gen-handler (gen/one-of - [(->> (gen/elements - (concat - (range 200 205) - (range 300 308) - (range 400 429) - (range 500 505))) - (gen/fmap - (fn [status] - {:handler (constantly {:status status}) - :exception? false - :labels {:status (str status) - :statusClass (str (quot status 100) "XX")}}))) + [(gen/let [status (gen/elements + (concat + (range 200 205) + (range 300 308) + (range 400 429) + (range 500 505))) + extra-field-in-response gen/string-alpha-numeric] + (gen/return + {:handler (constantly {:status status :extra-field-in-response extra-field-in-response}) + :exception? false + :labels {:status (str status) + :statusClass (str (quot status 100) "XX") + :extra-field-in-response extra-field-in-response}})) (gen/return {:handler (fn [_] (throw (Exception.))) :exception? true})])) (def gen-request - (gen/let [path (gen/fmap #(str "/" %) gen/string-alpha-numeric) - method (gen/elements [:get :post :put :delete :patch :options :head])] + (gen/let [path (gen/fmap #(str "/" %) gen/string-alpha-numeric) + method (gen/elements [:get :post :put :delete :patch :options :head]) + extra-field-in-request gen/string-alpha-numeric] (gen/return {:request-method method :uri path - :labels {:method (-> method name .toUpperCase) - :path path}}))) + :extra-field-in-request extra-field-in-request + :labels {:method (-> method name .toUpperCase) + :path path + :extra-field-in-request extra-field-in-request}}))) ;; ## Tests (defspec t-wrap-instrumentation 100 (prop/for-all - [registry-fn (g/registry-fn ring/initialize) + [registry-fn (g/registry-fn #(ring/initialize % {:extra-labels [:extra-field-in-response + :extra-field-in-request]})) {:keys [handler exception? labels]} gen-handler {labels' :labels, :as request} gen-request wrap (gen/elements [ring/wrap-instrumentation ring/wrap-metrics])] (let [registry (registry-fn) - handler' (wrap handler registry) + handler' (wrap handler registry {:extra-labels-request [:extra-field-in-request] + :extra-labels-response [:extra-field-in-response]}) start-time (System/nanoTime) response (try (handler' request) @@ -60,10 +66,11 @@ histogram (registry :http/request-latency-seconds labels) ex-counter (registry :http/exceptions-total ex-labels)] (if exception? - (and (= response ::error) - (= 0.0 (prometheus/value counter)) - (= 0.0 (:count (prometheus/value histogram))) - (= 1.0 (prometheus/value ex-counter))) + (do (println (prometheus/value ex-counter)) + (and (= response ::error) + (= 0.0 (prometheus/value counter)) + (= 0.0 (:count (prometheus/value histogram))) + (= 1.0 (prometheus/value ex-counter)))) (and (map? response) (= 0.0 (prometheus/value ex-counter)) (< 0.0 (:sum (prometheus/value histogram)) delta)