Display errors on screen even with display errors = off with PHP


Last month I was in a coding kata session playing with StingCalculator, and between iteration and iteration we were taking about the problems of shared hostings. Shared hosting are cheap, but normally they don’t allow us the use some kind of features. For example we cannot see the error log. That’s a problem when we need to see what happens within our application. Normally I work with my own servers, and I have got full access to error logs. But if we cannot see the error log and the server is configured with display errors = off in php.ini (typical configuration in shared hosting), we have a problem.

One of my post came to my mind “Real time monitoring PHP applications with websockets and node.js”. Basically we can solve the problem using this technique, but if we are speaking about shared hosting without error log access, it’s very probably that we cannot install a node.js server or even use sockets functions. So it’s not “Real” solution to our problem.

The idea is create a variation of the original script (one kind of script’s Spin-off ;)). In this scprit we will capture errors and exceptions and show them in script at the end of the script. We don’t have access to the error log but we will show it in the browser.

Imagine the following script:

$a = 1/0;
throw new Exception("myException");
echo "hi";

It will show one warning (1/0) and one exception (myException)

  • Warning: Division by zero
  • Fatal error: Uncaught exception ‘Exception’ with message ‘myException’

If we change php.ini show errors = off we will see a nice white screen.

The idea is add to the script:

include('ErrorSniffer.php');
ErrorSniffer::factory('127.0.0.1');

$a = 1/0;
throw new Exception("myException");
echo "hi";

Now with with ErrorSniffer library we will see a nice output, even with display errors = off.
I also add a IP in the class constructor to restrict the output message to one IP. The idea is to place this script at production, so we don’t want to expose error messages to whole users.

If we see the source code of ErrorSniffer class we a constructor like this:

    public function __construct($restingToIp)
    {
        if ($this->getip() == $restingToIp) {
            self::register_exceptionHandler($this);
            self::set_error_handler($this);
            self::register_shutdown_function($this);
        }
    }

    private static function set_error_handler(ErrorSniffer &$that)
    {
        set_error_handler(function ($errno, $errstr, $errfile, $errline) use (&$that) {
                $type = ErrorSniffer::getErrorName($errno);
                $that->registerError(array('type' => $type, 'message' => $errstr, 'file' => $errfile, 'line' => $errline));
                return false;
        });
    }

    private static function register_exceptionHandler(ErrorSniffer &$that)
    {
        set_exception_handler(function($exception) use (&$that) {
                $exceptionName = get_class($exception);
                $message = $exception->getMessage();
                $file  = $exception->getFile();
                $line  = $exception->getLine();
                $trace = $exception->getTrace();
    
                $that->registerError(array('type' => 'EXCEPTION', 'exception' => $exceptionName, 'message' => $message, 'file' => $file, 'line' => $line, 'trace' => $trace));
                return false;
        });
    }

    private static function register_shutdown_function(ErrorSniffer &$that)
    {
        register_shutdown_function(function() use (&$that) {
                $error = error_get_last();

                if ($error['type'] == E_ERROR) {
                    $type = ErrorSniffer::getErrorName($error['type']);
                    $that->registerError(array('type' => $type, 'message' => $error['message'], 'file' => $error['file'], 'line' => $error['line']));
                }

                $that->printErrors();
        });
    }

As we can see we will catch errors and exceptions, we populate the member variable $errors and we also use register_shutdown_function to show information on shutdown.

You can see the full script at github here.

What do you think?

12 thoughts on “Display errors on screen even with display errors = off with PHP

    1. If your shared hosting allows you to do that, obviously it’s a better solution. But sometimes, the hosting don’t allow you to change default ini variables, or you also can have problem reading filesystem.

      Shared hosting try to disable this kind of features because normally they are the source of administration problem. Files too big and full disk partition for logs, and crappy server behaviour if we change ini variables. Those kind of features normally cames with dedicated servers.

    1. Yes. I’ve changed my mind to PHP5.3 long time ago and sometimes I forget there still are hosting with PHP<5.3, even when php5.2 is no longer supported by PHP 😦 (they are from the past but they exist). Anyway it's easy to change it to use with PHP5.2. I'm using closures because I like them, but it's easy not to use them here.

  1. Hi, interesting solution.

    A question purely about style: why do you create an instance of the class, but then call static functions, then pass them the instance to work with? Why not just work with $this?

    It seems a very roundabout idiom, but I’m aware I could be missing some very good reasons for doing that.

    Also, it took me a while to find the constructor, hidden away at the bottom of the class

    Cheers,
    Dave

    1. Good comment. I really like to discuss about style.

      I use static functions because they don’t depend on the instance of the class. As you see I need to pass the instance of the class as parameter with the ugly trick of $that = $this. With PHP5.4 we won’t need to use this ugly trick whit the new feature “Added closure $this support back”. I need to create an instance to the class to store the $errors private member variable.

      But I must admit I’m not 100% agree with my reasons. Probably it is a bit mess mix static and non static functions. Maybe it’s better not to use static functions here.

      Also, constructor moved to top 😉

    1. The idea of this script is not to place it at production forever. it Is placed it when something really wrong happens and we don’t know why, and we remove it then. If we are thinking in placing forever at production, maybe it would be better to use another technique than check the IP to restring the visibility of the log (a cookie or something similar).

      1. I must say, I may use an inappropriate tool for my case. Our test system is on the same server as our prod system; I know how clunky it sounds, but may it be a result of managerial decision, my own inexperience, the fact that everything must be done “asap”, or a combination of those three factors, both system share the same environment and the same configuration. I like The fact that the tool allows me to do debugging without bleeding any user-unfriendly message on the prod system. It makes it very useful to me, more useful than the error log file at least, which grows too fast for my taste.

        But although I might use it incorrectly, I found a good tool in case something happens in the prod system which result in an unexpected white screen. I’ll try to find a better tool/technique for my current use case, and in the meantime will continue to use ErrorSniffer this way.

      2. The same server for production and test can be a problem, but if you use different virtual hosts for each environment, you can use easily different PHP configurations for each vhosts.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.