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

Skip to content

Commit 3b1cf07

Browse files
committed
[HttpKernel] Add support for configuring log level, and status code by exception class
1 parent 7b52f24 commit 3b1cf07

File tree

13 files changed

+171
-9
lines changed

13 files changed

+171
-9
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ CHANGELOG
88
* Deprecate the `AdapterInterface` autowiring alias, use `CacheItemPoolInterface` instead
99
* Deprecate the public `profiler` service to private
1010
* Deprecate `get()`, `has()`, `getDoctrine()`, and `dispatchMessage()` in `AbstractController`, use method/constructor injection instead
11+
* Add support for configuring log level, and status code by exception class
1112

1213
5.3
1314
---

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Doctrine\Common\Annotations\PsrCachedReader;
1616
use Doctrine\Common\Cache\Cache;
1717
use Doctrine\DBAL\Connection;
18+
use Psr\Log\LogLevel;
1819
use Symfony\Bundle\FullStack;
1920
use Symfony\Component\Asset\Package;
2021
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
@@ -139,6 +140,7 @@ public function getConfigTreeBuilder()
139140
$this->addPropertyInfoSection($rootNode, $enableIfStandalone);
140141
$this->addCacheSection($rootNode, $willBeAvailable);
141142
$this->addPhpErrorsSection($rootNode);
143+
$this->addExceptionsSection($rootNode);
142144
$this->addWebLinkSection($rootNode, $enableIfStandalone);
143145
$this->addLockSection($rootNode, $enableIfStandalone);
144146
$this->addMessengerSection($rootNode, $enableIfStandalone);
@@ -1163,6 +1165,64 @@ private function addPhpErrorsSection(ArrayNodeDefinition $rootNode)
11631165
;
11641166
}
11651167

1168+
private function addExceptionsSection(ArrayNodeDefinition $rootNode)
1169+
{
1170+
$logLevels = (new \ReflectionClass(LogLevel::class))->getConstants();
1171+
1172+
$rootNode
1173+
->children()
1174+
->arrayNode('exceptions')
1175+
->info('Exception handling configuration')
1176+
->beforeNormalization()
1177+
->ifArray()
1178+
->then(function (array $v): array {
1179+
if (!\array_key_exists('exception', $v)) {
1180+
return $v;
1181+
}
1182+
1183+
// Fix XML normalization
1184+
$data = isset($v['exception'][0]) ? $v['exception'] : [$v['exception']];
1185+
$exceptions = [];
1186+
foreach ($data as $exception) {
1187+
$config = [];
1188+
if (\array_key_exists('log-level', $exception)) {
1189+
$config['log_level'] = $exception['log-level'];
1190+
}
1191+
if (\array_key_exists('status-code', $exception)) {
1192+
$config['status_code'] = $exception['status-code'];
1193+
}
1194+
$exceptions[$exception['name']] = $config;
1195+
}
1196+
1197+
return $exceptions;
1198+
})
1199+
->end()
1200+
->prototype('array')
1201+
->fixXmlConfig('exception')
1202+
->children()
1203+
->scalarNode('log_level')
1204+
->info('The level of log message. Null to let Symfony decide.')
1205+
->validate()
1206+
->ifTrue(function ($v) use ($logLevels) { return !\in_array($v, $logLevels); })
1207+
->thenInvalid(sprintf('The log level is not valid. Pick one among "%s".', implode('", "', $logLevels)))
1208+
->end()
1209+
->defaultNull()
1210+
->end()
1211+
->scalarNode('status_code')
1212+
->info('The status code of the response. Null to let Symfony decide.')
1213+
->validate()
1214+
->ifTrue(function ($v) { return !\in_array($v, range(100, 499)); })
1215+
->thenInvalid('The log level is not valid. Pick one among between 100 et 599.')
1216+
->end()
1217+
->defaultNull()
1218+
->end()
1219+
->end()
1220+
->end()
1221+
->end()
1222+
->end()
1223+
;
1224+
}
1225+
11661226
private function addLockSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone)
11671227
{
11681228
$rootNode

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,8 @@ public function load(array $configs, ContainerBuilder $container)
428428
$this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader);
429429
$this->registerSecretsConfiguration($config['secrets'], $container, $loader);
430430

