From 60a8a759ed2e2d2cf8e8ea200f8b7de1e00292d4 Mon Sep 17 00:00:00 2001
From: Alex Barth <alex_b@53995.no-reply.drupal.org>
Date: Tue, 26 Oct 2010 03:05:58 +0000
Subject: [PATCH] #606612: More detailed log.

---
 CHANGELOG.txt                                 |   1 +
 feeds.info                                    |   6 +
 feeds.install                                 | 147 +++++++++
 feeds.module                                  | 177 +++++++----
 feeds.pages.inc                               |   3 +
 includes/FeedsSource.inc                      |   7 +
 plugins/FeedsProcessor.inc                    |   9 +-
 views/feeds.views.inc                         | 149 ++++++++-
 views/feeds.views_default.inc                 | 286 ++++++++++++++++++
 ...eds_views_handler_argument_importer_id.inc |  25 ++
 ...eeds_views_handler_field_importer_name.inc |  64 ++++
 .../feeds_views_handler_field_log_message.inc |  26 ++
 views/feeds_views_handler_field_severity.inc  |  26 ++
 views/feeds_views_handler_filter_severity.inc |  14 +
 14 files changed, 858 insertions(+), 82 deletions(-)
 create mode 100644 views/feeds.views_default.inc
 create mode 100644 views/feeds_views_handler_argument_importer_id.inc
 create mode 100644 views/feeds_views_handler_field_importer_name.inc
 create mode 100644 views/feeds_views_handler_field_log_message.inc
 create mode 100644 views/feeds_views_handler_field_severity.inc
 create mode 100644 views/feeds_views_handler_filter_severity.inc

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 77602c37..17188d99 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -3,6 +3,7 @@
 Feeds 7.x 2.0 XXXXXXXXXXXXXXXXXXX
 ---------------------------------
 
+- #606612 alex_b: More detailed log.
 - #949236 Ian Ward, alex_b: Allow mapping empty values to fields.
 - #912630 twistor, alex_b: FeedsParserResult: make items accessible for
   modification.
diff --git a/feeds.info b/feeds.info
index 6e0d6502..98e633b1 100644
--- a/feeds.info
+++ b/feeds.info
@@ -20,6 +20,12 @@ files[] = tests/feeds_processor_term.test
 files[] = tests/feeds_processor_user.test
 files[] = tests/feeds_scheduler.test
 files[] = tests/parser_csv.test
+files[] = views/feeds.views_default.inc
+files[] = views/feeds_views_handler_argument_importer_id.inc
+files[] = views/feeds_views_handler_field_importer_name.inc
+files[] = views/feeds_views_handler_field_log_message.inc
+files[] = views/feeds_views_handler_field_severity.inc
 files[] = views/feeds_views_handler_field_source.inc
+files[] = views/feeds_views_handler_filter_severity.inc
 core = 7.x
 php = 5.2
diff --git a/feeds.install b/feeds.install
index 188746f4..77ff800d 100644
--- a/feeds.install
+++ b/feeds.install
@@ -222,6 +222,76 @@ function feeds_schema() {
       'timestamp' => array('timestamp'),
     ),
   );
+  $schema['feeds_log'] = array(
+    'description' => 'Table that contains logs of feeds events.',
+    'fields' => array(
+      'flid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique feeds event ID.',
+      ),
+      'id' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The id of the importer that logged the event.',
+      ),
+      'feed_nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'Node id of the source, if available.',
+      ),
+      'log_time' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'Unix timestamp of when event occurred.',
+      ),
+      'request_time' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'Unix timestamp of the request when the event occurred.',
+      ),
+      'type' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Type of log message, for example "feeds_import"."',
+      ),
+      'message' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => 'Text of log message to be passed into the t() function.',
+      ),
+      'variables' => array(
+        'type' => 'blob',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => 'Serialized array of variables that match the message string and that is passed into the t() function.',
+      ),
+      'severity' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => 'The severity level of the event; ranges from 0 (Emergency) to 7 (Debug)',
+      ),
+    ),
+    'primary key' => array('flid'),
+    'indexes' => array(
+      'id' => array('id'),
+      'id_feed_nid' => array('id', 'feed_nid'),
+      'request_time' => array('request_time'),
+      'log_time' => array('log_time'),
+      'type' => array('type'),
+    ),
+  );
   return $schema;
 }
 
@@ -337,3 +407,80 @@ function feeds_update_7202(&$sandbox) {
   db_drop_table('feeds_node_item');
   db_drop_table('feeds_term_item');
 }
+
+/**
+ * Add feeds_log table.
+ */
+function feeds_update_7203(&$sandbox) {
+  $schema = array(
+    'description' => 'Table that contains logs of feeds events.',
+    'fields' => array(
+      'flid' => array(
+        'type' => 'serial',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique feeds event ID.',
+      ),
+      'id' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The id of the importer that logged the event.',
+      ),
+      'feed_nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => 'Node id of the source, if available.',
+      ),
+      'log_time' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'Unix timestamp of when event occurred.',
+      ),
+      'request_time' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => 'Unix timestamp of the request when the event occurred.',
+      ),
+      'type' => array(
+        'type' => 'varchar',
+        'length' => 64,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Type of log message, for example "feeds_import"."',
+      ),
+      'message' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => 'Text of log message to be passed into the t() function.',
+      ),
+      'variables' => array(
+        'type' => 'blob',
+        'not null' => TRUE,
+        'size' => 'big',
+        'description' => 'Serialized array of variables that match the message string and that is passed into the t() function.',
+      ),
+      'severity' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'tiny',
+        'description' => 'The severity level of the event; ranges from 0 (Emergency) to 7 (Debug)',
+      ),
+    ),
+    'primary key' => array('flid'),
+    'indexes' => array(
+      'id' => array('id'),
+      'id_feed_nid' => array('id', 'feed_nid'),
+      'request_time' => array('request_time'),
+      'log_time' => array('log_time'),
+      'type' => array('type'),
+    ),
+  );
+  db_create_table('feeds_log', $schema);
+}
diff --git a/feeds.module b/feeds.module
index 7df0e3da..fa57b043 100644
--- a/feeds.module
+++ b/feeds.module
@@ -37,6 +37,10 @@ function feeds_cron() {
     }
     feeds_reschedule(FALSE);
   }
