It is extremely unprofessional for a production website to output the content of any error messages generated. Not only does it mean the site visitor gets to see details of your flaws, the chances are that you’ll never hear about the problem because users generally just click off your site rather than take the time to email you the details (and why should they – it’s not their job).
One topic that gets quite a bit of coverage on the Internet is how to suppress errors, warnings and notices generated by PHP. PHP has it’s own function called set_error_handler that allows a developer to specify a function to act as a custom error handler that can do whatever they want when handling an error. As wonderful as this is, it has a couple of caveats. To quote PHP.net:
The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING, and most of E_STRICT raised in the file where set_error_handler() is called.
If errors occur before the script is executed (e.g. on file uploads) the custom error handler cannot be called since it is not registered at that time.
So if you have a file with a parse error, a function call to a non-existent function or any other manner of issues you might not want your users to see, simply handling it nicely through your own function will not work and the error will be logged into the default error log file.
For those who love to monitor their error log file manually every 5 minutes, the default dumping of errors into the file is not an issue. Much more convenient is to have a script that runs periodically to check the error log file for new errors and on finding any sends them out via email.
I have written such an automated script and it can be viewed here and below. You are free to use it as you wish. I have commented it as thoroughly as possible to assist with any desired modifications, though for most it should take very little to get going.
- The script is 100% PHP and should work fine on most version 4 installs – I wrote it under a version 5 environment, but I don’t think any of the functions I have used are that new.
- It is expected that the script will be run from cron. How often it runs is up to you, it depends how on-the-pulse you want to be regarding your error messages, every 5 minutes maximum I’d suggest though. See this guide on running a PHP script from cron. If you don’t have cron access, consider using a free service such as WebCron or building the script into, or calling it from your site’s footer – make sure you adjust it so it won’t run with every page request, just once every 5 minutes or so.
- The script uses the PHPMailer class to send the main error email, but the default PHP mail() function for start-up errors and fall back. I am expecting that you will run the script manually a couple of times first to ensure it works and that file permissions are set OK, so once it is set-up start-up errors should not be a problem. PHPMailer allows the use of a SMTP server or sendmail for outgoing mail in addition to the default mail() function. If you don’t want to download and use the PHPMailer class, it is very straight forward to remove its use from my script – and it’s all fully commented where and how, too.
In addition to implementing this script (or any other for that matter) you will of course want to ensure errors are logged and not displayed. This can be achieved using the ini_set and error_reporting functions in all your scripts (via a common include), setting the config in php.ini or using a .htaccess file. For those on a shared host who does not permit php.ini changes, but who do have the ability to change config through .htaccess, check out this Perishable Press tutorial on setting it up. I’ve also included details in the header comments of my file.
I know there are many other ways of accomplishing this task, including using the unix ‘tail’ program, but this script is intended to help those with little or no unix knowledge. There should be little modification required to get this running on a Windows system, but I cannot verify that as I have not tried it. If you do run in to errors on Windows, please post what changes were required to get it working in the comments below for the benefit of others.
The file
Below is a copy of the script, however it is intended for quick viewing now, if you want to use it save it from here – don’t forget to rename it. Wordpress has freaked with a few characters (such as new lines) meaning a direct copy and paste of the following will produce undesired results.
<?php
/**
* A script that can be called from cron to automatically email the content
* of a PHP error log file to a developer or webmaster.
*
* Author: James Caws
* Website: http://www.jamescaws.co.uk
*
* It uses the freely available PHPMailer class available from
* http://sourceforge.net/project/showfiles.php?group_id=26031
*
* This particular script has been configured to use SMTP, but it's a piece of cake
* to modify to use mail() and can even use sendmail if you make a few simple
* changes to configure PHPMailer to do so.
*
* This version of the script is using PHPMailer v2.0.2
* For help on all PHPMailer config issues and errors, refer to their documentation
* and website at http://phpmailer.codeworxtech.com/
*
* Ensure that all of your website PHP scripts include the following through a common
* include file or at the very top before anything else.
*
* ini_set('display_errors','0'); // Best practise on production sites
* ini_set('log_errors','1'); // We need to log them otherwise this script will be pointless!
* ini_set('error_log','/path/to/error_log); // Full path to a writable file - include the file name
* error_reporting(E_ALL ^ E_NOTICE); // What errors to log - see: http://www.php.net/error_reporting
*
* OR alternatively and more highly recommended, adjust php.ini or configure a .htaccess file if you can't do that.
* See http://perishablepress.com/press/2008/01/14/advanced-php-error-handling-via-htaccess/ for details and examples
*
* NOTE : READ ALL OF THE COMMENTS IN THIS FILE TO UNDERSTAND HOW IT WORKS. IT
* IS PARTICULARLY IMPORTANT YOU DO SO IF YOU DO NOT WANT TO USE THE PHPMAILER
* CLASS AS YOU WILL NEED TO COMMENT OUT OR REMOVE CODE.
*/
/**
* Set a few constants for ease of configuration
*/
define('ERROR_NOTIFY_EMAIL','recipient@domain.com'); // Where the errors should be emailed
define('ERROR_NOTIFY_FROM_EMAIL','sender@domain.com'); // The 'from' address to use in outgoing emails
define('ERROR_NOTIFY_FROM_NAME','PHP Error Log Monitor'); // The 'from name' to use in outgoing emails
define('ERROR_NOTIFY_SUBJECT','PHP error log report'); // Give the email a subject. Might come in useful if setting up a mailbox filter
define('ERROR_LOG_FILE','/path/to/error_log'); // The full path to your readable + writable error file as defined by PHP config value 'error_log'
define('MAX_CONTENT_SIZE',1024); // As a precaution, set the max size of the error log content to be emailed in case it grows exponentially
/**
* The following is not required if you are happy for the default PHP mail()
* function to be used. Some hosts don't allow it though or can be unreliable, so
* you might want to use SMTP for better reliablity.
*/
$aMParams["host"] = 'smtp.domain.com'; // - The server to connect. Default is localhost
$aMParams["port"] = 25; // - The port to connect. Default is 25
$aMParams["auth"] = true; // - Whether or not to use SMTP authentication. Default is FALSE
$aMParams["username"] = 'username'; // - The username to use for SMTP authentication.
$aMParams["password"] = 'password'; // - The password to use for SMTP authentication.
define('SMTP_MAIL_PARAMS',serialize($aMParams)); // remove this line if you are not using SMTP
/**
* Carry out a few basic file checks first. This script will itself run in to problems if the error log
* file does not exist, cannot be read or writen to.
*/
if (!file_exists(ERROR_LOG_FILE)) {
mail(ERROR_NOTIFY_EMAIL, 'Error log file does not exist', sprintf("The file '%s' does not exist. Error log monitor cannot continue.", ERROR_LOG_FILE));
exit;
} else if (!is_readable(ERROR_LOG_FILE)) {
mail(ERROR_NOTIFY_EMAIL, 'Error log file is not readable', sprintf("The file '%s' is not readable. Error log monitor cannot continue.", ERROR_LOG_FILE));
exit;
} else if (!is_writable(ERROR_LOG_FILE)) {
mail(ERROR_NOTIFY_EMAIL, 'Error log file is not writable', sprintf("The file '%s' is not writable. Error log monitor cannot continue.", ERROR_LOG_FILE));
exit;
}
/**
* Check the error log filesize - no point carrying on if there is nothing in it.
*/
if (filesize(ERROR_LOG_FILE) == 0) { exit; }
/**
* OK, so if we're still here there's work to do.
*/
/**
* Include the PHPMailer class. You still need this even if you have removed the above SMTP
* configuration. If you DON'T want to use PHPMailer though, comment out this line and then
* jump down to the comment 'Email it', remove the associated PHPMailer lines and uncomment
* the mail() call.
*
* REMEMBER : class.phpmailer.php should be in the same location as this script, or
* adjust the following line if it is somewhere else.
*/
require_once('class.phpmailer.php');
/**
* Retrieve the content from the file
* and assign it to a couple of variables.
*/
$sContent = $sFullContent = file_get_contents(ERROR_LOG_FILE);
/**
* If we don't want to mail huge amounts of data, extract only the amount we do want.
*/
if (defined('MAX_CONTENT_SIZE') && MAX_CONTENT_SIZE > 0) {
$sContent = substr($sContent, 0, MAX_CONTENT_SIZE);
if (strlen($sFullContent) > MAX_CONTENT_SIZE) {
$sContent .= "\n\n... Consult the error log archive for the full list of errors";
}
}
/**
* Email it.
*
* IF YOU DO NOT WANT TO USE PHPMAILER AND HAVE REMOVED THE require_once() INCLUSION ABOVE,
* REMOVE ALL LINES BETWEEN HERE AND THE COMMENT "END OF PHPMAILER"
*/
$oMail = new PHPMailer();
/**
* If we have SMTP detail held in a constant, use SMTP. Otherwise PHPMailer will default to using mail();
*/
if (defined('SMTP_MAIL_PARAMS')) {
$aSMTP = unserialize(SMTP_MAIL_PARAMS);
$oMail->IsSMTP(); // set mailer to use SMTP
$oMail->Host = $aSMTP['host']; // specify main and backup server
$oMail->Port = $aSMTP['port']; // specify SMTP port
$oMail->SMTPAuth = $aSMTP['auth']; // turn on SMTP authentication
$oMail->Username = $aSMTP['username']; // SMTP username
$oMail->Password = $aSMTP['password']; // SMTP password
}
$oMail->AddAddress(ERROR_NOTIFY_EMAIL, '');
$oMail->Body = $sContent;
$oMail->From = ERROR_NOTIFY_FROM_EMAIL;
$oMail->FromName = ERROR_NOTIFY_FROM_NAME;
$oMail->Subject = ERROR_NOTIFY_SUBJECT;
$oMail->Send();
if ($oMail->ErrorInfo && strlen($oMail->ErrorInfo)) {
$sContent = sprintf("This message has been sent via the backup mail() function call as PHPMailer failed reporting: %s\n\n---\n\n%s",
$oMail->ErrorInfo,
$sContent
);
mail(ERROR_NOTIFY_EMAIL, ERROR_NOTIFY_SUBJECT, $sContent);
}
/** END OF PHPMAILER **/
/**
* Uncomment this line to use the default PHP mail() function if not using PHPMailer
*/
//mail(ERROR_NOTIFY_EMAIL, ERROR_NOTIFY_SUBJECT, $sContent);
/**
* Truncate the error log so we don't email it all again on the next run.
*/
file_put_contents(ERROR_LOG_FILE,null);
/**
* We'll copy the full content of the error log to an archive. This is useful for a number
* of reasons, not least because we are only emailing a portion of the original errors if
* the log file was larger than our given MAX_CONTENT_SIZE - there could be errors in the
* full log not included in the email.
*/
$sHistoricalLogName = sprintf('%s.archive', ERROR_LOG_FILE);
$sFullContent = sprintf("----- Full content of error log as mailed @ %s -----\n%s\n", date('d/m/Y H:i:s'), $sFullContent);
file_put_contents($sHistoricalLogName, $sFullContent, FILE_APPEND);
/**
* And we're done.
*/
exit;
?>
Remember, if you want to use it save it from here.
This post is tagged: email, error, log, monitor, PHP, script

