Honeybadger for Clojure
Typical installation time: ~5 minutes
Hi there! You’ve found Honeybadger’s guide to Clojure exception and error tracking. Once installed, Honeybadger will report errors in your Clojure application.
Getting started
Section titled “Getting started”The library only has one public endpoint: notify. You can pass notify a
String, or anything which inherits from
Throwable
(e.g.
Exception):
(require '[honeybadger.core :as hb])
(def hb-config {:api-key "PROJECT_API_KEY" :env "development"})
(hb/notify hb-config "Something happened")(hb/notify hb-config (Exception. "Things ain't good"))(hb/notify hb-config (ex-info "99 problems" {:yet "Clojure isn't one of them"})):api-keyis the only required entry in the configuration map.notifyreturns a Manifold deferred wrapping the ID (String) of the newly-created Honeybadger fault—ornilif a filter (see below) caused the data not to be sent to Honeybadger. Because a deferred is used, the call returns immediately, not blocking your (e.g., web server) thread. This comes with the typical Clojure caveats about exceptions thrown on background threads, so I strongly recommend dereferencing these calls on the main thread unless / until you have an async error handling plan in place.- Honeybadger fault IDs can be handy—log them, pass them to other systems, or
display them to your users as incident identifiers they can send to your
support team. Manifold offers ways of
receiving this data asynchronously, but for a simple (synchronous) approach,
simply
derefthe return value:
(try ("kaboom") ; Strings aren't functions (catch Exception e (let [hb-id @(hb/notify hb-config e)] (println (str "Exception! Learn more here:\n" "https://www.honeybadger.io/notice/" hb-id)))));; (Output);; Exception! Learn more here:;; https://www.honeybadger.io/notice/12345678-669c-4178-b456-be3d0feb1551Metadata
Section titled “Metadata”The optional third parameter to notify can be used to pass all manner of
additional Honeybadger metadata. The following example shows all possible
metadata values:
(hb/notify hb-config (Exception. "Vapor Lock") {:tags [:serious :business] :component "robot-brain" ; ~= a Rails controller :action "think" ; ~= a Rails action :context {:name "Winston" :power 42 :grease 12} :request {:method :get :url "http://camdez.com" :params {"robot" "true"} :session {"session-id" "d34dc0d3"}}})All metadata is optional, so pick and choose what is useful for your project.
Keys and tags can be strings or keywords. :context and :request support
nested values. If you’re working with Ring, use the corresponding
ring-honeybadger library and the
:request metadata will be populated for you.
Filters
Section titled “Filters”For more advanced behavior, the library allows us to provide a sequence of
functions which will be invoked with all key details (viz. exception +
configuration) prior to reporting to Honeybadger. These functions can be used to
transform the data in arbitrary ways, or they can return nil, halting the
function chain and indicating that nothing should be reported.
For maximum flexibility we can provide a custom function, but we can handle many
common cases with the preexisting filters / filter combinators in
honeybadger.filter:
(require '[honeybadger.core :as hb] '[honeybadger.filter :as hbf])
(def hb-config {:api-key "PROJECT_API_KEY" :env "development" :filters [(hbf/only (hbf/env? :production)) (hbf/except (hbf/instance? ArithmeticException)) (hbf/obscure-params [[:config :password]])]})
(hb/notify hb-config "dag, yo")In this example, the first two filters are used to control which errors get reported to Honeybadger, and the third is used to transform the data we do send.
More precisely, the first two filter lines say only report errors in the
production environment, and don’t report errors of type ArithmeticException.
The third filter uses the obscure-params convenience function to replace
parameters at the given keypaths with a fixed string so that sensitive
parameters are not sent to be stored in Honeybadger. (Of course there isn’t a
param at [:config :password] in this case as we haven’t provided any request
metadata, so the filter won’t change anything here).
To make filtering both possible and convenient, all details about the error /
config / metadata / etc. are bundled up in a consistent format which filters are
expected to consume and produce (with the sole exception of filters which return
nil to suppress reporting of an error). You can see the details of that format
at honeybadger.schemas/Event, and if you use
Prismatic/schema in your own project,
then you can use the provided schemas to enforce correctness. One detail worth
calling out is that all map keys are normalized to keywords so that filters
don’t have to handle variations.
Here’s an example of a fully-custom filter, applying a logged-in tag to all
exception reports where we have a session-id:
(defn tag-logged-in [e] (if (get-in e [:metadata :request :session :session-id]) (update-in e [:metadata :tags] conj :logged-in) e))Using that is as simple as adding tag-logged-in to the list of filters.
Filters that suppress certain errors can typically be written with a simple
predicate function over Events which is passed to only? or except?:
(defn logged-in? [e] (get-in e [:metadata :request :session :session-id]))
(def hb-config {;; ... :filters [(hbf/only logged-in?)]})Last but not least, note that the (deferred) value returned by notify allows
us to ascertain whether or not a given error was reported because it will be
nil iff the error reporting was filtered out. We can use this to take
conditional actions:
(if-let [hb-id @(hb/notify {:api-key "PROJECT_API_KEY" :filters [(hbf/only (constantly nil))]} "chunky bacon")] (str "Reported error with ID " hb-id) "Error reporting suppressed by filter")If you’d like to use this project with Ring, check out camdez/ring-honeybadger.