RSS Feed

Lisp Project of the Day

lack-middleware-backtrace

You can support this project by donating at:

Donate using PatreonDonate using Liberapay

Or see the list of project sponsors.

lack-middleware-backtraceweb

Documentation๐Ÿฅบ
Docstrings๐Ÿฅบ
Tests ๐Ÿ˜€
Examples๐Ÿคจ
RepositoryActivity๐Ÿ˜€
CI ๐Ÿ˜€

You might consider this a cheating, but I really want to review all Lack middlewares regardless most of them are from the same Lack project. These middlewares are loadable as separate ASDF systems.

The problem of Lack middlewares is that they are not documented.

This middleware will output a backtrace and all request parameters to the stream or a file.

If you are using clack:clackup function to start your app, it will apply a backtrace middleware to it, unless :use-default-middlewares nil argument was given. Without configuration, all backtraces will be written to *error-output* stream.

Let's see how does it work!

POFTHEDAY> (defparameter *app*
             (lambda (env)
               (declare (ignorable env))
               (error "Oh my God!")))
*APP*
POFTHEDAY> (clack:clackup *app*
                          :port 8085)
Hunchentoot server is started.
Listening on 127.0.0.1:8085.

POFTHEDAY> (values (dex:get "http://localhost:8085/foo/bar"))
Backtrace for: #<SB-THREAD:THREAD "hunchentoot-worker-127.0.0.1:56469" RUNNING {1007707373}>
0: ((LAMBDA NIL :IN UIOP/IMAGE:PRINT-BACKTRACE))
1: ((FLET "THUNK" :IN UIOP/STREAM:CALL-WITH-SAFE-IO-SYNTAX))
2: (SB-IMPL::%WITH-STANDARD-IO-SYNTAX #<CLOSURE (FLET "THUNK" :IN UIOP/STREAM:CALL-WITH-SAFE-IO-SYNTAX) {D85A24B}>)
3: (UIOP/STREAM:CALL-WITH-SAFE-IO-SYNTAX #<CLOSURE (LAMBDA NIL :IN UIOP/IMAGE:PRINT-BACKTRACE) {100791B9EB}> :PACKAGE :CL)
4: (UIOP/IMAGE:PRINT-CONDITION-BACKTRACE #<SIMPLE-ERROR "Oh my God!" {100791B943}> :STREAM #<SYNONYM-STREAM :SYMBOL SLYNK::*CURRENT-ERROR-OUTPUT* {1001541093}> :COUNT NIL)
5: (LACK.MIDDLEWARE.BACKTRACE::PRINT-ERROR #<SIMPLE-ERROR "Oh my God!" {100791B943}> (:REQUEST-METHOD :GET :SCRIPT-NAME "" :PATH-INFO "/foo/bar" :SERVER-NAME "localhost" :SERVER-PORT 8085 :SERVER-PROTOCOL :HTTP/1.1 ...) #<SYNONYM-STREAM :SYMBOL SLYNK::*CURRENT-ERROR-OUTPUT* {1001541093}>)
6: ((FLET LACK.MIDDLEWARE.BACKTRACE::OUTPUT-BACKTRACE :IN "/Users/art/projects/lisp/lisp-project-of-the-day/.qlot/dists/ultralisp/software/fukamachi-lack-20200524065357/src/middleware/backtrace.lisp") #<SIMPLE-ERROR "Oh my God!" {100791B943}> (:REQUEST-METHOD :GET :SCRIPT-NAME "" :PATH-INFO "/foo/bar" :SERVER-NAME "localhost" :SERVER-PORT 8085 :SERVER-PROTOCOL :HTTP/1.1 ...))
...
31: (SB-THREAD::NEW-LISP-THREAD-TRAMPOLINE #<SB-THREAD:THREAD "hunchentoot-worker-127.0.0.1:56469" RUNNING {1007707373}> NIL #<CLOSURE (LABELS BORDEAUX-THREADS::%BINDING-DEFAULT-SPECIALS-WRAPPER :IN BORDEAUX-THREADS::BINDING-DEFAULT-SPECIALS) {100770731B}> NIL)
32: ("foreign function: call_into_lisp")
33: ("foreign function: new_thread_trampoline")
Above backtrace due to this condition:
Oh my God!

Request:
    REQUEST-METHOD: :GET
    SCRIPT-NAME: ""
    PATH-INFO: "/foo/bar"
    SERVER-NAME: "localhost"
    SERVER-PORT: 8085
    SERVER-PROTOCOL: :HTTP/1.1
    REQUEST-URI: "/foo/bar"
    URL-SCHEME: "http"
    REMOTE-ADDR: "127.0.0.1"
    REMOTE-PORT: 56469
    QUERY-STRING: NIL
    RAW-BODY: #<FLEXI-STREAMS:FLEXI-IO-STREAM {100791B313}>
    CONTENT-LENGTH: 0
    CONTENT-TYPE: NIL
    CLACK.STREAMING: T
    CLACK.IO: #<CLACK.HANDLER.HUNCHENTOOT::CLIENT {100791B363}>
    HEADERS:
        user-agent: "Dexador/0.9.14 (SBCL 2.0.2); Darwin; 19.5.0"
        host: "localhost:8085"
        accept: "*/*"

The problem here is that I did not receive a 500 error. An interactive debugger popped up instead and HTTP request finished with a timeout. To solve this problem, we need to pass a :debug nil argument to clackup:

(clack:clackup *app*
               :port 8085
               :debug nil)

Now we'll try other configuration of this backtrace middleware.

To write output to the file, you need to specify the output option. It can be either a string or a pathname:

POFTHEDAY> (clack:clackup
            (lack:builder
             (:backtrace :output "/tmp/errors.log")
             *app*)
            :port 8089
            :debug nil
            ;; If you don't turn off this,
            ;; backtrace also will be written to the
            ;; *error-output*.
            :use-default-middlewares nil)

Also, you can pass as the output a variable pointing to the stream:

POFTHEDAY> (clack:clackup
            (lack:builder
             (:backtrace :output *trace-output*)
             *app*)
            :port 8090
            :debug nil
            :use-default-middlewares nil)

Another interesting option is :result-on-error. It can be a function or a list with the response data. This way we can return a customized error response:

POFTHEDAY> (clack:clackup
            (lack:builder
             (:backtrace :output "/tmp/errors.log"
                         :result-on-error
                         '(500 (:content-type "text/plain")
                           ("Stay patient. "
                            "We already fixing this error in the REPL")))
             *app*)
            :port 8092
            :debug nil
            :use-default-middlewares nil)

POFTHEDAY> (handler-case (dex:get "http://localhost:8092/foo/bar")
             (error (condition)
               condition))

#<DEXADOR.ERROR:HTTP-REQUEST-INTERNAL-SERVER-ERROR {1009B3BA03}>

POFTHEDAY> (dexador:response-status *)
500
POFTHEDAY> (dexador:response-body **)
"Stay patient. We already fixing this error in the REPL"

Specifying a function as an error handler allows you to render an error response using information from the unhandled condition:

POFTHEDAY> (defun make-error-response (condition)
             (list 500 '(:content-type "text/plain")
                   (list (format nil
                                 "Unhandled error: ~A"
                                 condition))))

POFTHEDAY> (clack:clackup
            (lack:builder
             (:backtrace :output "/tmp/errors.log"
                         :result-on-error
                         #'make-error-response)
             *app*)
            :port 8093
            :use-default-middlewares nil)

POFTHEDAY> (handler-case (dex:get "http://localhost:8093/foo/bar")
             (error (condition)
               (values (dex:response-status condition)
                       (dex:response-body condition))))
500
"Unhandled error: Oh my God!"

Notice, I didn't specify a :debug nil argument for clackup. When you are using :result-on-error argument on backtrace middleware, it will return a response before the lisp debugger will have a chance to pop up.

If you want to invoke debugger in some cases, you can call a (invoke-debugger condition) somewhere inside make-error-response function.

Yesterday we'll review some other Lack's middleware.


Brought to you by 40Ants under Creative Commons License