Severity levels in PHP error handling & PSR-3

Severity levels for programms is a common concept, designed to segment technical messages the application genereates. Every programmer have heard (or better used) the severity levels in one form or another, for some – event across multiple programming languages, but there is usually a very solid definition for why and how to use severity levels.

For PHP language PSR-3 standart exists, which defines a universal interface for writing logs. This ensures that the third-party libraries an application uses can write to the centralized application logs, and also makes the log storage easily changable (file / database / remove server).

PSR-3 defines the following severity levels with the following examples of usage

  1. emergency – system is unusable (nothing is working)
  2. alert– action must be taken immidiately (database unavailable or simmilar scale problem)
  3. critical – critical conditions (application component unavailable)
  4. error – does not require immidiate actions, but should be monitored
  5. warning – undesirable things that are not necessarily wrong
  6. notice – normal, but significant events
  7. info – interesting events
  8. debug – detailed debug information

When designing the log collection solution for PHP application, different behaviour is programmed depending on the severity levels of the message. Debug, info and notice messages are usually left for development, when detailed information is necessary to ensure the process is programmed correctly. Warnings and errors are usually logged and monitored with analytical tools, counters, and programmers are periodically informed of such events. Critical, altert and emergency messages are Email-ed / SMS-ed / phone call-eed right away, as those endanger the integrity of the system.

The default PHP error handling does not provide these severities, for two reasons: 1) it was not designed to log alot of data. 2) it is less focused on what to do with error, but rather how the app should behave when encountering the error.

PHP offers a single function to log messages. It can generate a user-level error/warning/notice message, with the following interface

trigger_error ( string $error_msg [, int $error_type = E_USER_NOTICE ] ) : bool

The trigger_error method accepts error message as the first parameter and the severity as second. Interestingly, the user has only 3 severity constants to choose from

  1. E_USER_ERROR – these indicate errors that can not be recovered from, such as a memory allocation problem. Execution of the script is halted.
  2. E_USER_WARNING – non-fatal errors. Execution of the script is not halted.
  3. E_USER_NOTICE – indicate that the script encountered something that could indicate an error, but could also happen in the normal course of running a script.

By combining the universal log collection interface (PSR-3) and the default error handling methods in PHP, we can create a decent logging solution for beginner programmers. It would look simmilar to this

use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

class Logger implements LoggerInterface
{
    public function log($level, $message, array $context = array())
    {
        trigger_error(
            $level . ':' . $message . ' - ' . json_encode($context),
            $this->mapSeverity($level)
        );
    }

    public function mapSeverity($level)
    {
        $map = [
            LogLevel::EMERGENCY => E_USER_ERROR,
            LogLevel::ALERT     => E_USER_ERROR,
            LogLevel::CRITICAL  => E_USER_WARNING,
            LogLevel::ERROR     => E_USER_WARNING,
            LogLevel::WARNING   => E_USER_WARNING,
            LogLevel::NOTICE    => E_USER_NOTICE,
            LogLevel::INFO      => E_USER_NOTICE,
            LogLevel::DEBUG     => E_USER_NOTICE,
        ];
        if (array_key_exists($level, $map) === false) {
            throw new \RuntimeException('unsupported severity level received - ' . $level);
        }
        return $map[$level];
    }

    public function emergency($message, array $context = array())
    {
        $this->log(LogLevel::EMERGENCY, $message, $context);
    }

    public function alert($message, array $context = array())
    {
        $this->log(LogLevel::ALERT, $message, $context);
    }

    public function critical($message, array $context = array())
    {
        $this->log(LogLevel::CRITICAL, $message, $context);
    }

    public function error($message, array $context = array())
    {
        $this->log(LogLevel::ERROR, $message, $context);
    }

    public function warning($message, array $context = array())
    {
        $this->log(LogLevel::WARNING, $message, $context);
    }

    public function notice($message, array $context = array())
    {
        $this->log(LogLevel::NOTICE, $message, $context);
    }

    public function info($message, array $context = array())
    {
        $this->log(LogLevel::INFO, $message, $context);
    }

    public function debug($message, array $context = array())
    {
        $this->log(LogLevel::DEBUG, $message, $context);
    }
}

It is a straight forward wrapper for the trigger_error function, with the only addition of mapping the PSR-3 severity level to the PHP error constant.

Happy logging!