DEV Community

loading...
Cover image for How to log PHP errors and warnings into a file

How to log PHP errors and warnings into a file

dennismaina profile image Dennis Maina ・5 min read

When writing server-side code in PHP, it's always a good practice to consider security first. When dealing with PHP errors that could occur at runtime, it's advisable to hide these error messages from the users and instead log them into a file for developers or system admins to review them. This is a good security practice as the error messages can expose an existing vulnerability or give an attacker an entry point into your system.

Enough of talking, how can we do that effectively and efficiently?

We are going to create a class that's going to be invoked every time an error occurs in the system.

  • If you are using an MVC place this code at the top of your main index file.
  • If you are not using an MVC, place this code at the top of every PHP file that you want the errors handled.
include('<REPLACE WITH THE LOCATION OF YOUR FILE>.php');
if (ERR_HANDLER_ENABLED) { 
    register_shutdown_function('sysFatalErr'); 
    set_error_handler('sysErrorhandler'); 
}
Enter fullscreen mode Exit fullscreen mode

This class collects all the errors reported from a PHP script, classifies them as either general errors or fatal errors. After classification, it passes the error to the respective method which then calls the log function that writes them out.

Let's define some constant variables here.

/**
 * if your error class file resides in a folder called classes and the index file is in the base folder use:
 *
 * define('ERR_HANDLER_PATH', substr(dirname(__file__), 0, strpos(dirname(__file__), 'classes') - 1) . '/'); // DO NOT change!!
 * 
 * after using the above line comment or remove the fisrt line down here
**/
define('ERR_HANDLER_PATH', dirname(__file__).'/'); // DO NOT change!!
define('ERR_HANDLER_LOG_FOLDER', 'logs'); // Name of logs folder.. Create if it does not exist.
define('ERR_HANDLER_ENABLED', 1); // Enable custom error handler?
define('ERR_HANDLER_DISPLAY', 1); // Display a message on screen?
define('ERR_APPEND_RAND_STRING', 0); // Adds random string to file name for security. Prevents someone attempting browser access.
define('MASK_FILE_PATH', 0); // Hide file path if error occurs..
define('FILE_ERR_LOG_FILE', 'errors.log'); // File name of error log
define('FILE_FATAL_ERR_LOG_FILE', 'fatal_errors.log'); // File name of fatal error log
Enter fullscreen mode Exit fullscreen mode

We create our class which has four functions and each function calls the log function when invoked:

  • generalErr - This function is invoked when a general error occurs.
  • mailErr - This function is invoked when an email error occurs.(if you have an email sending script)
  • fataErr - This function is invoked when a fatal error occurs and no further execution can happen.
  • log - calls the write function which then writes the error into a file.
class Errs {

  public function generalErr($error) {
    Errs::log($error, FILE_ERR_LOG_FILE);
  }

  public function mailErr($error) {
    Errs::log($error, FILE_ERR_LOG_FILE);
  }

  public function fatalErr($error) {
    Errs::log($error, FILE_FATAL_ERR_LOG_FILE);
  }

  public function log($error, $file) {
    if (is_dir(ERR_HANDLER_PATH . ERR_HANDLER_LOG_FOLDER)) {
      write(ERR_HANDLER_PATH . ERR_HANDLER_LOG_FOLDER . '/' . Errs::raStr() . $file, trim($error) . linending() . '***** - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  - - - - - - - - - - - - *****' . linending());
    }
  }

  public function raStr() {
    return (ERR_APPEND_RAND_STRING ? substr(md5(uniqid(rand(),1)), 3, 30) . '-' : '');
  }


}

// Initiate the class..
$DDEH = new Errs();
Enter fullscreen mode Exit fullscreen mode

Now we check to see if the error handler is enabled. If it is enabled, we turn of displaying all the errors to the user.

if (ERR_HANDLER_ENABLED) {
  // Switch off display errors
  @ini_set('display_errors', 0);
  // Set error reporting level..
  error_reporting(E_ALL);
}
Enter fullscreen mode Exit fullscreen mode

On the Error reporting levels, we're going to be using E_ALL. PHP provides lots of them which you can look at here PHP Error Levels.
Here's just a few that I think are useful together with their error codes.

Error Error Code Description
E_ERROR 1 Fatal runtime error that can be recovered, The execution of the script is stopped immediately
E_WARNING 2 A runtime warning, not fatal and most errors fall here, execution is not stopped
E_NOTICE 8 Runtime notice indicating something that could possibly be an error was encountered when running a script normally
E_COMPILE_ERROR 64 Fatal error that occurs while the script was being compiled generated by the Zend Scripting engine
E_USER_ERROR 256 A fatal user-generated error, generated by the PHP code using the function trigger_error() rather than the PHP engine
E_USER_WARNING 512 NON FATAL USER GENERATED warning message, generated by the PHP code using the function trigger_error() rather than the PHP engine
E_STRICT 2048 Not strictly an error but it's triggered whenever PHP encounters code that could lead to problems or forward incompatibilities
E_ALL 32767 All errors and warnings except from level E_STRICT

TIP: Passing a value of (-1) to the error_reporting() function will show every possible error when new levels and constants are added in future PHP versions.

