File size: 4,421 Bytes
43203b4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | (in-package :cl-user)
(defpackage clack-errors
(:nicknames :lack.middleware.clack.errors)
(:use :cl)
(:import-from :trivial-backtrace
:print-backtrace)
(:export :*clack-error-middleware*
:*lack-middleware-clack-errors*))
(in-package :clack-errors)
(defparameter *dev-css-path*
(merge-pathnames
#p"static/style-dev.css"
(asdf:component-pathname (asdf:find-system :clack-errors))))
(defparameter *prod-css-path*
(merge-pathnames
#p"static/style-prod.css"
(asdf:component-pathname (asdf:find-system :clack-errors))))
(defparameter *highlight-css*
(merge-pathnames
#p"static/highlight-lisp/themes/github.css"
(asdf:component-pathname (asdf:find-system :clack-errors))))
(defparameter *highlight-js*
(merge-pathnames
#p"static/highlight-lisp/highlight-lisp.js"
(asdf:component-pathname (asdf:find-system :clack-errors))))
(defparameter +dev-template+
(djula:compile-template*
(asdf:system-relative-pathname :clack-errors #p"templates/dev-page.html")))
(defparameter +prod-template+
(djula:compile-template*
(asdf:system-relative-pathname :clack-errors #p"templates/prod-page.html")))
(defun condition-name (condition)
(symbol-name (class-name (class-of condition))))
(defun condition-slots (condition)
(mapcar #'closer-mop:slot-definition-name
(closer-mop:class-slots (class-of condition))))
(defun slot-values (obj)
(loop for slot in (condition-slots obj)
collecting
(list :slot (symbol-name slot)
:value (slot-value obj slot))))
(defparameter +backtrace-regex+ "\\n\\w*\\d+:"
"A regular expression to split backtraces")
(defun split-backtrace (str)
(ppcre:split +backtrace-regex+ str))
(defun parse-backtrace (bt)
(destructuring-bind (header &rest frames) (split-backtrace bt)
(let ((error-msg (subseq header
(position #\: header :from-end t)))
(date-time (subseq header
(1+ (position #\: header))
(position #\A header))))
(list error-msg date-time frames))))
(defun render (bt condition env)
(let* ((backtrace (parse-backtrace bt)))
(djula:render-template* +dev-template+
nil
:name (condition-name condition)
:slots (slot-values condition)
:datetime (nth 1 backtrace)
:backtrace (subseq (caddr backtrace) 6)
:url (getf env :path-info)
:method (getf env :request-method)
:query (getf env :query-string)
:css (concatenate 'string
(uiop:read-file-string *dev-css-path*)
(uiop:read-file-string *highlight-css*))
:js (uiop:read-file-string *highlight-js*)
:env (loop for (key value) on env by #'cddr collecting
(list :key key
:value value)))))
(defun render-prod (condition env)
(djula:render-template* +prod-template+
nil
:name (condition-name condition)
:url (getf env :path-info)
:css (uiop:read-file-string *prod-css-path*)))
(defparameter *clack-error-middleware*
(lambda (app &key (debug t) (prod-render #'render-prod))
(lambda (env)
(block nil
(handler-bind ((error
(lambda (condition)
(let ((backtrace (with-output-to-string (stream)
(write-string (print-backtrace condition :output nil)
stream))))
(return (list 500
'(:content-type "text/html")
(list
(if debug
(render backtrace condition env)
(funcall prod-render condition env)))))))))
(funcall app env))))))
(setf (symbol-value '*lack-middleware-clack-errors*) *clack-error-middleware*)
|