Skip to content

Commit

Permalink
[HttpKernel][ErrorHandler] Allow setting log level for exceptions wit…
Browse files Browse the repository at this point in the history
…h attribute
  • Loading branch information
angelov committed Dec 21, 2022
1 parent 65e65ac commit 9be54ba
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 16 deletions.
26 changes: 26 additions & 0 deletions src/Symfony/Component/ErrorHandler/Attribute/LogLevel.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?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\ErrorHandler\Attribute;

/**
* @author Dejan Angelov <angelovdejan@protonmail.com>
*/
#[\Attribute(\Attribute::TARGET_CLASS)]
final class LogLevel
{
public function __construct(public readonly string $level)
{
if (!defined(sprintf("\Psr\Log\LogLevel::%s", strtoupper($this->level)))) {
throw new \InvalidArgumentException(sprintf("Invalid log level - \"%s\".", $this->level));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);

namespace Symfony\Component\ErrorHandler\Tests\Attribute;

use PHPUnit\Framework\TestCase;
use Psr\Log\LogLevel as PsrLogLevel;
use Symfony\Component\ErrorHandler\Attribute\LogLevel;

/**
* @author Dejan Angelov <angelovdejan@protonmail.com>
*/
class LogLevelTest extends TestCase
{
public function testWithValidLogLevel()
{
$logLevel = PsrLogLevel::NOTICE;

$attribute = new LogLevel($logLevel);

$this->assertSame($logLevel, $attribute->level);
}

public function testWithInvalidLogLevel()
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Invalid log level - "invalid".');

new LogLevel("invalid");
}
}
49 changes: 33 additions & 16 deletions src/Symfony/Component/HttpKernel/EventListener/ErrorListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
namespace Symfony\Component\HttpKernel\EventListener;

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel as PsrLogLevel;
use Symfony\Component\ErrorHandler\Attribute\LogLevel;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Request;
Expand Down Expand Up @@ -52,14 +54,7 @@ public function __construct(string|object|array|null $controller, LoggerInterfac
public function logKernelException(ExceptionEvent $event)
{
$throwable = $event->getThrowable();
$logLevel = null;

foreach ($this->exceptionsMapping as $class => $config) {
if ($throwable instanceof $class && $config['log_level']) {
$logLevel = $config['log_level'];
break;
}
}
$logLevel = $this->resolveLogLevel($throwable);

foreach ($this->exceptionsMapping as $class => $config) {
if (!$throwable instanceof $class || !$config['status_code']) {
Expand Down Expand Up @@ -104,9 +99,10 @@ public function onKernelException(ExceptionEvent $event)
try {
$response = $event->getKernel()->handle($request, HttpKernelInterface::SUB_REQUEST, false);
} catch (\Exception $e) {
$logLevel = $this->resolveLogLevel($e);
$f = FlattenException::createFromThrowable($e);

$this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', $f->getClass(), $f->getMessage(), $e->getFile(), $e->getLine()));
$this->logException($e, sprintf('Exception thrown when handling an exception (%s: %s at %s line %s)', $f->getClass(), $f->getMessage(), $e->getFile(), $e->getLine()), $logLevel);

$prev = $e;
do {
Expand Down Expand Up @@ -168,17 +164,38 @@ public static function getSubscribedEvents(): array
/**
* Logs an exception.
*/
protected function logException(\Throwable $exception, string $message, string $logLevel = null): void
protected function logException(\Throwable $exception, string $message, string $logLevel): void
{
if (null !== $this->logger) {
if (null !== $logLevel) {
$this->logger->log($logLevel, $message, ['exception' => $exception]);
} elseif (!$exception instanceof HttpExceptionInterface || $exception->getStatusCode() >= 500) {
$this->logger->critical($message, ['exception' => $exception]);
} else {
$this->logger->error($message, ['exception' => $exception]);
$this->logger->log($logLevel, $message, ['exception' => $exception]);
}
}

/**
* Resolves the level to be used when logging the exception.
*/
private function resolveLogLevel(\Throwable $throwable): string
{
foreach ($this->exceptionsMapping as $class => $config) {
if ($throwable instanceof $class && $config['log_level']) {
return $config['log_level'];
}
}

$attributes = (new \ReflectionClass($throwable))->getAttributes(LogLevel::class);

if ($attributes) {
/** @var LogLevel $instance */
$instance = $attributes[0]->newInstance();

return $instance->level;
}

if (!$throwable instanceof HttpExceptionInterface || $throwable->getStatusCode() >= 500) {
return PsrLogLevel::CRITICAL;
}

return PsrLogLevel::ERROR;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Symfony\Component\ErrorHandler\Attribute\LogLevel;
use Symfony\Component\ErrorHandler\Exception\FlattenException;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\HttpFoundation\Request;
Expand Down Expand Up @@ -118,6 +119,40 @@ public function testHandleWithLoggerAndCustomConfiguration()
$this->assertCount(1, $logger->getLogs('warning'));
}

public function testHandleWithLogLevelAttribute()
{
$request = new Request();
$event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new WarningWithLogLevelAttribute());
$logger = new TestLogger();
$l = new ErrorListener('not used', $logger);

$l->logKernelException($event);
$l->onKernelException($event);

$this->assertEquals(0, $logger->countErrors());
$this->assertCount(0, $logger->getLogs('critical'));
$this->assertCount(1, $logger->getLogs('warning'));
}

public function testHandleWithLogLevelAttributeAndCustomConfiguration()
{
$request = new Request();
$event = new ExceptionEvent(new TestKernel(), $request, HttpKernelInterface::MAIN_REQUEST, new WarningWithLogLevelAttribute());
$logger = new TestLogger();
$l = new ErrorListener('not used', $logger, false, [
WarningWithLogLevelAttribute::class => [
'log_level' => 'info',
'status_code' => 401
],
]);
$l->logKernelException($event);
$l->onKernelException($event);

$this->assertEquals(0, $logger->countErrors());
$this->assertCount(0, $logger->getLogs('warning'));
$this->assertCount(1, $logger->getLogs('info'));
}

/**
* @dataProvider exceptionWithAttributeProvider
*/
Expand Down Expand Up @@ -312,3 +347,8 @@ class WithCustomUserProvidedAttribute extends \Exception
class WithGeneralAttribute extends \Exception
{
}

#[LogLevel(\Psr\Log\LogLevel::WARNING)]
class WarningWithLogLevelAttribute extends \Exception
{
}

0 comments on commit 9be54ba

Please sign in to comment.