Error and Exception Logging in PHP

Hi guys,

I believe that everyone is well aware of errors and exceptions in PHP. And I am sure, everyone must have their own way of handling them.

But there is problem in PHP. PHP can easily trap and log runtime errors but PHP error handlers cannot trap fatal errors. And it makes the debugging a troublesome job. I too have faced problems. So, I decided to solve this issue. After going through PHP Manual for hours I found a method called register_shutdown_function()

 

I wrote a class ErrorLogging which catch fatal errors along with notice, warnings and exception. I published it on PHPClasses.org. Here is the link – http://www.phpclasses.org/package/6512-PHP-Handle-PHP-fatal-and-non-fatal-execution-errors.html . I won first prize for this. 😀 (Yay!!!). This class will show you the stack-trace of errors/exceptions.

 

 

The idea.

Registers an error handler that is capable of a backtrace with the list of functions and arguments used to call the code that causes an error, send that information to the current page output or the PHP error log, or send an e-mail message to the administrator. The class can also trap fatal errors using a special PHP shutdown callback function.

The
code.

class.ErrorHandler.php.

<?php
/**
 * This class handles and logs the error that occurs in the project. Exceptions will also be caught by this class.
 *
 * @package 
 * @author Nitesh Apte
 * @copyright 2011
 * @version 1.1
 * @access private
 */

include_once 'core.php';

class ErrorHandler {

  private static $_singleInstance;

  /**
   * @var $_backTrace Backtrace message in _customError() method
   * @see _customError
   */
  private $_backTrace;

  /**
   * @var $_errorMessage Error message
   * @see _customError
   */
  private $_errorMessage;

  /**
   * @var $_traceMessage Contains the backtrace message from _debugBacktrace() method
   * @see _debugBacktrace()
   */
  private $_traceMessage = '';

  /**
   * @var $MAXLENGTH Maximum length for backtrace message
   * @see _debugBacktrace()
   */
  private $_MAXLENGTH = 64;

  /**
   * @var $_traceArray Contains from debug_backtrace()
   * @see _debugBacktrace()
   */
  private $_traceArray;

  /**
   * @var $_defineTabs
   */
  private $_defineTabs;

  /**
   * @var $_argsDefine
   */
  private $_argsDefine = array();

  /**
   * @var $_newArray
   */
  private $_newArray;

  /**
   * @var $_newValue
   */
  private $_newValue;

  /**
   * @var $_stringValue
   */
  private $_stringValue;

  /**
   * @var $_lineNumber
   */
  private $_lineNumber;

  /**
   * @var $_fileName
   */
  private $_fileName;

  /**
   * @var $_lastError
   */
  private $_lastError;

  /**
   * Create the single instance of class
   *
   * @param none
   * @return Object self::$_singleInstance Instance
   */
  public static function _getInstance($_requestFrom) {
    if(!self::$_singleInstance instanceof self) {
      self::$_singleInstance = new self($_requestFrom);
    }
    return self::$_singleInstance;
  }

  /**
   * Set custom error handler
   *
   * @param none
   * @return none
   */
  private function __construct($_requestFrom) {

    if($_requestFrom == 'web') {
      define('WEB', TRUE);
    }
    if($_requestFrom == 'device') {
      define('DEVICE', TRUE);
    }
    if($_requestFrom == 'webservice') {
      define('WEBSERVICE', TRUE);
    }
    error_reporting(1);
    set_error_handler(array($this,'_customError'), APP_ERROR);
    register_shutdown_function(array($this, '_fatalError'));
  }

