Excepciones: Cómo forzar un "backtrace" en un sistema que no usa try/catch

Para los que venimos de haber trabajado, aunque sea académicamente, en otros lenguajes/plataformas como Java o .Net, trabajar con excepciones es un tema de todos los días. Al principio, cuando uno está aprendiendo y surge el primer volcado de una excepción (aparecen en pantalla muchas líneas con la información del error) hay una que resalta sobre todas y es la que demoramos más aprender a interpretar: el "backtrace" o ruta de ejecución desde que se inicia el sistema, todas las invocaciones que van sucediendo, en qué línea salta la ejecución, hasta terminar en el lugar exacto donde falló el sistema.

Recuerdo cuando probé por primera vez PHP5 (una beta) y lo primero que fui a probar fue hacer un try / catch forzando el fallo de una conexión a la base de datos con funciones nativas del lenguaje. Mi sorpresa fue mayúscula al comprender que los try / catch no funcionan a menos que nos aseguremos que la función que estamos usando retorne una excepción, y para colmo, PHP no lo hace por defecto! ;-) Así que no quedó otra que dejar las excepciones para nuestros desarrollos donde todo método de nuestras clases debía tener un throw new Exception('mensaje de error');

A pesar de mi desilusión, esto no era problema para los sistemas que hacíamos de cero de ahora en más, pero... ¿cómo haríamos con los sistemas que ya están funcionando y que no puedes salir a modificar miles de líneas de código para que un try / catch funcione?

Particularmente considero que una de las informaciones más importantes para poder hacer un debug de qué falló es el "backtrace". No es lo mismo ver en el log del sistema donde falló algo que ver quién invocó antes y con qué información para que fallara esa rutina.

Bien, hace un tiempo que lo buscaba y lo encontré por accidente en un foro, así que les comento la forma de uso y cómo lo pueden aplicar a sus sistemas "legacy":


class Debug
{
public static function getBacktrace()
{
$ex = new Exception();
return $ex->getTrace();
}
public static function getBacktrace2String()
{
$ex = new Exception();
$trace = $ex->getTrace();

/* Elimino la primer linea que
siempre es la misma y
hace referencia a la
invocación de esta clase */

array_shift($trace);

$trace_ret = '';
$linea = 0 ;
foreach ($trace as $item){

$linea++;

$trace_ret .=
"(".$linea.")"
."[file: ".$item['file']
.":".$item['line']."]"
."[".$item['class']
.$item['type']
.$item['function']."]"
."[args: "
.implode(',',$item['args'])
."] ";
}
return $trace_ret;
}
}


Luego, a continuación, tenemos una clase Log que lo único que hace es persistir cualquier información del sistema que queramos en un archivo de log, por lo tanto ahora agregamos la ejecución del backtrace y lo registramos en el log tal cual nos llega:


/* Método modificado de la clase Log */

public static function setError()
{
$backtrace = Debug::getBacktrace2String();
self::logToFile('errores.log',$backtrace);
}


Listo, ahora automáticamente tenemos que cada vez que el sistema deba registrar un error, este, tendrá toda la información de backtrace, que se vería en nuestro sistema de la siguiente manera:

2009-10-26 09:40:11 (1)[file: /var/www/class/App.php:81][Log::errores][args: /var/www/class/Prueba.php,75,ERROR GRAVE: no se obtuvo el resultado esperado en el metodo: getProveedor,,0,SELECT * FROM proveedores WHERE estado = 1 AND id = 111 AND idCuenta = 1234] (2)[file: /var/www/public/sys/send.php:48][Prueba->getProveedor][args: 111,1234]

Lo cual si indentamos en base a los (n) que son los saltos que va dando la ejecución,

2009-10-26 09:40:11

(1)[file: /var/www/class/App.php:81][Log::errores][args: /var/www/class/Prueba.php,75,ERROR GRAVE: no se obtuvo el resultado esperado en el metodo: getProveedor,,0,SELECT * FROM proveedores WHERE estado = 1 AND id = "111" AND idCuenta = 1234]

(2)[file: /var/www/public/sys/send.php:48][Prueba->getProveedor][args: 111,1234]

Aquí se pueden ver dos saltos, el (1) es lo que veríamos siempre en nuestro log, la aplicación que propiamente falla, pero en el (2) estamos viendo desde donde realmente se inicio la ejecución que luego terminó fallando.

De todas formas, esto es un "parche", deberíamos usar try/catch en todos nuestros sistemas de ahora en más (si es que ya no lo estás usando), pero una forma de mejorar lo que ya existe es agregar un forzado "backtrace".

Espero que lo prueben en sus sistemas y les sea de utilidad ;-)

3 comentarios:

Anónimo dijo...

porque no http://php.net/manual/en/function.debug-backtrace.php ?

Enrique Place dijo...

Que tal Pichongol ;-)

Mmmm ... será porque no lo encontré antes en el manual? ;-)

Ahora lo estudio y luego te cuento.

Gracias por el aporte! ;-)

Carlos Ayala dijo...

Hola

Muy buen Blog.
Sobre esto, es mucho mejor usar
debug_print_backtrace();

Saludos!

Entradas populares