Aller au contenu

Handler d'erreurs global

QuietGuard\Monitor\ErrorHandler branche les handlers globaux de PHP afin qu'un hôte PHP simple, sans pipeline d'exceptions de framework, remonte tout de même les erreurs non capturées. Les frameworks (Laravel, Symfony) ont leurs propres hooks et ne doivent pas l'appeler.

Enregistrement

ErrorHandler::register() est un unique appel statique qui prend un Reporter configuré :

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

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

Appelez-le une fois, le plus tôt possible dans votre bootstrap, après la construction du Reporter.

Ce qu'il enregistre

register() câble trois hooks globaux :

  1. set_exception_handler, tout Throwable non capturé est envoyé via $reporter->reportException($e). Le handler chaîne : si un autre handler d'exceptions global était enregistré avant ErrorHandler::register(), il s'exécute toujours après que l'exception a été remontée (le monitoring remonte d'abord, puis délègue). En l'absence de handler précédent, l'exception (message, fichier:ligne et stack trace) est écrite dans error_log, à l'identique de ce que le comportement par défaut de PHP aurait consigné : installer le monitoring ne supprime donc jamais la trace locale du crash côté hôte.
  2. set_error_handler, une erreur PHP levée est encapsulée dans une ErrorException puis remontée, à condition que sa sévérité atteigne le plancher décrit plus bas. Il vérifie d'abord error_reporting() & $severity, de sorte que les valeurs supprimées par @ ou en dessous du niveau error_reporting courant sont ignorées. Il renvoie ensuite false pour que la gestion d'erreurs normale de PHP s'exécute quand même.
  3. register_shutdown_function, à l'arrêt, error_get_last() est inspecté et, s'il s'agit d'un type fatal (E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR), une ErrorException est remontée. Cela capture les fatales que le handler d'erreurs ne peut pas intercepter.

Tous les chemins passent par le Reporter, qui ne lève jamais d'exception : enregistrer ces handlers ne peut donc pas casser l'hôte.

Le plancher de sévérité, et le plafond qui va avec

register() prend deux arguments optionnels :

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

Par défaut, seules E_USER_ERROR et E_RECOVERABLE_ERROR sont remontées. Toute sévérité était auparavant transmise, en un POST synchrone chacune, sans plancher, sans dédoublonnage et sans plafond : un seul E_WARNING dans une boucle sur cinq cents lignes, c'était cinq cents appels HTTP bloquants dans un chargement de page, ce qui se lit comme une panne pour le visiteur et brûle le quota mensuel d'un forfait gratuit en une journée.

Les vraies fatales sont absentes du défaut exprès. PHP n'appelle aucun handler d'erreurs utilisateur pour E_ERROR, E_PARSE, E_CORE_ERROR ni E_COMPILE_ERROR, et c'est la fonction d'arrêt ci-dessus qui les attrape déjà. Ce que le défaut conserve, c'est la paire qu'un hôte lève vraiment et qu'il pense : une fatale applicative, et une erreur de type que PHP a laissé passer.

Deux garde-fous s'appliquent quel que soit le masque :

  • un rapport par site et par requête : la même erreur levée cent fois sur le même fichier:ligne est un seul fait, et les quatre-vingt-dix-neuf autres POST sont la latence de l'application hôte, pas la vôtre à dépenser ;
  • un plafond de $maxPerRequest, 20 par défaut, parce qu'un masque ne voit pas une boucle : cinquante lignes distinctes passent n'importe quel plancher et restent cinquante requêtes.

Passez le masque explicitement pour l'élargir, en connaissance de cause :

php
// L'ancien comportement : toute sévérité que error_reporting laisse passer
ErrorHandler::register($reporter, E_ALL);

// Les avertissements aussi, mais cinq rapports au plus dans une requête
ErrorHandler::register($reporter, E_USER_ERROR | E_RECOVERABLE_ERROR | E_WARNING, 5);

Un bootstrap PHP minimal

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: 'jeton-projet');

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

ErrorHandler::register($reporter);

// À partir d'ici, les exceptions non capturées, les erreurs PHP et les erreurs
// fatales d'arrêt sont transmises automatiquement à Quiet Guard.
Le SDK Laravel et le bundle Symfony n'utilisent pas ErrorHandler : chacun branche le pipeline d'exceptions de son framework. Le plugin WordPress, lui, l'utilise, WordPress n'ayant pas de pipeline d'exceptions à lui, et prend le plancher par défaut. Réservez ErrorHandler à un hôte dépourvu de son propre pipeline d'exceptions.

Vous lisez la documentation PHP Core v1.0.