Stubbed Toe

Tag: php

Custom PHP 404 Error Page with Logging

by James on Dec.18, 2008, under Dev

So recently I moved webhosts, and decided to setup some subdomains for my gallery and random shit, rather than just have sub directories so I can get better individual reports. At the time I didn’t think that this would have an impact on things except the images I had hotlinked on a few forums out of my gallery, which I didn’t particularly care about. The short story, I was wrong.

One day I was checking the error logs while I was debugging a script I was writing, and noticed a heck of a lot of 404 errors. I started looking through them all and was a bit shocked. There were so many search engines linking to files that I didn’t even think anyone would care about, and a lot of random people linking to a lot of random images.

It’s an absolute nightmare to get an overall picture of what is going on from the server error log, so I ended up making a custom php error page that would email me when a 404 error happened, then one day after work I came home and had 400 emails, so I changed it to log to MySQL instead, and wrote another page to give me a decent overview of that information.

There are four pieces to this setup, and you’ll require access to your .htaccess file. Put error.php into your root directory, execute the sql to create the database table, make the changes to the .htaccess file and you’re away! Stick errorlog.php where ever you want, and check it every now and then.

error.php – This is the main page that is displayed to the user, and logs the information.
Be sure to change the $database array information if you plan to use MySQL logging.

< ?php
ob_start();

$notification = 'sql'; //If you would rather have this emailed to you, change this to 'email'
$email_to = 'you@domain.co.nz'; //Change this to where you want it sent
$email_from = 'webmaster@domain.co.nz'; //Change this to the reply address
$email_sub = 'Web Error'; //Start of the subject line
error_reporting(0);
/*
We don't want the end user to see any php related errors, ie,
can not connect to database. As this shouldn't happen that
often, it's not really an issue. For testing of this script
however, you should change the above line from 0 to E_ALL
*/
//Database Configuration
$database = array(
	"server" => "localhost",
	"username" => "localuser",
	"password" => "localpass",
	"database" => "localbase",
);

$errors = array( //All of these were taken from http://www.askapache.com/wordpress/wordpress-404.html
	'400' => array('Bad Request','Your browser sent a request that this server could not understand.'),
	'401' => array('Authorization Required', 'This server could not verify that you are authorized to access the document requested. Either you supplied the wrong credentials (e.g., bad password), or your browser doesn\'t understand how to supply the credentials required.'),
	'402' => array('Payment Required','INTERROR'),
	'403' => array('Forbidden','You don\'t have permission to access THEREQUESTURI on this server.'),
	'404' => array('Not Found', 'We couldn\'t find <acronym title="THEREQUESTURI">that uri</acronym> on our server, though it\'s most certainly not your fault.'),
	'405' => array('Method Not Allowed', 'The requested method THEREQMETH is not allowed for the URL THEREQUESTURI.'),
	'406' => array('Not Acceptable', 'An appropriate representation of the requested resource THEREQUESTURI could not be found on this server.'),
	'407' => array('Proxy Authentication Required', 'This server could not verify that you are authorized to access the document requested. Either you supplied the wrong credentials (e.g., bad password), or your browser doesn\'t understand how to supply the credentials required.'),
	'408' => array('Request Time-out', 'Server timeout waiting for the HTTP request from the client.'),
	'409' => array('Conflict', 'INTERROR'),
	'410' => array('Gone', 'The requested resourceTHEREQUESTURIis no longer available on this server and there is no forwarding address. Please remove all references to this resource.'),
	'411' => array('Length Required', 'A request of the requested method GET requires a valid Content-length.'),
	'412' => array('Precondition Failed', 'The precondition on the request for the URL THEREQUESTURI evaluated to false.'),
	'413' => array('Request Entity Too Large', 'The requested resource THEREQUESTURI does not allow request data with GET requests, or the amount of data provided in the request exceeds the capacity limit.'),
	'414' => array('Request-URI Too Large', 'The requested URL\'s length exceeds the capacity limit for this server.'),
	'415' => array('Unsupported Media Type', 'The supplied request data is not in a format acceptable for processing by this resource.'),
	'416' => array('Requested Range Not Satisfiable', ''),
	'417' => array('Expectation Failed', 'The expectation given in the Expect request-header field could not be met by this server. The client sent <code>Expect:</code>'),
	'422' => array('Unprocessable Entity', 'The server understands the media type of the request entity, but was unable to process the contained instructions.'),
	'423' => array('Locked', 'The requested resource is currently locked. The lock must be released or proper identification given before the method can be applied.'),
	'424' => array('Failed Dependency', 'The method could not be performed on the resource because the requested action depended on another action and that other action failed.'),
	'425' => array('No code', 'INTERROR'),
	'426' => array('Upgrade Required', 'The requested resource can only be retrieved using SSL. The server is willing to upgrade the current connection to SSL, but your client doesn\'t support it. Either upgrade your client, or try requesting the page using https://'),
	'500' => array('Internal Server Error', 'INTERROR'),
	'501' => array('Method Not Implemented', 'GET to THEREQUESTURI not supported.'),
	'502' => array('Bad Gateway', 'The proxy server received an invalid response from an upstream server.'),
	'503' => array('Service Temporarily Unavailable', 'The server is temporarily unable to service your request due to maintenance downtime or capacity problems. Please try again later.'),
	'504' => array('Gateway Time-out', 'The proxy server did not receive a timely response from the upstream server.'),
	'505' => array('HTTP Version Not Supported', 'INTERROR'),
	'506' => array('Variant Also Negotiates', 'A variant for the requested resource <code>THEREQUESTURI</code> is itself a negotiable resource. This indicates a configuration error.'),
	'507' => array('Insufficient Storage', 'The method could not be performed on the resource because the server is unable to store the representation needed to successfully complete the request. There is insufficient free space left in your storage allocation.'),
	'510' => array('Not Extended', 'A mandatory extension policy in the request is not accepted by the server for this resource.')
);

