Skip to content

Commit 4b70b81

Browse files
janosmeszarosJanos MeszarosJanos Meszaros
authored
Add ability to handle async Ring requests (#19)
* Making instrumentation async ready * Handling async requests on exposing metrics * Simplifying handler invocation * Handling sync exceptions for async handler * Adding note about async ring support. --------- Co-authored-by: Janos Meszaros <jmeszaros@whitepages.com> Co-authored-by: Janos Meszaros <jmeszaros@ekata.com>
1 parent db029a9 commit 4b70b81

File tree

3 files changed

+259
-174
lines changed

3 files changed

+259
-174
lines changed

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ tasks.
3030
- [Standalone HTTP Server](#standalone-http-server)
3131
- [History](#history)
3232
- [License](#license)
33-
33+
3434

3535
## Basic Usage <a name="basic-usage"></a>
3636

@@ -324,6 +324,8 @@ These are, purposefully, compatible with the metrics produced by
324324
[prometheus-clj](https://github.com/soundcloud/prometheus-clj), as to allow a
325325
smooth migration.
326326

327+
Ring supports sync and async [handlers](https://github.com/ring-clojure/ring/wiki/Concepts#handlers), metrics are collected for both by `iapetos.collector.ring` ns.
328+
327329
#### Exception Handling <a name="exception-handling"></a>
328330

329331
By default, if your ring handler throws an exception, only the `http_exceptions_total` counter would be incremented.
@@ -332,12 +334,12 @@ This means that if you respond with a 500 error code on exceptions:
332334
1. These responses won't be counted on `http_requests_total`
333335
2. Their latencies won't be observed on `http_request_latency_seconds`
334336

335-
To overcome this, you can use the optional `:exception-status` to define a status code to be reported
337+
To overcome this, you can use the optional `:exception-status` to define a status code to be reported
336338
on both metrics, for example:
337339

338340
```clojure
339341
(def app
340-
(-> (fn [_] (throw (Exception.)))
342+
(-> (fn [_] (throw (Exception.)))
341343
(ring/wrap-metrics registry {:path "/metrics" :exception-status 500})))
342344
```
343345

src/iapetos/collector/ring.clj

Lines changed: 90 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,24 @@
3030
(defn- make-latency-collector
3131
[labels buckets]
3232
(prometheus/histogram
33-
:http/request-latency-seconds
34-
{:description "the response latency for HTTP requests."
35-
:labels (concat [:method :status :statusClass :path] labels)
36-
:buckets buckets}))
33+
:http/request-latency-seconds
34+
{:description "the response latency for HTTP requests."
35+
:labels (concat [:method :status :statusClass :path] labels)
36+
:buckets buckets}))
3737

3838
(defn- make-count-collector
3939
[labels]
4040
(prometheus/counter
41-
:http/requests-total
42-
{:description "the total number of HTTP requests processed."
43-
:labels (concat [:method :status :statusClass :path] labels)}))
41+
:http/requests-total
42+
{:description "the total number of HTTP requests processed."
43+
:labels (concat [:method :status :statusClass :path] labels)}))
4444

4545
(defn- make-exception-collector
4646
[labels]
4747
(ex/exception-counter
48-
:http/exceptions-total
49-
{:description "the total number of exceptions encountered during HTTP processing."
50-
:labels (concat [:method :path] labels)}))
48+
:http/exceptions-total
49+
{:description "the total number of exceptions encountered during HTTP processing."
50+
:labels (concat [:method :path] labels)}))
5151

5252
(defn initialize
5353
"Initialize all collectors for Ring handler instrumentation. This includes:
@@ -62,10 +62,10 @@
6262
& [{:keys [latency-histogram-buckets labels]
6363
: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]}}]]
6464
(prometheus/register
65-
registry
66-
(make-latency-collector labels latency-histogram-buckets)
67-
(make-count-collector labels)
68-
(make-exception-collector labels)))
65+
registry
66+
(make-latency-collector labels latency-histogram-buckets)
67+
(make-count-collector labels)
68+
(make-exception-collector labels)))
6969

7070
;; ## Response
7171

@@ -110,9 +110,9 @@
110110
(defn- record-metrics!
111111
[{:keys [registry] :as options} delta request response]
112112
(let [labels (merge
113-
{:status (status response)
114-
:statusClass (status-class response)}
115-
(labels-for options request response))
113+
{:status (status response)
114+
:statusClass (status-class response)}
115+
(labels-for options request response))
116116
delta-in-seconds (/ delta 1e9)]
117117
(-> registry
118118
(prometheus/inc :http/requests-total labels)
@@ -129,16 +129,66 @@
129129
(f)))
130130

