Skip to content

[HttpKernel][DX] Allow the log level of http exceptions to be overridden #25533

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

Closed
wants to merge 13 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ public function getConfigTreeBuilder()
$this->addPhpErrorsSection($rootNode);
$this->addWebLinkSection($rootNode);
$this->addLockSection($rootNode);
$this->addHttpExceptionLogLevels($rootNode);

return $treeBuilder;
}
Expand Down Expand Up @@ -902,4 +903,43 @@ private function addWebLinkSection(ArrayNodeDefinition $rootNode)
->end()
;
}

private function addHttpExceptionLogLevels(ArrayNodeDefinition $rootNode)
{
$rootNode
->children()
->arrayNode('http_exception_log_levels')
->info('The override log levels for http exceptions')
->example(array('403' => 'NOTICE', '404' => 'INFO'))
->useAttributeAsKey('http_exception_log_levels')
->prototype('variable')->end()
->validate()
->always(function ($v) {
$map = array();
foreach ($v as $status => $level) {
if (!(is_int($status) && $status >= 100 && $status <= 599)) {
throw new InvalidConfigurationException(sprintf(
'The configured status code "%s" in framework.http_exception_log_levels is not a valid http status code.',
$status
));
}

$levelConstant = 'Psr\Log\LogLevel::'.$level;
if (!defined($levelConstant)) {
throw new InvalidConfigurationException(sprintf(
'The configured log level "%s" in framework.http_exception_log_levels is invalid as it is not defined in Psr\\Log\\LogLevel.',
$level
));
}

$map[$status] = constant($levelConstant);
}

return $map;
})
->end()
->end()
->end()
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ public function load(array $configs, ContainerBuilder $container)
$container->setParameter('kernel.trusted_hosts', $config['trusted_hosts']);
$container->setParameter('kernel.default_locale', $config['default_locale']);

$container->setParameter('framework.exception_listener.http_log_levels', $config['http_exception_log_levels']);

if (!$container->hasParameter('debug.file_link_format')) {
if (!$container->hasParameter('templating.helper.code.file_link_format')) {
$links = array(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,14 @@
</service>

<service id="services_resetter" class="Symfony\Component\HttpKernel\DependencyInjection\ServicesResetter" public="true" />

<service id="exception_logger" class="Symfony\Component\HttpKernel\EventListener\LoggingExceptionListener">
<tag name="kernel.event_subscriber" />
<tag name="monolog.logger" channel="request" />
<argument type="service" id="logger" on-invalid="null" />
<argument>%framework.exception_listener.http_log_levels%</argument>
<argument type="service" id="twig.exception_listener" on-invalid="null" />
</service>

</services>
</container>
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
),
),
),
'http_exception_log_levels' => array(),
);
}
}
1 change: 1 addition & 0 deletions src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
<tag name="monolog.logger" channel="request" />
<argument>%twig.exception_listener.controller%</argument>
<argument type="service" id="logger" on-invalid="null" />
<argument>false</argument>
</service>

<service id="twig.controller.exception" class="Symfony\Bundle\TwigBundle\Controller\ExceptionController" public="true">
Expand Down
5 changes: 0 additions & 5 deletions src/Symfony/Component/HttpKernel/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
CHANGELOG
=========

4.1.0
-----

* `ExceptionListener` now logs and collects exceptions at priority `2048` (previously logged at `-128` and collected at `0`)

4.0.0
-----

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,36 @@ class ExceptionListener implements EventSubscriberInterface
{
protected $controller;
protected $logger;
private $isBCExceptionLoggingEnabled;

public function __construct($controller, LoggerInterface $logger = null)
public function __construct($controller, LoggerInterface $logger = null/*, bool $isBCExceptionLoggingEnabled = true*/)
{
$this->controller = $controller;
$this->logger = $logger;
$this->isBCExceptionLoggingEnabled = (func_num_args() >= 3) ? (bool) func_get_arg(2) : true;
}

public function logKernelException(GetResponseForExceptionEvent $event)
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
$request = $event->getRequest();
$eventDispatcher = func_num_args() > 2 ? func_get_arg(2) : null;

$this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine()));
}
if ($this->isBCExceptionLoggingEnabled) {
$this->logException($exception, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine()));
}

