Skip to content

Commit d3c39d8

Browse files
committed
Merge pull request #198 from metosin/exception-logging-2
Exception logging 2
2 parents e56e505 + bf0f49b commit d3c39d8

6 files changed

Lines changed: 80 additions & 35 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## Unreleased
2+
3+
- Added `compojure.api.exception/with-logging` helper to add logging to exception handlers.
4+
- Check extended wiki guide on [exception handling](https://github.com/metosin/compojure-api/wiki/Exception-handling#logging)
5+
16
## 0.24.4 (13.1.2016)
27

38
**[compare](https://github.com/metosin/compojure-api/compare/0.24.3...0.24.4)**

project.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
[lein-ring "0.9.7"]
3131
[funcool/codeina "0.3.0"]]
3232
:dependencies [[org.clojure/clojure "1.7.0"]
33+
[slingshot "0.12.2"]
3334
[peridot "0.4.2"]
3435
[javax.servlet/servlet-api "2.5"]
3536
[midje "1.8.3"]

src/compojure/api/exception.clj

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
1717
Error response only contains class of the Exception so that it won't accidentally
1818
expose secret details."
19-
[^Exception e _ _]
19+
[^Exception e data req]
2020
(logging/log! :error e (.getMessage e))
2121
(internal-server-error {:type "unknown-exception"
2222
:class (.getName (.getClass e))}))
@@ -34,29 +34,38 @@
3434

3535
(defn response-validation-handler
3636
"Creates error response based on Schema error."
37-
[_ data _]
37+
[e data req]
3838
(internal-server-error {:errors (stringify-error (su/error-val data))}))
3939

4040
(defn request-validation-handler
4141
"Creates error response based on Schema error."
42-
[_ data _]
42+
[e data req]
4343
(bad-request {:errors (stringify-error (su/error-val data))}))
4444

4545
(defn schema-error-handler
4646
"Creates error response based on Schema error."
47-
[_ data _]
47+
[e data req]
4848
; FIXME: Why error is not wrapped to ErrorContainer here?
4949
(bad-request {:errors (stringify-error (:error data))}))
5050

5151
(defn request-parsing-handler
52-
[^Exception ex _ _]
52+
[^Exception ex data req]
5353
(let [cause (.getCause ex)]
5454
(bad-request {:type (cond
5555
(instance? JsonParseException cause) "json-parse-exception"
5656
(instance? ParserException cause) "yaml-parse-exception"
5757
:else "parse-exception")
5858
:message (.getMessage cause)})))
5959

60+
;;
61+
;; Logging
62+
;;
63+
64+
(defn with-logging [handler]
65+
(fn [^Exception e data req]
66+
(logging/log! :error e (.getMessage e))
67+
(handler e data req)))
68+
6069
;;
6170
;; Mappings from other Exception types to our base types
6271
;;

src/compojure/api/meta.clj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
[ring.swagger.common :refer :all]
1010
[ring.swagger.json-schema :as js]
1111
[ring.util.http-response :refer [internal-server-error]]
12-
[slingshot.slingshot :refer [throw+]]
1312
[schema.core :as s]
1413
[schema.coerce :as sc]
1514
[schema.utils :as su]
@@ -88,7 +87,8 @@
8887
(let [coerce (coercer (value-of schema) matcher)
8988
body (coerce (:body response))]
9089
(if (su/error? body)
91-
(throw+ (assoc body :type ::ex/response-validation))
90+
(throw (ex-info "Response validation error"
91+
(assoc body :type ::ex/response-validation)))
9292
(assoc response
9393
::serializable? true
9494
:body body)))
@@ -105,7 +105,7 @@
105105
(let [coerce# (~+compojure-api-coercer+ ~schema matcher#)
106106
result# (coerce# value#)]
107107
(if (su/error? result#)
108-
(throw+ (assoc result# :type ::ex/request-validation))
108+
(throw (ex-info "Request validation failed" (assoc result# :type ::ex/request-validation)))
109109
result#))
110110
value#)))
111111

src/compojure/api/middleware.clj

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
[ring.swagger.middleware :as rsm]
1313
[ring.swagger.coerce :as rsc]
1414
[ring.util.http-response :refer :all]
15-
[slingshot.slingshot :refer [try+ throw+]]
1615
[schema.core :as s])
1716
(:import [com.fasterxml.jackson.core JsonParseException]
1817
[org.yaml.snakeyaml.parser ParserException]))
@@ -39,9 +38,9 @@
3938

4039
(def rethrow-exceptions? ::rethrow-exceptions?)
4140

42-
(defn- call-error-handler [error-handler error error-type request]
41+
(defn- call-error-handler [error-handler error data request]
4342
(try
44-
(error-handler error error-type request)
43+
(error-handler error data request)
4544
(catch clojure.lang.ArityException e
4645
(println "WARNING: Error-handler arity has been changed.")
4746
(error-handler error))))
@@ -55,18 +54,16 @@
5554
(let [default-handler (get handlers ::ex/default ex/safe-handler)]
5655
(assert (fn? default-handler) "Default exception handler must be a function.")
5756
(fn [request]
58-
(try+
57+
(try
5958
(handler request)
60-
(catch (get % :type) {:keys [type] :as data}
61-
(let [type (or (get ex/legacy-exception-types type) type)]
62-
(if-let [handler (get handlers type)]
63-
(call-error-handler handler (:throwable &throw-context) data request)
64-
(call-error-handler default-handler (:throwable &throw-context) data request))))
65-
(catch Object _
66-
; FIXME: Used for validate
67-
(if (rethrow-exceptions? request)
68-
(throw+)
69-
(call-error-handler default-handler (:throwable &throw-context) nil request)))))))
59+
(catch Throwable e
60+
(let [{:keys [type] :as data} (ex-data e)
61+
type (or (get ex/legacy-exception-types type) type)
62+
handler (or (get handlers type) default-handler)]
63+
; FIXME: Used for validate
64+
(if (rethrow-exceptions? request)
65+
(throw e)
66+
(call-error-handler handler e data request))))))))
7067

7168
;;
7269
;; Component integration
@@ -144,8 +141,8 @@
144141
;; i.e. (handler req) is inside try-catch. If r-m-f was changed to catch only
145142
;; exceptions from parsing the request, we wouldn't need to check the exception class.
146143
(if (or (instance? JsonParseException e) (instance? ParserException e))
147-
(throw+ {:type ::ex/request-parsing} e)
148-
(throw+ e)))
144+
(throw (ex-info "Error parsing request" {:type ::ex/request-parsing} e))
145+
(throw e)))
149146

150147
(defn serializable?
151148
"Predicate which returns true if the response body is serializable.
@@ -184,6 +181,9 @@
184181
:compojure.api.exception/response-validation compojure.api.exception/response-validation-handler
185182
:compojure.api.exception/default compojure.api.exception/safe-handler}
186183
184+
Note: Because the handlers are merged into default handlers map, to disable default handler you
185+
need to provide `nil` value as handler.
186+
187187
Note: To catch Schema errors use {:schema.core/error compojure.api.exception/schema-error-handler}
188188
189189
Note: Adding an alias for exception namespace makes it easier to define these options.

test/compojure/api/middleware_test.clj

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
(ns compojure.api.middleware-test
22
(:require [compojure.api.middleware :refer :all]
3+
[compojure.api.exception :as ex]
34
[midje.sweet :refer :all]
45
[ring.util.http-response :refer [ok]]
56
[ring.util.http-status :as status]
6-
ring.util.test)
7+
ring.util.test
8+
[slingshot.slingshot :refer [throw+]])
79
(:import [java.io PrintStream ByteArrayOutputStream]))
810

911
(defmacro without-err
@@ -38,15 +40,43 @@
3840

3941
(ring.util.test/string-input-stream "foobar") false false))
4042

