8000 [FrameworkBundle][HttpKernel] Allow configuring the logging channel p… · symfony/symfony@742a863 · GitHub
[go: up one dir, main page]

Skip to content

Commit 742a863

Browse files
Arkalo2nicolas-grekas
authored andcommitted
[FrameworkBundle][HttpKernel] Allow configuring the logging channel per type of exceptions
1 parent 1492e46 commit 742a863

File tree

8 files changed

+113
-10
lines changed

8 files changed

+113
-10
lines changed

src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ CHANGELOG
1717
* Auto-exclude DI extensions, test cases, entities and messenger messages
1818
* Add DI alias from `ServicesResetterInterface` to `services_resetter`
1919
* Add `methods` argument in `#[IsCsrfTokenValid]` attribute
20+
* Allow configuring the logging channel per type of exceptions
2021

2122
7.2
2223
---

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,6 +1458,10 @@ private function addExceptionsSection(ArrayNodeDefinition $rootNode): void
14581458
->end()
14591459
->defaultNull()
14601460
->end()
1461+
->scalarNode('log_channel')
1462+
->info('The channel of log message. Null to let Symfony decide.')
1463+
->defaultNull()
1464+
->end()
14611465
->end()
14621466
->end()
14631467
->end()

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,20 @@ public function load(array $configs, ContainerBuilder $container): void
417417
$this->registerPropertyAccessConfiguration($config['property_access'], $container, $loader);
418418
$this->registerSecretsConfiguration($config['secrets'], $container, $loader, $config['secret'] ?? null);
419419

420-
$container->getDefinition('exception_listener')->replaceArgument(3, $config['exceptions']);
420+
$exceptionListener = $container->getDefinition('exception_listener');
421+
422+
$loggers = [];
423+
foreach ($config['exceptions'] as $exception) {
424+
if (!isset($exception['log_channel'])) {
425+
continue;
426+
}
427+
$loggers[$exception['log_channel']] = new Reference('monolog.logger.'.$exception['log_channel'], ContainerInterface::NULL_ON_INVALID_REFERENCE);
428+
}
429+
430+
$exceptionListener
431+
->replaceArgument(3, $config['exceptions'])
432+
->setArgument(4, $loggers)
433+
;
421434

422435
if ($this->readConfigEnabled('serializer', $container, $config['serializer'])) {
423436
if (!class_exists(Serializer::class)) {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
service('logger')->nullOnInvalid(),
139139
param('kernel.debug'),
140140
abstract_arg('an exceptions to log & status code mapping'),
141+
abstract_arg('list of loggers by log_channel'),
141142
])
142143
->tag('kernel.event_subscriber')
143144
->tag('monolog.logger', ['channel' => 'request'])

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,21 +615,25 @@ public function testExceptionsConfig()
615615
], array_keys($configuration));
616616

617617
$this->assertEqualsCanonicalizing([
618+
'log_channel' => null,
618619
'log_level' => 'info',
619620
'status_code' => 422,
620621
], $configuration[\Symfony\Component\HttpKernel\Exception\BadRequestHttpException::class]);
621622

622623
$this->assertEqualsCanonicalizing([
624+
'log_channel' => null,
623625
'log_level' => 'info',
624626
'status_code' => null,
625627
], $configuration[\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class]);
626628

627629
$this->assertEqualsCanonicalizing([
630+
'log_channel' => null,
628631
'log_level' => 'info',
629632
'status_code' => null,
630633
], $configuration[\Symfony\Component\HttpKernel\Exception\ConflictHttpException::class]);
631634

632635
$this->assertEqualsCanonicalizing([
636+
'log_channel' => null,
633637
'log_level' => null,
634638
'status_code' => 500,
635639
], $configuration[\Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException::class]);

