To Err is Human; to Handle, Divine
Vinland has received a significant update in regard to error handling and debugging.
Executive summary:
- add debugging page for use in development; also optionally log to a stream or file
- add errors middleware: translates signalled errors to HTTP response status codes (4xx - 5xx range) and either serves a static error file or calls a handler function for the status code and the negotiated media-type
- add middleware to configure a request redaction policy: downstream middlewares, such as debug, can print the Clack ENV scrubbed of any sensitive data in accordance with the policy
- add media-type negotiation function for use in middlewares and applications
- add function to print hash-tables for use in middlewares and applications
- Raven routes defined with
DEFINE-ROUTE
are now functions, and failure to match on dispatch signalsNO-ROUTE-ERROR
New libraries
print-hash
The print-hash library does exactly what it says on the tin: function PRINT-HASH
prints a hash-table, using a literal syntax inspired by the make-hash reader syntax. Nested hash-tables are supported, and the spacing can be modified by overriding special variable *INDENTATION-SPACES*
.
This library was developed in conjunction with redact
and lack-middleware-debug
to enable easy inspection of any hash tables contained in the Clack ENV, such as request headers and the Lack session.
#{
foo bar
baaz #{
quux baar
bar foo
baaz #{
foo foo
}
}
}
http-response
Extracted from Vinland, the foo.lisp.http-response
ASDF system provides types for HTTP response status codes, functions to generate text/plain responses and text representations from these codes, and functions to translate between HTTP response codes and their representative keywords; also defines condition types CLIENT-ERROR
and SERVER-ERROR
(and the parent mixin, HTTP-ERROR
).
redact
The Redact repository contains two ASDF systems, foo.lisp.redact
and foo.lisp.lack-middleware-redact
.
Package LACK/MIDDLEWARE/REDACT
, defined in system foo.lisp.lack-middleware-redact
, is used to configure redaction policies used by downstream middlewares. The middleware is configured with a list of parameter names to redact, a list of headers to redact, and a list of cookie names for which to skip redaction (cookies are redacted by default). When a value is redacted, it is replaced with the string "[REDACTED]"
. Function PRINT-ENV
is used to print the entire Clack ENV with all relevant values redacted: it uses the print-hash library to format any hash-tables contained in the ENV, so that headers, the Lack session, or any other hash-tables are easily inspectable.
Package FOO.LISP.REDACT
provides functions for redacting the Clack ENV based on policies specified by the middleware. FOO.LISP.REDACT/BACKTRACE
allows for redacting backtraces.
Example:
(lack:builder
(:redact :parameters '("token" "code" "password")
:headers '("x-access-token")
:preserve-cookies '("lack.session"))
*app*)
lack-middleware-errors
The errors middleware defines a HANDLER-BIND
for all application errors and allows either rendering a static file or calling a provided function in response to an error. The INTERCEPT
option specifies a function called to translate an error to an HTTP response code, while the APP
option specifies a Clack application that should handle the request.
(lack/builder:
(:errors :app (foo.lisp.vinland/errors-app/simple/dynamic-override:make-app
:root todo-app/config:*static-errors-directory*
:static-file-types todo-app/http-error:*static-file-types*
:handlers todo-app/http-error:*http-errors*
:dynamic-override-p (lambda (env)
(declare (ignore env))
lack/middleware/user:*current-user*))
:intercept (lambda (condition)
(declare (type error condition))
(typecase condition
(foo.lisp.http-response:http-error (slot-value
condition
'foo.lisp.http-response:status-code))
(foo.lisp.raven:no-route-error 404)
(lack/middleware/session/store/redis-pool:redis-pool-timeout-error 503))))
todo-app/web:*web*)
lack-middleware-debug
This middleware prints the redacted ENV and backtrace to the console and renders a debug page whenever an error is signalled; it is meant to be included in the middleware pipeline in development and removed in production. The debug page includes the type of error, the error message, the redacted Clack ENV, specified special variables, the redacted backtrace, and system information. By default the debug page is unstyled, but an option is provided for specifying an external stylesheet.
This middleware should be included immediately after the errors middleware.
Example (open in a new tab to view in detail):

(lack/builder:
(:redact :parameters '("token" "code" "password")
:preserve-cookies '("lack.session"))
(:debug :special-variables '(foo.lisp.vinland:*route*
foo.lisp.vinland:*binding*))
*app*)
Updates
lack-request
foo.lisp.lack-request
, a fork of the official lack/request
library, has been updated to add a package related to content negotiation. The new package FOO.LISP.LACK/REQUEST/CONTENT-NEGOTIATION
exports function NEGOTIATE-MEDIA-TYPE
.
raven
Raven, the URL dispatcher library on which Vinland is based, has been updated to signal error NO-ROUTE-ERROR
when a route cannot be matched to a request path on dispatch. The previous behavior was to return a text/plain 404 Clack response list. Also, routes/controllers defined with DEFINE-ROUTE
are now functions (sets the FDEFINITION
for the route symbol), meaning that you can call TRACE
with any route name, or access documentation for any route in a uniform way.
vinland
The web and api skeletons have been updated:
- include redact, error, and debug middlewares in app.lisp Lack Builder pipeline
- see CHANGELOG.md for a full list of changes
- diff: https://github.com/lisplizards/vinland/compare/v1.3.0…v1.4.0
vinland-todo-app
The Vinland To Do App has been updated to reflect the latest additions:
- add generated static error pages
- add middlewares: Lack Middleware Redact, Lack Middleware Errors, Lack Middleware Debug
- serve static error pages to non-authenticated users
- serve dynamically rendered error pages served to authenticated users: the site header includes a logout button, so it’s necessary to embed the CSRF token from the session into the logout form
- add media-type negotiation to all turbo-stream enabled controllers to support browser clients that have JS disabled