-
Notifications
You must be signed in to change notification settings - Fork 9
Using a logging library
(The examples below can be found in the examples directory.)
This library's default behavior is to print explanations of type errors to standard output via println
. That's pretty bare-bones. You may want to use a logging library of some sort. This page shows how to integrate Timbre.
The checked
function produces a collection of oopsies. Each of them is about a particular predicate's reason for returning a falsey value. That collection is passed to a error handler that (typically) converts the oopsies into explanations and then does something with them.
You override the default error handler with replace-error-handler or, if you are using the implicit global type repo, on-error!.
So when following the recommended setup, println
can be replaced with Timbre's error
like this:
(ns my.types
(:require [structural-typing.type :as type]
[structural-typing.assist.oopsie :as oopsie]
[taoensso.timbre :as timbre])
...)
(def type-repo
(-> empty-type-repo
...
(replace-error-handler
(oopsie/mkfn:apply-to-each-explanation #(timbre/error %)))))
(error
is wrapped in a function because it's actually a macro, and I was too lazy to find out what function its expansion eventually calls.)
That having been done, you get errors with more detail:
user=> (require '[my.types :as type])
user=> (type/built-like :Point {:x "1"})
15-Jun-29 12:38:01 busted-2.local ERROR [timbre-define-1] - :x should be `integer?`; it is `"1"`
15-Jun-29 12:38:01 busted-2.local ERROR [timbre-define-1] - :y must exist and be non-nil
nil
Notice that the return value is still nil
, which mkfn:apply-to-each-explanation guarantees.
When I look at logs, I like to see both the entire value that provoked the error and also a stack trace that tells me where in the code it happened. To satisfy me, we'll do the following for any type error:
- Print the original value at the info level.
- Print each explanation, also at the info level.
- Print the stack trace at the error level (which my own variant of Timbre's
error
does automatically).
The code looks like this:
(timbre/set-level! :info)
(def pprint-to-string #(with-out-str pprint %))
(defn error-explainer [oopsies]
(timbre/info "While checking this:")
(-> (first oopsies) ; the error handler is always given at least one oopsie.
:whole-value ; the original candidate being checked
pprint-to-string
str/trimr ; be tidy by getting rid of pprint's trailing newline
timbre/info)
(doseq [e (oopsie/explanations oopsies)] (timbre/info e))
(timbre/error "Boundary type check failed"))
(def type-repo
(-> empty-type-repo
...
(replace-error-handler error-explainer)))
Notice that explanations is used to produce the text appropriate to each oopsie's predicate.
The result of the above is this:
user=> (type/checked :Point {:x "1"})
15-Jun-29 12:51:18 busted-2.local INFO [timbre-define-2] - While checking this:
15-Jun-29 12:51:18 busted-2.local INFO [timbre-define-2] - {:x "1"}
15-Jun-29 12:51:18 busted-2.local INFO [timbre-define-2] - :x should be `integer?`; it is `"1"`
15-Jun-29 12:51:18 busted-2.local INFO [timbre-define-2] - :y must exist and be non-nil
15-Jun-29 12:51:18 busted-2.local ERROR [timbre-define-2] - Boundary type check failed
[stack trace omitted]