diff --git a/core/authorize.php b/core/authorize.php index 2ddd7d6..e8ba8b5 100644 --- a/core/authorize.php +++ b/core/authorize.php @@ -148,7 +148,7 @@ function authorize_access_allowed() { } else { drupal_add_http_header('Status', '403 Forbidden'); - watchdog('access denied', 'authorize.php', NULL, WATCHDOG_WARNING); + watchdog('access denied', 'authorize.php', array(), WATCHDOG_WARNING); $page_title = t('Access denied'); $output = t('You are not allowed to access this page.'); } diff --git a/core/core.services.yml b/core/core.services.yml index 1d4d172..5071aa6 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -141,6 +141,15 @@ services: tags: - { name: needs_destruction } arguments: ['@database'] + logger.factory: + class: Drupal\Core\Logger\LoggerChannelFactory + calls: + - [setContainer, ['@service_container']] + logger.channel.default: + 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 @@ -246,7 +255,7 @@ services: arguments: ['@service_container'] controller_resolver: class: Drupal\Core\Controller\ControllerResolver - arguments: ['@service_container'] + arguments: ['@service_container', '@logger.channel.default'] title_resolver: class: Drupal\Core\Controller\TitleResolver arguments: ['@controller_resolver', '@string_translation'] @@ -317,7 +326,7 @@ services: - [setFinalMatcher, ['@router.matcher.final_matcher']] url_generator: class: Drupal\Core\Routing\UrlGenerator - arguments: ['@router.route_provider', '@path_processor_manager', '@route_processor_manager', '@config.factory', '@settings'] + arguments: ['@router.route_provider', '@path_processor_manager', '@route_processor_manager', '@config.factory', '@settings', '@logger.channel.default'] calls: - [setRequest, ['@?request']] - [setContext, ['@?router.request_context']] diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc index cbe749d..93110bb 100644 --- a/core/includes/bootstrap.inc +++ b/core/includes/bootstrap.inc @@ -1168,58 +1168,18 @@ function watchdog_exception($type, Exception $exception, $message = NULL, $varia * @param $link * A link to associate with the message. * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal::logger($channel)->log($severity, $message, $context), or any + * of the shortcut methods of \Psr\Log\LoggerTrait. + * * @see watchdog_severity_levels() * @see hook_watchdog() */ -function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG_NOTICE, $link = NULL) { - global $user, $base_root; - - static $in_error_state = FALSE; - - // It is possible that the error handling will itself trigger an error. In that case, we could - // end up in an infinite loop. To avoid that, we implement a simple static semaphore. - if (!$in_error_state && \Drupal::hasService('module_handler')) { - $in_error_state = TRUE; - - // The user object may not exist in all conditions, so 0 is substituted if needed. - $user_uid = isset($user) ? $user->id() : 0; - - // Prepare the fields to be logged - $log_entry = array( - 'type' => $type, - 'message' => $message, - 'variables' => $variables, - 'severity' => $severity, - 'link' => $link, - 'user' => $user, - 'uid' => $user_uid, - 'request_uri' => '', - 'referer' => '', - 'ip' => '', - // Request time isn't accurate for long processes, use time() instead. - 'timestamp' => time(), - ); - - try { - $request = \Drupal::request(); - $log_entry['request_uri'] = $request->getUri(); - $log_entry['referer'] = $request->headers->get('Referer', ''); - $log_entry['ip'] = $request->getClientIP(); - } - catch (DependencyInjectionRuntimeException $e) { - // We are not in a request context. - } - - // Call the logging hooks to log/process the message - foreach (\Drupal::moduleHandler()->getImplementations('watchdog') as $module) { - $function = $module . '_watchdog'; - $function($log_entry); - } - - // It is critical that the semaphore is only cleared here, in the parent - // watchdog() call (not outside the loop), to prevent recursive execution. - $in_error_state = FALSE; +function watchdog($type, $message, array $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) { + if ($link) { + $variables['link'] = $link; } + \Drupal::service('logger.factory')->get($type)->log($severity, $message, $variables); } /** diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index e1ae91a..c12e923 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -612,4 +612,18 @@ public static function formBuilder() { return static::$container->get('form_builder'); } + /** + * Returns a channel logger object. + * + * @param string $channel + * The name of the channel. Can be any string, but the general practice is + * to use the name of the subsystem calling this. + * + * @return \Drupal\Core\Logger\LoggerChannelInterface + * The logger for this channel. + */ + public static function logger($channel) { + return static::$container->get('logger.factory')->get($channel); + } + } diff --git a/core/lib/Drupal/Core/Controller/ControllerResolver.php b/core/lib/Drupal/Core/Controller/ControllerResolver.php index 7928c36..2ce142b 100644 --- a/core/lib/Drupal/Core/Controller/ControllerResolver.php +++ b/core/lib/Drupal/Core/Controller/ControllerResolver.php @@ -7,9 +7,9 @@ namespace Drupal\Core\Controller; +use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Controller\ControllerResolver as BaseControllerResolver; -use Symfony\Component\HttpKernel\Log\LoggerInterface; use Symfony\Component\DependencyInjection\ContainerAwareInterface; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -49,7 +49,7 @@ class ControllerResolver extends BaseControllerResolver implements ControllerRes * * @param \Symfony\Component\DependencyInjection\ContainerInterface $container * A ContainerInterface instance. - * @param \Symfony\Component\HttpKernel\Log\LoggerInterface $logger + * @param \Psr\Log\LoggerInterface $logger * (optional) A LoggerInterface instance. */ public function __construct(ContainerInterface $container, LoggerInterface $logger = NULL) { diff --git a/core/lib/Drupal/Core/Controller/ExceptionController.php b/core/lib/Drupal/Core/Controller/ExceptionController.php index d79567c..926d65f 100644 --- a/core/lib/Drupal/Core/Controller/ExceptionController.php +++ b/core/lib/Drupal/Core/Controller/ExceptionController.php @@ -136,7 +136,7 @@ public function on405Html(FlattenException $exception, Request $request) { */ public function on403Html(FlattenException $exception, Request $request) { $system_path = $request->attributes->get('_system_path'); - watchdog('access denied', $system_path, NULL, WATCHDOG_WARNING); + watchdog('access denied', $system_path, array(), WATCHDOG_WARNING); $system_config = $this->container->get('config.factory')->get('system.site'); $path = $this->container->get('path.alias_manager')->getSystemPath($system_config->get('page.403')); @@ -200,7 +200,7 @@ public function on403Html(FlattenException $exception, Request $request) { * A response object. */ public function on404Html(FlattenException $exception, Request $request) { - watchdog('page not found', String::checkPlain($request->attributes->get('_system_path')), NULL, WATCHDOG_WARNING); + watchdog('page not found', String::checkPlain($request->attributes->get('_system_path')), array(), WATCHDOG_WARNING); // Check for and return a fast 404 page if configured. $config = \Drupal::config('system.performance'); diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 184c767..e5b4e3e 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -15,6 +15,7 @@ use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\Compiler\ModifyServiceDefinitionsPass; use Drupal\Core\DependencyInjection\Compiler\RegisterKernelListenersPass; +use Drupal\Core\DependencyInjection\Compiler\RegisterLoggersPass; use Drupal\Core\DependencyInjection\Compiler\RegisterAccessChecksPass; use Drupal\Core\DependencyInjection\Compiler\RegisterPathProcessorsPass; use Drupal\Core\DependencyInjection\Compiler\RegisterRouteProcessorsPass; @@ -77,6 +78,8 @@ public function register(ContainerBuilder $container) { $container->addCompilerPass(new CacheContextsPass()); // Add the compiler pass for appending string translators. $container->addCompilerPass(new RegisterStringTranslatorsPass()); + // Add a compiler pass for registering logging services. + $container->addCompilerPass(new RegisterLoggersPass()); // Add the compiler pass that will process the tagged breadcrumb builder // services. $container->addCompilerPass(new RegisterBreadcrumbBuilderPass()); diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterLoggersPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterLoggersPass.php new file mode 100644 index 0000000..80723c6 --- /dev/null +++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterLoggersPass.php @@ -0,0 +1,33 @@ +getDefinition('logger.factory'); + // Loop through all available logger services (eg dblog, syslog) and add + // them to the factory. + foreach ($container->findTaggedServiceIds('logger') as $id => $attributes) { + $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0; + $factory->addMethodCall('addLogger', array(new Reference($id), $priority)); + } + } + +} diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 424b464..4b8fd4b 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -1736,7 +1736,7 @@ protected function drupalInstallationAttempted() { /** * Wraps watchdog(). */ - protected function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG_NOTICE, $link = NULL) { + protected function watchdog($type, $message, array $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) { watchdog($type, $message, $variables, $severity, $link); } diff --git a/core/lib/Drupal/Core/Logger/LogMessageParserTrait.php b/core/lib/Drupal/Core/Logger/LogMessageParserTrait.php new file mode 100644 index 0000000..309f1e1 --- /dev/null +++ b/core/lib/Drupal/Core/Logger/LogMessageParserTrait.php @@ -0,0 +1,62 @@ + $start) { + $has_psr3 = TRUE; + // Transform PSR3 style messages containing placeholders to + // \Drupal\Component\Utility\String::format() style. + $message = preg_replace('/\{(.*)\}/U', '@$1', $message); + } + foreach ($context as $key => $variable) { + // PSR3 style placeholders. + if ($has_psr3) { + // Keys are not prefixed with anything according to PSR3 specs. + // If the message is "User {username} created" the variable key will be + // just "username". + if (strpos($message, '@' . $key) !== FALSE) { + $key = '@' . $key; + } + } + if (!empty($key) && ($key[0] === '@' || $key[0] === '%' || $key[0] === '!')) { + // The key is now in \Drupal\Component\Utility\String::format() style. + $variables[$key] = $variable; + } + } + $context['variables'] = $variables; + } + +} diff --git a/core/lib/Drupal/Core/Logger/LoggerChannel.php b/core/lib/Drupal/Core/Logger/LoggerChannel.php new file mode 100644 index 0000000..3cc64e7 --- /dev/null +++ b/core/lib/Drupal/Core/Logger/LoggerChannel.php @@ -0,0 +1,157 @@ + WATCHDOG_EMERGENCY, + LogLevel::ALERT => WATCHDOG_ALERT, + LogLevel::CRITICAL => WATCHDOG_CRITICAL, + LogLevel::ERROR => WATCHDOG_ERROR, + LogLevel::WARNING => WATCHDOG_WARNING, + LogLevel::NOTICE => WATCHDOG_NOTICE, + LogLevel::INFO => WATCHDOG_INFO, + LogLevel::DEBUG => WATCHDOG_DEBUG, + ); + + /** + * An array of arrays of \Psr\Log\LoggerInterface keyed by priority. + * + * @var array + */ + protected $loggers = array(); + + /** + * The request object. + * + * @var \Symfony\Component\HttpFoundation\Request + */ + protected $request; + + /** + * The current user object. + * + * @var \Drupal\Core\Session\AccountInterface + */ + protected $currentUser; + + /** + * Constructs a LoggerChannel object + * + * @param string $channel + * The channel name for this instance. + */ + public function __construct($channel) { + $this->channel = $channel; + } + + /** + * {@inheritdoc} + */ + public function log($level, $message, array $context = array()) { + // Merge in defaults. + $context += array( + 'channel' => $this->channel, + 'link' => '', + 'user' => NULL, + 'uid' => 0, + 'request_uri' => '', + 'referer' => '', + 'ip' => '', + 'timestamp' => time(), + ); + if ($this->currentUser) { + $context['user'] = $this->currentUser; + $context['uid'] = $this->currentUser->id(); + } + // Some context values are only available when in a request context. + if ($this->request) { + $context['request_uri'] = $this->request->getUri(); + $context['referer'] = $this->request->headers->get('Referer', ''); + $context['ip'] = $this->request->getClientIP(); + } + + if (is_string($level)) { + // Convert to integer equivalent for BC. + $level = $this->levelTranslation[$level]; + } + // Call all available loggers. + foreach ($this->sortLoggers() as $logger) { + $logger->log($level, $message, $context); + } + } + + /** + * {@inheritdoc} + */ + public function setRequest(Request $request = NULL) { + $this->request = $request; + } + + /** + * {@inheritdoc} + */ + public function setCurrentUser(AccountInterface $current_user = NULL) { + $this->currentUser = $current_user; + } + + /** + * {@inheritdoc} + */ + public function setLoggers(array $loggers) { + $this->loggers = $loggers; + } + + /** + * {@inheritdoc} + */ + public function addLogger(LoggerInterface $logger, $priority = 0) { + $this->loggers[$priority][] = $logger; + } + + /** + * Sorts loggers according to priority. + * + * @return array + * An array of sorted loggers by priority. + */ + protected function sortLoggers() { + $sorted = array(); + krsort($this->loggers); + + foreach ($this->loggers as $loggers) { + $sorted = array_merge($sorted, $loggers); + } + return $sorted; + } + +} diff --git a/core/lib/Drupal/Core/Logger/LoggerChannelFactory.php b/core/lib/Drupal/Core/Logger/LoggerChannelFactory.php new file mode 100644 index 0000000..58c01ba --- /dev/null +++ b/core/lib/Drupal/Core/Logger/LoggerChannelFactory.php @@ -0,0 +1,69 @@ +channels[$channel])) { + $instance = new LoggerChannel($channel); + + // If the request is available, set it with the current user to the channel. + try { + $instance->setRequest($this->container->get('request')); + $instance->setCurrentUser($this->container->get('current_user')); + } + catch (RuntimeException $e) { + // We are not in a request context. + } + + // Pass the loggers to the channel. + $instance->setLoggers($this->loggers); + $this->channels[$channel] = $instance; + } + + return $this->channels[$channel]; + } + + /** + * {@inheritdoc} + */ + public function addLogger(LoggerInterface $logger, $priority = 0) { + // Store it so we can pass it to potential new logger instances. + $this->loggers[$priority][] = $logger; + // Add the logger to already instantiated channels. + foreach ($this->channels as $channel) { + $channel->addLogger($logger, $priority); + } + } + +} 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 @@ +insert('watchdog') - ->fields(array( - 'uid' => $log_entry['uid'], - 'type' => substr($log_entry['type'], 0, 64), - 'message' => $log_entry['message'], - 'variables' => serialize($log_entry['variables']), - 'severity' => $log_entry['severity'], - 'link' => substr($log_entry['link'], 0, 255), - 'location' => $log_entry['request_uri'], - 'referer' => $log_entry['referer'], - 'hostname' => substr($log_entry['ip'], 0, 128), - 'timestamp' => $log_entry['timestamp'], - )) - ->execute(); -} - -/** * Implements hook_form_FORM_ID_alter() for system_logging_settings(). */ function dblog_form_system_logging_settings_alter(&$form, $form_state) { diff --git a/core/modules/dblog/dblog.services.yml b/core/modules/dblog/dblog.services.yml new file mode 100644 index 0000000..c07cb32 --- /dev/null +++ b/core/modules/dblog/dblog.services.yml @@ -0,0 +1,6 @@ +services: + logger.dblog: + class: Drupal\dblog\Logger\DbLog + arguments: ['@database'] + tags: + - { name: logger } diff --git a/core/modules/dblog/lib/Drupal/dblog/Logger/DbLog.php b/core/modules/dblog/lib/Drupal/dblog/Logger/DbLog.php new file mode 100644 index 0000000..80d7136 --- /dev/null +++ b/core/modules/dblog/lib/Drupal/dblog/Logger/DbLog.php @@ -0,0 +1,67 @@ +database = $database; + } + + /** + * {@inheritdoc} + */ + public function log($level, $message, array $context = array()) { + // Remove any backtraces since they may contain an unserializable variable. + unset($context['backtrace']); + + // Convert PSR3-style messages to String::format() style, so they can be + // translated too in runtime. + $this->parseMessagePlaceholders($message, $context); + + $this->database + ->insert('watchdog') + ->fields(array( + 'uid' => $context['uid'], + 'type' => substr($context['channel'], 0, 64), + 'message' => $message, + 'variables' => serialize($context['variables']), + 'severity' => $level, + 'link' => substr($context['link'], 0, 255), + 'location' => $context['request_uri'], + 'referer' => $context['referer'], + 'hostname' => substr($context['ip'], 0, 128), + 'timestamp' => $context['timestamp'], + )) + ->execute(); + } + +} diff --git a/core/modules/dblog/lib/Drupal/dblog/Tests/DbLogTest.php b/core/modules/dblog/lib/Drupal/dblog/Tests/DbLogTest.php index 24add00..dd4a575 100644 --- a/core/modules/dblog/lib/Drupal/dblog/Tests/DbLogTest.php +++ b/core/modules/dblog/lib/Drupal/dblog/Tests/DbLogTest.php @@ -129,7 +129,7 @@ private function generateLogEntries($count, $type = 'custom', $severity = WATCHD // Prepare the fields to be logged $log = array( - 'type' => $type, + 'channel' => $type, 'message' => 'Log entry added to test the dblog row limit.', 'variables' => array(), 'severity' => $severity, @@ -144,7 +144,7 @@ private function generateLogEntries($count, $type = 'custom', $severity = WATCHD $message = 'Log entry added to test the dblog row limit. Entry #'; for ($i = 0; $i < $count; $i++) { $log['message'] = $message . $i; - dblog_watchdog($log); + $this->container->get('logger.dblog')->log($severity, $log['message'], $log); } } @@ -412,7 +412,7 @@ protected function testDBLogAddAndClear() { // Get a count of how many watchdog entries already exist. $count = db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(); $log = array( - 'type' => 'custom', + 'channel' => 'system', 'message' => 'Log entry added to test the doClearTest clear down.', 'variables' => array(), 'severity' => WATCHDOG_NOTICE, @@ -425,7 +425,7 @@ protected function testDBLogAddAndClear() { 'timestamp' => REQUEST_TIME, ); // Add a watchdog entry. - dblog_watchdog($log); + $this->container->get('logger.dblog')->log($log['severity'], $log['message'], $log); // Make sure the table count has actually been incremented. $this->assertEqual($count + 1, db_query('SELECT COUNT(*) FROM {watchdog}')->fetchField(), format_string('dblog_watchdog() added an entry to the dblog :count', array(':count' => $count))); // Login the admin user. @@ -530,7 +530,7 @@ protected function getLogEntries() { foreach ($table->tbody->tr as $row) { $entries[] = array( 'severity' => $this->getSeverityConstant($row['class']), - 'type' => $this->asText($row->td[1]), + 'channel' => $this->asText($row->td[1]), 'message' => $this->asText($row->td[3]), 'user' => $this->asText($row->td[4]), ); @@ -553,7 +553,7 @@ protected function getTypeCount(array $types) { $count = array_fill(0, count($types), 0); foreach ($entries as $entry) { foreach ($types as $key => $type) { - if ($entry['type'] == $type['type'] && $entry['severity'] == $type['severity']) { + if ($entry['channel'] == $type['type'] && $entry['severity'] == $type['severity']) { $count[$key]++; break; } diff --git a/core/modules/filter/lib/Drupal/filter/Tests/FilterSettingsTest.php b/core/modules/filter/lib/Drupal/filter/Tests/FilterSettingsTest.php index a11db5d..f37d48b 100644 --- a/core/modules/filter/lib/Drupal/filter/Tests/FilterSettingsTest.php +++ b/core/modules/filter/lib/Drupal/filter/Tests/FilterSettingsTest.php @@ -34,13 +34,11 @@ public static function getInfo() { */ function testFilterDefaults() { $filter_info = $this->container->get('plugin.manager.filter')->getDefinitions(); - $filters = array_fill_keys(array_keys($filter_info), array()); // Create text format using filter default settings. $filter_defaults_format = entity_create('filter_format', array( 'format' => 'filter_defaults', 'name' => 'Filter defaults', - 'filters' => $filters, )); $filter_defaults_format->save(); diff --git a/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php b/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php index 53e5ed7..0bebb19 100644 --- a/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php +++ b/core/modules/rest/lib/Drupal/rest/Tests/DBLogTest.php @@ -41,9 +41,9 @@ public function setUp() { */ public function testWatchdog() { // Write a log message to the DB. - watchdog('rest_test', 'Test message'); + watchdog('rest', 'Test message'); // Get the ID of the written message. - $id = db_query_range("SELECT wid FROM {watchdog} WHERE type = :type ORDER BY wid DESC", 0, 1, array(':type' => 'rest_test')) + $id = db_query_range("SELECT wid FROM {watchdog} WHERE type = :type ORDER BY wid DESC", 0, 1, array(':type' => 'rest')) ->fetchField(); // Create a user account that has the required permissions to read @@ -56,7 +56,7 @@ public function testWatchdog() { $this->assertHeader('content-type', $this->defaultMimeType); $log = Json::decode($response); $this->assertEqual($log['wid'], $id, 'Log ID is correct.'); - $this->assertEqual($log['type'], 'rest_test', 'Type of log message is correct.'); + $this->assertEqual($log['type'], 'rest', 'Type of log message is correct.'); $this->assertEqual($log['message'], 'Test message', 'Log message text is correct.'); // Request an unknown log entry. diff --git a/core/modules/syslog/lib/Drupal/syslog/Logger/SysLog.php b/core/modules/syslog/lib/Drupal/syslog/Logger/SysLog.php new file mode 100644 index 0000000..17dd040 --- /dev/null +++ b/core/modules/syslog/lib/Drupal/syslog/Logger/SysLog.php @@ -0,0 +1,86 @@ +config = $config_factory->get('syslog.settings'); + } + + /** + * Opens a connection to the system logger. + */ + protected function openConnection() { + if (!$this->connectionOpened) { + $facility = $this->config->get('facility'); + if ($facility === '') { + $facility = defined('LOG_LOCAL0') ? LOG_LOCAL0 : LOG_USER; + } + $this->connectionOpened = openlog($this->config->get('identity'), LOG_NDELAY, $facility); + } + } + + /** + * {@inheritdoc} + */ + public function log($level, $message, array $context = array()) { + global $base_url; + + // Ensure we have a connection available. + $this->openConnection(); + + // Populate the $context['variables'] with the message placeholders. + $this->parseMessagePlaceholders($message, $context); + + $entry = strtr($this->config->get('format'), array( + '!base_url' => $base_url, + '!timestamp' => $context['timestamp'], + '!type' => $context['channel'], + '!ip' => $context['ip'], + '!request_uri' => $context['request_uri'], + '!referer' => $context['referer'], + '!uid' => $context['uid'], + '!link' => strip_tags($context['link']), + '!message' => strip_tags(empty($context['variables']) ? $message : strtr($message, $context['variables'])), + )); + + syslog($level, $entry); + } + +} diff --git a/core/modules/syslog/syslog.module b/core/modules/syslog/syslog.module index a93ee93..ef282d1 100644 --- a/core/modules/syslog/syslog.module +++ b/core/modules/syslog/syslog.module @@ -87,36 +87,3 @@ function syslog_facility_list() { LOG_LOCAL7 => 'LOG_LOCAL7', ); } - -/** - * Implements hook_watchdog(). - */ -function syslog_watchdog(array $log_entry) { - global $base_url; - - $log_init = &drupal_static(__FUNCTION__, FALSE); - $config = \Drupal::config('syslog.settings'); - - if (!$log_init) { - $log_init = TRUE; - $facility = $config->get('facility'); - if ($facility === '') { - $facility = defined('LOG_LOCAL0') ? LOG_LOCAL0 : LOG_USER; - } - openlog($config->get('identity'), LOG_NDELAY, $facility); - } - - $message = strtr($config->get('format'), array( - '!base_url' => $base_url, - '!timestamp' => $log_entry['timestamp'], - '!type' => $log_entry['type'], - '!ip' => $log_entry['ip'], - '!request_uri' => $log_entry['request_uri'], - '!referer' => $log_entry['referer'], - '!uid' => $log_entry['uid'], - '!link' => strip_tags($log_entry['link']), - '!message' => strip_tags(!isset($log_entry['variables']) ? $log_entry['message'] : strtr($log_entry['message'], $log_entry['variables'])), - )); - - syslog($log_entry['severity'], $message); -} diff --git a/core/modules/syslog/syslog.services.yml b/core/modules/syslog/syslog.services.yml new file mode 100644 index 0000000..98f6041 --- /dev/null +++ b/core/modules/syslog/syslog.services.yml @@ -0,0 +1,6 @@ +services: + logger.syslog: + class: Drupal\syslog\Logger\SysLog + arguments: ['@config.factory'] + tags: + - { name: logger } diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php index 93fd554..1849f25 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityApiInfoTest.php @@ -48,9 +48,9 @@ function testEntityInfoChanges() { /** * Tests entity info cache after enabling a module with a dependency on an entity providing module. * - * @see entity_cache_test_watchdog() + * @see entity_cache_test_modules_enabled() */ - function testEntityInfoCacheWatchdog() { + function testEntityInfoCacheModulesEnabled() { \Drupal::moduleHandler()->install(array('entity_cache_test')); $entity_type = \Drupal::state()->get('entity_cache_test'); $this->assertEqual($entity_type->getLabel(), 'Entity Cache Test', 'Entity info label is correct.'); diff --git a/core/modules/system/lib/Drupal/system/Tests/Pager/PagerTest.php b/core/modules/system/lib/Drupal/system/Tests/Pager/PagerTest.php index dc8ead4..845aa28 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Pager/PagerTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Pager/PagerTest.php @@ -36,7 +36,7 @@ function setUp() { // Insert 300 log messages. for ($i = 0; $i < 300; $i++) { - watchdog('pager_test', $this->randomString(), NULL, WATCHDOG_DEBUG); + watchdog('pager_test', $this->randomString(), array(), WATCHDOG_DEBUG); } $this->admin_user = $this->drupalCreateUser(array( diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 8083f89..7189524 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -1230,94 +1230,6 @@ function hook_template_preprocess_default_variables_alter(&$variables) { } /** - * Log an event message. - * - * This hook allows modules to route log events to custom destinations, such as - * SMS, Email, pager, syslog, ...etc. - * - * @param array $log_entry - * An associative array containing the following keys: - * - type: The type of message for this entry. - * - user: The user object for the user who was logged in when the event - * happened. - * - uid: The user ID for the user who was logged in when the event happened. - * - request_uri: The request URI for the page the event happened in. - * - referer: The page that referred the user to the page where the event - * occurred. - * - ip: The IP address where the request for the page came from. - * - timestamp: The UNIX timestamp of the date/time the event occurred. - * - severity: The severity of the message; one of the following values as - * defined in @link http://www.faqs.org/rfcs/rfc3164.html RFC 3164: @endlink - * - WATCHDOG_EMERGENCY: Emergency, system is unusable. - * - WATCHDOG_ALERT: Alert, action must be taken immediately. - * - WATCHDOG_CRITICAL: Critical conditions. - * - WATCHDOG_ERROR: Error conditions. - * - WATCHDOG_WARNING: Warning conditions. - * - WATCHDOG_NOTICE: Normal but significant conditions. - * - WATCHDOG_INFO: Informational messages. - * - WATCHDOG_DEBUG: Debug-level messages. - * - link: An optional link provided by the module that called the watchdog() - * function. - * - message: The text of the message to be logged. Variables in the message - * are indicated by using placeholder strings alongside the variables - * argument to declare the value of the placeholders. See t() for - * documentation on how the message and variable parameters interact. - * - variables: An array of variables to be inserted into the message on - * display. Will be NULL or missing if a message is already translated or if - * the message is not possible to translate. - */ -function hook_watchdog(array $log_entry) { - global $base_url; - $language_interface = \Drupal::languageManager()->getCurrentLanguage(); - - $severity_list = array( - WATCHDOG_EMERGENCY => t('Emergency'), - WATCHDOG_ALERT => t('Alert'), - WATCHDOG_CRITICAL => t('Critical'), - WATCHDOG_ERROR => t('Error'), - WATCHDOG_WARNING => t('Warning'), - WATCHDOG_NOTICE => t('Notice'), - WATCHDOG_INFO => t('Info'), - WATCHDOG_DEBUG => t('Debug'), - ); - - $to = 'someone@example.com'; - $params = array(); - $params['subject'] = t('[@site_name] @severity_desc: Alert from your web site', array( - '@site_name' => \Drupal::config('system.site')->get('name'), - '@severity_desc' => $severity_list[$log_entry['severity']], - )); - - $params['message'] = "\nSite: @base_url"; - $params['message'] .= "\nSeverity: (@severity) @severity_desc"; - $params['message'] .= "\nTimestamp: @timestamp"; - $params['message'] .= "\nType: @type"; - $params['message'] .= "\nIP Address: @ip"; - $params['message'] .= "\nRequest URI: @request_uri"; - $params['message'] .= "\nReferrer URI: @referer_uri"; - $params['message'] .= "\nUser: (@uid) @name"; - $params['message'] .= "\nLink: @link"; - $params['message'] .= "\nMessage: \n\n@message"; - - $params['message'] = t($params['message'], array( - '@base_url' => $base_url, - '@severity' => $log_entry['severity'], - '@severity_desc' => $severity_list[$log_entry['severity']], - '@timestamp' => format_date($log_entry['timestamp']), - '@type' => $log_entry['type'], - '@ip' => $log_entry['ip'], - '@request_uri' => $log_entry['request_uri'], - '@referer_uri' => $log_entry['referer'], - '@uid' => $log_entry['uid'], - '@name' => $log_entry['user']->name, - '@link' => strip_tags($log_entry['link']), - '@message' => strip_tags($log_entry['message']), - )); - - drupal_mail('emaillog', 'entry', $to, $language_interface->id, $params); -} - -/** * Prepare a message based on parameters; called from drupal_mail(). * * Note that hook_mail(), unlike hook_mail_alter(), is only called on the 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 9b28815..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 @@ -6,7 +6,7 @@ */ /** - * Implements hook_watchdog(). + * Implements hook_modules_installed(). * * This hook is called during \Drupal\Core\Extension\ModuleHandler::install() * and since this hook implementation is invoked, we have to expect that this @@ -14,13 +14,11 @@ * expect to be able to retrieve the entity information that has been registered * by the required dependency module. * - * @see EnableDisableTestCase::testEntityCache() + * @see EntityApiInfoTest::testEntityInfoCacheModulesEnabled() */ -function entity_cache_test_watchdog($log_entry) { - if ($log_entry['type'] == 'system' && $log_entry['message'] == '%module module installed.') { - $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); - } +function entity_cache_test_modules_installed($modules_enabled) { + $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/EventSubscriber/SpecialAttributesRouteSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php index 0bb5006..65ba07d 100644 --- a/core/tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php +++ b/core/tests/Drupal/Tests/Core/EventSubscriber/SpecialAttributesRouteSubscriberTest.php @@ -118,7 +118,7 @@ public function testOnRouteBuildingInvalidVariables(Route $route) { namespace { if (!function_exists('watchdog')) { - function watchdog($type, $message, array $args = NULL) { + function watchdog($type, $message, array $args = array()) { } } if (!function_exists('drupal_set_message')) { diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php index a1d2ff9..d070209 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php @@ -752,6 +752,7 @@ public static function create(ContainerInterface $container) { namespace { function test_form_id_custom_submit(array &$form, array &$form_state) { } + // @todo Remove once watchdog() is removed. if (!defined('WATCHDOG_ERROR')) { define('WATCHDOG_ERROR', 3); } diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php index 44059c4..7cf98b7 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php +++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php @@ -297,7 +297,7 @@ protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = /** * {@inheritdoc} */ - protected function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG_NOTICE, $link = NULL) { + protected function watchdog($type, $message, array $variables = array(), $severity = WATCHDOG_NOTICE, $link = NULL) { } /** diff --git a/core/tests/Drupal/Tests/Core/Logger/LogMessageParserTraitTest.php b/core/tests/Drupal/Tests/Core/Logger/LogMessageParserTraitTest.php new file mode 100644 index 0000000..a7b81cf --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Logger/LogMessageParserTraitTest.php @@ -0,0 +1,95 @@ + 'Log message parser', + 'description' => 'Unit tests for the log message parser trait.', + 'group' => 'Logger', + ); + } + + /** + * {@inheritdoc} + */ + protected function setUp() { + $this->logger = $this->getObjectForTrait('Drupal\Core\Logger\LogMessageParserTrait'); + } + + /** + * Test for LogMessageParserTrait::parseMessagePlaceholders() + * + * @param array $value + * An array containing: + * - message: A string that contains a message with placeholders. + * - context: An array with placeholder values. + * @param array $expected + * An array with the expected values after the test has run. + * - message: The expected parsed message. + * - context: The expected values of the placeholders. + * + * @dataProvider providerTestParseMessagePlaceholders + * @covers ::parseMessagePlaceholders + */ + public function testParseMessagePlaceholders(array $value, array $expected) { + $class = new \ReflectionClass($this->logger); + $method = $class->getMethod('parseMessagePlaceholders'); + $method->setAccessible(TRUE); + $method->invokeArgs($this->logger, array(&$value['message'], &$value['context'])); + $this->assertEquals($expected['message'], $value['message']); + $this->assertEquals($expected['context'], $value['context']['variables']); + } + + /** + * Data provider for testParseMessagePlaceholders(). + */ + public function providerTestParseMessagePlaceholders() { + return array( + // PSR3 only message. + array( + array('message' => 'User {username} created', 'context' => array('username' => 'Dries')), + array('message' => 'User @username created', 'context' => array('@username' => 'Dries')), + ), + // PSR3 style mixed in a format_string style message. + array( + array('message' => 'User {username} created @time', 'context' => array('username' => 'Dries', '@time' => 'now')), + array('message' => 'User @username created @time', 'context' => array('@username' => 'Dries', '@time' => 'now')), + ), + // format_string style message only. + array( + array('message' => 'User @username created', 'context' => array('@username' => 'Dries')), + array('message' => 'User @username created', 'context' => array('@username' => 'Dries')), + ), + // Messsage without placeholders but wildcard characters. + array( + array('message' => 'User W-\\};~{&! created @', 'context' => array('' => '')), + array('message' => 'User W-\\};~{&! created @', 'context' => array()), + ), + // Messsage with double PSR3 style messages. + array( + array('message' => 'Test {with} two {encapsuled} strings', 'context' => array('with' => 'together', 'encapsuled' => 'awesome')), + array('message' => 'Test @with two @encapsuled strings', 'context' => array('@with' => 'together', '@encapsuled' => 'awesome')), + ), + ); + } + +} 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..23f2ef6 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Logger/LoggerChannelFactoryTest.php @@ -0,0 +1,63 @@ + '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..817f935 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Logger/LoggerChannelTest.php @@ -0,0 +1,155 @@ + '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; + } + +} diff --git a/core/update.php b/core/update.php index cd94972..5ab001b 100644 --- a/core/update.php +++ b/core/update.php @@ -230,7 +230,7 @@ function update_info_page() { function update_access_denied_page() { drupal_add_http_header('Status', '403 Forbidden'); header(\Drupal::request()->server->get('SERVER_PROTOCOL') . ' 403 Forbidden'); - watchdog('access denied', 'update.php', NULL, WATCHDOG_WARNING); + watchdog('access denied', 'update.php', array(), WATCHDOG_WARNING); $output = '

Access denied. You are not authorized to access this page. Log in using either an account with the administer software updates permission or the site maintenance account (the account you created during installation). If you cannot log in, you will have to edit settings.php to bypass this access check. To do this:

  1. With a text editor find the settings.php file on your system. From the main Drupal directory that you installed all the files into, go to sites/your_site_name if such directory exists, or else to sites/default which applies otherwise.