dblog.module 10.5 KB
Newer Older
Dries's avatar
 
Dries committed
1
<?php
2
// $Id$
Dries's avatar
 
Dries committed
3

Dries's avatar
 
Dries committed
4 5 6 7
/**
 * @file
 * System monitoring and logging for administrators.
 *
8
 * The dblog module monitors your site and keeps a list of
Dries's avatar
 
Dries committed
9 10 11 12 13 14
 * recorded events containing usage and performance data, errors,
 * warnings, and similar operational information.
 *
 * @see watchdog().
 */

Dries's avatar
Dries committed
15 16 17
/**
 * Implementation of hook_help().
 */
18
function dblog_help($section) {
Dries's avatar
 
Dries committed
19
  switch ($section) {
20 21 22 23
    case 'admin/help#dblog':
      $output = '<p>'. t('The dblog module monitors your system, capturing system events in a log to be reviewed by an authorized individual at a later time. This is useful for site administrators who want a quick overview of activities on their site. The logs also record the sequence of events, so it can be useful for debugging site errors.') .'</p>';
      $output .= '<p>'. t('The dblog log is simply a list of recorded events containing usage data, performance data, errors, warnings and operational information. Administrators should check the dblog report on a regular basis to ensure their site is working properly.') .'</p>';
      $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="@dblog">Dblog page</a>.', array('@dblog' => 'http://drupal.org/handbook/modules/dblog/')) .'</p>';
24 25
      return $output;
    case 'admin/logs':
26
      return '<p>'. t('The dblog module monitors your website, capturing system events in a log to be reviewed by an authorized individual at a later time. The dblog log is simply a list of recorded events containing usage data, performance data, errors, warnings and operational information. It is vital to check the dblog report on a regular basis as it is often the only way to tell what is going on.') .'</p>';
Dries's avatar
 
Dries committed
27
  }
28 29
}

30 31 32
/**
 * Implementation of hook_theme()
 */
33
function dblog_theme() {
34
  return array(
35
    'dblog_form_overview' => array(
36 37 38 39 40
      'arguments' => array('form' => NULL),
    ),
  );
}

Dries's avatar
 
Dries committed
41
/**
Dries's avatar
 
Dries committed
42
 * Implementation of hook_menu().
Dries's avatar
 
Dries committed
43
 */
44 45 46
function dblog_menu() {
  $items['admin/settings/logging/dblog'] = array(
    'title' => t('Database logging'),
47
    'description' => t('Settings for logging to the Drupal database logs. This is the most common method for small to medium sites on shared hosting. The logs are viewable from the admin pages.'),
48 49 50 51 52
    'page callback' => 'drupal_get_form',
    'page arguments' => array('dblog_admin_settings'),
  );

  $items['admin/logs/dblog'] = array(
53 54
    'title' => t('Recent log entries'),
    'description' => t('View events that have recently been logged.'),
55
    'page callback' => 'dblog_overview',
56 57 58 59 60
    'weight' => -1,
  );
  $items['admin/logs/page-not-found'] = array(
    'title' => t("Top 'page not found' errors"),
    'description' => t("View 'page not found' errors (404s)."),
61
    'page callback' => 'dblog_top',
62 63 64 65 66
    'page arguments' => array('page not found'),
  );
  $items['admin/logs/access-denied'] = array(
      'title' => t("Top 'access denied' errors"),
    'description' => t("View 'access denied' errors (403s)."),
67
    'page callback' => 'dblog_top',
68 69 70 71
    'page arguments' => array('access denied'),
  );
  $items['admin/logs/event/%'] = array(
    'title' => t('Details'),
72
    'page callback' => 'dblog_event',
73 74 75 76 77
    'page arguments' => array(3),
    'type' => MENU_CALLBACK,
  );
  return $items;
}
Dries's avatar
 
Dries committed
78

79
function dblog_init() {
80 81
  if (arg(0) == 'admin' && arg(1) == 'logs') {
    // Add the CSS for this module
82
    drupal_add_css(drupal_get_path('module', 'dblog') .'/dblog.css', 'module', 'all', FALSE);
83
  }
Dries's avatar
 
Dries committed
84 85
}

86 87 88 89 90 91 92 93 94 95 96 97
function dblog_admin_settings() {
  $form['dblog_row_limit'] = array(
    '#type' => 'select',
    '#title' => t('Discard log entries above the following row limit'),
    '#default_value' => variable_get('dblog_row_limit', 1000),
    '#options' => drupal_map_assoc(array(100, 1000, 10000, 100000, 1000000)),
    '#description' => t('The maximum number of rows to keep in the database log. Older entries will be automatically discarded. Requires crontab.')
  );

  return system_settings_form($form);
}