43+
(def default-options (:exceptions api-middleware-defaults))
44+
4145
(facts "wrap-exceptions"
4246
(with-out-str
43-
(without-err
44-
(let [exception (RuntimeException. "kosh")
45-
exception-class (.getName (.getClass exception))
46-
failure (fn [_] (throw exception))]
47-
48-
(fact "converts exceptions into safe internal server errors"
49-
((wrap-exceptions failure (:handlers (:exceptions api-middleware-defaults))) ..request..)
50-
=> (contains {:status status/internal-server-error
51-
:body (contains {:class exception-class
52-
:type "unknown-exception"})}))))))
47+
(without-err
48+
(let [exception (RuntimeException. "kosh")
49+
exception-class (.getName (.getClass exception))
50+
handler (-> (fn [_] (throw exception))
51+
(wrap-exceptions default-options))]
52+
53+
(fact "converts exceptions into safe internal server errors"
54+
(handler {}) => (contains {:status status/internal-server-error
55+
:body (contains {:class exception-class
56+
:type "unknown-exception"})})))))
57+
58+
(with-out-str
59+
(without-err
60+
(fact "Slingshot exception map type can be matched"
61+
(let [handler (-> (fn [_] (throw+ {:type ::test} (RuntimeException. "kosh")))
62+
(wrap-exceptions (assoc-in default-options [:handlers ::test] (fn [ex _ _] {:status 500 :body "hello"}))))]
63+
(handler {}) => (contains {:status status/internal-server-error
64+
:body "hello"})))))
65+
66+
(without-err
67+
(fact "Default handler logs exceptions to console"
68+
(let [handler (-> (fn [_] (throw (RuntimeException. "kosh")))
69+
(wrap-exceptions default-options))]
70+
(with-out-str (handler {})) => "ERROR kosh\n")))
71+
72+
(without-err
73+
(fact "Default request-parsing handler does not log messages"
74+
(let [handler (-> (fn [_] (throw (ex-info "Error parsing request" {:type ::ex/request-parsing} (RuntimeException. "Kosh"))))
75+
(wrap-exceptions default-options))]
76+
(with-out-str (handler {})) => "")))
77+
78+
(without-err
79+
(fact "Logging can be added to a exception handler"
80+
(let [handler (-> (fn [_] (throw (ex-info "Error parsing request" {:type ::ex/request-parsing} (RuntimeException. "Kosh"))))
81+
(wrap-exceptions (assoc-in default-options [:handlers ::ex/request-parsing] (ex/with-logging ex/request-parsing-handler))))]
82+
(with-out-str (handler {})) => "ERROR Error parsing request\n"))))

0 commit comments

Comments
 (0)