I've been trying for a while now to figure out the best way to handle exceptions with Dancer2. I like the simplicity of http_throw. However, I've not been able to get that to work successfully with Dancer2 without this workaround: https://pastebin.com/wLsUBJhN (The test shows a PSGI app with HTTPExceptions enabled, which allows proper use of http_throw. It also shows a sample Dancer2 app with my workaround to allow use of http_throw.) Basically, it appears that Dancer2 wants to intercept any exception and rethrow it as 500 (Internal Server Error) without some hacky hook code to intercept the exception and re-apply it later. Has anyone dealt with this issue? Is there a better way to handle this (some middleware or config option)? Thanks, Paul
On Wed, 3 Feb 2021 17:49:05 -0500 Paul Clements wrote:
I've been trying for a while now to figure out the best way to handle exceptions with Dancer2.
Do you mean "expected" or "unexpected" exceptions? I.e. exceptions you use for application flow, or ones that are as a result of internal bugs.
Basically, it appears that Dancer2 wants to intercept any exception and rethrow it as 500 (Internal Server Error) without some hacky hook code to intercept the exception and re-apply it later.
Has anyone dealt with this issue? Is there a better way to handle this (some middleware or config option)?
I wrote a plugin to try and sew together exceptions, both expected and unexpected, along with general user messages and logging. For any unexpected exception in a production application, the plugin will catch it, forward the user to a "safe" page in the application, and add a user-friendly message to the session. The actual exception with stacktrace is (optionally) reported to syslog. For expected exceptions, a special "process" keyword is added, which catches errors and adds them as messages to the session. It works well for me and is in production on several sites. Maybe I have misunderstood your requirements, but here's the plugin anyway: https://metacpan.org/pod/Dancer2::Plugin::LogReport Andy
On Thu, Feb 4, 2021 at 5:34 AM Andrew Beverley <andy@andybev.com> wrote:
On Wed, 3 Feb 2021 17:49:05 -0500 Paul Clements wrote:
I've been trying for a while now to figure out the best way to handle exceptions with Dancer2.
Do you mean "expected" or "unexpected" exceptions? I.e. exceptions you use for application flow, or ones that are as a result of internal bugs.
Basically, it appears that Dancer2 wants to intercept any exception and rethrow it as 500 (Internal Server Error) without some hacky hook code to intercept the exception and re-apply it later.
Has anyone dealt with this issue? Is there a better way to handle this (some middleware or config option)?
I wrote a plugin to try and sew together exceptions, both expected and unexpected, along with general user messages and logging. For any unexpected exception in a production application, the plugin will catch it, forward the user to a "safe" page in the application, and add a user-friendly message to the session. The actual exception with stacktrace is (optionally) reported to syslog.
For expected exceptions, a special "process" keyword is added, which catches errors and adds them as messages to the session.
It works well for me and is in production on several sites.
Maybe I have misunderstood your requirements, but here's the plugin anyway:
https://metacpan.org/pod/Dancer2::Plugin::LogReport
Andy
On Thu, Feb 4, 2021 at 5:34 AM Andrew Beverley <andy@andybev.com> wrote:
On Wed, 3 Feb 2021 17:49:05 -0500 Paul Clements wrote:
I've been trying for a while now to figure out the best way to handle exceptions with Dancer2.
Do you mean "expected" or "unexpected" exceptions? I.e. exceptions you use for application flow, or ones that are as a result of internal bugs.
Sorry, I should have been more clear. I'm developing an API, and I'm mostly looking to handle what you call expected exceptions. The PSGI HTTPExceptions module seems to be along the lines of what I'm looking for. The idea is to have a single, simple way from anywhere in the codebase to return, say, an HTTP 400 error (wrapped in JSON) to the API user. Some authentication util methods would probably also like to return a 401 or 403 for certain conditions. So, ideally, I'd rather not have anything grab and rethrow exceptions as strings (as seems to happen now in a few places in the Dancer2 codebase). I guess I'm really looking for a way to avoid or circumvent what seems to be the default Dancer exception handling behavior. -- Paul
On Thu, 4 Feb 2021 08:44:15 -0500 Paul Clements wrote:
On Thu, Feb 4, 2021 at 5:34 AM Andrew Beverley <andy@andybev.com> wrote:
On Wed, 3 Feb 2021 17:49:05 -0500 Paul Clements wrote:
I've been trying for a while now to figure out the best way to handle exceptions with Dancer2.
Do you mean "expected" or "unexpected" exceptions? I.e. exceptions you use for application flow, or ones that are as a result of internal bugs.
Sorry, I should have been more clear. I'm developing an API, and I'm mostly looking to handle what you call expected exceptions. The PSGI HTTPExceptions module seems to be along the lines of what I'm looking for.
Thanks for clarifying.
The idea is to have a single, simple way from anywhere in the codebase to return, say, an HTTP 400 error (wrapped in JSON) to the API user. Some authentication util methods would probably also like to return a 401 or 403 for certain conditions.
I do actually use the aforementioned module for an API too. I define a custom fatal handler which returns a JSON object based on either the request URL or requested content-type. E.g. https://github.com/ctrlo/GADS/blob/e532a06/lib/GADS/API.pm#L32 Examples also in the pod (although just noticed one isn't formatted properly): https://metacpan.org/pod/Dancer2::Plugin::LogReport#$obj-%3Efatal_handler() The idea is that you throw an exception anywhere: error __x"Invalid email address: {email}", email => $email Then it ends up in the fatal_handler to return the custom response
So, ideally, I'd rather not have anything grab and rethrow exceptions as strings (as seems to happen now in a few places in the Dancer2 codebase). I guess I'm really looking for a way to avoid or circumvent what seems to be the default Dancer exception handling behavior.
If you use the plugin, the exceptions will all be objects. You could also add classes to define response codes. E.g. error "You are forbidden to access this resource", _class => "forbidden" And then in the handler: if $msg->inClass('forbidden') ... Andy
On Thu, Feb 4, 2021 at 9:06 AM Andrew Beverley <andy@andybev.com> wrote:
Examples also in the pod (although just noticed one isn't formatted properly):
https://metacpan.org/pod/Dancer2::Plugin::LogReport#$obj-%3Efatal_handler()
The idea is that you throw an exception anywhere:
error __x"Invalid email address: {email}", email => $email
Then it ends up in the fatal_handler to return the custom response
error and fatal_handler seem to work much better than the http_throw approach I had been using...
So, ideally, I'd rather not have anything grab and rethrow exceptions as strings (as seems to happen now in a few places in the Dancer2 codebase). I guess I'm really looking for a way to avoid or circumvent what seems to be the default Dancer exception handling behavior.
If you use the plugin, the exceptions will all be objects. You could also add classes to define response codes. E.g.
error "You are forbidden to access this resource", _class => "forbidden"
And then in the handler:
if $msg->inClass('forbidden') ...
Yes, the ability to overload the error object allows me to pass enough information to get the error responses I need. Thanks for your help, Paul
On Fri, 5 Feb 2021 08:23:21 -0500 Paul Clements wrote:
error and fatal_handler seem to work much better than the http_throw approach I had been using...
Great, pleased to hear.
Yes, the ability to overload the error object allows me to pass enough information to get the error responses I need.
Good point, I was forgetting that too. You can extend the default message class: use Dancer2::Plugin::LogReport 'myapp', message_class => 'MyApp::Message';
Thanks for your help,
You're welcome. Andy
On Thu, Feb 4, 2021 at 9:06 AM Andrew Beverley <andy@andybev.com> wrote:
The idea is that you throw an exception anywhere:
error __x"Invalid email address: {email}", email => $email
Then it ends up in the fatal_handler to return the custom response
One question: This is all working great and I'm getting the correct HTTP responses, but (at least in development mode) I'm also getting a stack trace logged for every "error" call. Is there maybe a way to suppress the stack trace for expected exceptions? Ideally, I'd like to see stack traces for 500 errors, but not 4xx ones. Thanks, Paul
On Sat, 6 Feb 2021 09:34:23 -0500 Paul Clements wrote:
On Thu, Feb 4, 2021 at 9:06 AM Andrew Beverley <andy@andybev.com> wrote:
The idea is that you throw an exception anywhere:
error __x"Invalid email address: {email}", email => $email
Then it ends up in the fatal_handler to return the custom response
One question: This is all working great and I'm getting the correct HTTP responses, but (at least in development mode) I'm also getting a stack trace logged for every "error" call. Is there maybe a way to suppress the stack trace for expected exceptions? Ideally, I'd like to see stack traces for 500 errors, but not 4xx ones.
Yes, you just need to change the run mode: https://metacpan.org/pod/Log::Report#Run-modes The run mode defines both what is reported and whether there is a stack trace, as per the table of run modes in this section: https://metacpan.org/pod/Log::Report::Dispatcher#Processing-the-message You should just be able to define it in your config (better in environment/ so that you can switch between them), something like this: engines: logger: LogReport: app_name: MyApp dispatchers: default: type: SYSLOG identity: MyApp facility: local0 flags: "pid ndelay nowait" mode: NORMAL
participants (2)
-
Andrew Beverley -
Paul Clements