Programming Tips #9 “debug_backtrace”

If you're new here, you may want to subscribe to my RSS feed. Thanks for visiting!

programmer

Next up on my programming tips series we have another guest post. This one is written by Larry Garfield who blogs over at garfieldtech.

Despite the proliferation of debugging tools, especially real-time debuggers, “debugging by print statement” remains the most common form of debugging in most programming languages. It’s especially important when you run into a fatal error on a production server that you can’t use a real-time debugger on, display_errors is Off, and you can’t take the server down to test something (which is the only place that the most critical bugs appear, according to Murphy). Of course, even if you can hide debug statements throughout the code without the user knowing you still need to know which is which in order to trace code execution. (You wouldn’t believe the number of times I’ve had to do that to deal with weirdly configured servers.)

PHP offers __FILE__ and __LINE__ magic constants to show where a given line is executed, and you can always wrap a print statement in a “display:none” div, but that’s a lot of extra code to write or copy to everywhere you just want to know the status of a variable. Many frameworks offer their own debugging framework, but those don’t help if you’re not using that framework. Centralizing that code breaks the __FILE__ and __LINE__ constants. Now what?

Fortunately, PHP offers another debugging tool: debug_backtrace(). It returns an array that is a complete function stack up to the point it is called. The structure is a bit odd and verbose to work with at times, but for our needs we only need very specific values. After writing a series of one-off debug routines, I’ve finally settled on the following:

 
function debug($msg, $label = 'DEBUG', $stealth = FALSE) {
 
 if (defined('DEBUG') && DEBUG) {
   if (is_bool($msg)) {
      $msg = $msg ? 'TRUE' : 'FALSE';
   }
   $display = $stealth ? ' style="display: none;"' : '';
   $backtrace = debug_backtrace();
   $debug = array();
   $stack = (isset($backtrace[1]['class']) ? "{$backtrace[1]['class']}::" : '')
   	. (isset($backtrace[1]['function']) ? "{$backtrace[1]['function']}" : '');
   if ($stack) {
      $debug[] = $stack;
   }
   $debug[] = "Line {$backtrace[0]['line']} of {$backtrace[0]['file']}";
   $debug = implode('<br />', $debug);
   print "<pre{$display}>{$label}: {$debug}:<br />".print_r($msg, 1)."

\n”;
}
}

With that, we can easily get complete debug information from anywhere. The output will show the function or class/method in which the debug() statement was called, as well as the file and line number where it was called. We can also label each output independently if we want to, and hide it from users viewing the page so that we can debug a production site without taking it off line.

The print_r($msg, 1) returns, rather than prints, the structure of the variable we’re debugging, and wrapping it in [pre] tags makes it display nicely on screen. Finally, it even has a global killswitch: There must be a constant named DEBUG that evaluates to true or else the function is ignored entirely. And just to be pedantic, it’s all E_NOTICE and E_STRICT friendly to boot.

It’s no XDebug, but when XDebug is not available it can be the fastest way to track down a weird problem in a production configuration. debug_backtrace() has other uses as well. Those are left as an exercise for the reader.

-

If you have any programming tips that you would like to share with my readers please send them in via my contact form. I will be picking a ‘best tip’ which will be rewarded with a license for Zend Studio Professional.

There Are 8 Responses So Far. »

  1. I do something similar; I print out comment markers with debug_print_backtrace() in between, and use view-source to get at it. The output is verbose, but it’s easy to produce.

  2. Nick Halstead’s Blog: Programming Tips #9 “debug_backtrace”…

    Nick Halstead has posted the latest programming tip to his blog ……

  3. […] Halstead has posted the latest programming tip to his blog today (as written by LArry Garfield) concerning the use of debug_backtrace. Despite […]

  4. A couple of small changes to that will allow you to either use print_r or var_dump (so you can see proper booleans).
    This one also uses a fieldset and legend so it’s easy to see the distinction from backtrace and what you are trying to dump out.

    define(’DEBUG’,true);
    function debug($msg, $func = ‘var_dump’, $label = ‘DEBUG’, $stealth = FALSE) {
    ob_start();
    if (defined(’DEBUG’) && DEBUG) {
    if (is_bool($msg)) {
    $msg = $msg ? ‘TRUE’ : ‘FALSE’;
    }
    $display = $stealth ? ‘ style=”display: none;”‘ : ”;
    $backtrace = debug_backtrace();
    $debug = array();
    $stack = (isset($backtrace[1][’class’]) ? “{$backtrace[1][’class’]}::” : ”)
    . (isset($backtrace[1][’function’]) ? “{$backtrace[1][’function’]}” : ”);
    if ($stack) {
    $debug[] = $stack;
    }
    $debug[] = “Line {$backtrace[0][’line’]} of {$backtrace[0][’file’]}”;
    $debug = implode(”, $debug);
    print “{$label}: {$debug}”;
    call_user_func($func,$msg);
    print “\n”;
    $output = ob_get_contents();
    ob_end_clean();
    print $output;
    }
    }

  5. – oops, now with code tags.. sorry –

    A couple of small changes to that will allow you to either use print_r or var_dump (so you can see proper booleans).
    This one also uses a fieldset and legend so it’s easy to see the distinction from backtrace and what you are trying to dump out.


    define('DEBUG',true);
    function debug($msg, $func = 'var_dump', $label = 'DEBUG', $stealth = FALSE) {
    ob_start();
    if (defined('DEBUG') && DEBUG) {
    if (is_bool($msg)) {
    $msg = $msg ? 'TRUE' : 'FALSE';
    }
    $display = $stealth ? ' style="display: none;"' : '';
    $backtrace = debug_backtrace();
    $debug = array();
    $stack = (isset($backtrace[1]['class']) ? "{$backtrace[1]['class']}::" : '')
    . (isset($backtrace[1]['function']) ? "{$backtrace[1]['function']}" : '');
    if ($stack) {
    $debug[] = $stack;
    }
    $debug[] = "Line {$backtrace[0]['line']} of {$backtrace[0]['file']}";
    $debug = implode('', $debug);
    print "{$label}: {$debug}";
    call_user_func($func,$msg);
    print "\n";
    $output = ob_get_contents();
    ob_end_clean();
    print $output;
    }
    }

  6. […] Halstead has posted the latest programming tip to his blog today (as written by LArry Garfield) concerning the use of debug_backtrace. Despite […]

  7. I do the following :

    1) Log to file - so it doesn’t matter whether you’ve removed all your debug calls before deployment.

    2) If the input isn’t a string, print_r($x, true) it.

    3) Have info($msg), error($msg) and debug($msg) for appropriate use.

    4) Print a time/date stamp with the message… so if you can link up error reports from users with e.g. the error report that’s been logged to file.

    5) Sometimes it’s useful to have some sort of unique ID per user, so you can filter log messages for a given user at a later date.

    6) Use the debug_backtrace() functionality to put a file name & line number with a call. I was very happy when I ‘discovered’ this function months ago, as before I had to keep littering my code with __FILE__ & __LINE__ which is ugly.

    I know I’m merging maintenance/monitoring with development somewhat.

    I find using e.g. $log->debug($msg) is too much to type, so by writing a local function called e.g. debug($msg) makes life easier (and allows me to switch logging framework at a later date, should I so wish).

    David

  8. debug_backtrace() can be a handy function to use in a custom error handler set via set_error_handler(). You can use it to automatically log a stack trace whenever certain errors are triggered in your PHP. Log the date/time of the error too, and you always have something to go back and look at when an error occurs.

Post a Response