131131
(defn- run-instrumented
132-
[{:keys [handler exception-status] :as options} request]
133-
(ex/with-exceptions (exception-counter-for options request)
134-
(let [start-time (System/nanoTime)
135-
response (safe exception-status #(handler request))
136-
delta (- (System/nanoTime) start-time)]
137-
(->> (ensure-response-map response exception-status)
138-
(record-metrics! options delta request))
139-
(if-not (exception? response)
140-
response
141-
(throw response)))))
132+
([{:keys [handler exception-status] :as options} request]
133+
(ex/with-exceptions (exception-counter-for options request)
134+
(let [start-time (System/nanoTime)
135+
response (safe exception-status #(handler request))
136+
delta (- (System/nanoTime) start-time)]
137+
(->> exception-status
138+
(ensure-response-map response)
139+
(record-metrics! options delta request))
140+
(if-not (exception? response)
141+
response
142+
(throw response)))))
143+
144+
([{:keys [handler exception-status] :as options} request respond raise]
145+
(let [start-time (System/nanoTime)
146+
ex-counter (exception-counter-for options request)
147+
respond-fn #(let [delta (- (System/nanoTime) start-time)
148+
_ (->> exception-status
149+
(ensure-response-map %)
150+
(record-metrics! options delta request))]
151+
(respond %))
152+
raise-fn #(let [delta (- (System/nanoTime) start-time)]
153+
(when exception-status
154+
(->> exception-status
155+
(ensure-response-map %)
156+
(record-metrics! options delta request)))
157+
(ex/record-exception! ex-counter %)
158+
(raise %))]
159+
(try
160+
(handler request respond-fn raise-fn)
161+
(catch Throwable t
162+
(ex/record-exception! ex-counter t)
163+
(raise t))))))
164+
165+
(defn- run-expose
166+
([{:keys [path on-request registry handler] :as options}
167+
{:keys [request-method uri] :as request}]
168+
(if (= uri path)
169+
(if (= request-method :get)
170+
(do
171+
(on-request registry)
172+
(metrics-response registry))
173+
{:status 405})
174+
(handler request)))
175+
176+
([{:keys [path on-request registry handler] :as options}
177+
{:keys [request-method uri] :as request}
178+
respond
179+
raise]
180+
(if (= uri path)
181+
(if (= request-method :get)
182+
(do
183+
(on-request registry)
184+
(respond (metrics-response registry)))
185+
(respond {:status 405}))
186+
(handler request respond raise))))
187+
188+
(defn ring-fn [f options]
189+
(fn
190+
([request] (f options request))
191+
([request respond raise] (f options request respond raise))))
142192

143193
(defn wrap-instrumentation
144194
"Wrap the given Ring handler to write metrics to the given registry:
@@ -162,15 +212,15 @@
162212
[[initialize]]."
163213
[handler registry
164214
& [{:keys [path-fn label-fn]
165-
:or {path-fn :uri
166-
label-fn (constantly {})}
167-
:as options}]]
215+
:or {path-fn :uri
216+
label-fn (constantly {})}
217+
:as options}]]
168218
(let [options (assoc options
169219
:path-fn path-fn
170220
:label-fn label-fn
171221
:registry registry
172222
:handler handler)]
173-
#(run-instrumented options %)))
223+
(ring-fn run-instrumented options)))
174224

175225
;; ### Metrics Endpoint
176226

@@ -182,16 +232,15 @@
182232
the Prometheus scraper as a trigger for metrics collection."
183233
[handler registry
184234
& [{:keys [path on-request]
185-
:or {path "/metrics"
186-
on-request identity}}]]
187-
(fn [{:keys [request-method uri] :as request}]
188-
(if (= uri path)
189-
(if (= request-method :get)
190-
(do
191-
(on-request registry)
192-
(metrics-response registry))
193-
{:status 405})
194-
(handler request))))
235+
:or {path "/metrics"
236+
on-request identity}
237+
:as options}]]
238+
(let [options (assoc options
239+
:path path
240+
:on-request on-request
241+
:registry registry
242+
:handler handler)]
243+
(ring-fn run-expose options)))
195244

196245
;; ### Compound Middleware
197246

0 commit comments

Comments
 (0)