$cur_err_uri = $_SERVER['REQUEST_URI'];
$cur_err_ref = $_SERVER['HTTP_REFERER'];
$cur_err_code = $_SERVER['REDIRECT_STATUS'];
$cur_err_short = $errors[$cur_err_code][0];
$cur_err_long = str_replace("THEREQUESTURI", $cur_err_uri, $errors[$cur_err_code][1]);

header("HTTP/1.0 $cur_err_code $cur_err_short");
//If you don't output the correct header error, search engines will just assume that the content has changed.
?>
< !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Error - < ?php echo $cur_err_code?></title>
<style type="text/css">
<!--
body {
	font-family: Calibri, Tahoma, Verdana, Arial, Helvetica, sans-serif;
	font-size: 12px;
}
-->
</style>
</head>

<body>
<h1>< ?php echo $cur_err_code?></h1>
<h2>< ?php echo $cur_err_short?></h2>
<p>< ?php echo $cur_err_long?></p>
<p><a href="http://<?php echo $_SERVER['HTTP_HOST']?>" target="_top">Home</a>
< ?php
if ($cur_err_ref != '') {
        echo " | <a href=\"$cur_err_ref\" target=\"_top\">Back";
}
?>
</p>
</body>
</html>
< ?php
ob_end_flush();

if ($notification === 'email') { //Email

	$email_sub = "$email_sub: $cur_err_code";
	$email_msg = "Time: " . date("F j, Y, g:i:s a") . "\r\n";
	$email_msg .= "Error: $cur_err_code, $cur_err_short\r\n";
	$email_msg .= "Requested: " . $_SERVER['HTTP_HOST'] . " - $cur_err_uri \r\nReferred: $cur_err_ref\r\n";
	$email_msg .= "Remote_Addr: " . $_SERVER['REMOTE_ADDR'] . " \r\nBrowser: " . $_SERVER['HTTP_USER_AGENT'] . "\r\n";
	$email_head = "From: $email_from\r\nReply-To: $email_from\r\nX-Mailer: PHP/" . phpversion();
	mail($email_to, $email_sub, $email_msg, $email_head);

} elseif ($notification === 'sql') { //SQL

	$link = mysql_connect($database['server'], $database['username'], $database['password']);
	mysql_select_db($database['database'], $link);

	include('/home/stubbedt/www/shell/hair.php');
	$mysql_query = sprintf("INSERT INTO `m_error` (`id` ,`time` ,`err_code` ,`err_desc` ,`page` ,`refer` ,`remote_addr` ,`browser`) VALUES (NULL , UNIX_TIMESTAMP() , '%s', '%s', '%s', '%s', '%s', '%s');",
	    mysql_real_escape_string($cur_err_code),
		mysql_real_escape_string($cur_err_short),
		mysql_real_escape_string($_SERVER['HTTP_HOST'] . $cur_err_uri),
		mysql_real_escape_string($cur_err_ref),
		mysql_real_escape_string($_SERVER['REMOTE_ADDR']),
	    mysql_real_escape_string($_SERVER['HTTP_USER_AGENT']));
	mysql_query($mysql_query);

	mysql_close($link);

}
?>

