? 645978-dblog-features.patch ? mongodb_watchdog-capped.patch ? mongodb_watchdog-capped.patch.1 ? mongodb_watchdog-tests.patch Index: mongodb_watchdog.admin.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mongodb/mongodb_watchdog/mongodb_watchdog.admin.inc,v retrieving revision 1.3 diff -u -p -r1.3 mongodb_watchdog.admin.inc --- mongodb_watchdog.admin.inc 15 Dec 2009 01:32:05 -0000 1.3 +++ mongodb_watchdog.admin.inc 1 Jan 2010 14:56:20 -0000 @@ -228,7 +228,7 @@ function mongodb_watchdog_clear_log_form function mongodb_watchdog_clear_log_submit() { try { $collection = mongodb_collection(variable_get('mongodb_collectionname', 'watchdog')); - $collection->db->dropCollection($collection->getName()); + $collection->remove(); drupal_set_message(t('MongoDB log cleared.')); } @@ -260,3 +260,46 @@ function mongodb_watchdog_build_filter_q } return $find; } + +/** + * Settings for MongoDB watchdog. + */ +function mongodb_watchdog_settings() { + $form['mongodb_watchdog_size'] = array( + '#title' => t('Maximum size of the collection'), + '#type' => 'select', + '#default_value' => variable_get('mongodb_watchdog_size', 10000), + '#options' => array(0 => t('Unlimited')) + drupal_map_assoc(array(1000, 10000, 100000, 1000000, 10000000)), + '#description' => t('The maximum size of the collection in bytes.'), + ); + $form['mongodb_watchdog_row_limit'] = array( + '#title' => t('MongoDB log entries to keep'), + '#type' => 'select', + '#default_value' => variable_get('mongodb_watchdog_row_limit', 1000), + '#options' => array(0 => t('All')) + drupal_map_assoc(array(100, 1000, 10000, 100000, 1000000)), + '#description' => t('The maximum number of entries to keep in the database log. Old entries will be droped. Select Select 0 to keep all entries.'), + ); + + $form['#submit'][] = 'mongodb_watchdog_settings_submit'; + return system_settings_form($form); +} + +/** + * Submit handler for mongodb watchdog settings. + */ +function mongodb_watchdog_settings_submit(&$form, &$form_state) { + // if the row limit changed drop the old collection and setup a new with limited rows. + if (($form_state['values']['mongodb_watchdog_row_limit'] != variable_get('mongodb_watchdog_row_limit', 1000)) + || ($form_state['values']['mongodb_watchdog_size'] != variable_get('mongodb_watchdog_size', 10000))) { + $collection_name = variable_get('mongodb_collectionname', 'watchdog'); + $collection = mongodb_collection($collection_name); + $collection->db->dropCollection($collection_name); + if ($form_state['values']['mongodb_watchdog_size'] && $form_state['values']['mongodb_watchdog_row_limit']) { + $collection->db->createCollection($collection_name, TRUE, $form_state['values']['mongodb_watchdog_size'], $form_state['values']['mongodb_watchdog_row_limit']); + } + else { + $collection->db->createCollection($collection_name); + } + } +} + Index: mongodb_watchdog.info =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mongodb/mongodb_watchdog/mongodb_watchdog.info,v retrieving revision 1.1 diff -u -p -r1.1 mongodb_watchdog.info --- mongodb_watchdog.info 29 Nov 2009 22:42:13 -0000 1.1 +++ mongodb_watchdog.info 1 Jan 2010 14:56:20 -0000 @@ -1,5 +1,5 @@ ;$Id: mongodb_watchdog.info,v 1.1 2009/11/29 22:42:13 dereine Exp $ -name = MongoDB watchdog +name = MongoDB Watchdog description = 'A watchdog implementation using MongoDB' package = MongoDB version = VERSION @@ -7,4 +7,5 @@ core = 7.x dependencies[] = mongodb files[] = mongodb_watchdog.module files[] = mongodb_watchdog.admin.inc +files[] = mongodb_watchdog.test Index: mongodb_watchdog.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/mongodb/mongodb_watchdog/mongodb_watchdog.module,v retrieving revision 1.2 diff -u -p -r1.2 mongodb_watchdog.module --- mongodb_watchdog.module 14 Dec 2009 16:19:31 -0000 1.2 +++ mongodb_watchdog.module 1 Jan 2010 14:56:20 -0000 @@ -1,5 +1,6 @@ array('access site reports'), 'file' => 'mongodb_watchdog.admin.inc', ); + $items['admin/config/development/mongodb_watchdog'] = array( + 'title' => 'MongoDB Log Settings', + 'description' => 'Change settings of the mongodb watchdog implementation.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('mongodb_watchdog_settings'), + 'access arguments' => array('administer site configuration'), + 'file' => 'mongodb_watchdog.admin.inc', + ); return $items; } /** Index: mongodb_watchdog.test =================================================================== RCS file: mongodb_watchdog.test diff -N mongodb_watchdog.test --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ mongodb_watchdog.test 1 Jan 2010 14:56:21 -0000 @@ -0,0 +1,581 @@ + 'mongodb_watchdog functionality', + 'description' => 'Generate events and verify mongodb_watchdog entries; verify user access to log reports based on persmissions.', + 'group' => 'MongoDB', + ); + } + + /** + * Enable modules and create users with specific permissions. + */ + function setUp() { + parent::setUp('mongodb_watchdog', 'blog', 'poll', 'user', 'node'); + // Create users. + $this->big_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages', 'access site reports', 'administer users')); + $this->any_user = $this->drupalCreateUser(array()); + $this->collection = mongodb_collection(variable_get('mongodb_collectionname', 'watchdog')); + } + + /** + * Login users, create mongodb_watchdog events, and test mongodb_watchdog functionality through the admin and user interfaces. + */ + function testmongodb_watchdog() { + // Login the admin user. + $this->drupalLogin($this->big_user); + + $row_limit = 100; + $this->verifyRowLimit($row_limit); + //$this->verifyCron($row_limit); + //$this->verifyEvents(); + //$this->verifyReports(); + + // Login the regular user. + //$this->drupalLogin($this->any_user); + //$this->verifyReports(403); + } + + /** + * Verify setting of the mongodb_watchdog row limit. + * + * @param integer $count Log row limit. + */ + private function verifyRowLimit($row_limit) { + // Change the mongodb_watchdog row limit. + $edit = array(); + $edit['mongodb_watchdog_row_limit'] = $row_limit; + $this->drupalPost('admin/config/development/mongodb_watchdog', $edit, t('Save configuration')); + $this->assertResponse(200); + + // Check row limit variable. + $current_limit = variable_get('mongodb_watchdog_row_limit', 1000); + $this->assertTrue($current_limit == $row_limit, t('[Cache] Row limit variable of @count equals row limit of @limit', array('@count' => $current_limit, '@limit' => $row_limit))); + // Verify mongodb_watchdog row limit equals specified row limit. + $current_limit = unserialize(db_query("SELECT value FROM {variable} WHERE name = :mongodb_watchdog_limit", array(':mongodb_watchdog_limit' => 'mongodb_watchdog_row_limit'))->fetchField()); + $this->assertTrue($current_limit == $row_limit, t('[Variable table] Row limit variable of @count equals row limit of @limit', array('@count' => $current_limit, '@limit' => $row_limit))); + } + + /** + * Verify cron applies the mongodb_watchdog row limit. + * + * @param integer $count Log row limit. + */ + private function verifyCron($row_limit) { + // Generate additional log entries. + $this->generateLogEntries($row_limit + 10); + // Verify mongodb_watchdog row count exceeds row limit. + $count = $this->collection->count(); + $this->assertTrue($count > $row_limit, t('mongodb_watchdog row count of @count exceeds row limit of @limit', array('@count' => $count, '@limit' => $row_limit))); + + // Run cron job. + $this->drupalGet('admin/reports/status/run-cron'); + $this->assertResponse(200); + $this->assertText(t('Cron ran successfully'), t('Cron ran successfully')); + // Verify mongodb_watchdog row count equals row limit plus one because cron adds a record after it runs. + $count = $this->collection->count(); + $this->assertTrue($count == $row_limit + 1, t('mongodb_watchdog row count of @count equals row limit of @limit plus one', array('@count' => $count, '@limit' => $row_limit))); + } + + /** + * Generate mongodb_watchdog entries. + * + * @param integer $count + * Number of log entries to generate. + * @param $type + * The type of watchdog entry. + * @param $severity + * The severity of the watchdog entry. + */ + private function generateLogEntries($count, $type = 'custom', $severity = WATCHDOG_NOTICE) { + global $base_root; + + // Prepare the fields to be logged + $log = array( + 'type' => $type, + 'message' => 'Log entry added to test the mongodb_watchdog row limit.', + 'variables' => array(), + 'severity' => $severity, + 'link' => NULL, + 'user' => $this->big_user, + 'request_uri' => $base_root . request_uri(), + 'referer' => $_SERVER['HTTP_REFERER'], + 'ip' => ip_address(), + 'timestamp' => REQUEST_TIME, + ); + $message = 'Log entry added to test the mongodb_watchdog row limit.'; + for ($i = 0; $i < $count; $i++) { + $log['message'] = $this->randomString(); + mongodb_watchdog_watchdog($log); + } + } + + /** + * Verify the logged in user has the desired access to the various mongodb_watchdog nodes. + * + * @param integer $response HTTP response code. + */ + private function verifyReports($response = 200) { + $quote = '''; + + // View mongodb_watchdog help node. + $this->drupalGet('admin/help/mongodb_watchdog'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertText(t('Database logging'), t('mongodb_watchdog help was displayed')); + } + + // View mongodb_watchdog report node. + $this->drupalGet('admin/reports/mongodb'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertText(t('Recent log entries'), t('mongodb_watchdog report was displayed')); + } + + // View mongodb_watchdog page-not-found report node. + $this->drupalGet('admin/reports/page-not-found'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertText(t('Top ' . $quote . 'page not found' . $quote . ' errors'), t('mongodb_watchdog page-not-found report was displayed')); + } + + // View mongodb_watchdog access-denied report node. + $this->drupalGet('admin/reports/access-denied'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertText(t('Top ' . $quote . 'access denied' . $quote . ' errors'), t('mongodb_watchdog access-denied report was displayed')); + } + + // View mongodb_watchdog event node. + $this->drupalGet('admin/reports/event/1'); + $this->assertResponse($response); + if ($response == 200) { + $this->assertText(t('Details'), t('mongodb_watchdog event node was displayed')); + } + } + + /** + * Verify events. + */ + private function verifyEvents() { + // Invoke events. + $this->doUser(); + $this->doNode('article'); + $this->doNode('blog'); + $this->doNode('page'); + $this->doNode('poll'); + + // When a user account is canceled, any content they created remains but the + // uid = 0. Their blog entry shows as "'s blog" on the home page. Records + // in the watchdog table related to that user have the uid set to zero. + } + + /** + * Generate and verify user events. + * + */ + private function doUser() { + // Set user variables. + $name = $this->randomName(); + $pass = user_password(); + // Add user using form to generate add user event (which is not triggered by drupalCreateUser). + $edit = array(); + $edit['name'] = $name; + $edit['mail'] = $name . '@example.com'; + $edit['pass[pass1]'] = $pass; + $edit['pass[pass2]'] = $pass; + $edit['status'] = 1; + $this->drupalPost('admin/people/create', $edit, t('Create new account')); + $this->assertResponse(200); + // Retrieve user object. + $user = user_load_by_name($name); + $this->assertTrue($user != NULL, t('User @name was loaded', array('@name' => $name))); + $user->pass_raw = $pass; // Needed by drupalLogin. + // Login user. + $this->drupalLogin($user); + // Logout user. + $this->drupalLogout(); + // Fetch row ids in watchdog that relate to the user. + $result = $this->collection->find(array('uid' => $user->uid), array('wid')); + foreach ($result as $row) { + $ids[] = $row->wid; + } + foreach ($result as $row) { + $ids[] = $row->wid; + } + $count_before = (isset($ids)) ? count($ids) : 0; + $this->assertTrue($count_before > 0, t('mongodb_watchdog contains @count records for @name', array('@count' => $count_before, '@name' => $user->name))); + + // Login the admin user. + $this->drupalLogin($this->big_user); + // Delete user. + // We need to POST here to invoke batch_process() in the internal browser. + $this->drupalPost('user/' . $user->uid . '/cancel', array('user_cancel_method' => 'user_cancel_reassign'), t('Cancel account')); + + // Count rows that have uids for the user. + $count = $this->collection->count(array('uid' => $user->uid)); + + $this->assertTrue($count == 0, t('mongodb_watchdog contains @count records for @name', array('@count' => $count, '@name' => $user->name))); + + // Count rows in watchdog that previously related to the deleted user. + if ($ids) { + $query['wid'] = array('$in' => $ids); + } + else { + $query = array(); + } + $count_after = $this->collection->count($query); + $this->assertTrue($count_after == $count_before, t('mongodb_watchdog contains @count records for @name that now have uid = 0', array('@count' => $count_before, '@name' => $user->name))); + unset($ids); + // Fetch row ids in watchdog that relate to the user. + $result = $this->collection->find(array('uid' => $user->uid), array('wid')); + foreach ($result as $row) { + $ids[] = $row->wid; + } + $this->assertTrue(!isset($ids), t('mongodb_watchdog contains no records for @name', array('@name' => $user->name))); + + // View the mongodb_watchdog report. + $this->drupalGet('admin/reports/mongodb'); + $this->assertResponse(200); + + // Verify events were recorded. + // Add user. + // Default display includes name and email address; if too long then email is replaced by three periods. + $this->assertLogMessage(t('New user: %name (%email).', array('%name' => $name, '%email' => $user->mail)), t('mongodb_watchdog event was recorded: [add user]')); + // Login user. + $this->assertLogMessage(t('Session opened for %name', array('%name' => $name)), t('mongodb_watchdog event was recorded: [login user]')); + // Logout user. + $this->assertLogMessage(t('Session closed for %name', array('%name' => $name)), t('mongodb_watchdog event was recorded: [logout user]')); + // Delete user. + $this->assertLogMessage(t('Deleted user: %name', array('%name' => $name)), t('mongodb_watchdog event was recorded: [delete user]')); + } + + /** + * Generate and verify node events. + * + * @param string $type Content type. + */ + private function doNode($type) { + // Create user. + $perm = array('create ' . $type . ' content', 'edit own ' . $type . ' content', 'delete own ' . $type . ' content'); + $user = $this->drupalCreateUser($perm); + // Login user. + $this->drupalLogin($user); + + // Create node using form to generate add content event (which is not triggered by drupalCreateNode). + $edit = $this->getContent($type); + $langcode = LANGUAGE_NONE; + $title = $edit["title[$langcode][0][value]"]; + $this->drupalPost('node/add/' . $type, $edit, t('Save')); + $this->assertResponse(200); + // Retrieve node object. + $node = $this->drupalGetNodeByTitle($title); + $this->assertTrue($node != NULL, t('Node @title was loaded', array('@title' => $title))); + // Edit node. + $edit = $this->getContentUpdate($type); + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save')); + $this->assertResponse(200); + // Delete node. + $this->drupalPost('node/' . $node->nid . '/delete', array(), t('Delete')); + $this->assertResponse(200); + // View node (to generate page not found event). + $this->drupalGet('node/' . $node->nid); + $this->assertResponse(404); + // View the mongodb_watchdog report (to generate access denied event). + $this->drupalGet('admin/reports/mongodb'); + $this->assertResponse(403); + + // Login the admin user. + $this->drupalLogin($this->big_user); + // View the mongodb_watchdog report. + $this->drupalGet('admin/reports/mongodb'); + $this->assertResponse(200); + + // Verify events were recorded. + // Content added. + $this->assertLogMessage(t('@type: added %title.', array('@type' => $type, '%title' => $title)), t('mongodb_watchdog event was recorded: [content added]')); + // Content updated. + $this->assertLogMessage(t('@type: updated %title.', array('@type' => $type, '%title' => $title)), t('mongodb_watchdog event was recorded: [content updated]')); + // Content deleted. + $this->assertLogMessage(t('@type: deleted %title.', array('@type' => $type, '%title' => $title)), t('mongodb_watchdog event was recorded: [content deleted]')); + + // View mongodb_watchdog access-denied report node. + $this->drupalGet('admin/reports/access-denied'); + $this->assertResponse(200); + // Access denied. + $this->assertText(t('admin/reports/mongodb'), t('mongodb_watchdog event was recorded: [access denied]')); + + // View mongodb_watchdog page-not-found report node. + $this->drupalGet('admin/reports/page-not-found'); + $this->assertResponse(200); + // Page not found. + $this->assertText(t('node/@nid', array('@nid' => $node->nid)), t('mongodb_watchdog event was recorded: [page not found]')); + } + + /** + * Create content based on content type. + * + * @param string $type Content type. + * @return array Content. + */ + private function getContent($type) { + $langcode = LANGUAGE_NONE; + switch ($type) { + case 'poll': + $content = array( + "title[$langcode][0][value]" => $this->randomName(8), + 'choice[new:0][chtext]' => $this->randomName(32), + 'choice[new:1][chtext]' => $this->randomName(32), + ); + break; + + default: + $content = array( + "title[$langcode][0][value]" => $this->randomName(8), + "body[$langcode][0][value]" => $this->randomName(32), + ); + break; + } + return $content; + } + + /** + * Create content update based on content type. + * + * @param string $type Content type. + * @return array Content. + */ + private function getContentUpdate($type) { + switch ($type) { + case 'poll': + $content = array( + 'choice[chid:1][chtext]' => $this->randomName(32), + 'choice[chid:2][chtext]' => $this->randomName(32), + ); + break; + + default: + $langcode = LANGUAGE_NONE; + $content = array( + "body[$langcode][0][value]" => $this->randomName(32), + ); + break; + } + return $content; + } + + /** + * Login an admin user, create mongodb_watchdog event, and test clearing mongodb_watchdog functionality through the admin interface. + */ + protected function testmongodb_watchdogAddAndClear() { + global $base_root; + // Get a count of how many watchdog entries there are. + $count = $this->collection->count(); + $log = array( + 'type' => 'custom', + 'message' => 'Log entry added to test the doClearTest clear down.', + 'variables' => array(), + 'severity' => WATCHDOG_NOTICE, + 'link' => NULL, + 'user' => $this->big_user, + 'request_uri' => $base_root . request_uri(), + 'referer' => $_SERVER['HTTP_REFERER'], + 'ip' => ip_address(), + 'timestamp' => REQUEST_TIME, + ); + // Add a watchdog entry. + mongodb_watchdog_watchdog($log); + // Make sure the table count has actually incremented. + $this->assertEqual($count + 1, $this->collection->count(), t('mongodb_watchdog_watchdog() added an entry to the mongodb_watchdog :count', array(':count' => $count))); + // Login the admin user. + $this->drupalLogin($this->big_user); + // Now post to clear the db table. + $this->drupalPost('admin/reports/mongodb', array(), t('Clear log messages')); + // Count rows in watchdog that previously related to the deleted user. + $count = $this->collection->count(); + $this->assertEqual($count, 0, t('mongodb_watchdog contains :count records after a clear.', array(':count' => $count))); + } + + /** + * Test the mongodb_watchdog filter on admin/reports/mongodb. + */ + protected function testFilter() { + $this->drupalLogin($this->big_user); + + // Clear log to ensure that only generated entries are found. + module_load_include('inc', 'mongodb_watchdog', 'mongodb_watchdog.admin'); + mongodb_watchdog_clear_log_submit(); + + // Generate watchdog entries. + $type_names = array(); + $types = array(); + for ($i = 0; $i < 3; $i++) { + $type_names[] = $type_name = $this->randomName(); + $severity = WATCHDOG_EMERG; + for ($j = 0; $j < 3; $j++) { + $types[] = $type = array( + 'count' => mt_rand(1, 5), + 'type' => $type_name, + 'severity' => $severity++, + ); + $this->generateLogEntries($type['count'], $type['type'], $type['severity']); + } + } + + // View the mongodb_watchdog. + $this->drupalGet('admin/reports/mongodb'); + + // Confirm all the entries are displayed. + // TODO This count does not match the real count. + $count = $this->getTypeCount($types); + foreach ($types as $key => $type) { + $this->assertEqual($count[$key], $type['count'], 'Count matched'); + } + + // Filter by each type and confirm that entries with various severities are + // displayed. + foreach ($type_names as $type_name) { + $edit = array( + 'type[]' => array($type_name), + ); + $this->drupalPost(NULL, $edit, t('Filter')); + + // Count the number of entries of this type. + $type_count = 0; + foreach ($types as $type) { + if ($type['type'] == $type_name) { + $type_count += $type['count']; + } + } + + $count = $this->getTypeCount($types); + $this->assertEqual(array_sum($count), $type_count, 'Count matched'); + } + + // Set filter to match each of the three type attributes and confirm the + // number of entries displayed. + foreach ($types as $key => $type) { + $edit = array( + 'type[]' => array($type['type']), + 'severity[]' => array($type['severity']), + ); + $this->drupalPost(NULL, $edit, t('Filter')); + + $count = $this->getTypeCount($types); + $this->assertEqual(array_sum($count), $type['count'], 'Count matched'); + } + } + + /** + * Get the log entry information form the page. + * + * @return + * List of entries and their information. + */ + protected function getLogEntries() { + $entries = array(); + if ($table = $this->xpath('.//table[@id="admin-mongodb_watchdog"]')) { + $table = array_shift($table); + foreach ($table->tbody->tr as $row) { + $entries[] = array( + 'severity' => $this->getSeverityConstant($row['class']), + 'type' => $this->asText($row->td[1]), + 'message' => $this->asText($row->td[3]), + // FIXME why is here somethimes td[4] null + 'user' => $row->td[4] ? $this->asText($row->td[4]) : null, + ); + } + } + return $entries; + } + + /** + * Get the count of entries per type. + * + * @param $types + * The type information to compare against. + * @return + * The count of each type keyed by the key of the $types array. + */ + protected function getTypeCount(array $types) { + $entries = $this->getLogEntries(); + $count = array_fill(0, count($types), 0); + foreach ($entries as $entry) { + foreach ($types as $key => $type) { + if ($entry['type'] == $type['type'] && $entry['severity'] == $type['severity']) { + $count[$key]++; + break; + } + } + } + return $count; + } + + /** + * Get the watchdog severity constant corresponding to the CSS class. + * + * @param $class + * CSS class attribute. + * @return + * The watchdog severity constant or NULL if not found. + */ + protected function getSeverityConstant($class) { + // Reversed array from mongodb_watchdog_overview(). + $map = array( + 'mongodb_watchdog-debug' => WATCHDOG_DEBUG, + 'mongodb_watchdog-info' => WATCHDOG_INFO, + 'mongodb_watchdog-notice' => WATCHDOG_NOTICE, + 'mongodb_watchdog-warning' => WATCHDOG_WARNING, + 'mongodb_watchdog-error' => WATCHDOG_ERROR, + 'mongodb_watchdog-critical' => WATCHDOG_CRITICAL, + 'mongodb_watchdog-alert' => WATCHDOG_ALERT, + 'mongodb_watchdog-emerg' => WATCHDOG_EMERG, + ); + + // Find the class that contains the severity. + $classes = explode(' ', $class); + foreach ($classes as $class) { + if (isset($map[$class])) { + return $map[$class]; + } + } + return NULL; + } + + /** + * Extract the text contained by the element. + * + * @param $element + * Element to extract text from. + * @return + * Extracted text. + */ + protected function asText(SimpleXMLElement $element) { + if (!is_object($element)) { + return $this->fail('The element is not an element.'); + } + return trim(html_entity_decode(strip_tags($element->asXML()))); + } + + /** + * Assert messages appear on the log overview screen. + * + * @param $log_message + * The message to check. + * @param $message + * The message to pass to simpletest. + */ + protected function assertLogMessage($log_message, $message) { + // Truncate at 56 characters to compare with mongodb_watchdog's HTML output. + // @todo: Check the database instead for the exact error string. + $this->assertRaw(truncate_utf8($log_message, 56, TRUE, TRUE), $message); + } +} +