Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[FrameworkBundle][HttpKernel] Configure ErrorHandler on boot #49275

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

Merged
merged 1 commit into from
Feb 21, 2023

Conversation

HypeMC
Copy link
Contributor

@HypeMC HypeMC commented Feb 7, 2023

Q A
Branch? 6.3
Bug fix? no
New feature? yes
Deprecations? no
Tickets -
License MIT
Doc PR -

Currently the ErrorHandler is somewhat configured in FrameworkBundle::boot() and then later fully in DebugHandlersListener::configure().

public function boot()
{
ErrorHandler::register(null, false)->throwAt($this->container->getParameter('debug.error_handler.throw_at'), true);
if ($this->container->getParameter('kernel.http_method_override')) {

public function configure(object $event = null)
{
if ($event instanceof ConsoleEvent && !\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true)) {
return;
}
if (!$event instanceof KernelEvent ? !$this->firstCall : !$event->isMainRequest()) {
return;
}
$this->firstCall = $this->hasTerminatedWithException = false;
$handler = set_exception_handler('is_int');
$handler = \is_array($handler) ? $handler[0] : null;
restore_exception_handler();
if (!$handler instanceof ErrorHandler) {
$handler = $this->earlyHandler;
}
if ($handler instanceof ErrorHandler) {
if ($this->logger || $this->deprecationLogger) {
$this->setDefaultLoggers($handler);
if (\is_array($this->levels)) {
$levels = 0;
foreach ($this->levels as $type => $log) {
$levels |= $type;
}
} else {
$levels = $this->levels;
}
if ($this->scream) {
$handler->screamAt($levels);
}
if ($this->scope) {
$handler->scopeAt($levels & ~\E_USER_DEPRECATED & ~\E_DEPRECATED);
} else {
$handler->scopeAt(0, true);
}
$this->logger = $this->deprecationLogger = $this->levels = null;
}
if (null !== $this->throwAt) {
$handler->throwAt($this->throwAt, true);
}
}
if (!$this->exceptionHandler) {
if ($event instanceof KernelEvent) {
if (method_exists($kernel = $event->getKernel(), 'terminateWithException')) {
$request = $event->getRequest();
$hasRun = &$this->hasTerminatedWithException;
$this->exceptionHandler = static function (\Throwable $e) use ($kernel, $request, &$hasRun) {
if ($hasRun) {
throw $e;
}
$hasRun = true;
$kernel->terminateWithException($e, $request);
};
}
} elseif ($event instanceof ConsoleEvent && $app = $event->getCommand()->getApplication()) {
$output = $event->getOutput();
if ($output instanceof ConsoleOutputInterface) {
$output = $output->getErrorOutput();
}
$this->exceptionHandler = static function (\Throwable $e) use ($app, $output) {
$app->renderThrowable($e, $output);
};
}
}
if ($this->exceptionHandler) {
if ($handler instanceof ErrorHandler) {
$handler->setExceptionHandler($this->exceptionHandler);
}
$this->exceptionHandler = null;
}
}

However, I've noticed that there are some edge case when the handler doesn't get configured in time/at all.

One such example is when using the Sentry bundle. The bundle has its own error handler which gets registered when the Sentry client is instantiated. If you're using Sentry with Monolog the client gets instantiated when a Monolog logger is used. Since the http_kernel service requires a logger, the client gets instantiated before the kernel.request event and, as a result, when the DebugHandlersListener is trigger both $handler and $this->earlyHandler are null and the error handler is never configured properly.

Another example when the handler is not configured in time is when an error occurs in a kernel.request listener that has a higher priority then the DebugHandlersListener. In such cases the error handler's loggers are not yet configured and the errors are not logged properly.

These cases were tested with APP_DEBUG=0 since the error handler is registered earlier when debug is enabled.

The idea of this PR is to configure the error handler as early as possible to prevent such edge cases from ever being able to happen. The error handler is now fully configured in FrameworkBundle::boot().

Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

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

LGTM thanks, I just have some minor comments

->tag('monolog.logger', ['channel' => 'php'])

->set('debug.debug_handlers_listener', DebugHandlersListener::class)
->args([
null, // Exception handler
Copy link
Member

Choose a reason for hiding this comment

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

abstract arg?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as I can tell, this is actually never set anywhere, it remains null. Maybe it'd be best to remove it?

Copy link
Member

Choose a reason for hiding this comment

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

yes, let's remove it from this file (but keep the arg in the class just in case)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@HypeMC HypeMC force-pushed the error-handler-configuration branch from aba5dbb to 21b00ce Compare February 21, 2023 15:27
@HypeMC HypeMC force-pushed the error-handler-configuration branch from 21b00ce to 26e6a56 Compare February 21, 2023 15:33
@nicolas-grekas
Copy link
Member

Thank you @HypeMC.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants