PHP json_encode a debug_backtrace() with resource types
Question
Currently, I have a logger which logs errors together with a backtrace.
The logger serializes the backtrace to JSON via json_encode()
.
Let's look at some hypothetical code...
<?php
error_reporting(-1); // show all errors
function test($b){
echo json_encode(debug_backtrace()); // take a backtrace snapshot
}
$c = imagecreate(50,50); // create a resource...
test($c); // ...and pass to function
?>
If you run the code above, we will see something like:
Warning: json_encode() [function.json-encode]: type is unsupported, encoded as null in /code/ch6gVw on line 5 [{"file":"/code/ch6gVw","line":8,"function":"test","args":[null]}]
We can notice two things going on here:
- The logger itself is causing a warning! Bad bad bad!
- The logged data tells us we passed a null to the function?!?!
So, my proposed solution is something like:
foreach($trace as $i=>$v)
if(is_resource($v))
$trace[$i] = (string)$v.' ('.get_resource_type($v).')';
The result would look like Resource id #1 (gd)
This, however, may cause some grave issues.
- We need to somehow track which arrays we looped through so as to avoid ending up in infinite loops with arrays referencing themselves (
$GLOBALS
tend to cause this mess). - We would also have to convert resources of object properties, but objects, unlike arrays, are not a copy of the original thing, hence changing the property changes the live object. On the other hand, how safe is it to
clone()
the object? - Won't such a loop severely slow down the server (backtraces tend to be large, no)?
Solution
I ended up with the following function:
function clean_trace($branch){
if(is_object($branch)){
// object
$props = array();
$branch = clone($branch); // doesn't clone cause some issues?
foreach($props as $k=>$v)
$branch->$k = clean_trace($v);
}elseif(is_array($branch)){
// array
foreach($branch as $k=>$v)
$branch[$k] = clean_trace($v);
}elseif(is_resource($branch)){
// resource
$branch = (string)$branch.' ('.get_resource_type($branch).')';
}elseif(is_string($branch)){
// string (ensure it is UTF-8, see: https://bugs.php.net/bug.php?id=47130)
$branch = utf8_encode($branch);
}
// other (hopefully serializable) stuff
return $branch;
}
You can see it in action here. However, I'm not convinced:
- It is quite slow (iterating over lots of data)
- It is quite memory intensive (data needs to be copied to not mess the original)
- It is not safe in case where arrays/objects reference themselves
- Example:
$a = array(); $a['ref'] = &$a;
(PHP does this to some internal variables)
- Example:
- I'm concerned that cloning objects may have some serious side-effects (consider the magic method
__clone()
, an invitation to wreck havoc).
OTHER TIPS
So you are trying to store the backtrace as a data structure that can be used to pretty-print the results later on?
If that isn't needed I'd just store $result = print_r(debug_backtrace(), true)
and be done with it.
If not my first shot would be something like:
<?php
error_reporting(-1);
function test($b){
echo json_encode(clean(debug_backtrace()));
}
$c = fopen("/tmp/foo", "w");
test($c);
function clean($trace) {
array_walk_recursive($trace, function(&$element) {
if(is_object(&$element)) {
// work around unrealizable elements and preserve typing
$element = array(get_class($element), (object)$element);
} else if(is_resource($element)) {
$element = get_resource_type($element) . '#' .(int)$element;
}
});
return $trace;
}
It's just a rough sketch but I'm not aware of any project that stores backtracks for later inspection in a non textual or already processed format and looking around the mature frameworks didn't bring anything up