  /**
   * Custom error logging in custom format
   *
   * @param Int $errNo Error number
   * @param String $errStr Error string
   * @param String $errFile Error file
   * @param Int $errLine Error line
   * @return none
   */
  public function _customError($errNo, $errStr, $errFile, $errLine) {

    if(error_reporting() == 0) {
      return;
    }
    $this-&gt;_backTrace = $this-&gt;_debugBacktrace(2);

    $this-&gt;_errorMessage = "\n&lt;h1&gt;Website Generic Error!&lt;/h1&gt;";
    $this-&gt;_errorMessage .= "\n&lt;b&gt;ERROR NO : &lt;/b&gt;&lt;font color='red'&gt;{$errNo}&lt;/font&gt;";
    $this-&gt;_errorMessage .= "\n&lt;b&gt;TEXT : &lt;/b&gt;&lt;font color='red'&gt;{$errStr}&lt;/font&gt;";
    $this-&gt;_errorMessage .= "\n&lt;b&gt;LOCATION : &lt;/b&gt;&lt;font color='red'&gt;{$errFile}&lt;/font&gt;, &lt;b&gt;line&lt;/b&gt; {$errLine}, at ".date("F j, Y, g:i a");
    $this-&gt;_errorMessage .= "\n&lt;b&gt;Showing Backtrace : &lt;/b&gt;\n{$this-&gt;_backTrace} \n\n";

    if(SEND_ERROR_MAIL == TRUE) {
      error_log($this-&gt;_errorMessage, 1, ADMIN_ERROR_MAIL, "From: ".SEND_ERROR_FROM."\r\nTo: ".ADMIN_ERROR_MAIL);
    }

    if(ERROR_LOGGING==TRUE) {

      if(WEB == TRUE) {
        error_log($this-&gt;_errorMessage, 3, ERROR_LOGGING_FILE_WEB);
      }
      if(DEVICE == TRUE) {
        error_log($this-&gt;_errorMessage, 3, ERROR_LOGGING_FILE_DEVICE);
      }
      if(WEBSERVICE == TRUE) {
        error_log($this-&gt;_errorMessage, 3, ERROR_LOGGING_FILE_WEBSERVICE);
      }
    }

    if(DEBUGGING == TRUE) {
      echo "&lt;pre&gt;".$this-&gt;_errorMessage."&lt;/pre&gt;";
    } else {
      echo SITE_GENERIC_ERROR_MSG;
    }
    exit;
  }

  /**
   * Build backtrace message
   *
   * @param $_entriesMade Irrelevant entries in debug_backtrace, first two characters
   * @return
   */
  private function _debugBacktrace($_entriesMade) {
    $this-&gt;_traceArray = debug_backtrace();

    for($i=0;$i&lt;$_entriesMade;$i++) {
      array_shift($this-&gt;_traceArray);
    }

    $this-&gt;_defineTabs = sizeof($this-&gt;_traceArray)-1;
    foreach($this-&gt;_traceArray as $this-&gt;_newArray) {

      $this-&gt;_defineTabs -=1;
      if(isset($this-&gt;_newArray['class'])) {
        $this-&gt;_traceMessage .= $this-&gt;_newArray['class'].'.';
      }
      if(!empty($this-&gt;_newArray['args'])) {

        foreach($this-&gt;_newArray['args'] as $this-&gt;_newValue) {
          if(is_null($this-&gt;_newValue)) {
            $this-&gt;_argsDefine[] = NULL;
          } elseif(is_array($this-&gt;_newValue)) {
            $this-&gt;_argsDefine[] = 'Array['.sizeof($this-&gt;_newValue).']';
          } elseif(is_object($this-&gt;_newValue)) {
            $this-&gt;_argsDefine[] = 'Object: '.get_class($this-&gt;_newValue);
          }
          elseif(is_bool($this-&gt;_newValue)) {
            $this-&gt;_argsDefine[] = $this-&gt;_newValue ? 'TRUE' : 'FALSE';
          } else {
            $this-&gt;_newValue = (string)@$this-&gt;_newValue;
            $this-&gt;_stringValue = htmlspecialchars(substr($this-&gt;_newValue, 0, $this-&gt;_MAXLENGTH));
            if(strlen($this-&gt;_newValue)&gt;$this-&gt;_MAXLENGTH) {
              $this-&gt;_stringValue = '...';
            }
            $this-&gt;_argsDefine[] = "\"".$this-&gt;_stringValue."\"";
          }
        }
      }
      $this-&gt;_traceMessage .= $this-&gt;_newArray['function'].'('.implode(',', $this-&gt;_argsDefine).')';
      $this-&gt;_lineNumber = (isset($this-&gt;_newArray['line']) ? $this-&gt;_newArray['line']:"unknown");
      $this-&gt;_fileName = (isset($this-&gt;_newArray['file']) ? $this-&gt;_newArray['file']:"unknown");

      $this-&gt;_traceMessage .= sprintf(" # line %4d. file: %s", $this-&gt;_lineNumber, $this-&gt;_fileName, $this-&gt;_fileName);
      $this-&gt;_traceMessage .= "\n";
    }
    return $this-&gt;_traceMessage;
  }

