diff --git a/core/lib/Drupal/Core/Logger/LogInterface.php b/core/lib/Drupal/Core/Logger/LogInterface.php
new file mode 100644
index 0000000..59515e7
--- /dev/null
+++ b/core/lib/Drupal/Core/Logger/LogInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\Core\Logger;
+
+interface LogInterface {
+
+  /**
+   * Gets the log message.
+   * @return string
+   */
+  public function message();
+
+  /**
+   * Gets the level of the log.
+   * @return string
+   */
+  public function level();
+
+  /**
+   * Gets the context of the log event.
+   *
+   * @param string $key
+   *   Optional. A key to specify a specific context value.
+   * @return array|mixed
+   *   The entire context array if attributes is NULL. Or an specific context
+   *   value for the given context $key.
+   */
+  public function context($key = NULL);
+
+}
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..d4eb15f 100644
--- a/core/modules/dblog/dblog.module
+++ b/core/modules/dblog/dblog.module
@@ -61,34 +61,20 @@ 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);
 }
 
 /**
  * Gathers a 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.
+ *
  * @return array
  *   List of uniquely defined database log message types.
  */
 function _dblog_get_message_types() {
-  return db_query('SELECT DISTINCT(type) FROM {watchdog} ORDER BY type')
-    ->fetchAllKeyed(0, 0);
+  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/src/Controller/DbLogController.php b/core/modules/dblog/src/Controller/DbLogController.php
index 36ee204..fd995e2 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,37 +150,23 @@ 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);
-      if ($message && isset($dblog->wid)) {
+      $message = $dblog->formatMessage();
+      if ($message && $dblog->id()) {
         $title = Unicode::truncate(Html::decodeEntities(strip_tags($message)), 256, TRUE, TRUE);
         $log_text = Unicode::truncate($title, 56, TRUE, TRUE);
         // The link generator will escape any unsafe HTML entities in the final
         // text.
-        $message = $this->l($log_text, new Url('dblog.event', array('event_id' => $dblog->wid), array(
+        $message = $this->l($log_text, new Url('dblog.event', array('event_id' => $dblog->id()), array(
           'attributes' => array(
             // Provide a title for the link for useful hover hints. The
             // Attribute object will escape any unsafe HTML entities in the
@@ -192,20 +177,20 @@ public function overview() {
       }
       $username = array(
         '#theme' => 'username',
-        '#account' => $this->userStorage->load($dblog->uid),
+        '#account' => $this->userStorage->load($dblog->context('uid')),
       );
       $rows[] = array(
         'data' => array(
           // Cells.
           array('class' => array('icon')),
           $this->t($dblog->type),
-          $this->dateFormatter->format($dblog->timestamp, 'short'),
+          $this->dateFormatter->format($dblog->context('timestamp'), 'short'),
           $message,
           array('data' => $username),
-          array('data' => array('#markup' => $dblog->link)),
+          array('data' => array('#markup' => $dblog->context('link'))),
         ),
         // Attributes for table row.
-        'class' => array(Html::getClass('dblog-' . $dblog->type), $classes[$dblog->severity]),
+        'class' => array(Html::getClass('dblog-' . $dblog->context('type')), $classes[$dblog->level()]),
       );
     }
 
@@ -237,9 +222,11 @@ 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()) {
+    $dblog = $this->dblogStorage->load($event_id);
+    if (!empty($dblog)) {
       $severity = RfcLogLevel::getLevels();
-      $message = $this->formatMessage($dblog);
+      $message = $dblog->formatMessage();
+
       $username = array(
         '#theme' => 'username',
         '#account' => $dblog->uid ? $this->userStorage->load($dblog->uid) : User::getAnonymousUser(),
@@ -247,11 +234,11 @@ public function eventDetails($event_id) {
       $rows = array(
         array(
           array('data' => $this->t('Type'), 'header' => TRUE),
-          $this->t($dblog->type),
+          $this->t($dblog->context('type')),
         ),
         array(
           array('data' => $this->t('Date'), 'header' => TRUE),
-          $this->dateFormatter->format($dblog->timestamp, 'long'),
+          $this->dateFormatter->format($dblog->context('timestamp'), 'long'),
         ),
         array(
           array('data' => $this->t('User'), 'header' => TRUE),
@@ -259,11 +246,11 @@ public function eventDetails($event_id) {
         ),
         array(
           array('data' => $this->t('Location'), 'header' => TRUE),
-          $this->l($dblog->location, $dblog->location ? Url::fromUri($dblog->location) : Url::fromRoute('<none>')),
+          $this->l($dblog->location, $dblog->context('location') ? Url::fromUri($dblog->location) : Url::fromRoute('<none>')),
         ),
         array(
           array('data' => $this->t('Referrer'), 'header' => TRUE),
-          $this->l($dblog->referer, $dblog->referer ? Url::fromUri($dblog->referer) : Url::fromRoute('<none>')),
+          $this->l($dblog->context('referer'), $dblog->context('referer') ? Url::fromUri($dblog->context('referer')) : Url::fromRoute('<none>')),
         ),
         array(
           array('data' => $this->t('Message'), 'header' => TRUE),
@@ -271,7 +258,7 @@ public function eventDetails($event_id) {
         ),
         array(
           array('data' => $this->t('Severity'), 'header' => TRUE),
-          $severity[$dblog->severity],
+          $severity[$dblog->level()],
         ),
         array(
           array('data' => $this->t('Hostname'), 'header' => TRUE),
@@ -279,7 +266,7 @@ public function eventDetails($event_id) {
         ),
         array(
           array('data' => $this->t('Operations'), 'header' => TRUE),
-          array('data' => array('#markup' => $dblog->link)),
+          array('data' => array('#markup' => $dblog->context('link'))),
         ),
       );
       $build['dblog_table'] = array(
@@ -303,7 +290,7 @@ public function eventDetails($event_id) {
    */
   protected function buildFilterQuery() {
     if (empty($_SESSION['dblog_overview_filter'])) {
-      return;
+      return [];
     }
 
     $this->moduleHandler->loadInclude('dblog', 'admin.inc');
@@ -331,39 +318,6 @@ protected function buildFilterQuery() {
   }
 
   /**
-   * Formats a database log message.
-   *
-   * @param object $row
-   *   The record from the watchdog table. The object properties are: wid, uid,
-   *   severity, type, timestamp, message, variables, link, name.
-   *
-   * @return string|\Drupal\Core\StringTranslation\TranslatableMarkup|false
-   *   The formatted log message or FALSE if the message or variables properties
-   *   are not set.
-   */
-  public function formatMessage($row) {
-    // Check for required properties.
-    if (isset($row->message, $row->variables)) {
-      $variables = @unserialize($row->variables);
-      // Messages without variables or user specified text.
-      if ($variables === NULL) {
-        $message = Xss::filterAdmin($row->message);
-      }
-      elseif (!is_array($variables)) {
-        $message = $this->t('Log data is corrupted and cannot be unserialized: @message', ['@message' => Xss::filterAdmin($row->message)]);
-      }
-      // Message to translate with injected variables.
-      else {
-        $message = $this->t(Xss::filterAdmin($row->message), $variables);
-      }
-    }
-    else {
-      $message = FALSE;
-    }
-    return $message;
-  }
-
-  /**
    * Shows the most frequent log messages of a given event type.
    *
    * Messages are not truncated on this page because events detailed herein do
@@ -378,33 +332,17 @@ public function formatMessage($row) {
    *   A build array in the format expected by drupal_render().
    */
   public function topLogMessages($type) {
+    $result = $this->dblogStorage->topLogMessages($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 = $row['log']->formatMessage()) {
+        $rows[] = [$row['count'], $message];
       }
     }
 
diff --git a/core/modules/dblog/src/DbLog.php b/core/modules/dblog/src/DbLog.php
new file mode 100644
index 0000000..fc4d2d5
--- /dev/null
+++ b/core/modules/dblog/src/DbLog.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace Drupal\dblog;
+
+use Drupal\Core\Logger\LogInterface;
+use Drupal\Component\Utility\Xss;
+
+class DbLog implements LogInterface {
+
+  /**
+    * The log message.
+    * @var string
+    */
+  protected $message;
+
+  /**
+   * The severity level of the event ranges from 0 (Emergency) to 7 (Debug)
+   * @var int
+   */
+  protected $level;
+
+  /**
+   * The context of the log event.
+   * @var array
+   */
+  protected $context = [];
+
+  /**
+   * Creates a Log event object.
+   *
+   * @param string $message
+   *   The log message.
+   * @param int $level
+   *   The level or severity of the log event.
+   * @param array  $context
+   *   An array of context information for the log event.
+   * @param int $id
+   *   Optional. The id of the log event.
+   */
+  public function __construct(
+    $message,
+    $level,
+    $context = [],
+    $id = NULL) {
+
+    $this->message = $message;
+    $this->level = $level;
+    $this->context = $context;
+    $this->id = $id;
+  }
+
+  /**
+   * Gets the id of the log.
+   * @return int
+   */
+  public function id() {
+    return $this->id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function message() {
+    return $this->message;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function level() {
+    return $this->level;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function context($key = NULL) {
+    if ($key === NULL) {
+      return $this->context;
+    }
+    if (isset($this->context[$key])) {
+      return $this->context[$key];
+    }
+  }
+
+  /**
+   * Formats a database log message.
+   *
+   * @return string|\Drupal\Core\StringTranslation\TranslatableMarkup|false
+   *   The formatted log message or FALSE if the message or variables properties
+   *   are not set.
+   */
+  public function formatMessage() {
+    // Check for required properties.
+    if (isset($this->message, $this->context['variables'])) {
+      $variables = @unserialize($this->context['variables']);
+      // Messages without variables or user specified text.
+      if ($variables === NULL) {
+        $message = Xss::filterAdmin($this->message);
+      }
+      elseif (!is_array($variables)) {
+        $message = t('Log data is corrupted and cannot be unserialized: @message', ['@message' => Xss::filterAdmin($this->message)]);
+      }
+      // Message to translate with injected variables.
+      else {
+        $message = t(Xss::filterAdmin($this->message), $variables);
+      }
+    }
+    else {
+      $message = FALSE;
+    }
+    return $message;
+  }
+
+  /**
+   * For backward compatibility with $log->wid, $log->uid, $log->severity, etc.
+   * @param string $key
+   * @return mixed
+   */
+  public function __get($key) {
+    if ($key == 'wid') {
+      return $this->id();
+    }
+
+    if ($key == 'severity') {
+      return $this->level();
+    }
+
+    if ($key == 'message') {
+      return $this->message();
+    }
+
+    return $this->context($key);
+  }
+
+}
diff --git a/core/modules/dblog/src/Form/DblogClearLogConfirmForm.php b/core/modules/dblog/src/Form/DblogClearLogConfirmForm.php
index b1710df..2378390 100644
--- a/core/modules/dblog/src/Form/DblogClearLogConfirmForm.php
+++ b/core/modules/dblog/src/Form/DblogClearLogConfirmForm.php
@@ -2,10 +2,11 @@
 
 namespace Drupal\dblog\Form;
 
-use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Url;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\dblog\Storage\DbLogStorageInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -14,20 +15,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 +36,7 @@ public function __construct(Connection $connection) {
    */
   public static function create(ContainerInterface $container) {
     return new static(
-      $container->get('database')
+      $container->get('storage.dblog')
     );
   }
 
@@ -65,7 +66,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/Logger/DbLog.php b/core/modules/dblog/src/Logger/DbLog.php
index af77be4..b7746bd 100644
--- a/core/modules/dblog/src/Logger/DbLog.php
+++ b/core/modules/dblog/src/Logger/DbLog.php
@@ -2,13 +2,10 @@
 
 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\Storage\DbLogStorageInterface;
 use Psr\Log\LoggerInterface;
 
 /**
@@ -19,16 +16,11 @@ class DbLog implements LoggerInterface {
   use DependencySerializationTrait;
 
   /**
-   * The dedicated database connection target to use for log entries.
-   */
-  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 +32,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 +53,32 @@ 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_context = [
+      'uid' => $context['uid'],
+      'type' => $context['channel'],
+      'variables' => $message_placeholders,
+      'link' => $context['link'],
+      'location' => $context['request_uri'],
+      'referer' => $context['referer'],
+      'hostname' => $context['ip'],
+      'timestamp' => $context['timestamp'],
+    ];
+
+    $log = new \Drupal\dblog\DbLog(
+      $message,
+      $level,
+      $log_context
+    );
+
+    $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..6749f57 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();
+      $record = \Drupal::service('storage.dblog')->load($id);
+
       if (!empty($record)) {
-        return new ResourceResponse($record);
+        return new ResourceResponse((array) $record);
       }
 
       throw new NotFoundHttpException(t('Log entry with ID @id was not found', array('@id' => $id)));
diff --git a/core/modules/dblog/src/Storage/DbLogStorage.php b/core/modules/dblog/src/Storage/DbLogStorage.php
new file mode 100644
index 0000000..f2f814d
--- /dev/null
+++ b/core/modules/dblog/src/Storage/DbLogStorage.php
@@ -0,0 +1,261 @@
+<?php
+
+namespace Drupal\dblog\Storage;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Logger\LogInterface;
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\Database;
+use Drupal\Core\Database\DatabaseException;
+use Drupal\Core\Logger\LogMessageParserInterface;
+use Drupal\dblog\DbLog;
+
+/**
+ * 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 Path CRUD 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(LogInterface $log) {
+    try {
+      $this->connection
+        ->insert(self::TABLE)
+        ->fields([
+          'uid' => $log->context('uid'),
+          'type' => Unicode::substr($log->context('type'), 0, 64),
+          'message' => $log->message(),
+          'variables' => serialize($log->context('variables')),
+          'severity' => $log->level(),
+          'link' => $log->context('link'),
+          'location' => $log->context('location'),
+          'referer' => $log->context('referer'),
+          'hostname' => Unicode::substr($log->context('hostname'), 0, 128),
+          'timestamp' => $log->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->save($log);
+      }
+      else {
+        throw $e;
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($id) {
+    $log = $this->connection
+          ->select(self::TABLE, 'w')
+          ->fields('w')
+          ->condition('wid', $id)
+          ->range(0, 1)
+          ->execute()
+          ->fetchObject();
+
+    if (!empty($log)) {
+      return $this->convertLogObject($log);
+    }
+  }
+
+  /**
+   * Creates a Log instance from a database registry.
+   * @param Object $log
+   *   The log event directly from the database.
+   * @return LogInterface
+   */
+  protected function convertLogObject($log) {
+    $context = [];
+    $ignore = ['severity', 'message', 'wid'];
+    foreach (get_object_vars($log) as $key => $value) {
+      if (!in_array($key, $ignore)) {
+        $context[$key] = $value;
+      }
+    }
+
+    return new DbLog(
+      $log->message,
+      $log->severity,
+      $context,
+      $log->wid
+    );
+  }
+
+  /**
+   * {@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) {
+      $logs[] = $this->convertLogObject($log);
+    }
+
+    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 topLogMessages($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) {
+      $top_logs[] = [
+        'count' => $row->count,
+        'log' => new DbLog($row->message, 0, ['variables' => $row->variables])
+      ];
+    }
+    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..bf09e2c
--- /dev/null
+++ b/core/modules/dblog/src/Storage/DbLogStorageInterface.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Drupal\dblog\Storage;
+
+use Drupal\Core\Logger\LogInterface;
+
+/**
+ * Logs events into a database.
+ */
+interface DbLogStorageInterface {
+
+  /**
+   * Save a log entry into a database.
+   *
+   * @param LogInterface $log
+   *   The log entry to save.
+   */
+  public function save(LogInterface $log);
+
+  /**
+   * Loads a log entry for the specified ID.
+   *
+   * @param int $id
+   *   The ID of the log entry.
+   *
+   * @return LogInterface|false
+   *   The log entry for the given $id if it exists, or false 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 LogInterface 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 message types.
+   *
+   * @return array
+   *   An associative array of log types, where keys and values are the message
+   *   type.
+   */
+  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 associative array, of log messages. Each item contains a log instance
+   *   and the number of occurences for this log.
+   */
+  public function topLogMessages($type);
+
+}
diff --git a/core/modules/dblog/tests/src/Kernel/DbLogControllerTest.php b/core/modules/dblog/tests/src/Kernel/DbLogControllerTest.php
index d6d88fa..40e5a91 100644
--- a/core/modules/dblog/tests/src/Kernel/DbLogControllerTest.php
+++ b/core/modules/dblog/tests/src/Kernel/DbLogControllerTest.php
@@ -2,7 +2,7 @@
 
 namespace Drupal\Tests\dblog\Kernel;
 
-use Drupal\dblog\Controller\DbLogController;
+use Drupal\dblog\DbLog;
 use Drupal\KernelTests\KernelTestBase;
 
 /**
@@ -22,19 +22,19 @@ class DbLogControllerTest extends KernelTestBase {
    */
   public function testDbLogCorrupted() {
     $this->installEntitySchema('user');
-    $dblog_controller = DbLogController::create($this->container);
 
     // Check message with properly serialized data.
-    $message = (object) [
-      'message' => 'Sample message with placeholder: @placeholder',
-      'variables' => serialize(['@placeholder' => 'test placeholder']),
-    ];
+    $message = 'Sample message with placeholder: @placeholder';
+    $context = ['variables' => serialize(['@placeholder' => 'test placeholder'])];
+    $properly_serialized = new DbLog($message, 0, $context);
 
-    $this->assertEquals('Sample message with placeholder: test placeholder', $dblog_controller->formatMessage($message));
+    $this->assertEquals('Sample message with placeholder: test placeholder', $properly_serialized->formatMessage($message));
 
     // Check that controller work with corrupted data.
-    $message->variables = 'BAD SERIALIZED DATA';
-    $formatted = $dblog_controller->formatMessage($message);
+    $context = ['variables' => 'BAD SERIALIZED DATA'];
+    $corrupted_data = new DbLog($message, 0, $context);
+
+    $formatted = $corrupted_data->formatMessage($message);
     $this->assertEquals('Log data is corrupted and cannot be unserialized: Sample message with placeholder: @placeholder', $formatted);
   }
 
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..06c587e
--- /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->assertFalse($log);
+    $log = \Drupal::service('storage.dblog')->load('Not valid');
+    $this->assertFalse($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->level(), 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 ::topLogMessages
+   */
+  public function testTopLogMessages() {
+    \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');
+
+    $topLogMessages = \Drupal::service('storage.dblog')->topLogMessages('dblog_test');
+
+    $referenceTable = [
+      'Log entry A' => 3,
+      'Log entry B' => 1,
+      'Log entry C' => 2,
+    ];
+
+    foreach ($topLogMessages as $row) {
+      $this->assertEquals($row['count'], $referenceTable[$row['log']->message()]);
+    }
+  }
+
+}
