diff --git a/config/optional/monitoring.sensor_config.dblog_php_notices.yml b/config/optional/monitoring.sensor_config.dblog_php_notices.yml
new file mode 100644
index 0000000..ac4d954
--- /dev/null
+++ b/config/optional/monitoring.sensor_config.dblog_php_notices.yml
@@ -0,0 +1,22 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - dblog
+id: dblog_php_notices
+label: 'PHP notices'
+description: 'Monitors and displays most frequent PHP notices and errors'
+category: Watchdog
+plugin_id: php_notices
+result_class: null
+value_label: 'Watchdog events'
+value_type: number
+caching_time: 3600
+settings:
+  table: watchdog
+  time_interval_field: 'timestamp'
+  time_interval_value: 86400
+thresholds:
+  type: none
+  warning: 10
+  critical: 100
diff --git a/config/schema/monitoring.schema.yml b/config/schema/monitoring.schema.yml
index f63ad85..b9c4fb0 100644
--- a/config/schema/monitoring.schema.yml
+++ b/config/schema/monitoring.schema.yml
@@ -212,6 +212,10 @@ monitoring.settings.watchdog_aggregator:
   type: monitoring.settings.database_aggregator
   label: 'Watchdog sensor settings'
 
+monitoring.settings.php_notices:
+  type: monitoring.settings.watchdog_aggregator
+  label: 'PHP notices sensor settings'
+
 monitoring.settings.dblog_404:
   type: monitoring.settings.database_aggregator
   label: '404 settings'
