Skip to content

Commit 5e575fb

Browse files
Arkalo2nicolas-grekas
authored andcommitted
[FrameworkBundle][HttpKernel] Allow configuring the logging channel per type of exceptions
1 parent cc168c0 commit 5e575fb

File tree

3 files changed

+89
-9
lines changed

3 files changed

+89
-9
lines changed

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

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
}

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)