diff --git a/core/core.services.yml b/core/core.services.yml index cbea99b..4fbb0e0 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -131,6 +131,7 @@ services: class: Drupal\Core\Logger\LoggerChannel factory_method: get factory_service: logger.factory + arguments: ['system'] settings: class: Drupal\Component\Utility\Settings factory_class: Drupal\Component\Utility\Settings diff --git a/core/lib/Drupal/Core/Logger/LoggerBase.php b/core/lib/Drupal/Core/Logger/LogMessageParserTrait.php similarity index 78% rename from core/lib/Drupal/Core/Logger/LoggerBase.php rename to core/lib/Drupal/Core/Logger/LogMessageParserTrait.php index e099640..808c7a8 100644 --- a/core/lib/Drupal/Core/Logger/LoggerBase.php +++ b/core/lib/Drupal/Core/Logger/LogMessageParserTrait.php @@ -2,17 +2,15 @@ /** * @file - * Contains \Drupal\Core\Logger\LoggerBase. + * Contains \Drupal\Core\Logger\LogMessageParserTrait. */ namespace Drupal\Core\Logger; -use Psr\Log\AbstractLogger; - /** - * Defines a base PSR3 compatible logger class. + * Defines a trait for parsing log messages and their placeholders. */ -abstract class LoggerBase extends AbstractLogger { +trait LogMessageParserTrait { /** * Parses and transforms message and its placeholders to a common format. @@ -28,15 +26,19 @@ * * @param string $message * The message that contains the placeholders. + * If the message is in PSR3 style, it will be transformed to + * \Drupal\Component\Utility\String::format() style. * @param array $context * An array that may or may not contain placeholder variables. + * $context['variables'] will contain the transformed variables. */ protected function parseMessagePlaceholders(&$message, array &$context) { $variables = array(); $has_ps3 = FALSE; if (($start = strpos($message, '{')) !== FALSE && strpos($message, '}') > $start) { $has_ps3 = TRUE; - // Replace PSR3 style messages containing placeholders. + // Transform PSR3 style messages containing placeholders to + // \Drupal\Component\Utility\String::format() style. $message = preg_replace('/\{(.*)\}/U', '@$1', $message); } foreach ($context as $key => $variable) { diff --git a/core/lib/Drupal/Core/Logger/LoggerChannel.php b/core/lib/Drupal/Core/Logger/LoggerChannel.php index d96c7cd..6ba39d9 100644 --- a/core/lib/Drupal/Core/Logger/LoggerChannel.php +++ b/core/lib/Drupal/Core/Logger/LoggerChannel.php @@ -8,15 +8,17 @@ namespace Drupal\Core\Logger; use Drupal\Core\Session\AccountInterface; -use Psr\Log\AbstractLogger; use Psr\Log\LoggerInterface; +use Psr\Log\LoggerTrait; use Psr\Log\LogLevel; use Symfony\Component\HttpFoundation\Request; /** * Defines a logger channel that most implementations will use. */ -class LoggerChannel extends AbstractLogger { +class LoggerChannel implements LoggerChannelInterface { + + use LoggerTrait; /** * The name of the channel of this logger instance. @@ -57,7 +59,7 @@ class LoggerChannel extends AbstractLogger { * @param string $channel * The channel name for this instance. */ - public function __construct($channel = 'system') { + public function __construct($channel) { $this->channel = $channel; } @@ -98,42 +100,35 @@ public function log($level, $message, array $context = array()) { } /** - * Sets the request. - * - * @param \Symfony\Component\HttpFoundation\Request $request - * The current request object. + * {@inheritdoc} */ public function setRequest(Request $request = NULL) { $this->request = $request; } /** - * Sets the current user. - * - * @param \Drupal\Core\Session\AccountInterface $current_user - * The current user object. + * {@inheritdoc} */ public function setCurrentUser(AccountInterface $current_user = NULL) { $this->currentUser = $current_user; } /** - * Sets the loggers for this channel. - * - * @param array $loggers - * An array of arrays of \Psr\Log\LoggerInterface keyed by priority. + * {@inheritdoc} */ public function setLoggers(array $loggers) { + foreach ($loggers as $priority => $group) { + foreach ($group as $logger) { + if (!$logger instanceof LoggerInterface) { + throw new InvalidArgumentException('All loggers must implement \Psr\Log\LoggerInterface.'); + } + } + } $this->loggers = $loggers; } /** - * Adds a logger. - * - * @param \Psr\Log\LoggerInterface $logger - * The PSR-3 logger to add. - * @param int $priority - * The priority of the logger being added. + * {@inheritdoc} */ public function addLogger(LoggerInterface $logger, $priority = 0) { $this->loggers[$priority][] = $logger; diff --git a/core/lib/Drupal/Core/Logger/LoggerChannelFactory.php b/core/lib/Drupal/Core/Logger/LoggerChannelFactory.php index b489eaf..986af54 100644 --- a/core/lib/Drupal/Core/Logger/LoggerChannelFactory.php +++ b/core/lib/Drupal/Core/Logger/LoggerChannelFactory.php @@ -17,28 +17,23 @@ class LoggerChannelFactory extends ContainerAware { /** - * Array of all instantiated logger channels. + * Array of all instantiated logger channels keyed by channel name. * - * @var array + * @var Drupal\Core\Logger\LoggerChannelInterface[] */ protected $channels = array(); /** - * Array of \Psr\Log\LoggerInterface objects. + * An array of arrays of \Psr\Log\LoggerInterface keyed by priority. * * @var array */ protected $loggers = array(); /** - * Retrieves the registered logger for the requested channel. - * - * If the channel does not exist, the default "system" channel is being used. - * - * @return \Psr\Log\LoggerInterface - * The registered logger for this channel. + * {@inheritdoc} */ - public function get($channel = 'system') { + public function get($channel) { if (!isset($this->channels[$channel])) { $instance = new LoggerChannel($channel); @@ -60,17 +55,7 @@ public function get($channel = 'system') { } /** - * Adds a logger. - * - * Here is were all services tagged as 'logger' are being retrieved and then - * passed to the channels after instantiation. - * - * @param \Psr\Log\LoggerInterface $logger - * The PSR-3 logger to add. - * @param int $priority - * The priority of the logger being added. - * - * @see \Drupal\Core\DependencyInjection\Compiler\RegisterLoggersPass + * {@inheritdoc} */ public function addLogger(LoggerInterface $logger, $priority = 0) { // Store it so we can pass it to potential new logger instances. diff --git a/core/lib/Drupal/Core/Logger/LoggerChannelFactoryInterface.php b/core/lib/Drupal/Core/Logger/LoggerChannelFactoryInterface.php new file mode 100644 index 0000000..67d178c --- /dev/null +++ b/core/lib/Drupal/Core/Logger/LoggerChannelFactoryInterface.php @@ -0,0 +1,40 @@ +parseMessagePlaceholders($message, $context); diff --git a/core/modules/syslog/lib/Drupal/syslog/Logger/SysLog.php b/core/modules/syslog/lib/Drupal/syslog/Logger/SysLog.php index 2c19f0a..b0e31f8 100644 --- a/core/modules/syslog/lib/Drupal/syslog/Logger/SysLog.php +++ b/core/modules/syslog/lib/Drupal/syslog/Logger/SysLog.php @@ -8,12 +8,17 @@ namespace Drupal\syslog\Logger; use Drupal\Core\Config\ConfigFactory; -use Drupal\Core\Logger\LoggerBase; +use Drupal\Core\Logger\LogMessageParserTrait; +use Psr\Log\LoggerInterface; +use Psr\Log\LoggerTrait; /** * Redirects logging messages to syslog. */ -class SysLog extends LoggerBase { +class SysLog implements LoggerInterface { + + use LoggerTrait; + use LogMessageParserTrait; /** * A configuration object containin syslog settings. diff --git a/core/modules/system/tests/modules/entity_cache_test/entity_cache_test.module b/core/modules/system/tests/modules/entity_cache_test/entity_cache_test.module index 9a0676d..f91afde 100644 --- a/core/modules/system/tests/modules/entity_cache_test/entity_cache_test.module +++ b/core/modules/system/tests/modules/entity_cache_test/entity_cache_test.module @@ -17,7 +17,7 @@ * @see EntityApiInfoTest::testEntityInfoCacheModulesEnabled() */ function entity_cache_test_modules_installed($modules_enabled) { - $info = entity_get_info('entity_cache_test'); + $info = \Drupal::entityManager()->getDefinition('entity_cache_test'); // Store the information in a system variable to analyze it later in the // test case. \Drupal::state()->set('entity_cache_test', $info); diff --git a/core/tests/Drupal/Tests/Core/Logger/LoggerBaseTest.php b/core/tests/Drupal/Tests/Core/Logger/LogMessageParserTraitTest.php similarity index 80% rename from core/tests/Drupal/Tests/Core/Logger/LoggerBaseTest.php rename to core/tests/Drupal/Tests/Core/Logger/LogMessageParserTraitTest.php index 0f93256..a7b81cf 100644 --- a/core/tests/Drupal/Tests/Core/Logger/LoggerBaseTest.php +++ b/core/tests/Drupal/Tests/Core/Logger/LogMessageParserTraitTest.php @@ -2,25 +2,28 @@ /** * @file - * Contains \Drupal\Tests\Core\Logger\LoggerBaseTest + * Contains \Drupal\Tests\Core\Logger\LogMessageParserTraitTest */ namespace Drupal\Tests\Core\Logger; -use Drupal\Core\Logger\LoggerBase; use Drupal\Tests\UnitTestCase; /** - * Tests the base logger object. + * Tests the log message parser trait. * + * @see \Drupal\Core\Logger\LogMessageParserTrait + * @coversDefaultClass \Drupal\Core\Logger\LogMessageParserTrait + * + * @group Drupal * @group Logger */ -class LoggerBaseTest extends UnitTestCase { +class LogMessageParserTraitTest extends UnitTestCase { public static function getInfo() { return array( - 'name' => 'Logger base', - 'description' => 'Unit tests for the logger base.', + 'name' => 'Log message parser', + 'description' => 'Unit tests for the log message parser trait.', 'group' => 'Logger', ); } @@ -29,11 +32,11 @@ public static function getInfo() { * {@inheritdoc} */ protected function setUp() { - $this->logger = $this->getMockForAbstractClass('Drupal\Core\Logger\LoggerBase'); + $this->logger = $this->getObjectForTrait('Drupal\Core\Logger\LogMessageParserTrait'); } /** - * Test for LoggerBase::parseMessagePlaceholders() + * Test for LogMessageParserTrait::parseMessagePlaceholders() * * @param array $value * An array containing: @@ -45,9 +48,10 @@ protected function setUp() { * - context: The expected values of the placeholders. * * @dataProvider providerTestParseMessagePlaceholders + * @covers ::parseMessagePlaceholders */ public function testParseMessagePlaceholders(array $value, array $expected) { - $class = new \ReflectionClass('Drupal\Core\Logger\LoggerBase'); + $class = new \ReflectionClass($this->logger); $method = $class->getMethod('parseMessagePlaceholders'); $method->setAccessible(TRUE); $method->invokeArgs($this->logger, array(&$value['message'], &$value['context'])); diff --git a/core/tests/Drupal/Tests/Core/Logger/LoggerChannelFactoryTest.php b/core/tests/Drupal/Tests/Core/Logger/LoggerChannelFactoryTest.php new file mode 100644 index 0000000..61b3415 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Logger/LoggerChannelFactoryTest.php @@ -0,0 +1,60 @@ + 'Logger channel factory', + 'description' => 'Unit tests for the logger channel factory object.', + 'group' => 'Logger', + ); + } + + /** + * Test for LoggerChannelFactory::get() + * + * @covers ::get + */ + public function testGet() { + $factory = new LoggerChannelFactory(); + $factory->setContainer($this->getMock('Symfony\Component\DependencyInjection\ContainerInterface')); + + // Ensure that when called with the same argument, always the same instance + // will be returned. + $this->assertEquals($factory->get('test'), $factory->get('test')); + } + +} diff --git a/core/tests/Drupal/Tests/Core/Logger/LoggerChannelTest.php b/core/tests/Drupal/Tests/Core/Logger/LoggerChannelTest.php new file mode 100644 index 0000000..64c206e --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Logger/LoggerChannelTest.php @@ -0,0 +1,152 @@ + 'Logger channel', + 'description' => 'Unit tests for the logger channel object.', + 'group' => 'Logger', + ); + } + + /** + * Test for LoggerChannel::log() + * + * @param callable $expected + * An anonymous function to use with $this->callback() of the logger mock. + * The function should check the $context array for expected values. + * @param \Symfony\Component\HttpFoundation\Request $request + * Will be passed to the channel under test if present. + * @param \Drupal\Core\Session\AccountInterface $current_user + * Will be passed to the channel under test if present. + * + * @dataProvider providerTestLog + * @covers ::log + * @covers ::setCurrentUser + * @covers ::setRequest + */ + public function testLog(callable $expected, Request $request = NULL, AccountInterface $current_user = NULL) { + $channel = new LoggerChannel('test'); + $message = $this->randomName(); + $logger = $this->getMock('Psr\Log\LoggerInterface'); + $logger->expects($this->once()) + ->method('log') + ->with($this->anything(), $message, $this->callback($expected)); + $channel->addLogger($logger); + if ($request) { + $channel->setRequest($request); + } + if ($current_user) { + $channel->setCurrentUser($current_user); + } + $channel->log(rand(0, 7), $message); + } + + /** + * Test for LoggerChannel::addLoggers() + * + * @covers ::addLogger + * @covers ::sortLoggers + */ + public function testSortLoggers() { + $channel = new LoggerChannel($this->randomName()); + $index_order = ''; + for ($i = 0; $i < 4; $i++) { + $logger = $this->getMock('Psr\Log\LoggerInterface'); + $logger->expects($this->once()) + ->method('log') + ->will($this->returnCallback(function () use ($i, &$index_order) { + // Append the $i to the index order, so that we know the order that + // loggers got called with. + $index_order .= $i; + })); + $channel->addLogger($logger, $i); + } + + $channel->log(rand(0, 7), $this->randomName()); + // Ensure that the logger added in the end fired first. + $this->assertEquals($index_order, '3210'); + } + + /** + * Data provider for self::testLog(). + */ + public function providerTestLog() { + $account_mock = $this->getMock('Drupal\Core\Session\AccountInterface'); + $account_mock->expects($this->exactly(2)) + ->method('id') + ->will($this->returnValue(1)); + + $request_mock = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $request_mock->expects($this->exactly(2)) + ->method('getClientIp') + ->will($this->returnValue('127.0.0.1')); + $request_mock->headers = $this->getMock('Symfony\Component\HttpFoundation\ParameterBag'); + + // No request or account. + $cases [] = array( + function ($context) { + return $context['channel'] == 'test' && empty($contex['uid']) && empty($context['ip']); + }, + ); + // With account but not request. + $cases [] = array( + function ($context) { + return $context['uid'] === 1 && empty($context['ip']); + }, + NULL, + $account_mock, + ); + // With request but not account. + $cases [] = array( + function ($context) { + return $context['ip'] === '127.0.0.1' && empty($contex['uid']); + }, + $request_mock, + ); + // Both request and account. + $cases [] = array( + function ($context) { + return $context['ip'] === '127.0.0.1' && $context['uid'] === 1; + }, + $request_mock, + $account_mock, + ); + return $cases; + } + +}