Now, we create a function that inserts a newline depending on the OS architecture.

function linending() {
  $newline = "\r\n";
  if (isset($_SERVER["HTTP_USER_AGENT"]) && strstr(strtolower($_SERVER["HTTP_USER_AGENT"]), 'win')) {
    $newline = "\r\n";
  } else if (isset($_SERVER["HTTP_USER_AGENT"]) && strstr(strtolower($_SERVER["HTTP_USER_AGENT"]), 'mac')) {
    $newline = "\r";
  } else {
    $newline = "\n";
  }
  return (defined('PHP_EOL') ? PHP_EOL : $newline);
}
Enter fullscreen mode Exit fullscreen mode

This function will be called whenever a fatal error occurs.

function sysFatalErr() {
  global $DDEH;
  $error = error_get_last();
  if (isset($error['type'])) {
    if ((defined('E_ERROR') && $error['type'] == E_ERROR) || $error['type'] == 4) {
      $string = '[Error Code: ' . $error['type'] . '] ' . $error['message'] . linending();
      $string .= '[Date/Time: ' . date('j F Y @ H:iA') . ']' . linending();
      $string .= '[Fatal error on line ' . $error['line'] . ' in file ' . $error['file'] . ']';
      if (ERR_HANDLER_DISPLAY) {
        echo '<div style="background:#ff9999"><p style="padding:10px;color:#fff">A fatal error has occurred. For more details please view "' . ERR_HANDLER_LOG_FOLDER . '/' . FILE_FATAL_ERR_LOG_FILE . '".</div>';
      }
      $DDEH->fatalErr($string);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

This other function is called when general errors occur, and can further classify them accordingly. You can add more in the switch block.

function sysErrorhandler($errno, $errstr, $errfile, $errline) {
  global $DDEH;
  if (!(error_reporting() & $errno)) {
    return;
  }
  if (!method_exists($DDEH,'generalErr') || !method_exists($DDEH,'fatalErr')) {
    return;
  }
  switch ($errno) {
    case E_USER_ERROR:
      $string = '[Error Code: ' . $errno . '] ' . $errstr . linending();
      $string .= '[Date/Time: ' . date('j F Y @ H:iA') . ']' . linending();
      $string .= '[Error on line ' . $errline . ' in file ' . $errfile . ']';
      if (ERR_HANDLER_DISPLAY) {
        echo '<div style="background:#ff9999"><p style="padding:10px;color:#fff">A fatal error has occurred. For more details please view "' . ERR_HANDLER_LOG_FOLDER . '/' . FILE_FATAL_ERR_LOG_FILE . '".</div>';
      }
      $DDEH->fatalErr($string);
      exit;
      break;

    case E_USER_WARNING:
      $string = '[Error Code: ' . $errno . '] ' . $errstr;
      $string .= '[Date/Time: ' . date('j F Y @ H:iA') . ']' . linending();
      $string .= '[Error on line ' . $errline . ' in file ' . $errfile . ']';
      if (ERR_HANDLER_DISPLAY) {
        echo '<div style="background:#ff9999"><p style="padding:10px;color:#fff">An error has occurred. For more details please view "' . ERR_HANDLER_LOG_FOLDER . '/' . FILE_ERR_LOG_FILE . '".</div>';
      }
      $DDEH->generalErr($string);
      break;

    case E_USER_NOTICE:
      $string = '[Error Code: ' . $errno . '] ' . $errstr . linending();
      $string .= '[Date/Time: ' . date('j F Y @ H:iA') . ']' . linending();
      $string .= '[Error on line ' . $errline . ' in file ' . $errfile . ']';
      if (ERR_HANDLER_DISPLAY) {
        echo '<div style="background:#ff9999"><p style="padding:10px;color:#fff">An error has occurred. For more details please view "' . ERR_HANDLER_LOG_FOLDER . '/' . FILE_ERR_LOG_FILE . '".</div>';
      }
      $DDEH->generalErr($string);
      break;

    default:
      $string = '[Error Code: ' . $errno . '] ' . $errstr . linending();
      $string .= '[Date/Time: ' . date('j F Y @ H:iA') . ']' . linending();
      $string .= '[Error on line ' . $errline . ' in file ' . $errfile . ']';
      if (ERR_HANDLER_DISPLAY) {
        echo '<div style="background:#ff9999"><p style="padding:10px;color:#fff">An error has occurred. For more details please view "' . ERR_HANDLER_LOG_FOLDER . '/' . FILE_ERR_LOG_FILE . '".</div>';
      }
      $DDEH->generalErr($string);
      break;
  }
  return true;
}
Enter fullscreen mode Exit fullscreen mode

Finally, we write out our errors into a file. Remember to have the logs folder created.

function write($file, $data) {
  file_put_contents($file, $data, FILE_APPEND);
}
Enter fullscreen mode Exit fullscreen mode

You can get the whole code on my GitHub Here

Discussion (2)

pic
Editor guide
Collapse
alphpkeemik profile image
Mati Andreas

Use Symfony if possible, it will handle that for you.

Collapse
dennismaina profile image
Dennis Maina Author

This will help alot of people who are stuck at choosing the correct framework to build a site on 💪