Dries's avatar
Dries committed
98 99 100
/**
 * Implementation of hook_cron().
 *
Dries's avatar
 
Dries committed
101
 * Remove expired log messages and flood control events.
Dries's avatar
Dries committed
102
 */
103 104 105 106 107 108 109 110 111 112 113
function dblog_cron() {
  // Cleanup the watchdog table
  $min = db_result(db_query('SELECT MIN(wid) FROM {watchdog}'));
  if ($min) {
    $max = db_result(db_query('SELECT MAX(wid) FROM {watchdog}'));
    if ($max) {
      if (($max - $min) > variable_get('dblog_row_limit', 1000)) {
        db_query('DELETE FROM {watchdog} WHERE wid < %d', $max - $min);
      }
    }
  }
Dries's avatar
 
Dries committed
114
}
Dries's avatar
 
Dries committed
115

116 117 118
/**
 * Implementation of hook_user().
 */
119
function dblog_user($op, &$edit, &$user) {
120
  if ($op == 'delete') {
Steven Wittens's avatar
Steven Wittens committed
121
    db_query('UPDATE {watchdog} SET uid = 0 WHERE uid = %d', $user->uid);
122 123 124
  }
}

125
function dblog_form_overview() {
126
  $names['all'] = t('all messages');
127
  foreach (_dblog_get_message_types() as $type) {
128
    $names[$type] = t('!type messages', array('!type' => t($type)));
129 130
  }

131 132
  if (empty($_SESSION['dblog_overview_filter'])) {
    $_SESSION['dblog_overview_filter'] = 'all';
133 134
  }

135
  $form['filter'] = array(
136 137 138
    '#type' => 'select',
    '#title' => t('Filter by message type'),
    '#options' => $names,
139
    '#default_value' => $_SESSION['dblog_overview_filter']
140
  );
141
  $form['submit'] = array('#type' => 'submit', '#value' => t('Filter'));
142 143 144 145 146 147 148
  $form['#redirect'] = FALSE;

  return $form;
}
/**
 * Menu callback; displays a listing of log messages.
 */
149 150
function dblog_overview() {
  $rows = array();
151 152 153
  $icons = array(WATCHDOG_NOTICE  => '',
                 WATCHDOG_WARNING => theme('image', 'misc/watchdog-warning.png', t('warning'), t('warning')),
                 WATCHDOG_ERROR   => theme('image', 'misc/watchdog-error.png', t('error'), t('error')));
154
  $classes = array(WATCHDOG_NOTICE => 'dblog-notice', WATCHDOG_WARNING => 'dblog-warning', WATCHDOG_ERROR => 'dblog-error');
155

156
  $output = drupal_get_form('dblog_form_overview');
157

Dries's avatar
 
Dries committed
158
  $header = array(
159
    ' ',
160
    array('data' => t('Type'), 'field' => 'w.type'),
161
    array('data' => t('Date'), 'field' => 'w.wid', 'sort' => 'desc'),
Dries's avatar
 
Dries committed
162 163
    array('data' => t('Message'), 'field' => 'w.message'),
    array('data' => t('User'), 'field' => 'u.name'),
164
    array('data' => t('Operations'))
Dries's avatar
 
Dries committed
165
  );
166

167
  $sql = "SELECT w.wid, w.uid, w.severity, w.type, w.timestamp, w.message, w.link, u.name FROM {watchdog} w INNER JOIN {users} u ON w.uid = u.uid";
168
  $tablesort = tablesort_sql($header);
169
  $type = $_SESSION['dblog_overview_filter'];
170 171 172 173 174 175
  if ($type != 'all') {
    $result = pager_query($sql ." WHERE w.type = '%s'". $tablesort, 50, 0, NULL, $type);
  }
  else {
    $result = pager_query($sql . $tablesort, 50);
  }
Dries's avatar
 
Dries committed
176

177
  while ($dblog = db_fetch_object($result)) {
178 179 180
    $rows[] = array('data' =>
      array(
        // Cells
181 182 183 184 185 186
        $icons[$dblog->severity],
        t($dblog->type),
        format_date($dblog->timestamp, 'small'),
        l(truncate_utf8($dblog->message, 56, TRUE, TRUE), 'admin/logs/event/'. $dblog->wid, array('html' => TRUE)),
        theme('username', $dblog),
        $dblog->link,
187 188
      ),
      // Attributes for tr
189
      'class' => "dblog-". preg_replace('/[^a-z]/i', '-', $dblog->type) .' '. $classes[$dblog->severity]
Dries's avatar
 
Dries committed
190
    );
Dries's avatar
 
Dries committed
191
  }
Dries's avatar
 
Dries committed
192

Dries's avatar
 
Dries committed
193
  if (!$rows) {
194
    $rows[] = array(array('data' => t('No log messages available.'), 'colspan' => 6));
Dries's avatar
 
Dries committed
195
  }
Dries's avatar
 
Dries committed
196

197
  $output .= theme('table', $header, $rows);
198
  $output .= theme('pager', NULL, 50, 0);
199

200 201 202 203 204
  return $output;
}

