Save your Drupal logs!

At OWASP AppSec DC 09 a couple weeks ago my friend and colleague Greg Knaddison was telling me about a session he attended where the idea of reporting by follow-up important transaction data to the author was expressed. For example, if you transfer funds on your bank's website the bank follows up with an email stating that funds were transfered. If someone other than you had somehow gotten access to your bank account online and transfered themselves money you would be informed soon after to report the malicious activity.

This got me thinking about Drupal's logging system, watchdog. By default, and for ease of use, logs are stored in the database. The watchdog table is kept trim and old log entries are removed during Drupal's cron system run. These logs are an important source of transaction data and on some sites it can be beneficial to keep them around longer than Drupal's default. Rather than expanding the table I thought up a module that could write old entries to disk for other backup means.

What follows is some proof-of-concept code (that works) for saving old watchdog entries to disk. It writes to the files directory in the file dblog/watchdog.log. While the directory dblog is protected with an htaccess file it is not recommended that log data be left under the web root.

<?php

/**
* Implementation of hook_cron().
*/
function dblog_save_cron() {
 
$data = '';
 
$levels = watchdog_severity_levels();
 
$max = db_result(db_query('SELECT MAX(wid) FROM {watchdog}'));
 
$results = db_query("SELECT uid, type, message, variables, severity, link, location, referer, hostname, timestamp FROM {watchdog} WHERE wid <= %d", $max - variable_get('dblog_row_limit', 1000));
  while (
$result = db_fetch_array($results)) {
   
$data .= dblog_save_format($result, $levels) . "\n";
  }
  if (
$data != '') {
   
dblog_save_write($data);
  }
}

function

dblog_save_write($data) {
 
$filename = 'watchdog.log';
 
$filepath = 'dblog';
 
$filepath = file_create_path($filepath);
 
file_check_directory($filepath, FILE_CREATE_DIRECTORY);
  if (!
is_file("$filepath/.htaccess")) {
   
$htaccess_lines = "Order allow,deny\ndeny from all";
    if ((
$fp = fopen("$filepath/.htaccess", 'w')) && fwrite($fp, $htaccess_lines)) {
     
fclose($fp);
     
chmod($filepath .'/.htaccess', 0664);
    }
  }
 
$fp = fopen($filepath .'/' . $filename, 'a');
 
fwrite($fp, $data);
 
fclose($fp);
}

function

dblog_save_format($entry, $levels) {
  global
$base_url;
 
$entry['variables'] = unserialize($entry['variables']);
 
 
$message  = $base_url;
 
$message .= '|'. $levels[$entry['severity']];
 
$message .= '|'. $entry['timestamp'];
 
$message .= '|'. $entry['type'];
 
$message .= '|'. $entry['hostname'];
 
$message .= '|'. $entry['location'];
 
$message .= '|'. $entry['referer'];
 
$message .= '|'. $entry['uid'];
 
$message .= '|'. strip_tags($entry['link']);
 
$message .= '|'. strip_tags(is_null($entry['variables']) ? $entry['message'] : strtr($entry['message'], $entry['variables']));

  return

$message;
}
?>