  /**
   * Method to catch fatal and parse error
   *
   * @param none
   * @return none
   */
  public function _fatalError() {
    $this-&gt;_lastError = error_get_last();
    if($this-&gt;_lastError['type'] == 1 || $this-&gt;_lastError['type'] == 4 || $this-&gt;_lastError['type'] == 16 || $this-&gt;_lastError['type'] == 64 || $this-&gt;_lastError['type'] == 256 || $this-&gt;_lastError['type'] == 4096) {
      $this-&gt;_customError($this-&gt;_lastError['type'], $this-&gt;_lastError['message'], $this-&gt;_lastError['file'], $this-&gt;_lastError['line']);
    }
  }

  private function __clone() {
    throw new Exception("Cloning is not supported in singleton class");
  }
}
?>

Notice that there is core.php included in this class. This file contains the settings for emails, location for the log files.

core.php

<?php 
/**
* Global Default values that to be used throughout the project
*
* @package 
* @author Nitesh Apte
* @copyright 2011
* @version 1.0
* @access public
*/

/**
* Web root configuration
*/
define('SITE_ROOT', $_SERVER['DOCUMENT_ROOT']);

/**
 * Error Handling Values
 */
define('APP_ERROR', E_ALL ^ E_NOTICE);
define('DEBUGGING', TRUE);
define('ADMIN_ERROR_MAIL', 'administrator@portal.com');
define('SEND_ERROR_MAIL', FALSE);
define('SEND_ERROR_FROM', 'errors@portal.com');
define('IS_WARNING_FATAL', TRUE);
define('ERROR_LOGGING', TRUE);
define('ERROR_LOGGING_FILE_WEB', SITE_ROOT.'/logs/web/logs.ErrorWeb.log');
define('ERROR_LOGGING_FILE_WEBSERVICE', SITE_ROOT.'/logs/webservice/logs.ErrorWebService.log');
define('ERROR_LOGGING_FILE_DEVICE', SITE_ROOT.'/logs/mobile/logs.ErrorDevice.log');
define('SITE_GENERIC_ERROR_MSG', '&lt;h1&gt;Portal Error!&lt;/h1&gt;');
?>

Now the usage: header.php. Just put it at the top. That’s it. You are good now.

<?php
include_once "class.ErrorHandler.php";
ErrorHandler::_getInstance("web");
?>

I know you guys must be wondering what is “web” in parameter. It has been observed that a website is also accessed via mobile. And sometimes web services are
created to expose services. So, here “web” means if website is accessed directly then a separate error log file will be created for it, in the location configured in core.php file. So, in order to use this class for logging error when accessed through  mobile or web services or any other thing, make an entry in core.php for it. And in case you are using “any other thing”, you will need to edit the below section in code.

if(ERROR_LOGGING==TRUE) {
       if(WEB == TRUE) {
    error_log($this-&gt;_errorMessage, 3, ERROR_LOGGING_FILE_WEB);
  }
  if(DEVICE == TRUE) {
    error_log($this-&gt;_errorMessage, 3, ERROR_LOGGING_FILE_DEVICE);
  }
  if(WEBSERVICE == TRUE) {
    error_log($this-&gt;_errorMessage, 3, ERROR_LOGGING_FILE_WEBSERVICE);
  }
}

That’s it guys.

Critics/suggestions are very much welcome.

Have a nice day ahead.

 

Loading

Leave a Reply

Your email address will not be published. Required fields are marked *

Time limit is exhausted. Please reload CAPTCHA.

Follow Me