public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
$request = $this->duplicateRequest($exception, $event->getRequest());
$eventDispatcher = func_num_args() > 2 ? func_get_arg(2) : null;
$request = $this->duplicateRequest($exception, $request);

try {
$response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false);
} catch (\Exception $e) {
$this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine()));
$message = sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', get_class($e), $e->getMessage(), $e->getFile(), $e->getLine());
if ($this->isBCExceptionLoggingEnabled) {
$this->logException($e, $message);
} elseif (null !== $this->logger) {
$this->logger->critical($message, array('exception' => $e));
}

$wrapper = $e;

Expand Down Expand Up @@ -87,10 +92,7 @@ public function onKernelException(GetResponseForExceptionEvent $event)
public static function getSubscribedEvents()
{
return array(
KernelEvents::EXCEPTION => array(
array('logKernelException', 2048),
array('onKernelException', -128),
),
KernelEvents::EXCEPTION => array('onKernelException', -128),
);
}

Expand All @@ -99,9 +101,13 @@ public static function getSubscribedEvents()
*
* @param \Exception $exception The \Exception instance
* @param string $message The error message to log
*
* @deprecated since Symfony 4.1, to be removed in 5.0. Use LoggingExceptionListener instead to log exceptions.
*/
protected function logException(\Exception $exception, $message)
{
@trigger_error(sprintf('The %s() method is deprecated since Symfony 4.1 and will be removed in 5.0. Use %s instead.', __METHOD__, LoggingExceptionListener::class), E_USER_DEPRECATED);

if (null !== $this->logger) {
if (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) {
$this->logger->critical($message, array('exception' => $exception));
Expand Down Expand Up @@ -131,4 +137,14 @@ protected function duplicateRequest(\Exception $exception, Request $request)

return $request;
}

/**
* @deprecated since Symfony 4.1, to be removed in 5.0.
*
* @internal
*/
public function isLoggingExceptions(): bool
{
return $this->isBCExceptionLoggingEnabled;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Component\HttpKernel\EventListener;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Symfony\Component\HttpKernel\KernelEvents;

class LoggingExceptionListener implements EventSubscriberInterface
{
protected $logger;
protected $httpStatusCodeLogLevel;
private $isDisabled;

public function __construct(LoggerInterface $logger = null, array $httpStatusCodeLogLevel = array()/*, ExceptionListener $exceptionListener = null */)
{
$this->logger = $logger ?: new NullLogger();
$this->httpStatusCodeLogLevel = $httpStatusCodeLogLevel;

if (func_num_args() >= 3) {
$exceptionListener = func_get_arg(2);
$this->isDisabled = ($exceptionListener instanceof ExceptionListener) ? $exceptionListener->isLoggingExceptions() : false;
if ($this->isDisabled) {
$this->logger->debug('Logging disabled for backwards compatibility, ExceptionListener is already logging exceptions.');
}
}
}

public function logKernelException(GetResponseForExceptionEvent $event)
{
if ($this->isDisabled) {
return;
}

$exception = $event->getException();
$level = $this->getExceptionLogLevel($exception);
$message = sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', get_class($exception), $exception->getMessage(), $exception->getFile(), $exception->getLine());

$this->logger->log($level, $message, array('exception' => $exception));
}

public static function getSubscribedEvents(): array
{
return array(
KernelEvents::EXCEPTION => array(
array('logKernelException', 2048),
),
);
}

protected function getExceptionLogLevel(\Exception $exception): string
{
$logLevel = LogLevel::CRITICAL;
if ($exception instanceof HttpExceptionInterface) {
$statusCode = $exception->getStatusCode();
if (isset($this->httpStatusCodeLogLevel[$statusCode])) {
$logLevel = $this->httpStatusCodeLogLevel[$statusCode];
} elseif ($statusCode >= 400 && $statusCode < 500) {
$logLevel = LogLevel::WARNING;
}
}

return $logLevel;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public static function getSubscribedEvents()
{
return array(
KernelEvents::RESPONSE => array('onKernelResponse', -100),
KernelEvents::EXCEPTION => array('onKernelException', 2048),
KernelEvents::EXCEPTION => 'onKernelException',
KernelEvents::TERMINATE => array('onKernelTerminate', -1024),
);
}
Expand Down
Loading