Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new error handling stack #1843

Merged
merged 7 commits into from Aug 13, 2019
Merged

Conversation

franzliedke
Copy link
Contributor

Refs #1641

Changes proposed in this pull request:

This separates the error registry (mapping exception types to status
codes) from actual handling (the middleware) as well as error formatting
(Whoops, pretty error pages or JSON-API?) and reporting (log? Sentry?).

The components can be reused in different places (e.g. the API client
and the error handler middleware both need the registry to understand
all the exceptions Flarum knows how to handle), while still allowing to
change only the parts that need to change (the API stack always uses the
JSON-API formatter, and the forum stack switches between Whoops and
pretty error pages based on debug mode).

Finally, this paves the way for some planned features and extensibility:

  • A console error handler can build on top of the registry.
  • Extensions can register new exceptions and how to handle them.
  • Extensions can change how we report exceptions (e.g. Sentry).
  • We can build more pretty error pages, even different ones for
    exceptions having the same status code.

Reviewers should focus on:

Confirmed

  • Frontend changes: tested on a local Flarum installation.
  • Backend changes: tests are green (run php vendor/bin/phpunit).

This separates the error registry (mapping exception types to status
codes) from actual handling (the middleware) as well as error formatting
(Whoops, pretty error pages or JSON-API?) and reporting (log? Sentry?).

The components can be reused in different places (e.g. the API client
and the error handler middleware both need the registry to understand
all the exceptions Flarum knows how to handle), while still allowing to
change only the parts that need to change (the API stack always uses the
JSON-API formatter, and the forum stack switches between Whoops and
pretty error pages based on debug mode).

Finally, this paves the way for some planned features and extensibility:
- A console error handler can build on top of the registry.
- Extensions can register new exceptions and how to handle them.
- Extensions can change how we report exceptions (e.g. Sentry).
- We can build more pretty error pages, even different ones for
  exceptions having the same status code.
@dsevillamartin
Copy link
Member

dsevillamartin commented Aug 9, 2019

Looks good. I feel like we might want to support multiple Reporters, instead of just one though (unless I completely misread the code, an alias is used to set the reporter).

I'll report more whenever I attempt #1528 on top of this and see how well fof/sentry works with these new changes.

The error handling middleware now expects an array of reporters.
Extensions can register new reporters in the container like this:

    use Flarum\Foundation\ErrorHandling\Reporter;

    $container->tag(NewReporter::class, Reporter::class);

Note that this is just an implementation detail and will be hidden
behind an extender.
@franzliedke
Copy link
Contributor Author

franzliedke commented Aug 10, 2019

Good point.

My original idea was that extensions (or rather, an extender) could extend the binding with a composite reporter. Something like this:

class CompositeReporter implements Reporter
{
  public function __construct(array $reporters)
  {
    $this->reporters = $reporters;
  }
  public function report(HandledError $error)
  {
    foreach ($this->reporters as $reporter) {
      $reporter->report($error);
    }
  }
}

$container->extend(Reporter::class, function ($original) {
  return new CompositeReporter([
    $original,
    new SentryReporter,
  ]);
});

That class would have had to be provided by core, though.

Then I realized this can be drastically simplified using container tagging, so I implemented that. 😀

@franzliedke
Copy link
Contributor Author

Okay, this breaks some minor things such as error messages when logging in with incorrect credentials.

The reason for that is our inconsistent use / generation of status codes. I will fix that in a follow-up PR, though, to make it easier to review, and explain my thinking in detail. (Already half-finished.)

@franzliedke
Copy link
Contributor Author

TL;DR I consider this complete.

Copy link
Member

@dsevillamartin dsevillamartin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I reworked fof/sentry with these new changes, and it does seem better / easier.

I do have to ask, is there any reason for making ViewRenderer@determineView and ViewRenderer@getMessage private ? I created SentryRenderer which extends ViewRenderer, as all it needs to do is add some JavaScript to the view, not rework the whole logic, and I don't see why I'd need to copy/paste those two methods when I could just use them if they were protected.

Something weird happened, not sure if I did something wrong or what. The Request passed to Formatter@format doesn't seem to be the same request that is received by middleware. I had to use middleware to bind the request to the app, and use that instead of the one passed to the method. $request->getAttribute('actor') was null, so I had to use app('sentry.request')->getAttribute('actor').

You can see the code @ https://github.com/FriendsOfFlarum/sentry/tree/error-handling-refactoring. It's not great, and I should probably create a Sentry class instead of binding stuff to the application, but... 🤷‍♂

@tobyzerner
Copy link
Contributor

@franzliedke gave me a personal tour of this PR and I approve :D

@franzliedke
Copy link
Contributor Author

I do have to ask, is there any reason for making ViewRenderer@determineView and ViewRenderer@getMessage private ?

I just make them private by default. Inheriting from these classes means "your" classes can break when I change the internals of "my" classes, and I don't really consider that to be public API (i.e. free from BC breaks).

(That said, there are still many protected methods all over Flarum, because that used to be my / other people's default. I changed my mind over time.)

As for your use-case: since you're really just re-using the entire implementation of the ViewRenderer, you could decorate it:

$this->app->extend(ViewRenderer::class, function (ViewRenderer $renderer) {
    return new SentryRenderer($renderer);
});

Your SentryRenderer would simply call the existing renderer and then inject the JavaScript snippet directly into the PSR-7 response it receives from the ViewRenderer.

Something weird happened, not sure if I did something wrong or what. The Request passed to Formatter@format doesn't seem to be the same request that is received by middleware.

This is by far the harder problem. I congratulate you on your creative solution, though 😉.

The "problem" here is that PSR-7 requests and responses are immutable. The ErrorHandler middleware basically passes on the request it received from the outside world to inner middleware. One of these middlewares extracts cookies, headers etc. from the response to determine the actor, which is then stored as a "request attribute" on the request. Once you add a new request attribute, you're creating a new request instance, though - which means the request instance known by the ErrorHandler is unchanged. This was a design decision when PSR-7 was created.

I think it's unfortunate that we should have discovered this problem earlier: your previous solution only worked because you added your error middleware to the end of the stack. I would argue an error handler should be the first middleware in the stack, though.

Toby and I had an idea how to fix this, but it needs some further thought. For now, can I ask you to temporarily disable the user feature (or use your workaround) in your Sentry extension, and create an issue about accessing the request's actor in an error handler? That would be the right place for my proposal.

Copy link
Member

@dsevillamartin dsevillamartin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your response @franzliedke, and glad I was able to discover the bug now.

Decorating is a good idea, and not something I had thought of, so thanks for that.

As those were my only concerns, 👍. I'll go ahead an create the issue for the bug as well.

@franzliedke franzliedke merged commit cd9aa00 into master Aug 13, 2019
@franzliedke franzliedke deleted the fl/1641-exception-handling branch August 13, 2019 20:45
franzliedke added a commit to flarum/lang-english that referenced this pull request Aug 14, 2019
...instead of status code. There are (or will be) multiple different
keys for similar errors with the same status code. In the future, we
will use the error's "type" (see flarum/framework#1843) to distinguish them.

Refs flarum/framework#1641.
@luceos
Copy link
Member

luceos commented Aug 19, 2019

Any particular reason to name the contract Formatter and its implementations Renderers? Can we amend this to make their relation easier and possibly move the implementations to a subfolder?

@franzliedke
Copy link
Contributor Author

Thanks for the comment. I pushed some small updates to this:

  • Documentation for most of the classes involved
  • Renderers are now formatters, the interface is now called HttpFormatter
  • Some other small API changes (in Reporter and in HandledError)

@datitisev Heads-up. These changes will break your Sentry extension. I am happy with this now, though; so there shouldn't be any further API changes hopefully.

franzliedke added a commit that referenced this pull request Sep 4, 2019
franzliedke added a commit that referenced this pull request Sep 13, 2019
This fixes a regression from #1843 and #1854. Now, the frontend again
shows the proper "Incorrect login details" message instead of "You
do not have permission to do that".
franzliedke added a commit that referenced this pull request Jan 17, 2020
This extender implements several methods for extending the new error
handling stack implemented in #1843.

Most use-cases should be covered, but I expect some challenges for more
complex setups. We can tackle those once they come up, though. Basic
use-cases should be covered.

Fixes #1781.
@franzliedke franzliedke mentioned this pull request Jan 17, 2020
2 tasks
franzliedke added a commit that referenced this pull request Jan 18, 2020
This extender implements several methods for extending the new error
handling stack implemented in #1843.

Most use-cases should be covered, but I expect some challenges for more
complex setups. We can tackle those once they come up, though. Basic
use-cases should be covered.

Fixes #1781.
franzliedke added a commit that referenced this pull request Jan 31, 2020
This extender implements several methods for extending the new error
handling stack implemented in #1843.

Most use-cases should be covered, but I expect some challenges for more
complex setups. We can tackle those once they come up, though. Basic
use-cases should be covered.

Fixes #1781.
luceos pushed a commit that referenced this pull request Feb 4, 2020
luceos pushed a commit that referenced this pull request Feb 4, 2020
luceos pushed a commit that referenced this pull request Feb 4, 2020
This fixes a regression from #1843 and #1854. Now, the frontend again
shows the proper "Incorrect login details" message instead of "You
do not have permission to do that".
luceos pushed a commit that referenced this pull request Feb 4, 2020
This extender implements several methods for extending the new error
handling stack implemented in #1843.

Most use-cases should be covered, but I expect some challenges for more
complex setups. We can tackle those once they come up, though. Basic
use-cases should be covered.

Fixes #1781.
wzdiyb pushed a commit to wzdiyb/core that referenced this pull request Feb 16, 2020
wzdiyb pushed a commit to wzdiyb/core that referenced this pull request Feb 16, 2020
wzdiyb pushed a commit to wzdiyb/core that referenced this pull request Feb 16, 2020
This fixes a regression from flarum#1843 and flarum#1854. Now, the frontend again
shows the proper "Incorrect login details" message instead of "You
do not have permission to do that".
wzdiyb pushed a commit to wzdiyb/core that referenced this pull request Feb 16, 2020
This extender implements several methods for extending the new error
handling stack implemented in flarum#1843.

Most use-cases should be covered, but I expect some challenges for more
complex setups. We can tackle those once they come up, though. Basic
use-cases should be covered.

Fixes flarum#1781.
askvortsov1 pushed a commit to flarum/lang-english that referenced this pull request Mar 11, 2022
...instead of status code. There are (or will be) multiple different
keys for similar errors with the same status code. In the future, we
will use the error's "type" (see flarum/framework#1843) to distinguish them.

Refs flarum/framework#1641.
askvortsov1 pushed a commit to flarum/lang-english that referenced this pull request May 10, 2022
...instead of status code. There are (or will be) multiple different
keys for similar errors with the same status code. In the future, we
will use the error's "type" (see flarum/framework#1843) to distinguish them.

Refs flarum/framework#1641.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants