You are here

class DatabaseLogger in Ultimate Cron 8.2

Database logger.

Plugin annotation


@LoggerPlugin(
  id = "database",
  title = @Translation("Database"),
  description = @Translation("Stores logs in the database."),
  default = TRUE,
)

Hierarchy

Expanded class hierarchy of DatabaseLogger

1 file declares its use of DatabaseLogger
LoggerPluginTest.php in tests/src/Kernel/LoggerPluginTest.php

File

src/Plugin/ultimate_cron/Logger/DatabaseLogger.php, line 30

Namespace

Drupal\ultimate_cron\Plugin\ultimate_cron\Logger
View source
class DatabaseLogger extends LoggerBase implements PluginCleanupInterface, ContainerFactoryPluginInterface {
  const CLEANUP_METHOD_DISABLED = 1;
  const CLEANUP_METHOD_EXPIRE = 2;
  const CLEANUP_METHOD_RETAIN = 3;

  /**
   * Max length for message and init message fields.
   */
  const MAX_TEXT_LENGTH = 5000;

  /**
   * @var \Drupal\Core\Database\Connection
   */
  protected $connection;

  /**
   * Constructor.
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $connection) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);
    $this->connection = $connection;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static($configuration, $plugin_id, $plugin_definition, $container
      ->get('database'));
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return array(
      'method' => static::CLEANUP_METHOD_RETAIN,
      'expire' => 86400 * 14,
      'retain' => 1000,
    );
  }

  /**
   * {@inheritdoc}
   */
  public function cleanup() {
    $jobs = CronJob::loadMultiple();
    $current = 1;
    $max = 0;
    $counter = 0;
    $count_deleted = [];
    foreach ($jobs as $job) {
      if ($job
        ->getLoggerId() === $this
        ->getPluginId()) {
        $max++;
      }
    }
    foreach ($jobs as $job) {
      if ($job
        ->getLoggerId() === $this
        ->getPluginId()) {

        // Get the plugin through the job so it has the right configuration.
        $counter = $job
          ->getPlugin('logger')
          ->cleanupJob($job);
        $class = \Drupal::entityTypeManager()
          ->getDefinition('ultimate_cron_job')
          ->getClass();
        if ($class::$currentJob) {
          $class::$currentJob
            ->setProgress($current / $max);
          $current++;
        }
        if ($counter) {

          // Store number of deleted messages for each job.
          $count_deleted[$job
            ->id()] = $counter;
        }
      }
    }
    if ($count_deleted) {
      \Drupal::logger('database_logger')
        ->info('@count_entries log entries removed for @jobs_count jobs', array(
        '@count_entries' => array_sum($count_deleted),
        '@jobs_count' => count($count_deleted),
      ));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function cleanupJob(CronJobInterface $job) {
    switch ($this->configuration['method']) {
      case static::CLEANUP_METHOD_DISABLED:
        return;
      case static::CLEANUP_METHOD_EXPIRE:
        $expire = $this->configuration['expire'];

        // Let's not delete more than ONE BILLION log entries :-o.
        $max = 10000000000;
        $chunk = 100;
        break;
      case static::CLEANUP_METHOD_RETAIN:
        $expire = 0;
        $max = $this->connection
          ->query("SELECT COUNT(lid) FROM {ultimate_cron_log} WHERE name = :name", array(
          ':name' => $job
            ->id(),
        ))
          ->fetchField();
        $max -= $this->configuration['retain'];
        if ($max <= 0) {
          return;
        }
        $chunk = min($max, 100);
        break;
      default:
        \Drupal::logger('ultimate_cron')
          ->warning('Invalid cleanup method: @method', array(
          '@method' => $this->configuration['method'],
        ));
        return;
    }

    // Chunked delete.
    $count = 0;
    do {
      $lids = $this->connection
        ->select('ultimate_cron_log', 'l')
        ->fields('l', array(
        'lid',
      ))
        ->condition('l.name', $job
        ->id())
        ->condition('l.start_time', microtime(TRUE) - $expire, '<')
        ->range(0, $chunk)
        ->orderBy('l.start_time', 'ASC')
        ->orderBy('l.end_time', 'ASC')
        ->execute()
        ->fetchCol();
      if ($lids) {
        $count += count($lids);
        $max -= count($lids);
        $chunk = min($max, 100);
        $this->connection
          ->delete('ultimate_cron_log')
          ->condition('lid', $lids, 'IN')
          ->execute();
      }
    } while ($lids && $max > 0);
    return $count;
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $form['method'] = array(
      '#type' => 'select',
      '#title' => t('Log entry cleanup method'),
      '#description' => t('Select which method to use for cleaning up logs.'),
      '#options' => $this
        ->getMethodOptions(),
      '#default_value' => $this->configuration['method'],
    );
    $form['expire'] = array(
      '#type' => 'textfield',
      '#title' => t('Log entry expiration'),
      '#description' => t('Remove log entries older than X seconds.'),
      '#default_value' => $this->configuration['expire'],
      '#fallback' => TRUE,
      '#states' => array(
        'visible' => array(
          ':input[name="logger[settings][method]"]' => array(
            'value' => static::CLEANUP_METHOD_EXPIRE,
          ),
        ),
        'required' => array(
          ':input[name="logger[settings][method]"]' => array(
            'value' => static::CLEANUP_METHOD_EXPIRE,
          ),
        ),
      ),
    );
    $form['retain'] = array(
      '#type' => 'textfield',
      '#title' => t('Retain logs'),
      '#description' => t('Retain X amount of log entries.'),
      '#default_value' => $this->configuration['retain'],
      '#fallback' => TRUE,
      '#states' => array(
        'visible' => array(
          ':input[name="logger[settings][method]"]' => array(
            'value' => static::CLEANUP_METHOD_RETAIN,
          ),
        ),
        'required' => array(
          ':input[name="logger[settings][method]"]' => array(
            'value' => static::CLEANUP_METHOD_RETAIN,
          ),
        ),
      ),
    );
    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function load($name, $lock_id = NULL, array $log_types = [
    ULTIMATE_CRON_LOG_TYPE_NORMAL,
  ]) {
    if ($lock_id) {
      $log_entry = $this->connection
        ->select('ultimate_cron_log', 'l')
        ->fields('l')
        ->condition('l.lid', $lock_id)
        ->execute()
        ->fetchObject(LogEntry::class, array(
        $name,
        $this,
      ));
    }
    else {
      $log_entry = $this->connection
        ->select('ultimate_cron_log', 'l')
        ->fields('l')
        ->condition('l.name', $name)
        ->condition('l.log_type', $log_types, 'IN')
        ->orderBy('l.start_time', 'DESC')
        ->orderBy('l.end_time', 'DESC')
        ->range(0, 1)
        ->execute()
        ->fetchObject(LogEntry::class, array(
        $name,
        $this,
      ));
    }
    if ($log_entry) {
      $log_entry->finished = TRUE;
    }
    else {
      $log_entry = new LogEntry($name, $this);
    }
    return $log_entry;
  }

  /**
   * {@inheritdoc}
   */
  public function loadLatestLogEntries(array $jobs, array $log_types) {
    if ($this->connection
      ->databaseType() !== 'mysql') {
      return parent::loadLatestLogEntries($jobs, $log_types);
    }
    $result = $this->connection
      ->query("SELECT l.*\n    FROM {ultimate_cron_log} l\n    JOIN (\n      SELECT l3.name, (\n        SELECT l4.lid\n        FROM {ultimate_cron_log} l4\n        WHERE l4.name = l3.name\n        AND l4.log_type IN (:log_types)\n        ORDER BY l4.name desc, l4.start_time DESC\n        LIMIT 1\n      ) AS lid FROM {ultimate_cron_log} l3\n      GROUP BY l3.name\n    ) l2 on l2.lid = l.lid", array(
      ':log_types' => $log_types,
    ));
    $log_entries = array();
    while ($object = $result
      ->fetchObject()) {
      if (isset($jobs[$object->name])) {
        $log_entries[$object->name] = new LogEntry($object->name, $this);
        $log_entries[$object->name]
          ->setData((array) $object);
      }
    }
    foreach ($jobs as $name => $job) {
      if (!isset($log_entries[$name])) {
        $log_entries[$name] = new LogEntry($name, $this);
      }
    }
    return $log_entries;
  }

  /**
   * {@inheritdoc}
   */
  public function getLogEntries($name, array $log_types, $limit = 10) {
    $result = $this->connection
      ->select('ultimate_cron_log', 'l')
      ->fields('l')
      ->extend('Drupal\\Core\\Database\\Query\\PagerSelectExtender')
      ->condition('l.name', $name)
      ->condition('l.log_type', $log_types, 'IN')
      ->limit($limit)
      ->orderBy('l.start_time', 'DESC')
      ->execute();
    $log_entries = array();
    while ($object = $result
      ->fetchObject(LogEntry::class, array(
      $name,
      $this,
    ))) {
      $log_entries[$object->lid] = $object;
    }
    return $log_entries;
  }

  /**
   * {@inheritdoc}
   */
  public function save(LogEntry $log_entry) {
    if (!$log_entry->lid) {
      return;
    }
    try {
      $this->connection
        ->insert('ultimate_cron_log')
        ->fields([
        'lid' => $log_entry->lid,
        'name' => $log_entry->name,
        'log_type' => $log_entry->log_type,
        'start_time' => $log_entry->start_time,
        'end_time' => $log_entry->end_time,
        'uid' => $log_entry->uid,
        'init_message' => Unicode::truncate((string) $log_entry->init_message, static::MAX_TEXT_LENGTH, FALSE, TRUE),
        'message' => Unicode::truncate((string) $log_entry->message, static::MAX_TEXT_LENGTH, FALSE, TRUE),
        'severity' => $log_entry->severity,
      ])
        ->execute();
    } catch (IntegrityConstraintViolationException $e) {

      // Row already exists. Let's update it, if we can.
      $updated = $this->connection
        ->update('ultimate_cron_log')
        ->fields([
        'name' => $log_entry->name,
        'log_type' => $log_entry->log_type,
        'start_time' => $log_entry->start_time,
        'end_time' => $log_entry->end_time,
        'init_message' => Unicode::truncate((string) $log_entry->init_message, static::MAX_TEXT_LENGTH, FALSE, TRUE),
        'message' => Unicode::truncate((string) $log_entry->message, static::MAX_TEXT_LENGTH, FALSE, TRUE),
        'severity' => $log_entry->severity,
      ])
        ->condition('lid', $log_entry->lid)
        ->condition('end_time', 0)
        ->execute();
      if (!$updated) {

        // Row was not updated, someone must have beaten us to it.
        // Let's create a new log entry.
        $lid = $log_entry->lid . '-' . uniqid('', TRUE);
        $log_entry->message = (string) t('Lock #@original_lid was already closed and logged. Creating a new log entry #@lid', [
          '@original_lid' => $log_entry->lid,
          '@lid' => $lid,
        ]) . "\n" . $log_entry->message;
        $log_entry->severity = $log_entry->severity >= 0 && $log_entry->severity < RfcLogLevel::ERROR ? $log_entry->severity : RfcLogLevel::ERROR;
        $log_entry->lid = $lid;
        $this
          ->save($log_entry);
      }
    } catch (\Exception $e) {

      // In case the insert statement above results in a database exception.
      // To ensure that the causal error is written to the log,
      // we try once to open a dedicated connection and write again.
      if (($e instanceof DatabaseException || $e instanceof \PDOException) && $this->connection
        ->getTarget() != 'ultimate_cron' && !\Drupal::config('ultimate_cron')
        ->get('bypass_transactional_safe_connection')) {
        $key = $this->connection
          ->getKey();
        $info = Database::getConnectionInfo($key);
        Database::addConnectionInfo($key, 'ultimate_cron', $info['default']);
        $this->connection = Database::getConnection('ultimate_cron', $key);

        // Now try once to log the error again.
        $this
          ->save($log_entry);
      }
      else {
        throw $e;
      }
    }
  }

  /**
   * Returns the method options.
   *
   * @return array
   */
  protected function getMethodOptions() {
    return array(
      static::CLEANUP_METHOD_DISABLED => t('Disabled'),
      static::CLEANUP_METHOD_EXPIRE => t('Remove logs older than a specified age'),
      static::CLEANUP_METHOD_RETAIN => t('Retain only a specific amount of log entries'),
    );
  }

}

Members

Namesort descending Modifiers Type Description Overrides
CronPlugin::$globalOptions public static property
CronPlugin::$instances public static property
CronPlugin::$multiple public static property 1
CronPlugin::$weight public property
CronPlugin::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies
CronPlugin::cleanForm public function Clean form of empty fallback values.
CronPlugin::drupal_array_remove_nested_value public function Modified version drupal_array_get_nested_value().
CronPlugin::fallbackalize public function Process fallback form parameters.
CronPlugin::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
CronPlugin::getGlobalOption public static function Get global plugin option.
CronPlugin::getGlobalOptions public static function Get all global plugin options.
CronPlugin::getPluginTypes public static function Returns a list of plugin types.
CronPlugin::isValid public function Default plugin valid for all jobs. 1
CronPlugin::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration
CronPlugin::setGlobalOption public static function Set global plugin option.
CronPlugin::settingsLabel public function Get label for a specific setting. 1
CronPlugin::submitConfigurationForm public function Form submission handler. Overrides PluginFormInterface::submitConfigurationForm
CronPlugin::validateConfigurationForm public function Form validation handler. Overrides PluginFormInterface::validateConfigurationForm 1
DatabaseLogger::$connection protected property
DatabaseLogger::buildConfigurationForm public function Form constructor. Overrides CronPlugin::buildConfigurationForm
DatabaseLogger::cleanup public function Cleans and purges data stored by this plugin. Overrides PluginCleanupInterface::cleanup
DatabaseLogger::cleanupJob public function
DatabaseLogger::CLEANUP_METHOD_DISABLED constant
DatabaseLogger::CLEANUP_METHOD_EXPIRE constant
DatabaseLogger::CLEANUP_METHOD_RETAIN constant
DatabaseLogger::create public static function Creates an instance of the plugin. Overrides ContainerFactoryPluginInterface::create
DatabaseLogger::defaultConfiguration public function Gets default configuration for this plugin. Overrides CronPlugin::defaultConfiguration
DatabaseLogger::getLogEntries public function Get page with log entries for a job. Overrides LoggerInterface::getLogEntries
DatabaseLogger::getMethodOptions protected function Returns the method options.
DatabaseLogger::load public function Load a log. Overrides LoggerInterface::load
DatabaseLogger::loadLatestLogEntries public function Load latest log entry for multiple jobs. Overrides LoggerBase::loadLatestLogEntries
DatabaseLogger::MAX_TEXT_LENGTH constant Max length for message and init message fields.
DatabaseLogger::save public function Saves a log entry. Overrides LoggerInterface::save
DatabaseLogger::__construct public function Constructor. Overrides CronPlugin::__construct
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
LoggerBase::$log_entries public static property
LoggerBase::createEntry public function Create a new log entry. Overrides LoggerInterface::createEntry
LoggerBase::factoryLogEntry public function Factory method for creating a new unsaved log entry object. Overrides LoggerInterface::factoryLogEntry
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.