Skip to content
Advertisement

php fatal error handler… failing with no error_get_last() set

I have been using a fatal_handler() function that I expanded on for over a year now but for some reason I now have random errors popping up in the error_log file on the server and triggering the notification. Previous to a little while ago it was working fine.

The errors seem to come from the array $error which is suppose to be set by error_get_last(). With that, I am even unsure why the error event is firing. Any of my logs that the function creates are empty. It doesn’t give me any specific page causing the error, just the function itself.

The $notice->rMsg saves the notification in a session variable and then displays anything in that session variable the next page load. Sometime I have 10 or 20 of these notifications popping up when I shouldn’t. The page still loads, I am not redirected to the system logs, and I can continue on but the errors still keep coming. I do not know what is wrong but hopefully someone can point me in the right direction to look.

        if(empty($public)) {
            register_shutdown_function( "fatal_handler" );
        }
        elseif($public) {
            register_shutdown_function( "fatal_handler_public" );
        }


        /* Array to correspond error numbers to the text. */
        $log_errors = array(
                "E_ERROR" => 1,
                "E_WARNING" => 2, 
                "E_PARSE" => 4, 
                "E_CORE_ERROR" => 16,
                "E_USER_ERROR" => 256, 
                "E_USER_WARNING" => 512, 
                "E_USER_NOTICE" => 1024);

        /*  1. PHP Fatal Error Handler
        --------------------------
            Handles PHP Fatal errors... hopefully.
            */

        function fatal_handler() {
            global $data,$path,$notice,$user,$log_errors,$db,$admin;
                        
            $errfile = "Unknown File";
            $errstr  = "Shutdown Initiated";
            $errno   = E_CORE_ERROR;
            $errline = 0;
            $error = error_get_last();
            if($error !== NULL) {
                $errno   = $error["type"];    // Line 368
                $errfile = $error["file"];    // Line 369
                $errline = $error["line"];    // Line 370
                $errstr  = $error["message"]; // Line 371
            }
            
            $error["user"] = $user["id"];
            
            if(in_array($errno,$log_errors)) {
                $error["date"] = time();
                if(strpos($errstr,'mysqli') > 0) {
                    $type = "MySQL";
                }
                else {
                    $type = "PHP";
                }
                $admin->record('@C@ encountered a '.$type.' Fatal Error at line '.$errline.' of '.$errfile.'.');
                $log_file = $path["full"]."/system/logs/$type.log";
                if(file_exists($log_file)) {
                }
                else {
                    touch($log_file);
                }
            // another way to call error_log():
                error_log(json_encode($error)."rn", 3, $log_file);
                $_SESSION[$data["license"]]["error"]["shutdown"] = json_encode($error);

                if($_SESSION[$data["license"]]["userid"]==1) {
                    $disabled = "<script>$(document).ready(function() { $(document).on('click','#view-logs', function() { window.location = '/system/logs/'; }); });</script>";
                    $goto = '/system/logs/';
                } else { $goto = '/dashboard/'; $disabled = "<script>$(document).ready(function() { $(document).on('click','#view-logs', function() { alert('Sorry, log files are only accessible by the System Super Admins.'); }); }); </script>"; }
                $notice->rMsg($notice->danger("There was a fatal ".$type." error which shut down the software.  We have logged this error to our logs for review however, we also ask you submit a support request.<br>
                    Details included in the report are the error details, your e-mail and name, the date and time. By submitting a support request this will help us find and fix the error faster.<br><br>
                    <button class='btn btn-lg btn-danger' id='submit-support-request'>Submit Support Request</button> <a class='btn btn-lg btn-default' href='#' id='view-logs'>View System Logs</a>".$disabled,"FATAL ERROR"));
                $protocol = parse_url($_SERVER["REQUEST_URI"], PHP_URL_SCHEME);
                $urlParts = explode('/', str_ireplace($protocol, '', $_SERVER["REQUEST_URI"]));
                $url = '//'.$data["sub-domain"].'.'.$data["domain"].'/'.$urlParts[1].'/'.$urlParts[2].'/';
                //header("Location: ".preg_replace('/[0-9]+/', '', $_SERVER["REQUEST_URI"]));
                //Sent to Dashbord for Fatal Errors.
                    //header("Location: /dashboard/");
                    header("refresh:1;url=".$goto);
            }
        }

LOG FILE

[09-Jan-2022 01:18:22 America/Regina] PHP Notice:  Undefined index: line in /home/public_html/dsm/class/autoload.class.php on line 370
[09-Jan-2022 01:18:22 America/Regina] PHP Stack trace:
[09-Jan-2022 01:18:22 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:18:22 America/Regina] PHP Notice:  Undefined index: message in /home/public_html/dsm/class/autoload.class.php on line 371
[09-Jan-2022 01:18:22 America/Regina] PHP Stack trace:
[09-Jan-2022 01:18:22 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:19:27 America/Regina] PHP Notice:  Undefined index: type in /home/public_html/dsm/class/autoload.class.php on line 368
[09-Jan-2022 01:19:27 America/Regina] PHP Stack trace:
[09-Jan-2022 01:19:27 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:19:27 America/Regina] PHP Notice:  Undefined index: file in /home/public_html/dsm/class/autoload.class.php on line 369
[09-Jan-2022 01:19:27 America/Regina] PHP Stack trace:
[09-Jan-2022 01:19:27 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:19:27 America/Regina] PHP Notice:  Undefined index: line in /home/public_html/dsm/class/autoload.class.php on line 370
[09-Jan-2022 01:19:27 America/Regina] PHP Stack trace:
[09-Jan-2022 01:19:27 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:19:27 America/Regina] PHP Notice:  Undefined index: message in /home/public_html/dsm/class/autoload.class.php on line 371
[09-Jan-2022 01:19:27 America/Regina] PHP Stack trace:
[09-Jan-2022 01:19:27 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:20:22 America/Regina] PHP Notice:  Undefined index: type in /home/public_html/dsm/class/autoload.class.php on line 368
[09-Jan-2022 01:20:22 America/Regina] PHP Stack trace:
[09-Jan-2022 01:20:22 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:20:22 America/Regina] PHP Notice:  Undefined index: file in /home/public_html/dsm/class/autoload.class.php on line 369
[09-Jan-2022 01:20:22 America/Regina] PHP Stack trace:
[09-Jan-2022 01:20:22 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:20:22 America/Regina] PHP Notice:  Undefined index: line in /home/public_html/dsm/class/autoload.class.php on line 370
[09-Jan-2022 01:20:22 America/Regina] PHP Stack trace:
[09-Jan-2022 01:20:22 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:20:22 America/Regina] PHP Notice:  Undefined index: message in /home/public_html/dsm/class/autoload.class.php on line 371
[09-Jan-2022 01:20:22 America/Regina] PHP Stack trace:
[09-Jan-2022 01:20:22 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:21:28 America/Regina] PHP Notice:  Undefined index: type in /home/public_html/dsm/class/autoload.class.php on line 368
[09-Jan-2022 01:21:28 America/Regina] PHP Stack trace:
[09-Jan-2022 01:21:28 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:21:28 America/Regina] PHP Notice:  Undefined index: file in /home/public_html/dsm/class/autoload.class.php on line 369
[09-Jan-2022 01:21:28 America/Regina] PHP Stack trace:
[09-Jan-2022 01:21:28 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:21:28 America/Regina] PHP Notice:  Undefined index: line in /home/public_html/dsm/class/autoload.class.php on line 370
[09-Jan-2022 01:21:28 America/Regina] PHP Stack trace:
[09-Jan-2022 01:21:28 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0
[09-Jan-2022 01:21:28 America/Regina] PHP Notice:  Undefined index: message in /home/public_html/dsm/class/autoload.class.php on line 371
[09-Jan-2022 01:21:28 America/Regina] PHP Stack trace:
[09-Jan-2022 01:21:28 America/Regina] PHP   1. fatal_handler() /home/public_html/dsm/class/autoload.class.php:0

UPDATE: January 9, 2022 11:40am

It appears the script below is causing the “error” and the event to fire. This is for an administration system which if the user is not active it places a lock screen after so many minutes requiring a password again. This script keeps the session active so the lock screen continues to work for a couple hours instead of loosing the session and requiring a full login again. If the browser resets the session it will redirect back to the main login screen.

This is called via ajax every couple minutes or so; which is why when I reload the page I get multiple error notices produced by the fatal_handler(). However, I don’t know why php is registering a shutdown as the error_get_last() is still blank.

/* Session Expire */
include("../class/autoload.class.php");

if(isset($_SESSION[$data["license"]]["userid"])) {
    $_SESSION[$data["license"]]["userid"] = $_SESSION[$data["license"]]["userid"];
    $_SESSION[$data["license"]]["session-date"] = date("F j, Y g:i:s A");
    echo "Session Refresh:n--------------------nUser ID: ".$_SESSION[$data["license"]]["userid"]."nSession Time: ".$_SESSION[$data["license"]]["session-date"];
}
else {
    echo 'redirect';
}

Advertisement

Answer

As the name suggests, register_shutdown_function registers a handler to run every time PHP “shuts down” – that is, at the end of every request, regardless of whether it’s ending “normally” or after an error.

It’s therefore up to you, inside the handler function, to detect whether the shutdown was due to a fatal error or not. The common trick with this is to look at the last “error”, which might better be termed “diagnostic”, because it includes Warnings, Notices, and Deprecation messages. If there was a fatal error, we can expect it to be the last thing that happened before the shutdown handler is called, so if we immediately call error_get_last, we should get the information about it.

Let’s trace through some key parts of your function:

// if nothing else sets $errno, assume that every request ends in a fatal error
// that's probably not a sensible assumption (unless your code is really bad ????)
$errno = E_CORE_ERROR; 
// ...
// retrieve error information - will be NULL if no diagnostic has occurred
$error = error_get_last();
// add a 'user' key to the error information
// this doesn't check if $error was NULL, so if it was, it will turn it into an array
$error["user"] = $user["id"];
// now check if $error is NULL - but it never will be, because of the line above
if($error !== NULL) {
    // take the error type from the array
    // if it was actually NULL, this won't be set, so we'll get an $errno of NULL
    // that's what's causing all the Notices in your logs
    $errno   = $error["type"];
    // ...
}
// after gathering the error information, decide whether to log it
if(in_array($errno,$log_errors)) {

Luckily, the default of E_CORE_ERROR and the broken check for NULL cancel each other out, so that if there was no error returned from error_get_last(), we are checking in_array(NULL,$log_errors), which will be false.

However, looking at $log_errors, which is defined as a global variable earlier:

$log_errors = array(
                "E_ERROR" => 1,
                "E_WARNING" => 2, 
                "E_PARSE" => 4, 
                "E_CORE_ERROR" => 16,
                "E_USER_ERROR" => 256, 
                "E_USER_WARNING" => 512, 
                "E_USER_NOTICE" => 1024);

Apart from the slightly odd decision to hard-code the numbers rather than use the actual constants, note that this includes both Warnings and Notices. These can happen at any time, and there might have been a dozen Notices already logged on the page, but error_get_last() will just contain the most recent if no other diagnostics happened afterwards. It’s therefore not relevant to look for these inside the shutdown handler – you just want to spot things that would cause a shutdown.

A Notice or Warning will not cause a script to exit early, so it makes no sense to log them in a shutdown handler, which will only see whichever happened to come last. They will already have been logged based on your error_log settings, or you can use set_error_handler to have custom handling of all of them.

We can massively simplify the function by checking for fatal errors (and only fatal errors) straight away, and just ending the function if none are found:

function fatal_handler() {
    $fatal_errors = [
        E_ERROR,
        E_USER_ERROR,
        E_CORE_ERROR,
        E_COMPILE_ERROR,
        E_PARSE,
        E_RECOVERABLE_ERROR
    ];
    $error = error_get_last();
    if($error === NULL || !in_array($error['type'], $fatal_errors)) {
       // Clean shutdown. Nothing to do.
       return;
    }
    // Fatal error detected - proceed with logging based on value of $error
    // ...
}
User contributions licensed under: CC BY-SA
7 People found this is helpful
Advertisement