431+
$container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']);
432+
431433
if ($this->isConfigEnabled($container, $config['serializer'])) {
432434
if (!class_exists(\Symfony\Component\Serializer\Serializer::class)) {
433435
throw new LogicException('Serializer support cannot be enabled as the Serializer component is not installed. Try running "composer require symfony/serializer-pack".');

src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<xsd:element name="cache" type="cache" minOccurs="0" maxOccurs="1" />
3030
<xsd:element name="workflow" type="workflow" minOccurs="0" maxOccurs="unbounded" />
3131
<xsd:element name="php-errors" type="php-errors" minOccurs="0" maxOccurs="1" />
32+
<xsd:element name="exceptions" type="exceptions" minOccurs="0" maxOccurs="1" />
3233
<xsd:element name="lock" type="lock" minOccurs="0" maxOccurs="1" />
3334
<xsd:element name="messenger" type="messenger" minOccurs="0" maxOccurs="1" />
3435
<xsd:element name="http-client" type="http_client" minOccurs="0" maxOccurs="1" />
@@ -341,6 +342,18 @@
341342
<xsd:attribute name="logLevel" type="xsd:string" />
342343
</xsd:complexType>
343344

345+
<xsd:complexType name="exceptions">
346+
<xsd:sequence>
347+
<xsd:element name="exception" type="exception" minOccurs="0" maxOccurs="unbounded" />
348+
</xsd:sequence>
349+
</xsd:complexType>
350+
351+
<xsd:complexType name="exception">
352+
<xsd:attribute name="name" type="xsd:string" use="required" />
353+
<xsd:attribute name="log-level" type="xsd:string" />
354+
<xsd:attribute name="status-code" type="xsd:int" />
355+
</xsd:complexType>
356+
344357
<xsd:complexType name="marking_store">
345358
<xsd:sequence>
346359
<xsd:element name="argument" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />

src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
param('kernel.error_controller'),
103103
service('logger')->nullOnInvalid(),
104104
param('kernel.debug'),
105+
abstract_arg('an exceptions to log & status code mapping'),
105106
])
106107
->tag('kernel.event_subscriber')
107108
->tag('monolog.logger', ['channel' => 'request'])

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -578,6 +578,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
578578
'name_based_uuid_version' => 5,
579579
'time_based_uuid_version' => 6,
580580
],
581+
'exceptions' => [],
581582
];
582583
}
583584
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
4+
5+
$container->loadFromExtension('framework', [
6+
'exceptions' => [
7+
BadRequestHttpException::class => [
8+
'log_level' => 'info',
9+
'status_code' => 422,
10+
],
11+
],
12+
]);
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" ?>
2+
<container xmlns="http://symfony.com/schema/dic/services"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xmlns:framework="http://symfony.com/schema/dic/symfony"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
6+
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">
7+
8+
<framework:config>
9+
<framework:exceptions>
10+
<framework:exception name="Symfony\Component\HttpKernel\Exception\BadRequestHttpException" log-level="info" status-code="422" />
11+
</framework:exceptions>
12+
</framework:config>
13+
</container>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
framework:
2+
exceptions:
3+
Symfony\Component\HttpKernel\Exception\BadRequestHttpException:
4+
log_level: info
5+
status_code: 422

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,18 @@ public function testPhpErrorsWithLogLevels()
522522
], $definition->getArgument(2));
523523
}
524524