error.sql – This is the database table setup.
Be sure to change the `localtable` to what you want to use.

CREATE TABLE IF NOT EXISTS `m_error` (
  `id` int(8) NOT NULL auto_increment,
  `time` int(10) NOT NULL,
  `err_code` int(3) NOT NULL,
  `err_desc` varchar(100) NOT NULL,
  `page` varchar(255) NOT NULL,
  `refer` varchar(255) NOT NULL,
  `remote_addr` varchar(25) NOT NULL,
  `browser` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8;

.htaccess – These are the edits you have to make.
You can put as many or as little errors in here that you want, but the below five are the most common.

#Error Docs
ErrorDocument 400 /error.php?e=400
ErrorDocument 401 /error.php?e=401
ErrorDocument 403 /error.php?e=403
ErrorDocument 404 /error.php?e=404
ErrorDocument 500 /error.php?e=500

errorlog.php – This is a crude display of the errors that are being logged.
Again, be sure to change the $database array information if you plan to use MySQL logging.

< ?php
error_reporting(E_ALL); //To surpress erros, change from E_ALL to 0
//Database Configuration
$database = array(
	"server" => "localhost",
	"username" => "localuser",
	"password" => "localpass",
	"database" => "localbase",
);
$link = mysql_connect($database['server'], $database['username'], $database['password']);
mysql_select_db($database['database'], $link);
?>
< !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Error - 404</title>
<style type="text/css">
<!--
a {
	color: #000000;
	text-decoration: none;
	border-top-style: none;
	border-right-style: none;
	border-bottom-style: none;
	border-left-style: none;
}
acronym {
	color: #000000;
	text-decoration: none;
	border-top-style: none;
	border-right-style: none;
	border-bottom-style: none;
	border-left-style: none;
}
body {
	font-family: Calibri, Tahoma, Verdana, Arial, Helvetica, sans-serif;
	font-size: 12px;
}
td.lfb {
	border-left-width: thin;
	border-left-style: dotted;
	border-left-color: #CCCCCC;
}
td.lfb-d {
	border-left-width: thin;
	border-left-style: dotted;
	border-left-color: #999999;
}
td.bmb {
	border-bottom-width: thin;
	border-bottom-style: dotted;
	border-bottom-color: #CCCCCC;
}
td.bmb-d {
	border-bottom-width: thin;
	border-bottom-style: dotted;
	border-bottom-color: #999999;
}
-->
</style>
</head>

<body>
<h1>Error Log</h1>
<h2>Past 10 errors</h2>
<table border="0" cellspacing="1" cellpadding="2">
  <tr>
    <td class='lfb-d bmb-d'><b>time</b></td>
    <td class='lfb-d bmb-d'><b>err_code</b></td>
    <td class='lfb-d bmb-d'><b>err_desc</b></td>
    <td class='lfb-d bmb-d'><b>page</b></td>
    <td class='lfb-d bmb-d'><b>browser</b></td>
    <td class='lfb-d bmb-d'><b>remote_addr</b></td>
    <td class='lfb-d bmb-d'><b>refer</b></td>
  </tr>
< ?php
$mysql_query = "SELECT * FROM `m_error` ORDER BY time DESC LIMIT 0,50;";
$mysql_result = mysql_query($mysql_query);

while ($mysql_row = mysql_fetch_assoc($mysql_result)) {
?>
  <tr>
    <td class='lfb bmb'>< ?php echo date("d.m.y - H:i:s", $mysql_row['time']); ?></td>
    <td class='lfb bmb'>< ?php echo $mysql_row['err_code']; ?></td>
    <td class='lfb bmb'>< ?php echo $mysql_row['err_desc']; ?></td>
    <td class='lfb bmb'>< ?php echo "<a title=\"http://". $mysql_row['page'] ."\" href=\"http://". $mysql_row['page'] ."\">". substr($mysql_row['page'], 0, 50) .""; ?></td>
    <td class='lfb bmb'>< ?php echo "<acronym title=\"". $mysql_row['browser'] ."\">". substr($mysql_row['browser'], 0, 35) .""; ?></td>
    <td class='lfb bmb'>< ?php echo $mysql_row['remote_addr']; ?></td>
    <td class='lfb bmb'>< ?php echo "<a title=\"http://". $mysql_row['refer'] ."\" href=\"http://". $mysql_row['refer'] ."\">". substr($mysql_row['refer'], 0, 50) .""; ?></td>
  </tr>
< ?php
}
?>
</table>
<p>&amp;amp;amp;amp;nbsp;</p>
<h2>Amount of errors, per referer</h2>
<table border="0" cellspacing="1" cellpadding="2">
  <tr>
    <td class='lfb-d bmb-d'><b>refer_count</b></td>
    <td class='lfb-d bmb-d'><b>refer</b></td>
    <td class='lfb-d bmb-d'><b>page</b></td>
  </tr>
< ?php

$mysql_query = "SELECT count('refer') AS refer_count, refer, page FROM `m_error` GROUP BY refer ORDER BY count('refer') DESC LIMIT 0,50;";
$mysql_result = mysql_query($mysql_query);

while ($mysql_row = mysql_fetch_assoc($mysql_result)) {
?>
  <tr>
    <td class='lfb bmb'>< ?php echo $mysql_row['refer_count']; ?></td>
    <td class='lfb bmb'>< ?php echo "<a title=\"http://". $mysql_row['refer'] ."\" href=\"http://". $mysql_row['refer'] ."\">". substr($mysql_row['refer'], 0, 50) .""; ?></td>
    <td class='lfb bmb'>< ?php echo "<a title=\"http://". $mysql_row['page'] ."\" href=\"http://". $mysql_row['page'] ."\">". substr($mysql_row['page'], 0, 50) .""; ?></td>
  </tr>
< ?php
}
?>
</table>
<h2>Amount of errors, per page</h2>
<table border="0" cellspacing="1" cellpadding="2">
  <tr>
    <td class='lfb-d bmb-d'><b>page_count</b></td>
    <td class='lfb-d bmb-d'><b>page</b></td>
    <td class='lfb-d bmb-d'><b>err_code</b></td>
  </tr>
< ?php

$mysql_query = "SELECT count('page') AS page_count, page, err_code FROM `m_error` GROUP BY page ORDER BY count('page') DESC LIMIT 0,50";
$mysql_result = mysql_query($mysql_query);

while ($mysql_row = mysql_fetch_assoc($mysql_result)) {
?>
  <tr>
    <td class='lfb bmb'>< ?php echo $mysql_row['page_count']; ?></td>
    <td class='lfb bmb'>< ?php echo "<a title=\"http://". $mysql_row['page'] ."\" href=\"http://". $mysql_row['page'] ."\">". substr($mysql_row['page'], 0, 50) .""; ?></td>
    <td class='lfb bmb'>< ?php echo $mysql_row['err_code']; ?></td>
  </tr>
< ?php
}
?>
</table>
<h2>Amount of errors, per browser</h2>
<table border="0" cellspacing="1" cellpadding="2">
  <tr>
    <td class='lfb-d bmb-d'><b>browser_count</b></td>
    <td class='lfb-d bmb-d'><b>browser</b></td>
  </tr>
< ?php

$mysql_query = "SELECT count('browser') AS browser_count, browser FROM `m_error` GROUP BY browser ORDER BY count('browser') DESC LIMIT 0,50";
$mysql_result = mysql_query($mysql_query);

while ($mysql_row = mysql_fetch_assoc($mysql_result)) {
?>
  <tr>
    <td class='lfb bmb'>< ?php echo $mysql_row['browser_count']; ?></td>
    <td class='lfb bmb'>< ?php echo "<acronym title=\"". $mysql_row['browser'] ."\">". substr($mysql_row['browser'], 0, 100) .""; ?></td>
  </tr>
< ?php
}
?>
</table>
<p>&amp;amp;amp;amp;nbsp;</p>
<p><a href="http://www.stubbedtoe.co.nz" target="_top">Home</a>
</p>
</body>
</html>
< ?php
mysql_close($link);
?>

And there you have it.

2 Comments :, , , more...

Looking for something?

Use the form below to search the site:

Still not finding what you're looking for? Drop a comment on a post or contact us so we can take care of it!

All of the links!

A few highly recommended sites...