How can I get PHP to produce a backtrace upon errors?
Asked Answered
C

12

55

Trying to debug PHP using its default current-line-only error messages is horrible.

How can I get PHP to produce a backtrace (stack trace) when errors are produced?

Cohby answered 21/7, 2009 at 13:34 Comment(0)
D
50

Xdebug prints a backtrace table on errors, and you don't have to write any PHP code to implement it.

Downside is you have to install it as a PHP extension.

Dissimilation answered 21/7, 2009 at 13:50 Comment(4)
I definitly recommend Xdebug too : I wouldn't imagine developping in PHP without it ! Just note that you should absolutly not install it on a production server : it's kinda hurting performances (and you don't, in theory, need that kind of information in a production server)Accelerando
For MAMP: netbeans.org/kb/docs/php/…Leah
@PascalMARTIN Furthermore, it's a major security hole for it may reveal values of internal variables to the whole world – such as passwords, adresses, credit card infos ...Heindrick
sudo apt-get install php5-xdebug && sudo service php5-fpm restart worked well for me. Thanks!Tribunate
C
50

My script for installing an error handler that produces a backtrace:

<?php
function process_error_backtrace($errno, $errstr, $errfile, $errline, $errcontext) {
    if(!(error_reporting() & $errno))
        return;
    switch($errno) {
    case E_WARNING      :
    case E_USER_WARNING :
    case E_STRICT       :
    case E_NOTICE       :
    case E_USER_NOTICE  :
        $type = 'warning';
        $fatal = false;
        break;
    default             :
        $type = 'fatal error';
        $fatal = true;
        break;
    }
    $trace = array_reverse(debug_backtrace());
    array_pop($trace);
    if(php_sapi_name() == 'cli') {
        echo 'Backtrace from ' . $type . ' \'' . $errstr . '\' at ' . $errfile . ' ' . $errline . ':' . "\n";
        foreach($trace as $item)
            echo '  ' . (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()' . "\n";
    } else {
        echo '<p class="error_backtrace">' . "\n";
        echo '  Backtrace from ' . $type . ' \'' . $errstr . '\' at ' . $errfile . ' ' . $errline . ':' . "\n";
        echo '  <ol>' . "\n";
        foreach($trace as $item)
            echo '    <li>' . (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()</li>' . "\n";
        echo '  </ol>' . "\n";
        echo '</p>' . "\n";
    }
    if(ini_get('log_errors')) {
        $items = array();
        foreach($trace as $item)
            $items[] = (isset($item['file']) ? $item['file'] : '<unknown file>') . ' ' . (isset($item['line']) ? $item['line'] : '<unknown line>') . ' calling ' . $item['function'] . '()';
        $message = 'Backtrace from ' . $type . ' \'' . $errstr . '\' at ' . $errfile . ' ' . $errline . ': ' . join(' | ', $items);
        error_log($message);
    }
    if($fatal)
        exit(1);
}

set_error_handler('process_error_backtrace');
?>

Caveat: it is powerless to affect various 'PHP Fatal Errors', since Zend in their wisdom decided that these would ignore set_error_handler(). So you still get useless final-location-only errors with those.

Cohby answered 21/7, 2009 at 13:37 Comment(2)
And since debug_backtrace doesn't go beyond shutdown function (stack has been popped already) you may be interested in knowing you can still access globally scoped variables in shutdown functions. So find the line a E_ERROR happened at and set a globally scoped variable equal to the contents of debug_backtrace on the previous line. Viola, you've got a full backtrace starting at the line before your fatal error which is accessible from a register_shutdown_function defined function !Livelong
<unknown file> should be &lt;unknown file&gt; otherwise it will be treated as tag and debug_backtrace() just work for the scope you are in(which is process_error_backtrace)Angelangela
D
50

Xdebug prints a backtrace table on errors, and you don't have to write any PHP code to implement it.

Downside is you have to install it as a PHP extension.

Dissimilation answered 21/7, 2009 at 13:50 Comment(4)
I definitly recommend Xdebug too : I wouldn't imagine developping in PHP without it ! Just note that you should absolutly not install it on a production server : it's kinda hurting performances (and you don't, in theory, need that kind of information in a production server)Accelerando
For MAMP: netbeans.org/kb/docs/php/…Leah
@PascalMARTIN Furthermore, it's a major security hole for it may reveal values of internal variables to the whole world – such as passwords, adresses, credit card infos ...Heindrick
sudo apt-get install php5-xdebug && sudo service php5-fpm restart worked well for me. Thanks!Tribunate
I
28

PHP Error

This is better error reporting for PHP written in PHP. No extra extensions are required!

It is trivial to use where all errors are displayed in the browser for normal, AJAXy requests (in paused state). Then all errors provides you with a backtrace and code context across the whole stack trace, including function arguments, server variables.

All you need to do is to include one single file and call the function (at the beginning on your code), e.g.

require('php_error.php');
\php_error\reportErrors();

See the screenshots:

PHP Error | Improve Error Reporting for PHP - screenshot of backtrace PHP Error | Improve Error Reporting for PHP - screenshot of backtrace PHP Error | Improve Error Reporting for PHP - screenshot of backtrace

GitHub: https://github.com/JosephLenton/PHP-Error

My fork (with extra fixes): https://github.com/kenorb-contrib/PHP-Error

Debug PHP class

A complete PHP debugger class, with support for Exception, Errors, Alerts ( from user), code lines and highlight flags.

Example usage:

 <?php
        include( dirname(dirname(__FILE__))  . '/src/Debug.php' );
        //Catch all
        Debug::register();

        //Generate an errors
        if( this_function_does_not_exists( ) )
        {
            return false;
        }
    ?>

Error Handling in PHP

The example below shows the handling of internal exceptions by triggering errors and handling them with a user defined function:

Shorter way (PHP):

<?php
function e($number, $msg, $file, $line, $vars) {
   print_r(debug_backtrace());
   die();
}
set_error_handler('e');

Longer way (PHP):

// set to the user defined error handler
$old_error_handler = set_error_handler("myErrorHandler");

// error handler function
function myErrorHandler($errno, $errstr, $errfile, $errline)
{
    if (!(error_reporting() & $errno)) {
        // This error code is not included in error_reporting
        return;
    }

    switch ($errno) {
    case E_USER_ERROR:
        echo "<b>My ERROR</b> [$errno] $errstr<br />\n";
        echo "  Fatal error on line $errline in file $errfile";
        echo ", PHP " . PHP_VERSION . " (" . PHP_OS . ")<br />\n";
        echo "Aborting...<br />\n";
        var_dump(debug_backtrace());
        exit(1);
        break;

    case E_USER_WARNING:
        echo "<b>My WARNING</b> [$errno] $errstr<br />\n";
        break;

    case E_USER_NOTICE:
        echo "<b>My NOTICE</b> [$errno] $errstr<br />\n";
        break;

    default:
        echo "Unknown error type: [$errno] $errstr<br />\n";
        break;
    }

    /* Don't execute PHP internal error handler */
    return true;
}

See: http://www.php.net/manual/en/function.set-error-handler.php

Note: You can only have one error exception at a time. When you call the set_error_handler() function it will return the name of the old error handler. You can store this and call it yourself from your error handler – thus allowing you to have multiple error handlers.


XDebug

For more advanced solution, you can use XDebug extension for PHP.

By default when XDebug is loaded, it should show you automatically the backtrace in case of any fatal error. Or you trace into file (xdebug.auto_trace) to have a very big backtrace of the whole request or do the profiling (xdebug.profiler_enable) or other settings. If the trace file is too big, you can use xdebug_start_trace() and xdebug_stop_trace() to dump the partial trace.

Installation

Using PECL:

pecl install xdebug

On Linux:

sudo apt-get install php5-xdebug

On Mac (with Homebrew):

brew tap josegonzalez/php
brew search xdebug
php53-xdebug

Example of mine configuration:

[xdebug]

; Extensions
extension=xdebug.so
; zend_extension="/YOUR_PATH/php/extensions/no-debug-non-zts-20090626/xdebug.so"
; zend_extension="/Applications/MAMP/bin/php/php5.3.20/lib/php/extensions/no-debug-non-zts-20090626/xdebug.so" ; MAMP

; Data
xdebug.show_exception_trace=1       ; bool: Show a stack trace whenever an exception is raised.
xdebug.collect_vars = 1             ; bool: Gather information about which variables are used in a certain scope.
xdebug.show_local_vars=1            ; int: Generate stack dumps in error situations.
xdebug.collect_assignments=1        ; bool: Controls whether Xdebug should add variable assignments to function traces.
xdebug.collect_params=4             ; int1-4: Collect the parameters passed to functions when a function call is recorded.
xdebug.collect_return=1             ; bool: Write the return value of function calls to the trace files.
xdebug.var_display_max_children=256 ; int: Amount of array children and object's properties are shown.
xdebug.var_display_max_data=1024    ; int: Max string length that is shown when variables are displayed.
xdebug.var_display_max_depth=3      ; int: How many nested levels of array/object elements are displayed.
xdebug.show_mem_delta=0             ; int: Show the difference in memory usage between function calls.

; Trace
xdebug.auto_trace=0                 ; bool: The tracing of function calls will be enabled just before the script is run.
xdebug.trace_output_dir="/var/log/xdebug" ; string: Directory where the tracing files will be written to.
xdebug.trace_output_name="%H%R-%s-%t"     ; string: Name of the file that is used to dump traces into.

; Profiler
xdebug.profiler_enable=0            ; bool: Profiler which creates files read by KCacheGrind.
xdebug.profiler_output_dir="/var/log/xdebug"  ; string: Directory where the profiler output will be written to.
xdebug.profiler_output_name="%H%R-%s-%t"      ; string: Name of the file that is used to dump traces into.
xdebug.profiler_append=0            ; bool: Files will not be overwritten when a new request would map to the same file.

; CLI
xdebug.cli_color=1                  ; bool: Color var_dumps and stack traces output when in CLI mode.

; Remote debugging
xdebug.remote_enable=off            ; bool: Try to contact a debug client which is listening on the host and port.
xdebug.remote_autostart=off         ; bool: Start a remote debugging session even GET/POST/COOKIE variable is not present.
xdebug.remote_handler=dbgp          ; select: php3/gdb/dbgp: The DBGp protocol is the only supported protocol.
xdebug.remote_host=localhost        ; string: Host/ip where the debug client is running.
xdebug.remote_port=9000             ; integer: The port to which Xdebug tries to connect on the remote host.
xdebug.remote_mode=req              ; select(req,jit): Selects when a debug connection is initiated.
xdebug.idekey="xdebug-cli"          ; string: IDE Key Xdebug which should pass on to the DBGp debugger handler.
xdebug.remote_log="/var/log/xdebug.log" ; string: Filename to a file to which all remote debugger communications are logged.

Drupal 6&7

With Devel enabled:

/**
 * Implements hook_watchdog().
 */
function foo_watchdog($log_entry) {
  if ($log_entry['type'] == 'php' && $log_entry['severity'] <= WATCHDOG_WARNING) {
    function_exists('dd') && dd(debug_backtrace());
  }
}

Above function will log the backtraces on each error into temporary file (/tmp/drupal_debug.txt by default).

Or locate the file via: drush eval "echo file_directory_temp() . '/drupal_debug.txt'.

Without Devel enabled, use old school approach: var_dump(debug_backtrace()); instead of dd().

Impracticable answered 8/8, 2013 at 9:2 Comment(2)
xDebug is great, but it's not always available. Ioncube, for example, which is used in a lot of large php applications is not compatible with xDebug (I ran into this problem just last week working on a Magento site). I would also like to mention that Ioncube is crap. This is why we need to know how to manually backtrace too.Gerstner
Then you can use the standard way of error handler functions (I've updated the answer). Or old school var_dump(debug_backtrace());exit; on line before the error (and view the source in your web browser for better formatting). Other debug tools could include strace, dtrace, etc. With the right usage, you can find the source of the problem.Impracticable
G
9

I just tried setting a session variable containing the contents of debug_backtrace() at the offending line, then printing it using register_shutdown_function(). Worked like a charm.

Goffer answered 22/10, 2012 at 20:6 Comment(0)
C
3

You can use debug_backtrace

Concepcion answered 21/7, 2009 at 13:37 Comment(0)
N
2

As php debug extensions, there is Xdebug and PHP DBG. Each one has its advantages and disadvantages.

Nutritive answered 21/7, 2009 at 14:12 Comment(0)
C
2

This is how you do it:

set_error_handler(function($errorType){
    if(error_reporting() & $errorType){
        ?><pre><?
        debug_print_backtrace();
        ?></pre><?
    }
}) ;

It requires PHP 5.3+ since it uses a closure. If you need lower PHP support just convert the closure to a normal function.

Cotquean answered 15/6, 2013 at 23:9 Comment(4)
stop using short tags pleaseBrieta
@happy_marmoset, why? because you don't like them?Cotquean
because they will be removed in future versions of PHP, because they are incompatible with PSR-1. but notice that short-echo tags <?= ?> are allowedBrieta
@happy_marmoset, deprecating them is an unfortunate decision. They should've made them enabled by default. I'll keep using them as long as I can because they are handier.Cotquean
B
1
$backtrace = debug_backtrace();

i wrote a little article about backtracing a while back

Bagger answered 21/7, 2009 at 13:37 Comment(0)
F
1

set_error_handler() + debug_backtrace() + debug_print_backtrace() in PHP5

Fruitful answered 5/5, 2010 at 10:21 Comment(0)
L
1

If you can't install a debugger then use this function sorrounding the fatal error to get the "fatal stack". Check the code and example below that explains better how to use it:

// Give an extra parameter to the filename
// to save multiple log files
function _fatalog_($extra = false)
{
    static $last_extra;

    // CHANGE THIS TO: A writeable filepath in your system...
    $filepath = '/var/www/html/sites/default/files/fatal-'.($extra === false ? $last_extra : $extra).'.log';

    if ($extra===false) {
        unlink($filepath);
    } else {
        // we write a log file with the debug info
        file_put_contents($filepath, json_encode(debug_backtrace()));
        // saving last extra parameter for future unlink... if possible...
        $last_extra = $extra;
    }
}

Here's an example of how to use it:

// A function which will produce a fatal error
function fatal_example()
{
    _fatalog_(time()); // writing the log
    $some_fatal_code = array()/3; // fatality!
    _fatalog_(); // if we get here then delete last file log
}

Finally to read the contents of the log...

var_dump(json_decode(file_get_contents('/path/to-the-fatal.log')));

Hope that helps!

Leadwort answered 28/9, 2016 at 16:11 Comment(0)
B
1

You can use (new \Exception())->getTraceAsString() instead of debug_backtrace():

set_error_handler(function(int $severity, string $message, string $file, int $line) {
    if (!(error_reporting() & $severity)) {
        return false;
    }

    $severities = [
        E_ERROR => 'ERROR',
        E_WARNING => 'WARNING',
        E_PARSE => 'PARSE',
        E_NOTICE => 'NOTICE',
        E_CORE_ERROR => 'CORE_ERROR',
        E_CORE_WARNING => 'CORE_WARNING',
        E_COMPILE_ERROR => 'COMPILE_ERROR',
        E_COMPILE_WARNING => 'COMPILE_WARNING',
        E_USER_ERROR => 'USER_ERROR',
        E_USER_WARNING => 'USER_WARNING',
        E_USER_NOTICE => 'USER_NOTICE',
        E_STRICT => 'STRICT',
        E_RECOVERABLE_ERROR => 'RECOVERABLE_ERROR',
        E_DEPRECATED => 'DEPRECATED',
        E_USER_DEPRECATED => 'USER_DEPRECATED',
    ];

    $e = new \Exception;

    // Just remove the current point from the trace
    $reflection = new \ReflectionProperty(get_class($e), 'trace');
    $reflection->setAccessible(true);
    $trace = $reflection->getValue($e);
    array_shift($trace);
    $reflection->setValue($e, $trace);

    $text = '';
    $text .= ($severities[$severity] ?? $severity) . ': ';
    $text .= "$message in $file($line)\n";
    $text .= "Stack trace:\n";
    $text .= $e->getTraceAsString();

    error_log($text);

    if (in_array($severity, [
        E_CORE_ERROR,
        E_ERROR,
        E_RECOVERABLE_ERROR,
        E_PARSE,
        E_COMPILE_ERROR,
        E_USER_ERROR,
    ], true)) {
        http_response_code(500);
        exit(255);
    }

    return true;
});

Output:

[16-Feb-2022 00:01:09 UTC] DEPRECATED: trim(): Passing null to parameter #1 ($string) of type string is deprecated in /home/user/project/doSomething.php(3)
Stack trace:
#0 /home/user/project/doSomething.php(3): trim(NULL)
#1 /home/user/project/index.php(4): doSomething()
#2 {main}
Bosporus answered 15/2, 2022 at 20:14 Comment(0)
T
0

PHP DeBugger also does a back trace similiar to PHP Error with more options.
If you want you can easily make your own with set_error_handler and debug_backtrace

set_error_handler ($error_handler, error_reporting);
/**
 * @var int $errno the error number
 * @var string $errstr the error message
 * @var string $errfile the error file
 * @var int $errline the line of the error
 */
$error_handler = function($errno, $errstr, $errfile, $errline){
    $trace = debug_backtrace();
    array_shift($backtrace);//remove the stack about this handler
    foreach($trace as $k => $v){
        //parse your backtrace
    }
}

Also note that for internal stacks in the backtrace some of the keys will not be set. Be sure to check if the key exist before you do something with it if you have all errors on :)

Toitoiboid answered 3/11, 2012 at 21:0 Comment(1)
set_error_handler doesn't handle can handle E_WARNING but not E_ERROR so you have to do something else for fatal errors register_shutdown_function isn't a bad choice.Livelong

© 2022 - 2024 — McMap. All rights reserved.