525+
public function testExceptionsConfig()
526+
{
527+
$container = $this->createContainerFromFile('exceptions');
528+
529+
$this->assertSame([
530+
\Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class => [
531+
'log_level' => 'info',
532+
'status_code' => 422,
533+
],
534+
], $container->getDefinition('exception_listener')->getArgument(3));
535+
}
536+
525537
public function testRouter()
526538
{
527539
$container = $this->createContainerFromFile('full');

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ CHANGELOG
66

77
* Deprecate `AbstractTestSessionListener::getSession` inject a session in the request instead
88
* Deprecate the `fileLinkFormat` parameter of `DebugHandlersListener`
9+
* Add support for configuring log level, and status code by exception class
910

1011
5.3
1112
---

src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,30 @@ class ErrorListener implements EventSubscriberInterface
3232
protected $controller;
3333
protected $logger;
3434
protected $debug;
35+
protected $exceptionsMapping;
3536

36-
public function __construct($controller, LoggerInterface $logger = null, bool $debug = false)
37+
public function __construct($controller, LoggerInterface $logger = null, bool $debug = false, array $exceptionsMapping = [])
3738
{
3839
$this->controller = $controller;
3940
$this->logger = $logger;
4041
$this->debug = $debug;
42+
$this->exceptionsMapping = $exceptionsMapping;
4143
}
4244

4345
public function logKernelException(ExceptionEvent $event)
4446
{
45-
$e = FlattenException::createFromThrowable($event->getThrowable());
47+
$throwable = $event->getThrowable();
48+
$logLevel = null;
49+
foreach ($this->exceptionsMapping as $class => $config) {
50+
if ($throwable instanceof $class && $config['log_level']) {
51+
$logLevel = $config['log_level'];
52+
break;
53+
}
54+
}
55+
56+
$e = FlattenException::createFromThrowable($throwable);
4657

47-
$this->logException($event->getThrowable(), sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), $e->getFile(), $e->getLine()));
58+
$this->logException($throwable, sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), $e->getFile(), $e->getLine()), $logLevel);
4859
}
4960

5061
public function onKernelException(ExceptionEvent $event)
@@ -53,8 +64,8 @@ public function onKernelException(ExceptionEvent $event)
5364
return;
5465
}
5566

56-
$exception = $event->getThrowable();
57-
$request = $this->duplicateRequest($exception, $event->getRequest());
67+
$throwable = $event->getThrowable();
68+
$request = $this->duplicateRequest($throwable, $event->getRequest());
5869

5970
try {
6071
$response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false);
@@ -65,18 +76,25 @@ public function onKernelException(ExceptionEvent $event)
6576

6677
$prev = $e;
6778
do {
68-
if ($exception === $wrapper = $prev) {
79+
if ($throwable === $wrapper = $prev) {
6980
throw $e;
7081
}
7182
} while ($prev = $wrapper->getPrevious());
7283

7384
$prev = new \ReflectionProperty($wrapper instanceof \Exception ? \Exception::class : \Error::class, 'previous');
7485
$prev->setAccessible(true);
75-
$prev->setValue($wrapper, $exception);
86+
$prev->setValue($wrapper, $throwable);
7687

7788
throw $e;
7889
}
7990

91+
foreach ($this->exceptionsMapping as $exception => $config) {
92+
if ($throwable instanceof $exception && $config['status_code']) {
93+
$response->setStatusCode($config['status_code']);
94+
break;
95+
}
96+
}
97+
8098
$event->setResponse($response);
8199

82100
if ($this->debug) {
@@ -124,10 +142,12 @@ public static function getSubscribedEvents(): array
124142
/**
125143
* Logs an exception.
126144
*/
127-
protected function logException(\Throwable $exception, string $message): void
145+
protected function logException(\Throwable $exception, string $message, string $logLevel = null): void
128146
{
129147
if (null !== $this->logger) {
130-
if (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) {
148+
if (null !== $logLevel) {
149+
$this->logger->log($logLevel, $message, ['exception' => $exception]);
150+
} elseif (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) {
131151
$this->logger->critical($message, ['exception' => $exception]);
132152
} else {
133153
$this->logger->error($message, ['exception' => $exception]);

src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,27 @@ public function testHandleWithLogger($event, $event2)
9797
$this->assertCount(3, $logger->getLogs('critical'));
9898
}
9999

100+
public function testHandleWithLoggerAndCustomConfiguration()
101+
{
102+
$request = new Request();
103+
$event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new \RuntimeException('bar'));
104+
$logger = new TestLogger();
105+
$l = new ErrorListener('not used', $logger, false, [
106+
\RuntimeException::class => [
107+
'log_level' => 'warning',
108+
'status_code' => 401,
109+
],
110+
]);
111+
$l->logKernelException($event);
112+
$l->onKernelException($event);
113+
114+
$this->assertEquals(new Response('foo', 401), $event->getResponse());
115+
116+
$this->assertEquals(0, $logger->countErrors());
117+
$this->assertCount(0, $logger->getLogs('critical'));
118+
$this->assertCount(1, $logger->getLogs('warning'));
119+
}
120+
100121
public function provider()
101122
{
102123
if (!class_exists(Request::class)) {

0 commit comments

Comments
 (0)