Thanks for the script! This is incredibly useful for anyone looking to keep a close eye on their PHP errors. I track any/all PHP errors through a log file on the server that I check every few days, but having the information sent directly to my mobile account is definitely more convenient. Great work!
Thanks. This could literally save my career. (Parse errors are evil little suckers).
Thanks for putting the effort in to create this nifty utility. I was pulling my hair out thinking of ways to code a custom error handler to send email alerts; this is a far better solution.
Hi James,
thanks for this script, after a little fiddling around I got it working, however there is a persistent error with the permissions when creating the log archive:
[21-Oct-2008 23:30:02] PHP Warning: file_put_contents(/home/……/PHP_errors.log.archive) [function.file-put-contents]: failed to open stream: Permission denied in /home/……/error.log.php on line 175
I’ve chmod 0777 and tried what I can to give it correct permissions, the file is created and owned by nobody – do you know what I can do to correct this?
thanks in advance.
Hi Murray,
If you ensure PHP_errors.log.archive is in the directory the script is looking for it and the permissions on it (the file) are 0666 (read and write for all), I don’t see why there should be any problem, regardless of who owns the file. Confusingly you have said the file exists and is owned by nobody, which indicates the system has had no problem creating the error log archive file.
Without seeing your adjusted version of this script it is hard for me to comment or provide any further suggestions.
James
Hi,
thanks for this – i assume it will work on MySql logs just the same, however what i would like is a way that an email will be sent only when the string [ERROR] has been added to the MySql log. Also, i am not sure why in this script it needs to have WRITE permissions.
Thanks
Duddy
Hi Duddy,
The script needs write permissions on the log file so that it can truncate the contents using : file_put_contents(ERROR_LOG_FILE,null);. If the user running the script doesn’t have write permissions, the script will fail to truncate the file and then the next time it runs it will send the same content all over again.
As for only sending an email when the string [ERROR] is present, I would add in some code after the line :
$sContent = $sFullContent = file_get_contents(ERROR_LOG_FILE);
that explodes $sContent on new lines creating an array with each line as an array element. I’d then iterate through those elements pattern matching for ‘[ERROR]‘ and for each line containing a match I’d add it to a new variable, perhaps $sContentMatched (.= $sMatchingLine). Once all the content has been checked, I’d then set $sContent and $sFullContent to equal $sContentMatched and then allow the script to continue as normal, though you’ll want to check that you actually have matching content first, and if not tell the script to exit.
Thanks this works very well, (so far at least.)
One problem I think might happen with your original script is the archive log could become big enough to crash some file systems, (over 2 gig) eventually.
I’ve changed the last section to avoid this:
/**
* We’ll copy the full content of the error log to an archive. This is useful for a number
* of reasons, not least because we are only emailing a portion of the original errors if
* the log file was larger than our given MAX_CONTENT_SIZE – there could be errors in the
* full log not included in the email.
*/
$sHistoricalLogName = ‘<>\\archive\\’ . date(‘Y-m-d’) . ‘.log’;
$sFullContent = sprintf(“—– Full content of error log as mailed @ %s —–\n%s\n”, date(‘d/m/Y H:i:s’), $sFullContent);
file_put_contents($sHistoricalLogName, $sFullContent, FILE_APPEND);
/**
* And we’re done.
*/
Hi Brian,
Thanks for your contribution. My main motivation for the error log archive is so that if in between error log check runs, which are say every 5 minutes, a lot of errors are recorded, only the first few get emailed so as to avoid clogging up inbox space. Once the full archive is reviewed by an admin, I envisaged it more often than not being cleared, or at least moved elsewhere. You have a valid point though and particularly if a high traffic site has a lot of PHP errors, the log file could fill quite quickly.
To keep the naming of the archive file in line with the original file name (as defined by the admin), I’d suggest something like this:
$sHistoricalLogName = sprintf(‘%s.%s.archive’, ERROR_LOG_FILE, date(‘Y-m-d’));
or for monthly archives:
$sHistoricalLogName = sprintf(‘%s.%s.archive’, ERROR_LOG_FILE, date(‘Y-m’));
James
[...] the original here: Automatically monitor and email PHP error log content Tags: photography, PHP, [...]