[MonologBridge] Add monolog processors adding route and command info

This commit is contained in:
Piotr Stankowski 2018-08-31 23:21:26 +02:00 committed by Fabien Potencier
parent 3778585210
commit 669f6b2726
5 changed files with 398 additions and 0 deletions

View File

@ -1,6 +1,12 @@
CHANGELOG
=========
4.3.0
-----
* added `ConsoleCommandProcessor`: monolog processor that adds command name and arguments
* added `RouteProcessor`: monolog processor that adds route name, controller::action and route params
4.2.0
-----

View File

@ -0,0 +1,69 @@
<?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\Bridge\Monolog\Processor;
use Symfony\Component\Console\ConsoleEvents;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Contracts\Service\ResetInterface;
/**
* Adds the current console command information to the log entry.
*
* @author Piotr Stankowski <git@trakos.pl>
*/
class ConsoleCommandProcessor implements EventSubscriberInterface, ResetInterface
{
private $commandData;
private $includeArguments;
private $includeOptions;
public function __construct(bool $includeArguments = true, bool $includeOptions = false)
{
$this->includeArguments = $includeArguments;
$this->includeOptions = $includeOptions;
}
public function __invoke(array $records)
{
if (null !== $this->commandData && !isset($records['extra']['command'])) {
$records['extra']['command'] = $this->commandData;
}
return $records;
}
public function reset()
{
$this->commandData = null;
}
public function addCommandData(ConsoleEvent $event)
{
$this->commandData = array(
'name' => $event->getCommand()->getName(),
);
if ($this->includeArguments) {
$this->commandData['arguments'] = $event->getInput()->getArguments();
}
if ($this->includeOptions) {
$this->commandData['options'] = $event->getInput()->getOptions();
}
}
public static function getSubscribedEvents()
{
return array(
ConsoleEvents::COMMAND => array('addCommandData', 1),
);
}
}

View File

@ -0,0 +1,86 @@
<?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\Bridge\Monolog\Processor;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Contracts\Service\ResetInterface;
/**
* Adds the current route information to the log entry.
*
* @author Piotr Stankowski <git@trakos.pl>
*/
class RouteProcessor implements EventSubscriberInterface, ResetInterface
{
private $routeData;
private $includeParams;
public function __construct(bool $includeParams = true)
{
$this->includeParams = $includeParams;
$this->reset();
}
public function __invoke(array $records)
{
if ($this->routeData && !isset($records['extra']['requests'])) {
$records['extra']['requests'] = array_values($this->routeData);
}
return $records;
}
public function reset()
{
$this->routeData = array();
}
public function addRouteData(GetResponseEvent $event)
{
if ($event->isMasterRequest()) {
$this->reset();
}
$request = $event->getRequest();
if (!$request->attributes->has('_controller')) {
return;
}
$currentRequestData = array(
'controller' => $request->attributes->get('_controller'),
'route' => $request->attributes->get('_route'),
);
if ($this->includeParams) {
$currentRequestData['route_params'] = $request->attributes->get('_route_params');
}
$this->routeData[spl_object_id($request)] = $currentRequestData;
}
public function removeRouteData(FinishRequestEvent $event)
{
$requestId = spl_object_id($event->getRequest());
unset($this->routeData[$requestId]);
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => array('addRouteData', 1),
KernelEvents::FINISH_REQUEST => array('removeRouteData', 1),
);
}
}

View File

@ -0,0 +1,75 @@
<?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\Bridge\Monolog\Tests\Processor;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Monolog\Processor\ConsoleCommandProcessor;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Event\ConsoleEvent;
use Symfony\Component\Console\Input\InputInterface;
class ConsoleCommandProcessorTest extends TestCase
{
private const TEST_ARGUMENTS = array('test' => 'argument');
private const TEST_OPTIONS = array('test' => 'option');
private const TEST_NAME = 'some:test';
public function testProcessor()
{
$processor = new ConsoleCommandProcessor();
$processor->addCommandData($this->getConsoleEvent());
$record = $processor(array('extra' => array()));
$this->assertArrayHasKey('command', $record['extra']);
$this->assertEquals(
array('name' => self::TEST_NAME, 'arguments' => self::TEST_ARGUMENTS),
$record['extra']['command']
);
}
public function testProcessorWithOptions()
{
$processor = new ConsoleCommandProcessor(true, true);
$processor->addCommandData($this->getConsoleEvent());
$record = $processor(array('extra' => array()));
$this->assertArrayHasKey('command', $record['extra']);
$this->assertEquals(
array('name' => self::TEST_NAME, 'arguments' => self::TEST_ARGUMENTS, 'options' => self::TEST_OPTIONS),
$record['extra']['command']
);
}
public function testProcessorDoesNothingWhenNotInConsole()
{
$processor = new ConsoleCommandProcessor(true, true);
$record = $processor(array('extra' => array()));
$this->assertEquals(array('extra' => array()), $record);
}
private function getConsoleEvent(): ConsoleEvent
{
$input = $this->getMockBuilder(InputInterface::class)->getMock();
$input->method('getArguments')->willReturn(self::TEST_ARGUMENTS);
$input->method('getOptions')->willReturn(self::TEST_OPTIONS);
$command = $this->getMockBuilder(Command::class)->disableOriginalConstructor()->getMock();
$command->method('getName')->willReturn(self::TEST_NAME);
$consoleEvent = $this->getMockBuilder(ConsoleEvent::class)->disableOriginalConstructor()->getMock();
$consoleEvent->method('getCommand')->willReturn($command);
$consoleEvent->method('getInput')->willReturn($input);
return $consoleEvent;
}
}