diff --git a/src/Plugin/monitoring/SensorPlugin/DatabaseAggregatorSensorPlugin.php b/src/Plugin/monitoring/SensorPlugin/DatabaseAggregatorSensorPlugin.php
index eef80c9..9abbe1e 100644
--- a/src/Plugin/monitoring/SensorPlugin/DatabaseAggregatorSensorPlugin.php
+++ b/src/Plugin/monitoring/SensorPlugin/DatabaseAggregatorSensorPlugin.php
@@ -171,40 +171,51 @@ class DatabaseAggregatorSensorPlugin extends DatabaseAggregatorSensorPluginBase
    *   Render array where the result will be added.
    */
   public function verboseResultUnaggregated(array &$output) {
+    $rows = $this->buildTableRows();
+    $output['result'] = array(
+      '#type' => 'table',
+      '#rows' => $rows,
+      '#header' => $this->buildTableHeader($rows),
+      '#empty' => 'There are no results for this sensor to display.',
+    );
+  }
+
+  /**
+   * Builds the header for a table.
+   *
+   * @param array $rows
+   *   The array of rows for which a header will be built.
+   *
+   * @return array $header
+   *   The associative header array for the table.
+   */
+  protected function buildTableHeader($rows = []) {
+    // Provide consistent keys for header and data rows for easy altering.
+    $keys = array_keys($rows[0]);
+    $header = array_combine($keys, $keys);
+    return $header;
+  }
+
+  /**
+   * Builds the rows of a table.
+   *
+   * @return array $rows
+   *   The render array with the table rows.
+   */
+  protected function buildTableRows() {
+    $rows = [];
     // Fetch the last 10 matching entries, unaggregated.
     $query_result = $this->getQuery()
       ->range(0, 10)
       ->execute();
-    // Render rows.
-    $rows = [];
-    foreach ($query_result as $record) {
-      $row = [];
+    foreach ($query_result as $delta => $record) {
+      $rows[$delta] = [];
       foreach ($record as $key => $value) {
-        $row[$key] = $value;
+        $rows[$delta][$key] = $value;
       }
-
-      $rows[] = array(
-        'data' => $row,
-        'class' => 'entity',
-      );
     }
 
-    if (count($rows) > 0) {
-      // Provide consistent keys for header and data rows for easy altering.
-      $keys = array_keys($rows[0]['data']);
-      $header = array_combine($keys, $keys);
-      $output['result'] = array(
-        '#type' => 'table',
-        '#header' => $header,
-        '#rows' => $rows,
-      );
-    }
-    else {
-      $output['result'] = [
-        '#type' => 'item',
-        '#markup' => t('No results were found in the table.'),
-      ];
-    }
+    return $rows;
   }
 
   /**
@@ -291,7 +302,7 @@ class DatabaseAggregatorSensorPlugin extends DatabaseAggregatorSensorPluginBase
       '#suffix' => '</div>',
       '#tree' => FALSE,
     );
-    // Fill the keys text field with keys.
+    // Fill the verbose config with fields to filter.
     $form['output_table']['keys'] = array(
       '#type' => 'textarea',
       '#tree' => FALSE,
@@ -406,7 +417,7 @@ class DatabaseAggregatorSensorPlugin extends DatabaseAggregatorSensorPluginBase
 
     // Cleanup conditions, remove empty.
     $settings['conditions'] = [];
-    foreach ($form_state->getValue('conditions') as $key => $condition) {
+    foreach ($form_state->getValue('conditions', []) as $key => $condition) {
       if (!empty($condition['field'])) {
         $settings['conditions'][] = $condition;
       }
diff --git a/src/Plugin/monitoring/SensorPlugin/PhpNoticesSensorPlugin.php b/src/Plugin/monitoring/SensorPlugin/PhpNoticesSensorPlugin.php
new file mode 100644
index 0000000..8d324a3
--- /dev/null
+++ b/src/Plugin/monitoring/SensorPlugin/PhpNoticesSensorPlugin.php
@@ -0,0 +1,119 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\monitoring\Plugin\monitoring\SensorPlugin\PhpNoticesSensorPlugin.
+ */
+
+namespace Drupal\monitoring\Plugin\monitoring\SensorPlugin;
+
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\Xss;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\monitoring\Result\SensorResultInterface;
+
+/**
+ * Monitors and displays the most frequent PHP notices and errors.
+ *
+ * @SensorPlugin(
+ *   id = "php_notices",
+ *   provider = "dblog",
+ *   label = @Translation("PHP notices (database log)"),
+ *   description = @Translation("Monitors and displays the most frequent PHP notices and errors."),
+ *   addable = FALSE
+ * )
+ */
+
+class PhpNoticesSensorPlugin extends WatchdogAggregatorSensorPlugin {
+  /**
+   * {@inheritdoc}
+   */
+  public function runSensor(SensorResultInterface $result) {
+    parent::runSensor($result);
+    if (!empty($this->fetchedObject->variables)) {
+      $result->setMessage('@count times: @error', ['@count' => (int) $this->fetchedObject->records_count, '@error' => SafeMarkup::xssFilter(SafeMarkup::format('%type: !message in %function (line %line of %file).', unserialize($this->fetchedObject->variables)), Xss::getAdminTagList())]);
+    };
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $form = parent::buildConfigurationForm($form, $form_state);
+    unset($form['conditions_table']);
+    unset($form['output_table']);
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getAggregateQuery() {
+    $query = parent::getAggregateQuery();
+    $query->addField('watchdog', 'variables');
+    $query->condition('type', 'php', NULL);
+    // The message is the most recurring php error.
+    $query->groupBy('variables');
+    $query->orderBy('records_count', 'DESC');
+    $query->range(0, 1);
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuery() {
+    $query = parent::getQuery();
+    $query->addField('watchdog', 'variables');
+    $this->addAggregateExpression($query);
+    $query->condition('type', 'php', NULL);
+    $query->groupBy('variables');
+    $query->orderBy('records_count', 'DESC');
+    $query->range(0, 20);
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function buildTableHeader($rows = []) {
+    $header = [
+      'count' => 'Count',
+      'type' => 'Type',
+      'message' => 'Message',
+      'function' => 'Caller',
+      'file' => 'File',
+    ];
+    return $header;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function buildTableRows() {
+    $rows_input = parent::buildTableRows();
+    $rows = [];
+    foreach ($rows_input as $delta => $row) {
+      $variables = unserialize($row['variables']);
+      $rows[$delta]['count'] = $row['records_count'];
+      $rows[$delta]['type'] = $variables['%type'];
+      $rows[$delta]['message'] = SafeMarkup::xssFilter($variables['!message'], Xss::getAdminTagList());
+      $rows[$delta]['function'] = $variables['%function'];
+      $rows[$delta]['file'] = $variables['%file'] . ' on line ' . $variables['%line'];
+    }
+    return $rows;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function verboseResultUnaggregated(array &$output) {
+    $rows = $this->buildTableRows();
+    $output['result'] = array(
+      '#type' => 'table',
+      '#rows' => $rows,
+      '#header' => $this->buildTableHeader(),
+      '#empty' => 'There are no results for this sensor to display.',
+    );
+  }
+
+}
diff --git a/src/Plugin/monitoring/SensorPlugin/WatchdogAggregatorSensorPlugin.php b/src/Plugin/monitoring/SensorPlugin/WatchdogAggregatorSensorPlugin.php
index 03bf1f9..56f9e33 100644
--- a/src/Plugin/monitoring/SensorPlugin/WatchdogAggregatorSensorPlugin.php
+++ b/src/Plugin/monitoring/SensorPlugin/WatchdogAggregatorSensorPlugin.php
@@ -6,13 +6,10 @@
 
 namespace Drupal\monitoring\Plugin\monitoring\SensorPlugin;
 
-use Drupal\Core\Database\DatabaseExceptionWrapper;
-use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\Component\Utility\Xss;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\monitoring\Result\SensorResultInterface;
 use Drupal\monitoring\SensorPlugin\ExtendedInfoSensorPluginInterface;
-use Drupal\monitoring\SensorPlugin\DatabaseAggregatorSensorPluginBase;
-use Drupal\Core\Entity\DependencyTrait;
 use Drupal\Component\Utility\SafeMarkup;
 
 /**
@@ -24,7 +21,6 @@ use Drupal\Component\Utility\SafeMarkup;
  *   description = @Translation("Simple aggregator able to query the watchdog table."),
  *   addable = TRUE
  * )
- *
  */
 class WatchdogAggregatorSensorPlugin extends DatabaseAggregatorSensorPlugin implements ExtendedInfoSensorPluginInterface {
   /**
@@ -32,15 +28,14 @@ class WatchdogAggregatorSensorPlugin extends DatabaseAggregatorSensorPlugin impl
    */
   public function verboseResultUnaggregated(array &$output) {
     parent::verboseResultUnaggregated($output);
-    if (isset($output['result']['#rows'])) {
-      if (array_key_exists('message', $output['result']['#header']) && array_key_exists('variables', $output['result']['#header'])) {
-        unset($output['result']['#header']['variables']);
-      }
+    // If sensor has message and variables, remove variables header.
+    if (isset($output['result']['#rows']) && array_key_exists('message', $output['result']['#header']) && array_key_exists('variables', $output['result']['#header'])) {
+      unset($output['result']['#header']['variables']);
+      // Replace the message for every row.
       foreach ($output['result']['#rows'] as $delta => $row) {
-        if (array_key_exists('message', $row['data']) && array_key_exists('variables', $row['data'])) {
-          $output['result']['#rows'][$delta]['data']['message'] = SafeMarkup::format($row['data']['message'], unserialize($row['data']['variables']));
-          unset($output['result']['#rows'][$delta]['data']['variables']);
-        };
+        $output['result']['#rows'][$delta]['message'] = Safemarkup::xssFilter(SafeMarkup::format($row['message'], unserialize($row['variables']), Xss::getAdminTagList()));
+        // Do not render the variables in the row.
+        unset($output['result']['#rows'][$delta]['variables']);
       };
     }
   }
@@ -52,7 +47,9 @@ class WatchdogAggregatorSensorPlugin extends DatabaseAggregatorSensorPlugin impl
     $form = parent::buildConfigurationForm($form, $form_state);
     $form['#title'] = t('Watchdog Sensor plugin settings');
     // The following fields should not be edited, so we disable them.
+    $form['table']['#default_value'] = 'watchdog';
     $form['table']['#disabled'] = TRUE;
+    $form['aggregation']['time_interval_field']['#default_value'] = 'timestamp';
     $form['aggregation']['time_interval_field']['#disabled'] = TRUE;
     return $form;
   }
diff --git a/src/Tests/MonitoringCoreKernelTest.php b/src/Tests/MonitoringCoreKernelTest.php
index b602a70..ea728dd 100644
--- a/src/Tests/MonitoringCoreKernelTest.php
+++ b/src/Tests/MonitoringCoreKernelTest.php
@@ -125,6 +125,55 @@ class MonitoringCoreKernelTest extends MonitoringUnitTestBase {
   }
 
   /**
+   * Tests php notices watchdog sensor.
+   */
+  public function testPhpNoticesSensor() {
+
+    // Prepare a fake PHP error.
+    $error = [
+      '%type' => 'Recoverable fatal error',
+      '!message' => 'Argument 1 passed to Drupal\Core\Form\ConfigFormBase::buildForm() must be of the type array, null given, called in /usr/local/var/www/d8/www/core/modules/system/src/Form/CronForm.php on line 127 and defined',
+      '%function' => 'Drupal\Core\Form\ConfigFormBase->buildForm()',
+      '%line' => '42',
+      '%file' => '/usr/local/var/www/d8/www/core/lib/Drupal/Core/Form/ConfigFormBase.php',
+      'severity_level' => 3,
+    ];
+    // Log it twice.
+    \Drupal::logger('php')->log($error['severity_level'], '%type: !message in %function (line %line of %file).', $error);
+    \Drupal::logger('php')->log($error['severity_level'], '%type: !message in %function (line %line of %file).', $error);
+
+    $result = $this->runSensor('dblog_php_notices');
+    $message = $result->getMessage();
+    // Assert the message has been set and replaced successfully.
+    $this->assertEqual($message, SafeMarkup::format('2 times: %type: !message in %function (line %line of %file).', $error), 'PHP notice was found and replaced successfully.');
+
+    // Prepare another fake PHP notice.
+    $new_error = [
+      '%type' => 'Notice',
+      '!message' => 'Use of undefined constant B - assumed \'B\'',
+      '%function' => 'Drupal\system\Form\CronForm->buildForm()',
+      '%line' => '126',
+      '%file' => '/usr/local/var/www/d8/www/core/modules/system/src/Form/CronForm.php',
+      'severity_level' => 5,
+    ];
+    \Drupal::logger('php')->log($new_error['severity_level'], '%type: !message in %function (line %line of %file).', $new_error);
+    $result = $this->runSensor('dblog_php_notices');
+    $message = $result->getMessage();
+    // Assert the message is still the one from above and not the new message.
+    $this->assertEqual($message, SafeMarkup::format('2 times: %type: !message in %function (line %line of %file).', $error), 'The sensor message is still the old message.');
+    $this->assertNotEqual($message, SafeMarkup::format('%type: !message in %function (line %line of %file).', $new_error), 'The sensor message is not the new message.');
+
+    // Log the new error twice more, check it is now the sensor message.
+    \Drupal::logger('php')->log($new_error['severity_level'], '%type: !message in %function (line %line of %file).', $new_error);
+    \Drupal::logger('php')->log($new_error['severity_level'], '%type: !message in %function (line %line of %file).', $new_error);
+    $result = $this->runSensor('dblog_php_notices');
+    $message = $result->getMessage();
+    // Assert the new message is returned as a message.
+    $this->assertEqual($message, SafeMarkup::format('3 times: %type: !message in %function (line %line of %file).', $new_error), 'The new message is now the sensor message.');
+    $this->assertNotEqual($message, SafeMarkup::format('2 times: %type: !message in %function (line %line of %file).', $error), 'The old message is not the sensor message anymore.');
+  }
+
+  /**
    * Tests dblog 404 errors sensor.
    *
    * Logged through watchdog.
@@ -437,7 +486,6 @@ class MonitoringCoreKernelTest extends MonitoringUnitTestBase {
     $sensor_config->settings['status_cmd'] = 'printf "A addedfile.txt\nM sites/all/modules/monitoring/test/tests/monitoring.core.test\nD deleted file.txt"';
     $sensor_config->settings['ahead_cmd'] = 'true';
     $sensor_config->save();
-
     $result = $this->runSensor('monitoring_git_dirty_tree');
     $this->assertTrue($result->isCritical());
     // The verbose output should contain the cmd output.
@@ -453,6 +501,7 @@ class MonitoringCoreKernelTest extends MonitoringUnitTestBase {
 
     // Now echo empty string.
     $sensor_config->settings['status_cmd'] = 'true';
+    $sensor_config->settings['cmd'] = 'true';
     $sensor_config->save();
 
     $result = $this->runSensor('monitoring_git_dirty_tree');
