Ir al contenido

Handler de errores global

QuietGuard\Monitor\ErrorHandler conecta los handlers globales de PHP para que un host PHP simple, sin pipeline de excepciones de framework, reporte igualmente los errores no capturados. Los frameworks (Laravel, Symfony) tienen sus propios hooks y no deben llamarlo.

Registro

ErrorHandler::register() es una única llamada estática que recibe un Reporter configurado:

php
use QuietGuard\Monitor\ErrorHandler;
use QuietGuard\Monitor\Reporter;

/** @var Reporter $reporter */
ErrorHandler::register($reporter);

Llámelo una vez, lo antes posible en su bootstrap, después de construir el Reporter.

Qué registra

register() cablea tres hooks globales:

  1. set_exception_handler: todo Throwable no capturado se envía mediante $reporter->reportException($e). El handler encadena: si otro handler de excepciones global estaba registrado antes de ErrorHandler::register(), sigue ejecutándose después de que la excepción se haya reportado (el monitor reporta primero y luego delega). Cuando no existe ningún handler previo, la excepción (mensaje, fichero:línea y stack trace) se escribe en error_log, igual que lo que el comportamiento por defecto de PHP habría registrado: instalar la monitorización nunca elimina, por tanto, el registro local del fallo en el host.
  2. set_error_handler: un error de PHP lanzado se encapsula en una ErrorException y luego se reporta, siempre que su severidad alcance el umbral descrito más abajo. Comprueba primero error_reporting() & $severity, de modo que los valores suprimidos con @ o por debajo del nivel error_reporting actual se ignoran. A continuación devuelve false para que la gestión de errores normal de PHP se ejecute igualmente.
  3. register_shutdown_function: en el apagado, se inspecciona error_get_last() y, si se trata de un tipo fatal (E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR), se reporta una ErrorException. Esto captura las fatales que el handler de errores no puede interceptar.

Todos los caminos pasan por el Reporter, que nunca lanza excepciones: registrar estos handlers no puede, por tanto, romper el host.

El umbral de severidad, y el tope que lo acompaña

register() recibe dos argumentos opcionales:

php
public static function register(
    Reporter $reporter,
    ?int $severities = null,   // null toma E_USER_ERROR | E_RECOVERABLE_ERROR
    int $maxPerRequest = 20,
): void

Por defecto solo se reportan E_USER_ERROR y E_RECOVERABLE_ERROR. Antes se transmitía toda severidad, con un POST síncrono cada una, sin umbral, sin deduplicación y sin tope: un solo E_WARNING dentro de un bucle sobre quinientas filas eran quinientas llamadas HTTP bloqueantes en una carga de página, lo que el visitante lee como una caída y consume la cuota mensual de un plan gratuito en un día.

Las verdaderas fatales están ausentes del valor por defecto a propósito. PHP no llama a ningún handler de errores de usuario para E_ERROR, E_PARSE, E_CORE_ERROR ni E_COMPILE_ERROR, y es el hook de apagado anterior el que ya las captura. Lo que el valor por defecto conserva es el par que un host lanza de verdad y con intención: una fatal de aplicación, y un error de tipo que PHP dejó pasar.

Se aplican dos protecciones sea cual sea la máscara:

  • un reporte por sitio y por petición: el mismo error lanzado cien veces en el mismo archivo:línea es un solo hecho, y los otros noventa y nueve POST son latencia de la aplicación anfitriona, no algo que a usted le toque gastar;
  • un tope de $maxPerRequest, 20 por defecto, porque una máscara no ve un bucle: cincuenta líneas distintas pasan cualquier umbral y siguen siendo cincuenta peticiones.

Pase la máscara de forma explícita para ampliarla, sabiendo lo que cuesta:

php
// El comportamiento antiguo: toda severidad que error_reporting deje pasar
ErrorHandler::register($reporter, E_ALL);

// También las advertencias, pero como mucho cinco reportes en una petición
ErrorHandler::register($reporter, E_USER_ERROR | E_RECOVERABLE_ERROR | E_WARNING, 5);

Un bootstrap PHP mínimo

php
use QuietGuard\Monitor\Config;
use QuietGuard\Monitor\ErrorHandler;
use QuietGuard\Monitor\Reporter;
use QuietGuard\Monitor\Http\CurlHttpClient;
use QuietGuard\Monitor\Payload\ExceptionPayloadBuilder;
use QuietGuard\Monitor\Support\Scrubber;

require __DIR__.'/vendor/autoload.php';

$config = new Config(url: 'https://monitor.example.com', key: 'token-de-proyecto');

$reporter = new Reporter(
    config: $config,
    http: new CurlHttpClient(),
    scrubber: new Scrubber(['password', 'authorization']),
    builder: new ExceptionPayloadBuilder($config->traceLimit, $config->release),
);

ErrorHandler::register($reporter);

// A partir de aquí, las excepciones no capturadas, los errores de PHP y los
// errores fatales de apagado se transmiten automáticamente a Quiet Guard.
El SDK de Laravel y el bundle de Symfony no utilizan ErrorHandler: cada uno conecta el pipeline de excepciones de su framework. El plugin de WordPress sí lo utiliza, ya que WordPress no tiene un pipeline de excepciones propio, y toma el umbral por defecto. Reserve ErrorHandler para un host que carezca de su propio pipeline de excepciones.

Está leyendo la documentación PHP Core v1.0.