diff --git a/nodejs_watchdog/nodejs.watchdog.js b/nodejs_watchdog/nodejs.watchdog.js
index cf705bc..491f181 100644
--- a/nodejs_watchdog/nodejs.watchdog.js
+++ b/nodejs_watchdog/nodejs.watchdog.js
@@ -1,4 +1,3 @@
-
 (function ($) {
 
 Drupal.Nodejs.callbacks.nodejsWatchdog = {
@@ -10,4 +9,3 @@ Drupal.Nodejs.callbacks.nodejsWatchdog = {
 };
 
 }(jQuery));
-
diff --git a/nodejs_watchdog/nodejs_watchdog.info b/nodejs_watchdog/nodejs_watchdog.info
deleted file mode 100644
index 9fcd0c7..0000000
--- a/nodejs_watchdog/nodejs_watchdog.info
+++ /dev/null
@@ -1,8 +0,0 @@
-name = Nodejs Watchdog
-description = Adds watchdog messages to the dblog page in realtime.
-package = Nodejs
-version = VERSION
-core = 7.x
-dependencies[] = nodejs
-dependencies[] = nodejs_ajax
-dependencies[] = dblog
diff --git a/nodejs_watchdog/nodejs_watchdog.info.yml b/nodejs_watchdog/nodejs_watchdog.info.yml
new file mode 100644
index 0000000..738158a
--- /dev/null
+++ b/nodejs_watchdog/nodejs_watchdog.info.yml
@@ -0,0 +1,10 @@
+name: Nodejs Watchdog
+type: module
+description: 'Adds watchdog messages to the dblog page in realtime.'
+package: Nodejs
+version: VERSION
+core: 8.x
+dependencies:
+  - nodejs
+  - nodejs_ajax
+  - dblog
\ No newline at end of file
diff --git a/nodejs_watchdog/nodejs_watchdog.module b/nodejs_watchdog/nodejs_watchdog.module
index 1463262..2e79e54 100644
--- a/nodejs_watchdog/nodejs_watchdog.module
+++ b/nodejs_watchdog/nodejs_watchdog.module
@@ -5,99 +5,36 @@
  */
 function nodejs_watchdog_form_dblog_filter_form_alter(&$form, &$form_state, $form_id) {
   nodejs_send_content_channel_token('watchdog_dblog');
-  drupal_add_js(drupal_get_path('module', 'nodejs_watchdog') . '/nodejs.watchdog.js', array('type' => 'file'));
-}
-
-/**
- * Implements hook_watchdog().
- */
-function nodejs_watchdog_watchdog(array $entry) {
-  $classes = array(
-    WATCHDOG_DEBUG     => 'dblog-debug',
-    WATCHDOG_INFO      => 'dblog-info',
-    WATCHDOG_NOTICE    => 'dblog-notice',
-    WATCHDOG_WARNING   => 'dblog-warning',
-    WATCHDOG_ERROR     => 'dblog-error',
-    WATCHDOG_CRITICAL  => 'dblog-critical',
-    WATCHDOG_ALERT     => 'dblog-alert',
-    WATCHDOG_EMERGENCY => 'dblog-emerg',
-  );
-
-  $entry['variables'] = isset($entry['variables']) ? serialize($entry['variables']) : serialize(array());
-
-  $row = array(
-    'data' => array(
-      array('class' => 'icon'),
-      t($entry['type']),
-      format_date($entry['timestamp'], 'short'),
-      theme('dblog_message', array('event' => (object) $entry, 'link' => TRUE)),
-      theme('username', array('account' => $entry['user'])),
-      $entry['link'],
+  $js_libs = array(
+    '#attached' => array(
+      'js' => array(
+        drupal_get_path('module', 'nodejs_watchdog') . '/nodejs.watchdog.js' => array(),
+      ),
     ),
-    'class' => array(drupal_html_class('dblog-' . $entry['type']), $classes[$entry['severity']]),
   );
-
-  $commands[] = ajax_command_before('#admin-dblog tr:eq(1)', nodejs_watchdog_theme_row($row));
-  $message = (object) array(
-    'channel' => 'watchdog_dblog',
-    'commands' => $commands,
-    'callback' => 'nodejsWatchdog',
-  );
-  nodejs_send_content_channel_message($message);
-}
-
-/**
- * Theme a watchdog row.
- */
-function nodejs_watchdog_theme_row($row) {
-  $attributes = array();
-  $flip = array(
-    'even' => 'odd',
-    'odd' => 'even',
-  );
-
-  $class = 'even';
-  $output = "";
-  // Check if we're dealing with a simple or complex row
-  if (isset($row['data'])) {
-    foreach ($row as $key => $value) {
-      if ($key == 'data') {
-        $cells = $value;
-      }
-      else {
-        $attributes[$key] = $value;
-      }
-    }
-  }
-  else {
-    $cells = $row;
-  }
-  if (count($cells)) {
-    $output .= '<tr' . drupal_attributes($attributes) . '>';
-    $i = 0;
-    foreach ($cells as $cell) {
-      $output .= _theme_table_cell($cell);
-    }
-    $output .= "</tr>";
-  }
-  return $output;
+  drupal_render($js_libs);
 }
 
 /**
 * Implements hook_nodejs_user_channels().
 */
 function nodejs_watchdog_nodejs_user_channels($account) {
-  if (user_access('access site reports', $account)) {
+//  if (!empty($account) && $account->hasPermission('access site reports')) {
     return array('watchdog_dblog');
-  }
+//  }
   return array();
 }
 
 /**
-* Implements hook_nodejs_handlers_info().
-*/
-function nodejs_watchdog_nodejs_handlers_info() {
+ * Implements hook_theme().
+ */
+function nodejs_watchdog_theme() {
   return array(
-    drupal_get_path('module', 'nodejs_watchdog') . '/nodejs.watchdog.js',
+    'nodejs_watchdog_row' => array(
+      'template' => 'nodejs-watchdog-row',
+      'variables' => array(
+        'row' => array(),
+      ),
+    ),
   );
-}
+}
\ No newline at end of file
diff --git a/nodejs_watchdog/nodejs_watchdog.services.yml b/nodejs_watchdog/nodejs_watchdog.services.yml
new file mode 100644
index 0000000..8bc1f79
--- /dev/null
+++ b/nodejs_watchdog/nodejs_watchdog.services.yml
@@ -0,0 +1,6 @@
+services:
+  logger.nodejs_watchdog:
+    class: Drupal\nodejs_watchdog\Logger\Nodejs
+    arguments: ['@logger.log_message_parser']
+    tags:
+      - { name: logger }
\ No newline at end of file
diff --git a/nodejs_watchdog/src/Logger/Nodejs.php b/nodejs_watchdog/src/Logger/Nodejs.php
new file mode 100644
index 0000000..41949e1
--- /dev/null
+++ b/nodejs_watchdog/src/Logger/Nodejs.php
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\nodejs_watchdog\Logger\Nodejs.
+ */
+
+namespace Drupal\nodejs_watchdog\Logger;
+
+use Drupal\Core\Logger\LogMessageParserInterface;
+use Drupal\Core\Datetime\Date;
+use Drupal\Component\Utility\Unicode;
+use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Xss;
+use Drupal\Core\Template\Attribute;
+
+use Psr\Log\LoggerInterface;
+use Psr\Log\LoggerTrait;
+
+use Drupal\Core\Ajax\BeforeCommand;
+
+use Drupal\dblog\Controller\DbLogController;
+
+
+/**
+ * Logs events in the watchdog database table.
+ */
+class Nodejs implements LoggerInterface {
+  use LoggerTrait;
+
+  /**
+   * The message's placeholders parser.
+   *
+   * @var \Drupal\Core\Logger\LogMessageParserInterface
+   */
+  protected $parser;
+
+  /**
+   * Constructs a DbLog object.
+   *
+   * @param \Drupal\Core\Logger\LogMessageParserInterface $parser
+   *   The parser to use when extracting message variables.
+   */
+  public function __construct(LogMessageParserInterface $parser) {
+    $this->parser = $parser;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function log($level, $message, array $context = array()) {
+    $commands = array();
+
+    $classes = DbLogController::getLogLevelClassMap();
+
+    $message_placeholders = $this->parser->parseMessagePlaceholders($message, $context);
+
+    // Message to translate with injected variables.
+    if (!empty($message_placeholders)) {
+      $message = $this->t($message, $message_placeholders);
+    }
+    if (isset($context['link'])) {
+      // Truncate link_text to 56 chars of message.
+      $log_text = Unicode::truncate(Xss::filter($message, array()), 56, TRUE, TRUE);
+      $message = l($log_text, 'dblog.event',  array('event_id' => $context['link']), array('html' => TRUE));
+    }
+
+    $username = array(
+      '#theme' => 'username',
+      '#account' => user_load($context['uid']),
+    );
+    $rows[] = array(
+      'data' => array(
+        // Cells.
+        array('class' => array('icon')),
+        substr($context['channel'], 0, 64),
+        \Drupal::service('date')->format($context['timestamp'], 'short'),
+        $message,
+        array('data' => $username),
+        Xss::filter(substr($context['link'], 0, 255)),
+      ),
+      // Attributes for table row.
+      'class' => array(drupal_html_class('dblog-' . substr($context['channel'], 0, 64)), $classes[$level]),
+    );
+
+    $build['dblog_table'] = array(
+      '#type' => 'table',
+      '#rows' => $rows,
+      '#attributes' => array('id' => 'admin-dblog', 'class' => array('admin-dblog')),
+    );
+
+    // FIXME: This is a terrible quick hack!
+    // It would be better to use a custom theme template for this. 
+    $output = drupal_render($build);
+    $loc = stripos($output, "<tr class");
+    $output = substr("$output", $loc);
+    $loc = stripos($output, "</tr>");
+    $output = substr("$output", 0, $loc+5);
+
+    // TODO: Get this custom template working
+    // $build['row'] = array(
+    //   '#theme' => 'nodejs_watchdog_row',
+    //   '#row' => $row,
+    // );
+  
+    $command = new BeforeCommand('#admin-dblog tr:eq(1)', $output);
+    $commands[] = $command->render();
+
+    $message = (object) array(
+      'channel' => 'watchdog_dblog',
+      'commands' => $commands,
+      'callback' => 'nodejsWatchdog',
+    );
+    nodejs_send_content_channel_message($message);
+  }
+
+}
diff --git a/nodejs_watchdog/templates/nodejs-watchdog-row.html.twig b/nodejs_watchdog/templates/nodejs-watchdog-row.html.twig
new file mode 100644
index 0000000..97a5a65
--- /dev/null
+++ b/nodejs_watchdog/templates/nodejs-watchdog-row.html.twig
@@ -0,0 +1,46 @@
+{#
+/**
+ * @file
+ * Default theme implementation to display a table.
+ *
+ * Available variables:
+ * - attributes: HTML attributes to apply to the <table> tag.
+ * - caption: A localized string for the <caption> tag.
+ * - colgroups: Column groups. Each group contains the following properties:
+ *   - attributes: HTML attributes to apply to the <col> tag.
+ *     Note: Drupal currently supports only one table header row, see
+ *     http://drupal.org/node/893530 and
+ *     http://api.drupal.org/api/drupal/includes!theme.inc/function/theme_table/7#comment-5109.
+ * - header: Table header cells. Each cell contains the following properties:
+ *   - tag: The HTML tag name to use; either TH or TD.
+ *   - attributes: HTML attributes to apply to the tag.
+ *   - content: A localized string for the title of the column.
+ *   - field: Field name (required for column sorting).
+ *   - sort: Default sort order for this column ("asc" or "desc").
+ * - sticky: A flag indicating whether to use a "sticky" table header.
+ * - rows: Table rows. Each row contains the following properties:
+ *   - attributes: HTML attributes to apply to the <tr> tag.
+ *   - data: Table cells.
+ *   - no_striping: A flag indicating that the row should receive no
+ *     'even / odd' styling. Defaults to FALSE.
+ *   - cells: Table cells of the row. Each cell contains the following keys:
+ *     - tag: The HTML tag name to use; either TH or TD.
+ *     - attributes: Any HTML attributes, such as "colspan", to apply to the
+ *       table cell.
+ *     - content: The string to display in the table cell.
+ * - empty: The message to display in an extra row if table does not have
+ *   any rows.
+ *
+ * @see template_preprocess_table()
+ *
+ * @ingroup themeable
+ */
+#}
+TEST
+<tr{{ row.attributes }}>
+  {% for cell in row.cells %}
+    <{{ cell.tag }}{{ cell.attributes }}>
+      {{- cell.content -}}
+    </{{ cell.tag }}>
+  {% endfor %}
+</tr>