src/Symfony/Component/HttpKernel/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ CHANGELOG
77
* Add `$key` argument to `#[MapQueryString]` that allows using a specific key for argument resolving
88
* Support `Uid` in `#[MapQueryParameter]`
99
* Add `ServicesResetterInterface`, implemented by `ServicesResetter`
10-
10+
* Allow configuring the logging channel per type of exceptions in ErrorListener
11+
1112
7.2
1213
---
1314

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

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,20 +34,22 @@
3434
class ErrorListener implements EventSubscriberInterface
3535
{
3636
/**
37-
* @param array<class-string, array{log_level: string|null, status_code: int<100,599>|null}> $exceptionsMapping
37+
* @param array<class-string, array{log_level: string|null, status_code: int<100,599>|null, log_channel: string|null}> $exceptionsMapping
3838
*/
3939
public function __construct(
4040
protected string|object|array|null $controller,
4141
protected ?LoggerInterface $logger = null,
4242
protected bool $debug = false,
4343
protected array $exceptionsMapping = [],
44+
protected array $loggers = [],
4445
) {
4546
}
4647

4748
public function logKernelException(ExceptionEvent $event): void
4849
{
4950
$throwable = $event->getThrowable();
5051
$logLevel = $this->resolveLogLevel($throwable);
52+
$logChannel = $this->resolveLogChannel($throwable);
5153

5254
foreach ($this->exceptionsMapping as $class => $config) {
5355
if (!$throwable instanceof $class || !$config['status_code']) {
@@ -69,7 +71,7 @@ public function logKernelException(ExceptionEvent $event): void
6971

7072
$e = FlattenException::createFromThrowable($throwable);
7173

72-
$this->logException($throwable, \sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), basename($e->getFile()), $e->getLine()), $logLevel);
74+
$this->logException($throwable, \sprintf('Uncaught PHP Exception %s: "%s" at %s line %s', $e->getClass(), $e->getMessage(), basename($e->getFile()), $e->getLine()), $logLevel, $logChannel);
7375
}
7476

7577
public function onKernelException(ExceptionEvent $event): void
@@ -159,16 +161,20 @@ public static function getSubscribedEvents(): array
159161

160162
/**
161163
* Logs an exception.
164+
*
165+
* @param ?string $logChannel
162166
*/
163-
protected function logException(\Throwable $exception, string $message, ?string $logLevel = null): void
167+
protected function logException(\Throwable $exception, string $message, ?string $logLevel = null, /* ?string $logChannel = null */): void
164168
{
165-
if (null === $this->logger) {
169+
$logChannel = (3 < \func_num_args() ? \func_get_arg(3) : null) ?? $this->resolveLogChannel($exception);
170+
171+
$logLevel ??= $this->resolveLogLevel($exception);
172+
173+
if(!$logger = $this->getLogger($logChannel)) {
166174
return;
167175
}
168176

169-
$logLevel ??= $this->resolveLogLevel($exception);
170-
171-
$this->logger->log($logLevel, $message, ['exception' => $exception]);
177+
$logger->log($logLevel, $message, ['exception' => $exception]);
172178
}
173179

174180
/**
@@ -193,6 +199,17 @@ private function resolveLogLevel(\Throwable $throwable): string
193199
return LogLevel::ERROR;
194200
}
195201

202+
private function resolveLogChannel(\Throwable $throwable): ?string
203+
{
204+
foreach ($this->exceptionsMapping as $class => $config) {
205+
if ($throwable instanceof $class && isset($config['log_channel'])) {
206+
return $config['log_channel'];
207+
}
208+
}
209+
210+
return null;
211+
}
212+
196213
/**
197214
* Clones the request for the exception.
198215
*/
@@ -201,7 +218,7 @@ protected function duplicateRequest(\Throwable $exception, Request $request): Re
201218
$attributes = [
202219
'_controller' => $this->controller,
203220
'exception' => $exception,
204-
'logger' => DebugLoggerConfigurator::getDebugLogger($this->logger),
221+
'logger' => DebugLoggerConfigurator::getDebugLogger($this->getLogger($exception)),
205222
];
206223
$request = $request->duplicate(null, null, $attributes);
207224
$request->setMethod('GET');
@@ -249,4 +266,9 @@ private function getInheritedAttribute(string $class, string $attribute): ?objec
249266

250267
return $attributeReflector?->newInstance();
251268
}
269+
270+
private function getLogger(?string $logChannel): ?LoggerInterface
271+
{
272+
return $logChannel ? $this->loggers[$logChannel] ?? $this->logger : $this->logger;
273+
}
252274
}

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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,63 @@ public function testHandleWithLogLevelAttribute()
143143
$this->assertCount(1, $logger->getLogsForLevel('warning'));
144144
}
145145

146+
public function testHandleWithLogChannel()
147+
{
148+
$request = new Request();
149+
$event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new \RuntimeException('bar'));
150+
151+
$defaultLogger = new TestLogger();
152+
$channelLoger = new TestLogger();
153+
154+
$l = new ErrorListener('not used', $defaultLogger, false, [
155+
\RuntimeException::class => [
156+
'log_level' => 'warning',
157+
'status_code' => 401,
158+
'log_channel' => 'channel',
159+
],
160+
\Exception::class => [
161+
'log_level' => 'error',
162+
'status_code' => 402,
163+
],
164+
], ['channel' => $channelLoger]);
165+
166+
$l->logKernelException($event);
167+
$l->onKernelException($event);
168+
169+
$this->assertCount(0, $defaultLogger->getLogsForLevel('error'));
170+
$this->assertCount(0, $defaultLogger->getLogsForLevel('warning'));
171+
$this->assertCount(0, $channelLoger->getLogsForLevel('error'));
172+
$this->assertCount(1, $channelLoger->getLogsForLevel('warning'));
173+
}
174+
175+
public function testHandleWithLoggerChannelNotUsed()
176+
{
177+
$request = new Request();
178+
$event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new \RuntimeException('bar'));
179+
$defaultLogger = new TestLogger();
180+
$channelLoger = new TestLogger();
181+
$l = new ErrorListener('not used', $defaultLogger, false, [
182+
\RuntimeException::class => [
183+
'log_level' => 'warning',
184+
'status_code' => 401,
185+
],
186+
\ErrorException::class => [
187+
'log_level' => 'error',
188+
'status_code' => 402,
189+
'log_channel' => 'channel',
190+
],
191+
], ['channel' => $channelLoger]);
192+
$l->logKernelException($event);
193+
$l->onKernelException($event);
194+
195+
$this->assertSame(0, $defaultLogger->countErrors());
196+
$this->assertCount(0, $defaultLogger->getLogsForLevel('critical'));
197+
$this->assertCount(1, $defaultLogger->getLogsForLevel('warning'));
198+
$this->assertCount(0, $channelLoger->getLogsForLevel('warning'));
199+
$this->assertCount(0, $channelLoger->getLogsForLevel('error'));
200+
$this->assertCount(0, $channelLoger->getLogsForLevel('critical'));
201+
}
202+
146203
public function testHandleClassImplementingInterfaceWithLogLevelAttribute()
147204
{
148205
$request = new Request();

0 commit comments

Comments
 (0)
0