diff --git a/core/modules/dblog/dblog.admin.inc b/core/modules/dblog/dblog.admin.inc
index 2b08878..c75658e 100644
--- a/core/modules/dblog/dblog.admin.inc
+++ b/core/modules/dblog/dblog.admin.inc
@@ -21,7 +21,7 @@
 function dblog_filters() {
   $filters = array();
 
-  foreach (_dblog_get_message_types() as $type) {
+  foreach (\Drupal::service('storage.dblog')->getTypes() as $type) {
     $types[$type] = t($type);
   }
 
diff --git a/core/modules/dblog/dblog.module b/core/modules/dblog/dblog.module
index 06fef10..16db824 100644
--- a/core/modules/dblog/dblog.module
+++ b/core/modules/dblog/dblog.module
@@ -61,23 +61,7 @@ function dblog_cron() {
   // Cleanup the watchdog table.
   $row_limit = \Drupal::config('dblog.settings')->get('row_limit');
 
-  // For row limit n, get the wid of the nth row in descending wid order.
-  // Counting the most recent n rows avoids issues with wid number sequences,
-  // e.g. auto_increment value > 1 or rows deleted directly from the table.
-  if ($row_limit > 0) {
-    $min_row = db_select('watchdog', 'w')
-      ->fields('w', array('wid'))
-      ->orderBy('wid', 'DESC')
-      ->range($row_limit - 1, 1)
-      ->execute()->fetchField();
-
-    // Delete all table entries older than the nth row, if nth row was found.
-    if ($min_row) {
-      db_delete('watchdog')
-        ->condition('wid', $min_row, '<')
-        ->execute();
-    }
-  }
+  \Drupal::service('storage.dblog')->deleteOldItems($row_limit);
 }
 
 /**
@@ -85,10 +69,15 @@ function dblog_cron() {
  *
  * @return array
  *   List of uniquely defined database log message types.
+ *
+ * @deprecated in Drupal 8.4.x and will be removed before 9.0.0. Use
+ *   \Drupal::service('storage.dblog')->getTypes() instead.
+ *
+ * @see https://www.drupal.org/node/2857426
  */
 function _dblog_get_message_types() {
-  return db_query('SELECT DISTINCT(type) FROM {watchdog} ORDER BY type')
-    ->fetchAllKeyed(0, 0);
+  @trigger_error("Deprecated in Drupal 8.4.x and will be removed before 9.0.0. Use \Drupal::service('storage.dblog')->getTypes() instead. See https://www.drupal.org/node/2857426", E_USER_DEPRECATION);
+  return \Drupal::service('storage.dblog')->getTypes();
 }
 
 /**
diff --git a/core/modules/dblog/dblog.services.yml b/core/modules/dblog/dblog.services.yml
index 96ac2ee..7277bad 100644
--- a/core/modules/dblog/dblog.services.yml
+++ b/core/modules/dblog/dblog.services.yml
@@ -1,7 +1,12 @@
 services:
+  storage.dblog:
+    class: Drupal\dblog\Storage\DbLogStorage
+    arguments: ['@database']
+    tags:
+      - { name: backend_overridable }
   logger.dblog:
     class: Drupal\dblog\Logger\DbLog
-    arguments: ['@database', '@logger.log_message_parser']
+    arguments: ['@storage.dblog', '@logger.log_message_parser']
     tags:
       - { name: logger }
       - { name: backend_overridable }
diff --git a/core/modules/dblog/dblog.views.inc b/core/modules/dblog/dblog.views.inc
index dfc1fde..59b15ca 100644
--- a/core/modules/dblog/dblog.views.inc
+++ b/core/modules/dblog/dblog.views.inc
@@ -68,8 +68,7 @@ function dblog_views_data() {
       'id' => 'string',
     ),
     'filter' => array(
-      'id' => 'in_operator',
-      'options callback' => '_dblog_get_message_types',
+      'id' => 'dblog_types',
     ),
     'sort' => array(
       'id' => 'standard',
diff --git a/core/modules/dblog/src/Controller/DbLogController.php b/core/modules/dblog/src/Controller/DbLogController.php
index 36ee204..2b60908 100644
--- a/core/modules/dblog/src/Controller/DbLogController.php
+++ b/core/modules/dblog/src/Controller/DbLogController.php
@@ -6,12 +6,12 @@
 use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Controller\ControllerBase;
-use Drupal\Core\Database\Connection;
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\FormBuilderInterface;
 use Drupal\Core\Logger\RfcLogLevel;
 use Drupal\Core\Url;
+use Drupal\dblog\Storage\DbLogStorageInterface;
 use Drupal\user\Entity\User;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -21,13 +21,6 @@
 class DbLogController extends ControllerBase {
 
   /**
-   * The database service.
-   *
-   * @var \Drupal\Core\Database\Connection
-   */
-  protected $database;
-
-  /**
    * The module handler service.
    *
    * @var \Drupal\Core\Extension\ModuleHandlerInterface
@@ -56,35 +49,42 @@ class DbLogController extends ControllerBase {
   protected $userStorage;
 
   /**
+   * The dblog storage service.
+   *
+   * @var \Drupal\dblog\Logger\DbLogStorageInterface
+   */
+  protected $dblogStorage;
+
+  /**
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('database'),
       $container->get('module_handler'),
       $container->get('date.formatter'),
-      $container->get('form_builder')
+      $container->get('form_builder'),
+      $container->get('storage.dblog')
     );
   }
 
   /**
    * Constructs a DbLogController object.
    *
-   * @param \Drupal\Core\Database\Connection $database
-   *   A database connection.
    * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
    *   A module handler.
    * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
    *   The date formatter service.
    * @param \Drupal\Core\Form\FormBuilderInterface $form_builder
    *   The form builder service.
+   * @param Drupal\dblog\Logger\DbLogInterface $dblog_storage
+   *   The dblogStorage service.
    */
-  public function __construct(Connection $database, ModuleHandlerInterface $module_handler, DateFormatterInterface $date_formatter, FormBuilderInterface $form_builder) {
-    $this->database = $database;
+  public function __construct(ModuleHandlerInterface $module_handler, DateFormatterInterface $date_formatter, FormBuilderInterface $form_builder, DbLogStorageInterface $dblog_storage) {
     $this->moduleHandler = $module_handler;
     $this->dateFormatter = $date_formatter;
     $this->formBuilder = $form_builder;
     $this->userStorage = $this->entityManager()->getStorage('user');
+    $this->dblogStorage = $dblog_storage;
   }
 
   /**
@@ -120,7 +120,6 @@ public static function getLogLevelClassMap() {
    */
   public function overview() {
 
-    $filter = $this->buildFilterQuery();
     $rows = array();
 
     $classes = static::getLogLevelClassMap();
@@ -151,28 +150,14 @@ public function overview() {
         'class' => array(RESPONSIVE_PRIORITY_LOW)),
     );
 
-    $query = $this->database->select('watchdog', 'w')
-      ->extend('\Drupal\Core\Database\Query\PagerSelectExtender')
-      ->extend('\Drupal\Core\Database\Query\TableSortExtender');
-    $query->fields('w', array(
-      'wid',
-      'uid',
-      'severity',
-      'type',
-      'timestamp',
-      'message',
-      'variables',
-      'link',
-    ));
-    $query->leftJoin('users_field_data', 'ufd', 'w.uid = ufd.uid');
-
-    if (!empty($filter['where'])) {
-      $query->where($filter['where'], $filter['args']);
-    }
-    $result = $query
-      ->limit(50)
-      ->orderByHeader($header)
-      ->execute();
+    $filter = $this->buildFilterQuery();
+    $sortingOptions = $header;
+
+    $result = $this->dblogStorage->loadMultiple(
+      $filter,
+      $sortingOptions,
+      50
+    );
 
     foreach ($result as $dblog) {
       $message = $this->formatMessage($dblog);
@@ -237,9 +222,10 @@ public function overview() {
    */
   public function eventDetails($event_id) {
     $build = array();
-    if ($dblog = $this->database->query('SELECT w.*, u.uid FROM {watchdog} w LEFT JOIN {users} u ON u.uid = w.uid WHERE w.wid = :id', array(':id' => $event_id))->fetchObject()) {
+    if ($dblog = $this->dblogStorage->load($event_id)) {
       $severity = RfcLogLevel::getLevels();
       $message = $this->formatMessage($dblog);
+
       $username = array(
         '#theme' => 'username',
         '#account' => $dblog->uid ? $this->userStorage->load($dblog->uid) : User::getAnonymousUser(),
@@ -303,7 +289,7 @@ public function eventDetails($event_id) {
    */
   protected function buildFilterQuery() {
     if (empty($_SESSION['dblog_overview_filter'])) {
-      return;
+      return [];
     }
 
     $this->moduleHandler->loadInclude('dblog', 'admin.inc');
@@ -378,33 +364,17 @@ public function formatMessage($row) {
    *   A build array in the format expected by drupal_render().
    */
   public function topLogMessages($type) {
+    $result = $this->dblogStorage->mostFrequentLogs($type);
+
     $header = array(
       array('data' => $this->t('Count'), 'field' => 'count', 'sort' => 'desc'),
       array('data' => $this->t('Message'), 'field' => 'message'),
     );
 
-    $count_query = $this->database->select('watchdog');
-    $count_query->addExpression('COUNT(DISTINCT(message))');
-    $count_query->condition('type', $type);
-
-    $query = $this->database->select('watchdog', 'w')
-      ->extend('\Drupal\Core\Database\Query\PagerSelectExtender')
-      ->extend('\Drupal\Core\Database\Query\TableSortExtender');
-    $query->addExpression('COUNT(wid)', 'count');
-    $query = $query
-      ->fields('w', array('message', 'variables'))
-      ->condition('w.type', $type)
-      ->groupBy('message')
-      ->groupBy('variables')
-      ->limit(30)
-      ->orderByHeader($header);
-    $query->setCountQuery($count_query);
-    $result = $query->execute();
-
     $rows = array();
-    foreach ($result as $dblog) {
-      if ($message = $this->formatMessage($dblog)) {
-        $rows[] = array($dblog->count, $message);
+    foreach ($result as $row) {
+      if ($message = $this->formatMessage($row['log'])) {
+        $rows[] = [$row['count'], $message];
       }
     }
 
diff --git a/core/modules/dblog/src/Form/DblogClearLogConfirmForm.php b/core/modules/dblog/src/Form/DblogClearLogConfirmForm.php
index b1710df..5192b29 100644
--- a/core/modules/dblog/src/Form/DblogClearLogConfirmForm.php
+++ b/core/modules/dblog/src/Form/DblogClearLogConfirmForm.php
@@ -2,10 +2,10 @@
 
 namespace Drupal\dblog\Form;
 
+use Drupal\Core\Form\ConfirmFormBase;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
-use Drupal\Core\Database\Connection;
-use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\dblog\Storage\DbLogStorageInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -14,20 +14,20 @@
 class DblogClearLogConfirmForm extends ConfirmFormBase {
 
   /**
-   * The database connection.
+   * The dblog storage service.
    *
-   * @var \Drupal\Core\Database\Connection
+   * @var \Drupal\dblog\Storage\DbLogStorageInterface
    */
-  protected $connection;
+  protected $dblogStorage;
 
   /**
    * Constructs a new DblogClearLogConfirmForm.
    *
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection.
+   * @param Drupal\dblog\Storage\DbLogStorageInterface $dblog_storage
+   *   The dblogStorage service.
    */
-  public function __construct(Connection $connection) {
-    $this->connection = $connection;
+  public function __construct(DbLogStorageInterface $dblog_storage) {
+    $this->dblogStorage = $dblog_storage;
   }
 
   /**
@@ -35,7 +35,7 @@ public function __construct(Connection $connection) {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('database')
+      $container->get('storage.dblog')
     );
   }
 
@@ -65,7 +65,7 @@ public function getCancelUrl() {
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
     $_SESSION['dblog_overview_filter'] = array();
-    $this->connection->truncate('watchdog')->execute();
+    $this->dblogStorage->deleteAll();
     drupal_set_message($this->t('Database log cleared.'));
     $form_state->setRedirectUrl($this->getCancelUrl());
   }
diff --git a/core/modules/dblog/src/LogEntry.php b/core/modules/dblog/src/LogEntry.php
new file mode 100644
index 0000000..6395520
--- /dev/null
+++ b/core/modules/dblog/src/LogEntry.php
@@ -0,0 +1,241 @@
+<?php
+
+namespace Drupal\dblog;
+
+/**
+ * A Log entry that contains information about an event registered by the dblog
+ * module.
+ */
+class LogEntry {
+
+  /**
+   * Unique log event ID.
+   *
+   * @var int
+   */
+  protected $wid;
+
+  /**
+   * The user id of the user who triggered the event.
+   *
+   * @var int
+   */
+  protected $uid;
+
+  /**
+   * Type of log message, for example "user" or "page not found."
+   *
+   * @var string
+   */
+  protected $type;
+
+  /**
+    * Text of log message to be passed into the t() function.
+    *
+    * @var string
+    */
+  protected $message;
+
+  /**
+   * Serialized array of variables that match the message string and that is
+   * passed into the t() function.
+   *
+   * @var string
+   */
+  protected $variables;
+
+  /**
+   * The severity level of the event ranges from 0 (Emergency) to 7 (Debug)
+   *
+   * @var int
+   */
+  protected $severity;
+
+  /**
+   * Link to view the result of the event.
+   *
+   * @var string
+   */
+  protected $link;
+
+  /**
+   * URL of the origin of the event.
+   *
+   * @var string
+   */
+  protected $location;
+
+  /**
+   * URL of referring page.
+   *
+   * @var string
+   */
+  protected $referer;
+
+  /**
+   * Hostname of the user who triggered the event.
+   *
+   * @var string
+   */
+  protected $hostname;
+
+  /**
+   * Unix timestamp of when event occurred.
+   *
+   * @var int
+   */
+  protected $timestamp;
+
+  /**
+   * Creates a Log entry.
+   *
+   * @param array $data
+   *   An array that contains the info of the log. Where keys are the colums of
+   *   the watchdog table.
+   */
+  public function __construct(array $data) {
+    $internal_properties = array_keys(get_object_vars($this));
+
+    foreach ($data as $key => $value) {
+      if (is_string($key) && in_array($key, $internal_properties)) {
+        $this->$key = $value;
+      }
+    }
+  }
+
+  /**
+   * Gets the id of the log.
+   *
+   * @return int
+   */
+  public function id() {
+    return $this->wid;
+  }
+
+  /**
+   * Gets the user id of the user who triggered the event.
+   *
+   * @return int
+   */
+  public function getUid() {
+    return $this->uid;
+  }
+
+  /**
+   * Gets the type of the log.
+   *
+   * @return string
+   */
+  public function getType() {
+    return $this->type;
+  }
+
+  /**
+   * Gets the message of the log.
+   *
+   * @return string
+   */
+  public function getMessage() {
+    return $this->message;
+  }
+
+  /**
+   * Gets the variables of the log.
+   *
+   * @return string
+   */
+  public function getVariables() {
+    return $this->variables;
+  }
+
+  /**
+   * Gets the severity of the log. Ranges from 0 (Emergency) to 7 (Debug).
+   *
+   * @return int
+   */
+  public function getSeverity() {
+    return $this->severity;
+  }
+
+  /**
+   * Gets the link of the log.
+   *
+   * @return string
+   */
+  public function getLink() {
+    return $this->link;
+  }
+
+  /**
+   * Gets the URL of the origin of the event.
+   *
+   * @return string
+   */
+  public function getLocation() {
+    return $this->location;
+  }
+
+  /**
+   * Gets the referer of the log.
+   *
+   * @return string
+   */
+  public function getReferer() {
+    return $this->referer;
+  }
+
+  /**
+   * Gets the hostname of the user who triggered the event.
+   *
+   * @return string
+   */
+  public function getHostname() {
+    return $this->hostname;
+  }
+
+  /**
+   * Gets the unix timestamp of when the event occurred.
+   *
+   * @return int
+   */
+  public function getTimestamp() {
+    return $this->timestamp;
+  }
+
+  /**
+   * For backward compatibility with $log->wid, $log->uid, $log->severity, etc.
+   *
+   * @param string $key
+   *
+   * @return mixed
+   */
+  public function __get($key) {
+    if (is_string($key) && isset($this->$key)) {
+      return $this->$key;
+    }
+  }
+
+  /**
+   * For backward compatibility with $log->wid, $log->uid, $log->severity, etc.
+   *
+   * @param string $key
+   * @return boolean
+   */
+  public function __isset($key) {
+    return is_string($key) && isset($this->$key);
+  }
+
+  /**
+   * Returns a log event represented as an array.
+   *
+   * @return array
+   */
+  public function toArray() {
+    $return = [];
+    foreach (get_object_vars($this) as $key => $value) {
+      $return[$key] = $value;
+    }
+    return $return;
+  }
+
+}
diff --git a/core/modules/dblog/src/Logger/DbLog.php b/core/modules/dblog/src/Logger/DbLog.php
index af77be4..c076d28 100644
--- a/core/modules/dblog/src/Logger/DbLog.php
+++ b/core/modules/dblog/src/Logger/DbLog.php
@@ -2,13 +2,11 @@
 
 namespace Drupal\dblog\Logger;
 
-use Drupal\Component\Utility\Unicode;
-use Drupal\Core\Database\Connection;
-use Drupal\Core\Database\Database;
-use Drupal\Core\Database\DatabaseException;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\Logger\LogMessageParserInterface;
 use Drupal\Core\Logger\RfcLoggerTrait;
+use Drupal\dblog\LogEntry;
+use Drupal\dblog\Storage\DbLogStorageInterface;
 use Psr\Log\LoggerInterface;
 
 /**
@@ -20,15 +18,19 @@ class DbLog implements LoggerInterface {
 
   /**
    * The dedicated database connection target to use for log entries.
+   *
+   * @deprecated in Drupal 8.4.x and will be removed before 9.0.0. Use
+   *  Drupal\dblog\Storage\DbLogStorage::DEDICATED_DBLOG_CONNECTION_TARGET
+   *  instead.
    */
   const DEDICATED_DBLOG_CONNECTION_TARGET = 'dedicated_dblog';
 
   /**
-   * The database connection object.
+   * The log storage system.
    *
-   * @var \Drupal\Core\Database\Connection
+   * @var \Drupal\dblog\Storage\DbLogStorageInterface
    */
-  protected $connection;
+  protected $storage;
 
   /**
    * The message's placeholders parser.
@@ -40,13 +42,13 @@ class DbLog implements LoggerInterface {
   /**
    * Constructs a DbLog object.
    *
-   * @param \Drupal\Core\Database\Connection $connection
-   *   The database connection object.
+   * @param \Drupal\dblog\Storage\DbLogStorageInterface $storage
+   *   The storage handler for logs.
    * @param \Drupal\Core\Logger\LogMessageParserInterface $parser
    *   The parser to use when extracting message variables.
    */
-  public function __construct(Connection $connection, LogMessageParserInterface $parser) {
-    $this->connection = $connection;
+  public function __construct(DbLogStorageInterface $storage, LogMessageParserInterface $parser) {
+    $this->storage = $storage;
     $this->parser = $parser;
   }
 
@@ -61,52 +63,28 @@ public function log($level, $message, array $context = array()) {
     // translated too in runtime.
     $message_placeholders = $this->parser->parseMessagePlaceholders($message, $context);
 
-    try {
-      $this->connection
-        ->insert('watchdog')
-        ->fields(array(
-          'uid' => $context['uid'],
-          'type' => Unicode::substr($context['channel'], 0, 64),
-          'message' => $message,
-          'variables' => serialize($message_placeholders),
-          'severity' => $level,
-          'link' => $context['link'],
-          'location' => $context['request_uri'],
-          'referer' => $context['referer'],
-          'hostname' => Unicode::substr($context['ip'], 0, 128),
-          'timestamp' => $context['timestamp'],
-        ))
-        ->execute();
-    }
-    catch (\Exception $e) {
-      // When running Drupal on MySQL or MariaDB you can run into several errors
-      // that corrupt the database connection. Some examples for these kind of
-      // errors on the database layer are "1100 - Table 'xyz' was not locked
-      // with LOCK TABLES" and "1153 - Got a packet bigger than
-      // 'max_allowed_packet' bytes". If such an error happens, the MySQL server
-      // invalidates the connection and answers all further requests in this
-      // connection with "2006 - MySQL server had gone away". In that 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 (
-        // Only handle database related exceptions.
-        ($e instanceof DatabaseException || $e instanceof \PDOException) &&
-        // Avoid an endless loop of re-write attempts.
-        $this->connection->getTarget() != self::DEDICATED_DBLOG_CONNECTION_TARGET
-      ) {
-        // Open a dedicated connection for logging.
-        $key = $this->connection->getKey();
-        $info = Database::getConnectionInfo($key);
-        Database::addConnectionInfo($key, self::DEDICATED_DBLOG_CONNECTION_TARGET, $info['default']);
-        $this->connection = Database::getConnection(self::DEDICATED_DBLOG_CONNECTION_TARGET, $key);
-        // Now try once to log the error again.
-        $this->log($level, $message, $context);
-      }
-      else {
-        throw $e;
-      }
-    }
+    $log = new LogEntry([
+      'uid' => $context['uid'],
+      'message' => $message,
+      'severity' => $level,
+      'type' => $context['channel'],
+      'variables' => serialize($message_placeholders),
+      'link' => $context['link'],
+      'location' => $context['request_uri'],
+      'referer' => $context['referer'],
+      'hostname' => $context['ip'],
+      'timestamp' => $context['timestamp'],
+    ]);
+
+    $this->storage->save($log);
+  }
+
+  /**
+   * Returns the storage handler for logs.
+   * @return \Drupal\dblog\Storage\DbLogStorageInterface
+   */
+  public function getStorage() {
+    return $this->storage;
   }
 
 }
diff --git a/core/modules/dblog/src/Plugin/rest/resource/DBLogResource.php b/core/modules/dblog/src/Plugin/rest/resource/DBLogResource.php
index 4ac76f0..82fba2d 100644
--- a/core/modules/dblog/src/Plugin/rest/resource/DBLogResource.php
+++ b/core/modules/dblog/src/Plugin/rest/resource/DBLogResource.php
@@ -38,10 +38,10 @@ class DBLogResource extends ResourceBase {
    */
   public function get($id = NULL) {
     if ($id) {
-      $record = db_query("SELECT * FROM {watchdog} WHERE wid = :wid", array(':wid' => $id))
-        ->fetchAssoc();
-      if (!empty($record)) {
-        return new ResourceResponse($record);
+      $log = \Drupal::service('storage.dblog')->load($id);
+
+      if (!empty($log)) {
+        return new ResourceResponse($log->toArray());
       }
 
       throw new NotFoundHttpException(t('Log entry with ID @id was not found', array('@id' => $id)));
diff --git a/core/modules/dblog/src/Plugin/views/filter/DblogTypes.php b/core/modules/dblog/src/Plugin/views/filter/DblogTypes.php
new file mode 100644
index 0000000..fa265fe
--- /dev/null
+++ b/core/modules/dblog/src/Plugin/views/filter/DblogTypes.php
@@ -0,0 +1,24 @@
+<?php
+
+namespace Drupal\dblog\Plugin\views\filter;
+
+use Drupal\views\Plugin\views\filter\InOperator;
+
+/**
+ * Exposes log types to views module.
+ *
+ * @ViewsFilter("dblog_types")
+ */
+class DblogTypes extends InOperator {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValueOptions() {
+    if (!isset($this->valueOptions)) {
+      $this->valueOptions = \Drupal::service('storage.dblog')->getTypes();
+    }
+    return $this->valueOptions;
+  }
+
+}
diff --git a/core/modules/dblog/src/Storage/DbLogStorage.php b/core/modules/dblog/src/Storage/DbLogStorage.php
new file mode 100644
index 0000000..f5dde9b
--- /dev/null
+++ b/core/modules/dblog/src/Storage/DbLogStorage.php
@@ -0,0 +1,236 @@
+<?php
+
+namespace Drupal\dblog\Storage;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\DatabaseException;
+use Drupal\Core\Logger\LogMessageParserInterface;
+use Drupal\dblog\LogEntry;
+
+/**
+ * Provides a class for CRUD operations on database logs.
+ */
+class DbLogStorage implements DbLogStorageInterface {
+
+  /**
+   * The table for the log storage.
+   */
+  const TABLE = 'watchdog';
+
+  /**
+   * The dedicated database connection target to use for log entries.
+   */
+  const DEDICATED_DBLOG_CONNECTION_TARGET = 'dedicated_dblog';
+
+  /**
+   * The database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $connection;
+
+  /**
+   * Constructs a dblog storage object.
+   *
+   * @param \Drupal\Core\Database\Connection $connection
+   *   A database connection for reading and writing path aliases.
+   */
+  public function __construct(Connection $connection) {
+    $this->connection = $connection;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(LogEntry $log) {
+    try {
+      $this->connection
+        ->insert(self::TABLE)
+        ->fields([
+          'uid' => $log->getUid(),
+          'type' => Unicode::substr($log->getType(), 0, 64),
+          'message' => $log->getMessage(),
+          'variables' => $log->getVariables(),
+          'severity' => $log->getSeverity(),
+          'link' => $log->getLink(),
+          'location' => $log->getLocation(),
+          'referer' => $log->getReferer(),
+          'hostname' => Unicode::substr($log->getHostname(), 0, 128),
+          'timestamp' => $log->getTimestamp(),
+        ])
+        ->execute();
+    }
+    catch (\Exception $e) {
+      // When running Drupal on MySQL or MariaDB you can run into several errors
+      // that corrupt the database connection. Some examples for these kind of
+      // errors on the database layer are "1100 - Table 'xyz' was not locked
+      // with LOCK TABLES" and "1153 - Got a packet bigger than
+      // 'max_allowed_packet' bytes". If such an error happens, the MySQL server
+      // invalidates the connection and answers all further requests in this
+      // connection with "2006 - MySQL server had gone away". In that 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 (
+        // Only handle database related exceptions.
+        ($e instanceof DatabaseException || $e instanceof \PDOException) &&
+        // Avoid an endless loop of re-write attempts.
+        $this->connection->getTarget() != self::DEDICATED_DBLOG_CONNECTION_TARGET
+      ) {
+        // Open a dedicated connection for logging.
+        $key = $this->connection->getKey();
+        $info = Database::getConnectionInfo($key);
+        Database::addConnectionInfo($key, self::DEDICATED_DBLOG_CONNECTION_TARGET, $info['default']);
+        $this->connection = Database::getConnection(self::DEDICATED_DBLOG_CONNECTION_TARGET, $key);
+        // Now try once to log the error again.
+        $this->save($log);
+      }
+      else {
+        throw $e;
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($id) {
+    $log_record = $this->connection
+      ->select(self::TABLE, 'w')
+      ->fields('w')
+      ->condition('wid', $id)
+      ->range(0, 1)
+      ->execute()
+      ->fetchAssoc();
+
+    if (!empty($log_record)) {
+      return new LogEntry($log_record);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadMultiple(array $filter = [], array $sort = [], $limit = 50) {
+    $query = $this->connection->select(self::TABLE, 'w')
+      ->extend('\Drupal\Core\Database\Query\PagerSelectExtender')
+      ->extend('\Drupal\Core\Database\Query\TableSortExtender');
+
+    $query->fields('w', [
+      'wid',
+      'uid',
+      'severity',
+      'type',
+      'timestamp',
+      'message',
+      'variables',
+      'link',
+    ]);
+
+    $query->leftJoin('users_field_data', 'ufd', 'w.uid = ufd.uid');
+
+    if (!empty($filter['where'])) {
+      $query->where($filter['where'], $filter['args']);
+    }
+
+    $result = $query
+      ->limit($limit)
+      ->orderByHeader($sort)
+      ->execute();
+
+    $logs = [];
+    foreach ($result as $log_record) {
+      $logs[] = new LogEntry((array)$log_record);
+    }
+
+    return $logs;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteAll() {
+    $this->connection->truncate(self::TABLE)->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteOldItems($logs_to_keep) {
+    // For row limit n, get the wid of the nth row in descending wid order.
+    // Counting the most recent n rows avoids issues with wid number sequences,
+    // e.g. auto_increment value > 1 or rows deleted directly from the table.
+    if ($logs_to_keep > 0) {
+      $min_row = $this->connection
+        ->select(self::TABLE, 'w')
+        ->fields('w', ['wid'])
+        ->orderBy('wid', 'DESC')
+        ->range($logs_to_keep - 1, 1)
+        ->execute()
+        ->fetchField();
+
+      // Delete all table entries older than the nth row, if nth row was found.
+      if ($min_row) {
+        $this->connection
+          ->delete(self::TABLE)
+          ->condition('wid', $min_row, '<')
+          ->execute();
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTypes() {
+    return $this->connection
+      ->select(self::TABLE, 'w')
+      ->fields('w', ['type'])
+      ->distinct()
+      ->orderBy('type')
+      ->execute()
+      ->fetchAllKeyed(0, 0);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mostFrequentLogs($type) {
+    $count_query = $this->connection->select(self::TABLE);
+    $count_query->addExpression('COUNT(DISTINCT(message))');
+    $count_query->condition('type', $type);
+
+    $query = $this->connection->select(self::TABLE, 'w')
+      ->extend('\Drupal\Core\Database\Query\PagerSelectExtender')
+      ->extend('\Drupal\Core\Database\Query\TableSortExtender');
+    $query->addExpression('COUNT(wid)', 'count');
+
+    $header = [
+      ['field' => 'count', 'sort' => 'desc'],
+      ['field' => 'message'],
+    ];
+    $query = $query
+      ->fields('w', array('message', 'variables'))
+      ->condition('w.type', $type)
+      ->groupBy('message')
+      ->groupBy('variables')
+      ->limit(30)
+      ->orderByHeader($header);
+
+    $query->setCountQuery($count_query);
+
+    $result = $query->execute();
+
+    $top_logs = [];
+    foreach ($result as $row) {
+      $log = new LogEntry(['message' => $row->message, 'variables' => $row->variables]);
+      $top_logs[] = [
+        'count' => $row->count,
+        'log' => $log,
+      ];
+    }
+    return $top_logs;
+  }
+}
diff --git a/core/modules/dblog/src/Storage/DbLogStorageInterface.php b/core/modules/dblog/src/Storage/DbLogStorageInterface.php
new file mode 100644
index 0000000..c863b50
--- /dev/null
+++ b/core/modules/dblog/src/Storage/DbLogStorageInterface.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Drupal\dblog\Storage;
+
+use Drupal\dblog\LogEntry;
+
+/**
+ * Logs events into a database.
+ */
+interface DbLogStorageInterface {
+
+  /**
+   * Save a log entry into a database.
+   *
+   * @param Drupal\dblog\LogEntry $log
+   *   The log entry to save.
+   */
+  public function save(LogEntry $log);
+
+  /**
+   * Loads a log entry for the specified ID.
+   *
+   * @param int $id
+   *   The ID of the log entry.
+   *
+   * @return Drupal\dblog\LogEntry|NULL
+   *   The log entry for the given $id if it exists, or NULL otherwise.
+   */
+  public function load($id);
+
+  /**
+   * Get a collection of logs.
+   *
+   * @param array $filter
+   *   An array with the filter data to filter the collection. The following
+   *   array keys can be set:
+   *   - where: The filter condition.
+   *   - args: A list of arguments.
+   * @param array $sort
+   *   An array of sorting options to sort the collection.
+   * @param int $limit
+   *   The number of logs to retrieve.
+   *
+   * @return array
+   *   A collection of Drupal\dblog\LogEntry objects.
+   */
+  public function loadMultiple(array $filter, array $sort, $limit);
+
+  /**
+   * Deletes all the logs.
+   */
+  public function deleteAll();
+
+  /**
+   * Deletes old log items.
+   *
+   * @param int $logs_to_keep
+   *   The number of items to keep.
+   */
+  public function deleteOldItems($logs_to_keep);
+
+  /**
+   * Gathers a list of uniquely defined log types.
+   *
+   * @return array
+   *   An associative array of log types, where keys and values are log types.
+   */
+  public function getTypes();
+
+  /**
+   * Returns the most frequent log messages of a given event type.
+   *
+   * @param string $type
+   *   Type of database log events to display (e.g., 'search').
+   *
+   * @return array
+   *   An array of arrays. Each item of the array is another array with the
+   *   following keys:
+   *   - log: a Drupal\dblog\LogEntry instance.
+   *   - count: the number of occurrences for the log message.
+   */
+  public function mostFrequentLogs($type);
+
+}
diff --git a/core/modules/dblog/tests/src/Kernel/DbLogStorageTest.php b/core/modules/dblog/tests/src/Kernel/DbLogStorageTest.php
new file mode 100644
index 0000000..05972de
--- /dev/null
+++ b/core/modules/dblog/tests/src/Kernel/DbLogStorageTest.php
@@ -0,0 +1,159 @@
+<?php
+
+namespace Drupal\Tests\dblog\Kernel;
+
+use Drupal\Core\Logger\RfcLogLevel;
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * @coversDefaultClass Drupal\dblog\Storage\DbLogStorage
+ *
+ * @group dblog
+ */
+class DbLogStorageTest extends KernelTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['dblog', 'user'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installEntitySchema('user');
+    $this->installSchema('dblog', array('watchdog'));
+  }
+
+  /**
+   * @covers ::load
+   */
+  public function testLoad() {
+    \Drupal::logger('dblog_test')->warning('Test message');
+
+    $log = \Drupal::service('storage.dblog')->load(1);
+
+    $this->assertEquals($log->message, 'Test message');
+    $this->assertEquals($log->severity, RfcLogLevel::WARNING);
+    $this->assertEquals($log->type, 'dblog_test');
+
+    $log = \Drupal::service('storage.dblog')->load(-1);
+    $this->assertNull($log);
+    $log = \Drupal::service('storage.dblog')->load('Not valid');
+    $this->assertNull($log);
+  }
+
+  /**
+   * @covers ::loadMultiple
+   */
+  public function testGetLoadMultiple() {
+    for ($a = 1; $a <= 30 ; $a += 3) {
+      \Drupal::logger('dblog_test')->warning('Log entry ' . $a);
+      $b = $a + 1;
+      \Drupal::logger('dblog_test')->notice('Log entry ' . $b);
+      $c = $a + 2;
+      \Drupal::logger('dblog_test')->info('Log entry ' . $c);
+    }
+
+    $collection = \Drupal::service('storage.dblog')->loadMultiple();
+    $this->assertEquals(30, count($collection));
+    $i = 1;
+    $vars = [];
+    foreach ($collection as $log) {
+      $this->assertEquals($log->message, 'Log entry ' . $i++);
+    }
+
+    $filter = [
+      'where' => 'w.severity = ?',
+      'args' => [RfcLogLevel::WARNING]
+    ];
+    // Get the first 5 warnings.
+    $collection = \Drupal::service('storage.dblog')->loadMultiple($filter, [], 5);
+    $this->assertEquals(5, count($collection));
+    foreach ($collection as $log) {
+      $this->assertEquals($log->getSeverity(), RfcLogLevel::WARNING);
+    }
+  }
+
+  /**
+   * @covers ::deleteAll
+   */
+  public function testDeleteAll() {
+    for ($i = 1; $i <= 10 ; $i++) {
+      \Drupal::logger('dblog_test')->warning('Log entry ' . $i);
+    }
+
+    $collection = \Drupal::service('storage.dblog')->loadMultiple();
+    $this->assertEquals(10, count($collection));
+
+    \Drupal::service('storage.dblog')->deleteAll();
+
+    $collection = \Drupal::service('storage.dblog')->loadMultiple();
+    $this->assertEquals(0, count($collection));
+  }
+
+  /**
+   * @covers ::deleteOldItems
+   */
+  public function testDeleteOldItems() {
+    // Create 10 items
+    for ($i = 1; $i <= 10 ; $i++) {
+      \Drupal::logger('dblog_test')->warning('Log entry ' . $i);
+    }
+
+    $collection = \Drupal::service('storage.dblog')->loadMultiple();
+    $this->assertEquals(10, count($collection));
+
+    // Delete everything but the last 3 logs entries.
+    \Drupal::service('storage.dblog')->deleteOldItems(3);
+
+    $collection = \Drupal::service('storage.dblog')->loadMultiple();
+    $this->assertEquals(3, count($collection));
+  }
+
+  /**
+   * @covers ::getTypes
+   */
+  public function testGetTypes() {
+    \Drupal::logger('dblog_test_1')->warning('Log entry');
+    \Drupal::logger('dblog_test_2')->warning('Log entry');
+    \Drupal::logger('dblog_test_3')->warning('Log entry');
+    \Drupal::logger('dblog_test_1')->warning('Log entry');
+    \Drupal::logger('dblog_test_1')->warning('Log entry');
+
+    $log_types = $collection = \Drupal::service('storage.dblog')->getTypes();
+
+    $this->assertEquals([
+      'dblog_test_1' => 'dblog_test_1',
+      'dblog_test_2' => 'dblog_test_2',
+      'dblog_test_3' => 'dblog_test_3'
+      ], $log_types);
+  }
+
+  /**
+   * @covers ::mostFrequentLogs
+   */
+  public function testMostFrequentLogs() {
+    \Drupal::logger('dblog_test')->warning('Log entry A');
+    \Drupal::logger('dblog_test')->warning('Log entry B');
+    \Drupal::logger('dblog_test')->warning('Log entry C');
+    \Drupal::logger('dblog_test')->warning('Log entry A');
+    \Drupal::logger('dblog_test')->warning('Log entry A');
+    \Drupal::logger('dblog_test')->warning('Log entry C');
+
+    $most_frequent_logs = \Drupal::service('storage.dblog')->mostFrequentLogs('dblog_test');
+
+    $reference_table = [
+      'Log entry A' => 3,
+      'Log entry B' => 1,
+      'Log entry C' => 2,
+    ];
+
+    foreach ($most_frequent_logs as $row) {
+      $this->assertEquals($row['count'], $reference_table[$row['log']->getMessage()]);
+    }
+  }
+
+}
