Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 94c4de3

Browse filesBrowse files
lyrixxfabpot
authored andcommitted
[HttpKernel] Add support for configuring log level, and status code by exception class
1 parent 0e843d9 commit 94c4de3
Copy full SHA for 94c4de3

File tree

13 files changed

+171
-9
lines changed
Filter options

13 files changed

+171
-9
lines changed

‎src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ CHANGELOG
1111
* Deprecate the `cache.adapter.doctrine` service
1212
* Add support for resetting container services after each messenger message
1313
* Add `configureContainer()`, `configureRoutes()`, `getConfigDir()` and `getBundlesPath()` to `MicroKernelTrait`
14+
* Add support for configuring log level, and status code by exception class
1415

1516
5.3
1617
---

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php
+60Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,8 @@ public function load(array $configs, ContainerBuilder $container)
427427
$this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader);
428428
$this->registerSecretsConfiguration($config['secrets'], $container, $loader);
429429

430+
$container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']);
431+
430432
if ($this->isConfigEnabled($container, $config['serializer'])) {
431433
if (!class_exists(\Symfony\Component\Serializer\Serializer::class)) {
432434
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/schema/symfony-1.0.xsd
+13Lines 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" />
@@ -346,6 +347,18 @@
346347
<xsd:attribute name="enabled" type="xsd:boolean" />
347348
</xsd:complexType>
348349

350+
<xsd:complexType name="exceptions">
351+
<xsd:sequence>
352+
<xsd:element name="exception" type="exception" minOccurs="0" maxOccurs="unbounded" />
353+
</xsd:sequence>
354+
</xsd:complexType>
355+
356+
<xsd:complexType name="exception">
357+
<xsd:attribute name="name" type="xsd:string" use="required" />
358+
<xsd:attribute name="log-level" type="xsd:string" />
359+
<xsd:attribute name="status-code" type="xsd:int" />
360+
</xsd:complexType>
361+
349362
<xsd:complexType name="marking_store">
350363
<xsd:sequence>
351364
<xsd:element name="argument" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/web.php
+1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,7 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
579579
'name_based_uuid_version' => 5,
580580
'time_based_uuid_version' => 6,
581581
],
582+
'exceptions' => [],
582583
];
583584
}
584585
}
+12Lines 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+
]);
+13Lines 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>
+5Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+12Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,18 @@ public function testPhpErrorsWithLogLevels()
521521
], $definition->getArgument(2));
522522
}
523523

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

‎src/Symfony/Component/HttpKernel/CHANGELOG.md

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/CHANGELOG.md
+1Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php
+29-9Lines 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

Copy file name to clipboardExpand all lines: src/Symfony/Component/HttpKernel/Tests/EventListener/ErrorListenerTest.php
+21Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.