Mostrar errores ocultos en PHP

En numerosas ocasiones necesitamos depurar errores de scripts PHP que corren en un servidor con la visualización de errores deshabilitada, lo que nos impide ver qué está fallando en pantalla. Esto, recomendable en producción, se vuelve un problema cuando el error no se repite en el entorno de desarrollo. Aquí tienes cómo mostrar los errores solo donde los necesitas, cómo registrarlos sin exponerlos y cómo llevar la depuración un paso más allá.

· · ·

01 El problema: errores ocultos en producción

Por defecto, muchos servidores y paneles traen display_errors = Off y error_reporting limitado. Es lo correcto para producción: un error fatal no debería mostrar el path, el SQL ni la traza al visitante (fuga de información). Pero cuando el fallo solo aparece en el servidor, necesitamos activar la visualización temporalmente, idealmente en una sola página y no en todo el sitio.

Regla de oro

En producción nunca muestres el error completo en pantalla; regístralo en un log. Mostrar la traza al usuario final es una puerta abierta a filtrar rutas, credenciales o consultas. Usa la técnica de esta página solo para depurar, y desactívala al terminar.

· · ·

02 Mostrar errores solo en la página a depurar

Para activar la visualización de errores únicamente en un script, usamos ini_set() (cambia un parámetro del servidor solo para la ejecución en curso, no se mantiene para las siguientes peticiones) y error_reporting() para elegir el nivel. Dos líneas al comienzo de la página (después de <?php) bastan:

<?php
error_reporting(E_ALL & ~E_NOTICE);   // todos los errores salvo los avisos poco relevantes
ini_set("display_errors", 1);          // mostrar errores en pantalla
?>

Si además quieres forzar el reporte aunque el código lo cambie después, puedes añadir ini_set('error_reporting', E_ALL). Para solo consultar un valor actual (sin cambiarlo): ini_get("display_errors") o ini_get("error_reporting").

· · ·

03 Niveles de error en PHP

El parámetro de error_reporting() es un suma binaria de los niveles que quieres ver. Estos son los más relevantes (los combinas con |):

Valor Etiqueta Qué significa
1 E_ERROR Error fatal en tiempo de ejecución; no se puede recuperar, la ejecución se interrumpe.
2 E_WARNING Advertencia (no fatal); la ejecución continúa.
4 E_PARSE Error de análisis en tiempo de compilación.
8 E_NOTICE Aviso (posible error, pero puede ser normal en la ejecución).
256 E_USER_ERROR Error generado por el código con trigger_error().
2048 E_STRICT Desaconseja cambios para compatibilidad con futuras versiones.
4096 E_RECOVERABLE_ERROR Error capturable; si nadie lo captura, la app se aborta.
8192 E_DEPRECATED Aviso de código que no funcionará en futuras versiones.
30719 E_ALL Todos los errores y advertencias soportados (salvo E_STRICT).

Combinaciones típicas: error_reporting(E_ALL) (todo, para desarrollo), error_reporting(E_ALL & ~E_NOTICE & ~E_DEPRECATED) (todo menos avisos ruidosos).

· · ·

04 Registrar en log en lugar de mostrar en pantalla

Para producción, lo correcto es escribir los errores en un fichero y no mostrarlos:

ini_set("display_errors", 0);   // nunca en pantalla en producción
ini_set("log_errors",       1);   // activa el registro
ini_set("error_log",        "/var/log/php_errors.log"); // dónde escribirlo

También puedes escribir un mensaje puntual con la función error_log(), muy útil para «marcar» un punto concreto del código:

error_log("Llegué aquí, valor de \$x = " . var_export($x, true));

Para verlo en directo mientras desarrollas: tail -f /var/log/php_errors.log.

· · ·

05 Gestor de errores propio y excepciones

Con set_error_handler() puedes capturar los errores (incluidos avisos) y decidir qué hacer con ellos. Y para errores que lanzas desde tu código, captúralos con try/catch, capturando \Throwable (abarca Error y Exception):

set_error_handler(function ($severity, $message, $file, $line) {
    error_log("[$severity] $message en $file:$line");
    // no interrumpir la ejecución para avisos no fatales
    if ($severity === E_USER_ERROR) { return false; }
    return true;
});

try {
    // código que puede fallar
} catch (\Throwable $e) {
    error_log($e->getMessage());
    echo "Ha ocurrido un problema; revisa el log.";
}
· · ·

06 Otros ajustes útiles para depurar

ini_set("memory_limit",        "256M");  // límite de memoria del script
ini_set("max_execution_time",  "60");    // tiempo máximo de ejecución en segundos
ini_set("display_errors",      1);
ini_set("html_errors",         1);       // traza con formato HTML (más legible)
var_dump($variable);                     // volcar el valor de una variable

Si el hosting usa Apache y ini_set() no surte efecto (por ejemplo en .htaccess), puedes declarar valores por directorio (según versión de PHP):

php_value display_errors 1
php_value error_reporting 32767
· · ·

07 Depurar desde la línea de comandos

Cuando puedes ejecutar por terminal, la depuración es mucho más directa:

php -l script.php                    # comprueba la sintaxis (lint) sin ejecutar
php -d display_errors=1 script.php    # ejecuta mostrando errores
php -d error_reporting=32767 script.php

Si además tienes Xdebug, puedes forzar el registro con xdebug.log y activar la pila de llamadas; y en muchos entornos, con xdebug.mode=debug y un cliente, haces step debugging real.

· · ·

08 Conclusión

Mostrar errores ocultos en PHP es tan sencillo como dos líneas (error_reporting(E_ALL) + ini_set("display_errors",1)) y tan peligroso como dejarlas en producción. La buena práctica es: en desarrollo muestra todo, en producción registra en log, y usa set_error_handler()/`try-catch` para gestionar lo que te importa y no mostrar trazas al usuario.

· · ·

09 Notas y referencias