View File

@ -0,0 +1,162 @@
<?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\Bridge\Monolog\Tests\Processor;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Monolog\Processor\RouteProcessor;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Event\FinishRequestEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
class RouteProcessorTest extends TestCase
{
private const TEST_CONTROLLER = 'App\Controller\SomeController::someMethod';
private const TEST_ROUTE = 'someRouteName';
private const TEST_PARAMS = array('param1' => 'value1');
public function testProcessor()
{
$request = $this->mockFilledRequest();
$processor = new RouteProcessor();
$processor->addRouteData($this->mockGetResponseEvent($request));
$record = $processor(array('extra' => array()));
$this->assertArrayHasKey('requests', $record['extra']);
$this->assertCount(1, $record['extra']['requests']);
$this->assertEquals(
array('controller' => self::TEST_CONTROLLER, 'route' => self::TEST_ROUTE, 'route_params' => self::TEST_PARAMS),
$record['extra']['requests'][0]
);
}
public function testProcessorWithoutParams()
{
$request = $this->mockFilledRequest();
$processor = new RouteProcessor(false);
$processor->addRouteData($this->mockGetResponseEvent($request));
$record = $processor(array('extra' => array()));
$this->assertArrayHasKey('requests', $record['extra']);
$this->assertCount(1, $record['extra']['requests']);
$this->assertEquals(
array('controller' => self::TEST_CONTROLLER, 'route' => self::TEST_ROUTE),
$record['extra']['requests'][0]
);
}
public function testProcessorWithSubRequests()
{
$controllerFromSubRequest = 'OtherController::otherMethod';
$mainRequest = $this->mockFilledRequest();
$subRequest = $this->mockFilledRequest($controllerFromSubRequest);
$processor = new RouteProcessor(false);
$processor->addRouteData($this->mockGetResponseEvent($mainRequest));
$processor->addRouteData($this->mockGetResponseEvent($subRequest));
$record = $processor(array('extra' => array()));
$this->assertArrayHasKey('requests', $record['extra']);
$this->assertCount(2, $record['extra']['requests']);
$this->assertEquals(
array('controller' => self::TEST_CONTROLLER, 'route' => self::TEST_ROUTE),
$record['extra']['requests'][0]
);
$this->assertEquals(
array('controller' => $controllerFromSubRequest, 'route' => self::TEST_ROUTE),
$record['extra']['requests'][1]
);
}
public function testFinishRequestRemovesRelatedEntry()
{
$mainRequest = $this->mockFilledRequest();
$subRequest = $this->mockFilledRequest('OtherController::otherMethod');
$processor = new RouteProcessor(false);
$processor->addRouteData($this->mockGetResponseEvent($mainRequest));
$processor->addRouteData($this->mockGetResponseEvent($subRequest));
$processor->removeRouteData($this->mockFinishRequestEvent($subRequest));
$record = $processor(array('extra' => array()));
$this->assertArrayHasKey('requests', $record['extra']);
$this->assertCount(1, $record['extra']['requests']);
$this->assertEquals(
array('controller' => self::TEST_CONTROLLER, 'route' => self::TEST_ROUTE),
$record['extra']['requests'][0]
);
$processor->removeRouteData($this->mockFinishRequestEvent($mainRequest));
$record = $processor(array('extra' => array()));
$this->assertArrayNotHasKey('requests', $record['extra']);
}
public function testProcessorWithEmptyRequest()
{
$request = $this->mockEmptyRequest();
$processor = new RouteProcessor();
$processor->addRouteData($this->mockGetResponseEvent($request));
$record = $processor(array('extra' => array()));
$this->assertEquals(array('extra' => array()), $record);
}
public function testProcessorDoesNothingWhenNoRequest()
{
$processor = new RouteProcessor();
$record = $processor(array('extra' => array()));
$this->assertEquals(array('extra' => array()), $record);
}
private function mockGetResponseEvent(Request $request): GetResponseEvent
{
$event = $this->getMockBuilder(GetResponseEvent::class)->disableOriginalConstructor()->getMock();
$event->method('getRequest')->willReturn($request);
return $event;
}
private function mockFinishRequestEvent(Request $request): FinishRequestEvent
{
$event = $this->getMockBuilder(FinishRequestEvent::class)->disableOriginalConstructor()->getMock();
$event->method('getRequest')->willReturn($request);
return $event;
}
private function mockEmptyRequest(): Request
{
return $this->mockRequest(array());
}
private function mockFilledRequest(string $controller = self::TEST_CONTROLLER): Request
{
return $this->mockRequest(array(
'_controller' => $controller,
'_route' => self::TEST_ROUTE,
'_route_params' => self::TEST_PARAMS,
));
}
private function mockRequest(array $attributes): Request
{
$request = $this->getMockBuilder(Request::class)->disableOriginalConstructor()->getMock();
$request->attributes = new ParameterBag($attributes);
return $request;
}
}