/**
 * Menu callback; generic function to display a page of the most frequent
205
 * dblog events of a specified type.
206
 */
207
function dblog_top($type) {
208 209 210 211 212 213 214 215

  $header = array(
    array('data' => t('Count'), 'field' => 'count', 'sort' => 'desc'),
    array('data' => t('Message'), 'field' => 'message')
  );

  $result = pager_query("SELECT COUNT(wid) AS count, message FROM {watchdog} WHERE type = '%s' GROUP BY message ". tablesort_sql($header), 30, 0, "SELECT COUNT(DISTINCT(message)) FROM {watchdog} WHERE type = '%s'", $type);

216
  $rows = array();
217 218
  while ($dblog = db_fetch_object($result)) {
    $rows[] = array($dblog->count, truncate_utf8($dblog->message, 56, TRUE, TRUE));
219 220
  }

221
  if (empty($rows)) {
222 223 224 225 226 227
    $rows[] = array(array('data' => t('No log messages available.'), 'colspan' => 2));
  }

  $output  = theme('table', $header, $rows);
  $output .= theme('pager', NULL, 30, 0);

Dries's avatar
 
Dries committed
228
  return $output;
Dries's avatar
 
Dries committed
229 230
}

231
function theme_dblog_form_overview($form) {
232
  return '<div class="container-inline">'. drupal_render($form) .'</div>';
233 234
}

235 236
function dblog_form_overview_submit($form_id, $form_values) {
  $_SESSION['dblog_overview_filter'] = $form_values['filter'];
237 238
}

Dries's avatar
Dries committed
239 240 241
/**
 * Menu callback; displays details about a log message.
 */
242
function dblog_event($id) {
243
  $severity = array(WATCHDOG_NOTICE => t('notice'), WATCHDOG_WARNING => t('warning'), WATCHDOG_ERROR => t('error'));
Dries's avatar
Dries committed
244 245
  $output = '';
  $result = db_query('SELECT w.*, u.name, u.uid FROM {watchdog} w INNER JOIN {users} u ON w.uid = u.uid WHERE w.wid = %d', $id);
246
  if ($dblog = db_fetch_object($result)) {
247 248 249
    $rows = array(
      array(
        array('data' => t('Type'), 'header' => TRUE),
250
        t($dblog->type),
251 252 253
      ),
      array(
        array('data' => t('Date'), 'header' => TRUE),
254
        format_date($dblog->timestamp, 'large'),
255 256 257
      ),
      array(
        array('data' => t('User'), 'header' => TRUE),
258
        theme('username', $dblog),
259 260 261
      ),
      array(
        array('data' => t('Location'), 'header' => TRUE),
262
        l($dblog->location, $dblog->location),
263 264 265
      ),
      array(
        array('data' => t('Referrer'), 'header' => TRUE),
266
        l($dblog->referer, $dblog->referer),
267 268 269
      ),
      array(
        array('data' => t('Message'), 'header' => TRUE),
270
        $dblog->message,
271 272 273
      ),
      array(
        array('data' => t('Severity'), 'header' => TRUE),
274
        $severity[$dblog->severity],
275 276 277
      ),
      array(
        array('data' => t('Hostname'), 'header' => TRUE),
278
        $dblog->hostname,
279
      ),
280 281
      array(
        array('data' => t('Operations'), 'header' => TRUE),
282
        $dblog->link,
283
      ),
284
    );
285
    $attributes = array('class' => 'dblog-event');
286
    $output = theme('table', array(), $rows, $attributes);
287 288 289 290
  }
  return $output;
}

291
function _dblog_get_message_types() {
Dries's avatar
 
Dries committed
292 293
  $types = array();

294
  $result = db_query('SELECT DISTINCT(type) FROM {watchdog} ORDER BY type');
Dries's avatar
 
Dries committed
295 296 297 298 299 300
  while ($object = db_fetch_object($result)) {
    $types[] = $object->type;
  }

  return $types;
}
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321

function dblog_watchdog($log = array()) {
  $current_db = db_set_active();
  db_query("INSERT INTO {watchdog}
    (uid, type, message, severity, link, location, referer, hostname, timestamp)
    VALUES
    (%d, '%s', '%s', %d, '%s', '%s', '%s', '%s', %d)",
    $log['user']->uid,
    $log['type'],
    $log['message'],
    $log['severity'],
    $log['link'],
    $log['request_uri'],
    $log['referer'],
    $log['ip'],
    $log['timestamp']);

  if ($current_db) {
    db_set_active($current_db);
  }
}