+  // Expire old log entries.
+  db_delete('feeds_log')
+    ->condition('request_time', REQUEST_TIME - 604800, '<')
+    ->execute();
 }
 
 /**
@@ -88,7 +92,7 @@ function feeds_source_import($job) {
   }
   catch (FeedsNotExistingException $e) {}
   catch (Exception $e) {
-    watchdog('feeds_source_import()', $e->getMessage(), array(), WATCHDOG_ERROR);
+    $source->log('import', $e->getMessage(), array(), WATCHDOG_ERROR);
   }
   $source->scheduleImport();
 }
@@ -103,7 +107,7 @@ function feeds_source_clear($job) {
   }
   catch (FeedsNotExistingException $e) {}
   catch (Exception $e) {
-    watchdog('feeds_source_clear()', $e->getMessage(), array(), WATCHDOG_ERROR);
+    $source->log('clear', $e->getMessage(), array(), WATCHDOG_ERROR);
   }
   $source->scheduleClear();
 }
@@ -118,7 +122,7 @@ function feeds_importer_expire($job) {
   }
   catch (FeedsNotExistingException $e) {}
   catch (Exception $e) {
-    watchdog('feeds_importer_expire()', $e->getMessage(), array(), WATCHDOG_ERROR);
+    $importer->log('expire', $e->getMessage(), array(), WATCHDOG_ERROR);
   }
   $importer->scheduleExpire();
 }
@@ -218,72 +222,68 @@ function feeds_forms() {
  * Implements hook_menu().
  */
 function feeds_menu() {
-  // Register a callback for all feed configurations that are not attached to a content type.
   $items = array();
+  $items['import'] = array(
+    'title' => 'Import',
+    'page callback' => 'feeds_page',
+    'access callback' => 'feeds_page_access',
+    'file' => 'feeds.pages.inc',
+  );
+  $items['import/%'] = array(
+    'title callback' => 'feeds_importer_title',
+    'title arguments' => array(1),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('feeds_import_form', 1),
+    'access callback' => 'feeds_access',
+    'access arguments' => array('import', 1),
+    'file' => 'feeds.pages.inc',
+  );
+  $items['import/%/import'] = array(
+    'title' => 'Import',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['import/%/delete-items'] = array(
+    'title' => 'Delete items',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('feeds_delete_tab_form', 1),
+    'access callback' => 'feeds_access',
+    'access arguments' => array('clear', 1),
+    'file' => 'feeds.pages.inc',
+    'type' => MENU_LOCAL_TASK,
+  );
+  $items['import/%feeds_importer/template'] = array(
+    'page callback' => 'feeds_importer_template',
+    'page arguments' => array(1),
+    'access callback' => 'feeds_access',
+    'access arguments' => array('import', 1),
+    'file' => 'feeds.pages.inc',
+    'type' => MENU_CALLBACK,
+  );
+  $items['node/%node/import'] = array(
+    'title' => 'Import',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('feeds_import_tab_form', 1),
+    'access callback' => 'feeds_access',
+    'access arguments' => array('import', 1),
+    'file' => 'feeds.pages.inc',
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 10,
+  );
+  $items['node/%node/delete-items'] = array(
+    'title' => 'Delete items',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('feeds_delete_tab_form', NULL, 1),
+    'access callback' => 'feeds_access',
+    'access arguments' => array('clear', 1),
+    'file' => 'feeds.pages.inc',
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 11,
+  );
+  // @todo Eliminate this step and thus eliminate clearing menu cache when
+  // manipulating importers.
   foreach (feeds_importer_load_all() as $importer) {
-    if (empty($importer->config['content_type'])) {
-      $items['import/'. $importer->id] = array(
-        'title' => $importer->config['name'],
-        'page callback' => 'drupal_get_form',
-        'page arguments' => array('feeds_import_form', 1),
-        'access callback' => 'feeds_access',
-        'access arguments' => array('import', $importer->id),
-        'file' => 'feeds.pages.inc',
-      );
-      $items['import/'. $importer->id .'/import'] = array(
-        'title' => 'Import',
-        'type' => MENU_DEFAULT_LOCAL_TASK,
-        'weight' => -10,
-      );
-      $items['import/'. $importer->id .'/delete-items'] = array(
-        'title' => 'Delete items',
-        'page callback' => 'drupal_get_form',
-        'page arguments' => array('feeds_delete_tab_form', 1),
-        'access callback' => 'feeds_access',
-        'access arguments' => array('clear', $importer->id),
-        'file' => 'feeds.pages.inc',
-        'type' => MENU_LOCAL_TASK,
-      );
-    }
-    else {
-      $items['node/%node/import'] = array(
-        'title' => 'Import',
-        'page callback' => 'drupal_get_form',
-        'page arguments' => array('feeds_import_tab_form', 1),
-        'access callback' => 'feeds_access',
-        'access arguments' => array('import', 1),
-        'file' => 'feeds.pages.inc',
-        'type' => MENU_LOCAL_TASK,
-        'weight' => 10,
-      );
-      $items['node/%node/delete-items'] = array(
-        'title' => 'Delete items',
-        'page callback' => 'drupal_get_form',
-        'page arguments' => array('feeds_delete_tab_form', NULL, 1),
-        'access callback' => 'feeds_access',
-        'access arguments' => array('clear', 1),
-        'file' => 'feeds.pages.inc',
-        'type' => MENU_LOCAL_TASK,
-        'weight' => 11,
-      );
-    }
     $items += $importer->fetcher->menuItem();
-    $items['import/' . $importer->id .'/template'] = array(
-      'page callback' => 'feeds_importer_template',
-      'page arguments' => array(1),
-      'access callback' => 'feeds_access',
-      'access arguments' => array('import', $importer->id),
-      'file' => 'feeds.pages.inc',
-      'type' => MENU_CALLBACK,
-    );
-  }
-  if (count($items)) {
-    $items['import'] = array(
-      'title' => 'Import',
-      'page callback' => 'feeds_page',
-      'access callback' => 'feeds_page_access',
-      'file' => 'feeds.pages.inc',
-    );
   }
   return $items;
 }
@@ -295,6 +295,14 @@ function feeds_importer_load($id) {
   return feeds_importer($id);
 }
 
+/**
+ * Title callback.
+ */
+function feeds_importer_title($id) {
+  $importer = feeds_importer($id);
+  return $importer->config['name'];
+}
+
 /**
  * Implements hook_theme().
  */
@@ -356,6 +364,15 @@ function feeds_page_access() {
   return FALSE;
 }
 
+/**
+ * Implements hook_exit().
+ */
+function feeds_exit() {
+  if (drupal_static('feeds_log_error', FALSE)) {
+    watchdog('feeds', 'Feeds reported errors, visit the Feeds log for details.', array(), WATCHDOG_ERROR, 'admin/reports/dblog/feeds');
+  }
+}
+
 /**
  * Implements hook_views_api().
  */
@@ -498,7 +515,10 @@ function feeds_node_delete($node) {
     ->condition('entity_id', $node->nid)
     ->execute();
   // Source attached to node.
-  if ($importer_id = feeds_get_importer_id($node->type)) {
+  // Make sure we don't leave any orphans behind: Do not use
+  // feeds_get_importer_id() to determine importer id as the importer may have
+  // been deleted.
+  if ($importer_id = db_query("SELECT id FROM {feeds_source} WHERE feed_nid = :nid", array(':nid' => $node->nid))->fetchField()) {
     feeds_source($importer_id, $node->nid)->delete();
   }
 }
@@ -550,6 +570,7 @@ function feeds_user_delete($account) {
     ->condition('entity_id', $account->uid)
     ->execute();
 }
+
 /**
  * Implements hook_form_alter().
  */
@@ -694,6 +715,28 @@ function feeds_dbg($msg) {
   }
 }
 
+/**
+ * Writes to feeds log.
+ */
+function feeds_log($importer_id, $feed_nid, $type, $message, $variables = array(), $severity = WATCHDOG_NOTICE) {
+  if ($severity < WATCHDOG_NOTICE) {
+    $error = &drupal_static('feeds_log_error', FALSE);
+    $error = TRUE;
+  }
+  db_insert('feeds_log')
+    ->fields(array(
+      'id' => $importer_id,
+      'feed_nid' => $feed_nid,
+      'log_time' => time(),
+      'request_time' => REQUEST_TIME,
+      'type' => $type,
+      'message' => $message,
+      'variables' => serialize($variables),
+      'severity' => $severity,
+    ))
+    ->execute();
+}
+
 /**
  * Loads an item info object.
  *
diff --git a/feeds.pages.inc b/feeds.pages.inc
index 8d72d2a4..a3c90e3f 100644
--- a/feeds.pages.inc
+++ b/feeds.pages.inc
@@ -33,6 +33,9 @@ function feeds_page() {
       );
     }
   }
+  if (empty($rows)) {
+    drupal_set_message(t('There are no importers, go to !importers to create one or enable an existing one.', array('!importers' => l(t('Feeds importers'), 'admin/structure/feeds'))));
+  }
   $header = array(
     t('Import'),
     t('Description'),
diff --git a/includes/FeedsSource.inc b/includes/FeedsSource.inc
index a886e1de..729cbacc 100644
--- a/includes/FeedsSource.inc
+++ b/includes/FeedsSource.inc
@@ -625,6 +625,13 @@ class FeedsSource extends FeedsConfigurable {
     }
   }
 
+  /**
+   * Writes to feeds log.
+   */
+  public function log($type, $message, $variables = array(), $severity = WATCHDOG_NOTICE) {
+    feeds_log($this->id, $this->feed_nid, $type, $message, $variables, $severity);
+  }
+
   /**
    * Background job helper. Starts a background job using Job Scheduler.
    *
diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc
index 471e74bf..3743b6d8 100644
--- a/plugins/FeedsProcessor.inc
+++ b/plugins/FeedsProcessor.inc
@@ -139,7 +139,12 @@ abstract class FeedsProcessor extends FeedsPlugin {
         catch (Exception $e) {
           $state->failed++;
           drupal_set_message($e->getMessage(), 'warning');
-          watchdog('feeds', $e->getMessage(), array(), WATCHDOG_WARNING);
+          $message = $e->getMessage();
+          $message .= '<h3>Original item</h3>';
+          $message .= '<pre>' . var_export($item, true) . '</pre>';
+          $message .= '<h3>Entity</h3>';
+          $message .= '<pre>' . var_export($entity, true) . '</pre>';
+          $source->log('import', $message, array(), WATCHDOG_ERROR);
         }
       }
     }
@@ -183,6 +188,7 @@ abstract class FeedsProcessor extends FeedsPlugin {
     }
     foreach ($messages as $message) {
       drupal_set_message($message);
+      $source->log('import', $message, array(), WATCHDOG_INFO);
     }
   }
 
@@ -249,6 +255,7 @@ abstract class FeedsProcessor extends FeedsPlugin {
             '@entities' => strtolower($info['label plural']),
           )
         );
+        $source->log('clear', $message, array(), WATCHDOG_INFO);
         drupal_set_message($message);
       }
       else {
diff --git a/views/feeds.views.inc b/views/feeds.views.inc
index 6ccd42b5..e143b4ca 100644
--- a/views/feeds.views.inc
+++ b/views/feeds.views.inc
@@ -183,22 +183,143 @@ function feeds_views_data() {
       ),
     );
   }
-  return $data;
-}
 
-/**
- * Implements hook_views_handlers().
- */
-function feeds_views_handlers() {
-  return array(
-    'info' => array(
-      'path' => drupal_get_path('module', 'feeds') .'/views',
-    ),
-    'handlers' => array(
-      // field handlers
-      'feeds_views_handler_field_source' => array(
-        'parent' => 'views_handler_field',
+  /**
+   * Expose feeds_log table to views.
+   */
+  $data['feeds_log']['table'] = array(
+    'group' => 'Feeds log',
+    'base' => array(
+      'field' => array('flid'),
+      'title' => 'Feeds log',
+      'help' => 'Logs events during importing, clearing, expiry.',
+    ),
+  );
+  $data['feeds_log']['id'] = array(
+    'title' => 'Importer id',
+    'help' => 'The id of an importer.',
+    'field' => array(
+      'handler' => 'feeds_views_handler_field',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_string',
+      'allow empty' => TRUE,
+      'help' => 'Filter on an importer id.',
+    ),
+    'argument' => array(
+      'handler' => 'feeds_views_handler_argument_importer_id',
+      'help' => 'Filter on an importer id.',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+      'help' => 'Sort by importer id.',
+    ),
+    'relationship' => array(
+      'title' => t('Importer'),
+      'help' => t('Relate a log entry to its importer if available.'),
+      'label' => t('Importer'),
+      'base' => 'feeds_importer',
+      'base field' => 'id',
+    ),
+  );
+  $data['feeds_log']['importer_name'] = array(
+    'real field' => 'id',
+    'title' => 'Importer name',
+    'help' => 'The human readable name of an importer.',
+    'field' => array(
+      'handler' => 'feeds_views_handler_field_importer_name',
+    ),
+  );
+  $data['feeds_log']['feed_nid'] = array(
+    'title' => 'Feed node id',
+    'help' => 'Contains the node id of a feed node if the feed\'s configuration is attached to a content type, otherwise contains 0.',
+    'field' => array(
+      'handler' => 'feeds_views_handler_field_numeric',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_numeric',
+      'allow empty' => TRUE,
+      'help' => 'Filter on a Feeds Source\'s feed_nid field.',
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_numeric',
+      'numeric' => TRUE,
+      'validate type' => 'nid',
+      'help' => 'Argument on a Feeds Source\'s feed_nid field.',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+      'help' => 'Sort Feeds Source\'s feed_nid field.',
+    ),
+    'relationship' => array(
+      'title' => t('Feed node'),
+      'help' => t('Relate a log entry to its feed node if available.'),
+      'label' => t('Feed node'),
+      'base' => 'node',
+      'base field' => 'nid',
+    ),
+  );
+  $data['feeds_log']['log_time'] = array(
+    'title' => t('Log time'),
+    'help' => t('The time of the event.'),
+    'field' => array(
+      'handler' => 'views_handler_field_date',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort_date',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_date',
+    ),
+  );
+  $data['feeds_log']['request_time'] = array(
+    'title' => t('Request time'),
+    'help' => t('The time of the page request of an event.'),
+    'field' => array(
+      'handler' => 'views_handler_field_date',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort_date',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_date',
+    ),
+  );
+  $data['feeds_log']['message'] = array(
+    'title' => 'Log message',
+    'help' => 'The message logged by the event.',
+    'field' => array(
+      'handler' => 'feeds_views_handler_field_log_message',
+      'click sortable' => FALSE,
+      'additional fields' => array(
+        'variables',
       ),
     ),
   );
+  $data['feeds_log']['severity'] = array(
+    'title' => 'Severity',
+    'help' => 'The severity of the event logged.',
+    'field' => array(
+      'handler' => 'feeds_views_handler_field_severity',
+      'click sortable' => FALSE,
+    ),
+    'filter' => array(
+      'handler' => 'feeds_views_handler_filter_severity',
+      'allow empty' => TRUE,
+      'help' => 'Filter on the severity of a log message.',
+    ),
+  );
+  $data['feeds_log']['table']['join'] = array(
+    'node' => array(
+      'left_field' => 'nid',
+      'field' => 'feed_nid',
+      'type' => 'LEFT',
+    ),
+  );
+
+  return $data;
 }
diff --git a/views/feeds.views_default.inc b/views/feeds.views_default.inc
new file mode 100644
index 00000000..09d836f2
--- /dev/null
+++ b/views/feeds.views_default.inc
@@ -0,0 +1,286 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Default view definitions for Feeds.
+ */
+
+/**
+ * Implementation of hook_views_default_views().
+ */
+function feeds_views_default_views() {
+  $views = array();
+
+  $view = new view;
+  $view->name = 'feeds_log';
+  $view->description = 'Feeds log displays for overview, standalone importers and feed nodes.';
+  $view->tag = 'Feeds';
+  $view->base_table = 'feeds_log';
+  $view->api_version = '3.0-alpha1';
+  $view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
+
+  /* Display: Defaults */
+  $handler = $view->new_display('default', 'Defaults', 'default');
+  $handler->display->display_options['access']['type'] = 'perm';
+  $handler->display->display_options['access']['perm'] = 'administer feeds';
+  $handler->display->display_options['cache']['type'] = 'none';
+  $handler->display->display_options['query']['type'] = 'views_query';
+  $handler->display->display_options['exposed_form']['type'] = 'basic';
+  $handler->display->display_options['pager']['type'] = 'full';
+  $handler->display->display_options['pager']['options']['items_per_page'] = '50';
+  $handler->display->display_options['pager']['options']['offset'] = '0';
+  $handler->display->display_options['pager']['options']['id'] = '0';
+  $handler->display->display_options['style_plugin'] = 'table';
+  $handler->display->display_options['style_options']['columns'] = array(
+    'log_time' => 'log_time',
+    'request_time' => 'request_time',
+    'message' => 'message',
+    'severity' => 'severity',
+  );
+  $handler->display->display_options['style_options']['default'] = '-1';
+  $handler->display->display_options['style_options']['info'] = array(
+    'log_time' => array(
+      'sortable' => 0,
+      'align' => '',
+      'separator' => '',
+    ),
+    'request_time' => array(
+      'sortable' => 0,
+      'align' => '',
+      'separator' => '',
+    ),
+    'message' => array(
+      'align' => '',
+      'separator' => '',
+    ),
+    'severity' => array(
+      'align' => '',
+      'separator' => '',
+    ),
+  );
+  $handler->display->display_options['style_options']['override'] = 1;
+  $handler->display->display_options['style_options']['sticky'] = 0;
+  /* Empty text: Global: Text area */
+  $handler->display->display_options['empty']['area']['id'] = 'area';
+  $handler->display->display_options['empty']['area']['table'] = 'views';
+  $handler->display->display_options['empty']['area']['field'] = 'area';
+  $handler->display->display_options['empty']['area']['empty'] = FALSE;
+  $handler->display->display_options['empty']['area']['content'] = 'There are no log messages.';
+  $handler->display->display_options['empty']['area']['format'] = '1';
+  /* Field: Feeds log: Log time */
+  $handler->display->display_options['fields']['log_time']['id'] = 'log_time';
+  $handler->display->display_options['fields']['log_time']['table'] = 'feeds_log';
+  $handler->display->display_options['fields']['log_time']['field'] = 'log_time';
+  $handler->display->display_options['fields']['log_time']['alter']['alter_text'] = 0;
+  $handler->display->display_options['fields']['log_time']['alter']['make_link'] = 0;
+  $handler->display->display_options['fields']['log_time']['alter']['absolute'] = 0;
+  $handler->display->display_options['fields']['log_time']['alter']['trim'] = 0;
+  $handler->display->display_options['fields']['log_time']['alter']['word_boundary'] = 1;
+  $handler->display->display_options['fields']['log_time']['alter']['ellipsis'] = 1;
+  $handler->display->display_options['fields']['log_time']['alter']['strip_tags'] = 0;
+  $handler->display->display_options['fields']['log_time']['alter']['html'] = 0;
+  $handler->display->display_options['fields']['log_time']['hide_empty'] = 0;
+  $handler->display->display_options['fields']['log_time']['empty_zero'] = 0;
+  $handler->display->display_options['fields']['log_time']['date_format'] = 'custom';
+  $handler->display->display_options['fields']['log_time']['custom_date_format'] = 'Y-m-d H:i:s';
+  /* Field: Feeds log: Request time */
+  $handler->display->display_options['fields']['request_time']['id'] = 'request_time';
+  $handler->display->display_options['fields']['request_time']['table'] = 'feeds_log';
+  $handler->display->display_options['fields']['request_time']['field'] = 'request_time';
+  $handler->display->display_options['fields']['request_time']['alter']['alter_text'] = 0;
+  $handler->display->display_options['fields']['request_time']['alter']['make_link'] = 0;
+  $handler->display->display_options['fields']['request_time']['alter']['absolute'] = 0;
+  $handler->display->display_options['fields']['request_time']['alter']['trim'] = 0;
+  $handler->display->display_options['fields']['request_time']['alter']['word_boundary'] = 1;
+  $handler->display->display_options['fields']['request_time']['alter']['ellipsis'] = 1;
+  $handler->display->display_options['fields']['request_time']['alter']['strip_tags'] = 0;
+  $handler->display->display_options['fields']['request_time']['alter']['html'] = 0;
+  $handler->display->display_options['fields']['request_time']['hide_empty'] = 0;
+  $handler->display->display_options['fields']['request_time']['empty_zero'] = 0;
+  $handler->display->display_options['fields']['request_time']['date_format'] = 'custom';
+  $handler->display->display_options['fields']['request_time']['custom_date_format'] = 'Y-m-d H:i:s';
+  /* Field: Feeds log: Log message */
+  $handler->display->display_options['fields']['message']['id'] = 'message';
+  $handler->display->display_options['fields']['message']['table'] = 'feeds_log';
+  $handler->display->display_options['fields']['message']['field'] = 'message';
+  $handler->display->display_options['fields']['message']['label'] = 'Message';
+  $handler->display->display_options['fields']['message']['hide_empty'] = 0;
+  $handler->display->display_options['fields']['message']['empty_zero'] = 0;
+  /* Field: Feeds log: Severity */
+  $handler->display->display_options['fields']['severity']['id'] = 'severity';
+  $handler->display->display_options['fields']['severity']['table'] = 'feeds_log';
+  $handler->display->display_options['fields']['severity']['field'] = 'severity';
+  $handler->display->display_options['fields']['severity']['hide_empty'] = 0;
+  $handler->display->display_options['fields']['severity']['empty_zero'] = 0;
+  /* Sort criterion: Feeds log: Log time */
+  $handler->display->display_options['sorts']['log_time']['id'] = 'log_time';
+  $handler->display->display_options['sorts']['log_time']['table'] = 'feeds_log';
+  $handler->display->display_options['sorts']['log_time']['field'] = 'log_time';
+  $handler->display->display_options['sorts']['log_time']['order'] = 'DESC';
+  /* Argument: Feeds log: Importer id */
+  $handler->display->display_options['arguments']['id']['id'] = 'id';
+  $handler->display->display_options['arguments']['id']['table'] = 'feeds_log';
+  $handler->display->display_options['arguments']['id']['field'] = 'id';
+  $handler->display->display_options['arguments']['id']['default_action'] = 'empty';
+  $handler->display->display_options['arguments']['id']['style_plugin'] = 'default_summary';
+  $handler->display->display_options['arguments']['id']['default_argument_type'] = 'fixed';
+  $handler->display->display_options['arguments']['id']['validate_fail'] = 'empty';
+  $handler->display->display_options['arguments']['id']['glossary'] = 0;
+  $handler->display->display_options['arguments']['id']['limit'] = '0';
+  $handler->display->display_options['arguments']['id']['transform_dash'] = 0;
+  /* Filter: Feeds log: Feed node id */
+  $handler->display->display_options['filters']['feed_nid']['id'] = 'feed_nid';
+  $handler->display->display_options['filters']['feed_nid']['table'] = 'feeds_log';
+  $handler->display->display_options['filters']['feed_nid']['field'] = 'feed_nid';
+  $handler->display->display_options['filters']['feed_nid']['value']['value'] = '0';
+  /* Filter: Feeds log: Severity */
+  $handler->display->display_options['filters']['severity']['id'] = 'severity';
+  $handler->display->display_options['filters']['severity']['table'] = 'feeds_log';
+  $handler->display->display_options['filters']['severity']['field'] = 'severity';
+  $handler->display->display_options['filters']['severity']['exposed'] = TRUE;
+  $handler->display->display_options['filters']['severity']['expose']['operator'] = 'severity_op';
+  $handler->display->display_options['filters']['severity']['expose']['label'] = 'Severity';
+  $handler->display->display_options['filters']['severity']['expose']['use_operator'] = FALSE;
+  $handler->display->display_options['filters']['severity']['expose']['identifier'] = 'severity';
+  $handler->display->display_options['filters']['severity']['expose']['reduce'] = 0;
+
+  /* Display: Standalone importer page */
+  $handler = $view->new_display('page', 'Standalone importer page', 'page_1');
+  $handler->display->display_options['path'] = 'import/%/log';
+  $handler->display->display_options['menu']['type'] = 'tab';
+  $handler->display->display_options['menu']['title'] = 'Log';
+  $handler->display->display_options['menu']['weight'] = '0';
+
+  /* Display: Feed node page */
+  $handler = $view->new_display('page', 'Feed node page', 'page_2');
+  $handler->display->display_options['defaults']['arguments'] = FALSE;
+  /* Argument: Feeds log: Feed node id */
+  $handler->display->display_options['arguments']['feed_nid']['id'] = 'feed_nid';
+  $handler->display->display_options['arguments']['feed_nid']['table'] = 'feeds_log';
+  $handler->display->display_options['arguments']['feed_nid']['field'] = 'feed_nid';
+  $handler->display->display_options['arguments']['feed_nid']['default_action'] = 'not found';
+  $handler->display->display_options['arguments']['feed_nid']['style_plugin'] = 'default_summary';
+  $handler->display->display_options['arguments']['feed_nid']['default_argument_type'] = 'fixed';
+  $handler->display->display_options['arguments']['feed_nid']['break_phrase'] = 0;
+  $handler->display->display_options['arguments']['feed_nid']['not'] = 0;
+  $handler->display->display_options['defaults']['filters'] = FALSE;
+  /* Filter: Feeds log: Severity */
+  $handler->display->display_options['filters']['severity']['id'] = 'severity';
+  $handler->display->display_options['filters']['severity']['table'] = 'feeds_log';
+  $handler->display->display_options['filters']['severity']['field'] = 'severity';
+  $handler->display->display_options['filters']['severity']['exposed'] = TRUE;
+  $handler->display->display_options['filters']['severity']['expose']['operator'] = 'severity_op';
+  $handler->display->display_options['filters']['severity']['expose']['label'] = 'Severity';
+  $handler->display->display_options['filters']['severity']['expose']['use_operator'] = FALSE;
+  $handler->display->display_options['filters']['severity']['expose']['identifier'] = 'severity';
+  $handler->display->display_options['filters']['severity']['expose']['reduce'] = 0;
+  $handler->display->display_options['path'] = 'node/%/log';
+  $handler->display->display_options['menu']['type'] = 'tab';
+  $handler->display->display_options['menu']['title'] = 'Log';
+  $handler->display->display_options['menu']['weight'] = '12';
+
+  /* Display: All entries */
+  $handler = $view->new_display('page', 'All entries', 'page_3');
+  $handler->display->display_options['defaults']['title'] = FALSE;
+  $handler->display->display_options['title'] = 'Feeds log';
+  $handler->display->display_options['defaults']['relationships'] = FALSE;
+  /* Relationship: Feeds log: Feed node */
+  $handler->display->display_options['relationships']['feed_nid']['id'] = 'feed_nid';
+  $handler->display->display_options['relationships']['feed_nid']['table'] = 'feeds_log';
+  $handler->display->display_options['relationships']['feed_nid']['field'] = 'feed_nid';
+  $handler->display->display_options['defaults']['fields'] = FALSE;
+  /* Field: Feeds log: Log time */
+  $handler->display->display_options['fields']['log_time']['id'] = 'log_time';
+  $handler->display->display_options['fields']['log_time']['table'] = 'feeds_log';
+  $handler->display->display_options['fields']['log_time']['field'] = 'log_time';
+  $handler->display->display_options['fields']['log_time']['alter']['alter_text'] = 0;
+  $handler->display->display_options['fields']['log_time']['alter']['make_link'] = 0;
+  $handler->display->display_options['fields']['log_time']['alter']['absolute'] = 0;
+  $handler->display->display_options['fields']['log_time']['alter']['trim'] = 0;
+  $handler->display->display_options['fields']['log_time']['alter']['word_boundary'] = 1;
+  $handler->display->display_options['fields']['log_time']['alter']['ellipsis'] = 1;
+  $handler->display->display_options['fields']['log_time']['alter']['strip_tags'] = 0;
+  $handler->display->display_options['fields']['log_time']['alter']['html'] = 0;
+  $handler->display->display_options['fields']['log_time']['hide_empty'] = 0;
+  $handler->display->display_options['fields']['log_time']['empty_zero'] = 0;
+  $handler->display->display_options['fields']['log_time']['date_format'] = 'custom';
+  $handler->display->display_options['fields']['log_time']['custom_date_format'] = 'Y-m-d H:i:s';
+  /* Field: Feeds log: Request time */
+  $handler->display->display_options['fields']['request_time']['id'] = 'request_time';
+  $handler->display->display_options['fields']['request_time']['table'] = 'feeds_log';
+  $handler->display->display_options['fields']['request_time']['field'] = 'request_time';
+  $handler->display->display_options['fields']['request_time']['alter']['alter_text'] = 0;
+  $handler->display->display_options['fields']['request_time']['alter']['make_link'] = 0;
+  $handler->display->display_options['fields']['request_time']['alter']['absolute'] = 0;
+  $handler->display->display_options['fields']['request_time']['alter']['trim'] = 0;
+  $handler->display->display_options['fields']['request_time']['alter']['word_boundary'] = 1;
+  $handler->display->display_options['fields']['request_time']['alter']['ellipsis'] = 1;
+  $handler->display->display_options['fields']['request_time']['alter']['strip_tags'] = 0;
+  $handler->display->display_options['fields']['request_time']['alter']['html'] = 0;
+  $handler->display->display_options['fields']['request_time']['hide_empty'] = 0;
+  $handler->display->display_options['fields']['request_time']['empty_zero'] = 0;
+  $handler->display->display_options['fields']['request_time']['date_format'] = 'custom';
+  $handler->display->display_options['fields']['request_time']['custom_date_format'] = 'Y-m-d H:i:s';
+  /* Field: Feeds log: Log message */
+  $handler->display->display_options['fields']['message']['id'] = 'message';
+  $handler->display->display_options['fields']['message']['table'] = 'feeds_log';
+  $handler->display->display_options['fields']['message']['field'] = 'message';
+  $handler->display->display_options['fields']['message']['label'] = 'Message';
+  $handler->display->display_options['fields']['message']['hide_empty'] = 0;
+  $handler->display->display_options['fields']['message']['empty_zero'] = 0;
+  /* Field: Feeds log: Severity */
+  $handler->display->display_options['fields']['severity']['id'] = 'severity';
+  $handler->display->display_options['fields']['severity']['table'] = 'feeds_log';
+  $handler->display->display_options['fields']['severity']['field'] = 'severity';
+  $handler->display->display_options['fields']['severity']['hide_empty'] = 0;
+  $handler->display->display_options['fields']['severity']['empty_zero'] = 0;
+  /* Field: Feeds log: Importer name */
+  $handler->display->display_options['fields']['importer_name']['id'] = 'importer_name';
+  $handler->display->display_options['fields']['importer_name']['table'] = 'feeds_log';
+  $handler->display->display_options['fields']['importer_name']['field'] = 'importer_name';
+  $handler->display->display_options['fields']['importer_name']['label'] = 'Importer';
+  $handler->display->display_options['fields']['importer_name']['hide_empty'] = 0;
+  $handler->display->display_options['fields']['importer_name']['empty_zero'] = 0;
+  $handler->display->display_options['fields']['importer_name']['link'] = '2';
+  /* Field: Node: Title */
+  $handler->display->display_options['fields']['title']['id'] = 'title';
+  $handler->display->display_options['fields']['title']['table'] = 'node';
+  $handler->display->display_options['fields']['title']['field'] = 'title';
+  $handler->display->display_options['fields']['title']['relationship'] = 'feed_nid';
+  $handler->display->display_options['fields']['title']['label'] = 'Feed node';
+  $handler->display->display_options['fields']['title']['alter']['alter_text'] = 0;
+  $handler->display->display_options['fields']['title']['alter']['make_link'] = 0;
+  $handler->display->display_options['fields']['title']['alter']['absolute'] = 0;
+  $handler->display->display_options['fields']['title']['alter']['trim'] = 1;
+  $handler->display->display_options['fields']['title']['alter']['max_length'] = '40';
+  $handler->display->display_options['fields']['title']['alter']['word_boundary'] = 1;
+  $handler->display->display_options['fields']['title']['alter']['ellipsis'] = 1;
+  $handler->display->display_options['fields']['title']['alter']['strip_tags'] = 0;
+  $handler->display->display_options['fields']['title']['alter']['html'] = 0;
+  $handler->display->display_options['fields']['title']['hide_empty'] = 0;
+  $handler->display->display_options['fields']['title']['empty_zero'] = 0;
+  $handler->display->display_options['fields']['title']['link_to_node'] = 1;
+  $handler->display->display_options['defaults']['arguments'] = FALSE;
+  $handler->display->display_options['defaults']['filters'] = FALSE;
+  /* Filter: Feeds log: Severity */
+  $handler->display->display_options['filters']['severity']['id'] = 'severity';
+  $handler->display->display_options['filters']['severity']['table'] = 'feeds_log';
+  $handler->display->display_options['filters']['severity']['field'] = 'severity';
+  $handler->display->display_options['filters']['severity']['exposed'] = TRUE;
+  $handler->display->display_options['filters']['severity']['expose']['operator'] = 'severity_op';
+  $handler->display->display_options['filters']['severity']['expose']['label'] = 'Severity';
+  $handler->display->display_options['filters']['severity']['expose']['use_operator'] = FALSE;
+  $handler->display->display_options['filters']['severity']['expose']['identifier'] = 'severity';
+  $handler->display->display_options['filters']['severity']['expose']['reduce'] = 0;
+  $handler->display->display_options['path'] = 'admin/reports/feeds';
+  $handler->display->display_options['menu']['type'] = 'normal';
+  $handler->display->display_options['menu']['title'] = 'Feeds log';
+  $handler->display->display_options['menu']['description'] = 'Review log messages of imports and subscriptions to feeds.';
+  $handler->display->display_options['menu']['weight'] = '0';
+  $handler->display->display_options['menu']['name'] = 'management';
+
+  $views[$view->name] = $view;
+
+  return $views;
+}
diff --git a/views/feeds_views_handler_argument_importer_id.inc b/views/feeds_views_handler_argument_importer_id.inc
new file mode 100644
index 00000000..cfd54e30
--- /dev/null
+++ b/views/feeds_views_handler_argument_importer_id.inc
@@ -0,0 +1,25 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Argument handler for importer ids.
+ */
+class feeds_views_handler_argument_importer_id extends views_handler_argument_string {
+
+  /**
+   * Argument must be a valid importer id.
+   */
+  function validate_arg($arg) {
+    // By using % in URLs, arguments could be validated twice; this eases
+    // that pain.
+    if (isset($this->argument_validated)) {
+      return $this->argument_validated;
+    }
+    $this->argument_validated = FALSE;
+    if (in_array($arg, feeds_enabled_importers())) {
+      $this->argument_validated = TRUE;
+    }
+    return $this->argument_validated;
+  }
+}
diff --git a/views/feeds_views_handler_field_importer_name.inc b/views/feeds_views_handler_field_importer_name.inc
new file mode 100644
index 00000000..7d29a6ae
--- /dev/null
+++ b/views/feeds_views_handler_field_importer_name.inc
@@ -0,0 +1,64 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Render an importer name.
+ */
+
+class feeds_views_handler_field_importer_name extends views_handler_field {
+
+  /**
+   * Overrides parent::option_definition().
+   */
+  function option_definition() {
+    $options = parent::option_definition();
+    $options['link'] = array('default' => 0);
+    return $options;
+  }
+
+  /**
+   * Overrides parent::options_form().
+   */
+  function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
+    $options = array(
+      0 => t('Do not link'),
+      1 => t('Link to configuration form'),
+      2 => t('Link to standalone form'),
+    );
+    $form['link'] = array(
+      '#type' => 'radios',
+      '#title' => t('Link importer name'),
+      '#description' => t('If "Link to standalone form" is used, only importers that use a standalone form will be linked.'),
+      '#default_value' => isset($this->options['link']) ? $this->options['link'] : FALSE,
+      '#options' => $options,
+    );
+  }
+
+  /**
+   * Overrides parent::render().
+   */
+  function render($values) {
+    try {
+      $importer = feeds_importer($values->{$this->field_alias})->existing();
+      if ($this->options['link'] == 1) {
+        return l($importer->config['name'], 'admin/structure/feeds/' . $importer->id);
+      }
+      elseif ($this->options['link'] == 2 && empty($importer->config['content_type'])) {
+        return l($importer->config['name'], 'import/' . $importer->id);
+      }
+      return check_plain($importer->config['name']);
+    }
+    catch (Exception $e) {
+      return t('Missing importer');
+    }
+  }
+
+  /**
+   * Disallows advanced rendering.
+   */
+  function allow_advanced_render() {
+    return FALSE;
+  }
+}
diff --git a/views/feeds_views_handler_field_log_message.inc b/views/feeds_views_handler_field_log_message.inc
new file mode 100644
index 00000000..b9fe5b21
--- /dev/null
+++ b/views/feeds_views_handler_field_log_message.inc
@@ -0,0 +1,26 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Views handler for displaying a log message.
+ */
+
+class feeds_views_handler_field_log_message extends views_handler_field {
+
+  /**
+   * Override parent::render().
+   */
+  function render($values) {
+    $message = $values->{$this->field_alias};
+    $variables = unserialize($values->{$this->aliases['variables']});
+    return t($message, $variables);
+  }
+
+  /**
+   * Disallow advanced rendering.
+   */
+  function allow_advanced_render() {
+    return FALSE;
+  }
+}
diff --git a/views/feeds_views_handler_field_severity.inc b/views/feeds_views_handler_field_severity.inc
new file mode 100644
index 00000000..07413d30
--- /dev/null
+++ b/views/feeds_views_handler_field_severity.inc
@@ -0,0 +1,26 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Views handler for displaying the logs severity field.
+ */
+
+class feeds_views_handler_field_severity extends views_handler_field {
+
+  /**
+   * Override parent::render().
+   */
+  function render($values) {
+    $value = $values->{$this->field_alias};
+    $levels = watchdog_severity_levels();
+    return $levels[$value];
+  }
+
+  /**
+   * Disallow advanced rendering.
+   */
+  function allow_advanced_render() {
+    return FALSE;
+  }
+}
diff --git a/views/feeds_views_handler_filter_severity.inc b/views/feeds_views_handler_filter_severity.inc
new file mode 100644
index 00000000..58be7d70
--- /dev/null
+++ b/views/feeds_views_handler_filter_severity.inc
@@ -0,0 +1,14 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Filter by severity.
+ */
+
+class feeds_views_handler_filter_severity extends views_handler_filter_in_operator {
+  function get_value_options() {
+    $this->value_title = t('Severity');
+    $this->value_options = watchdog_severity_levels();
+  }
+}
-- 
GitLab