Remonter les exceptions

QuietGuard\Monitor\Reporter est le client neutre vis-à-vis du framework. Les adaptateurs le branchent sur les hooks d'exceptions et de logs d'un hôte ; vous pouvez aussi le piloter à la main.

Câbler un Reporter

Le constructeur du Reporter prend un Config, un Http\HttpClient, un Support\Scrubber, un Payload\ExceptionPayloadBuilder et un LoggerInterface PSR optionnel :

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

$config = new Config(
    url: 'https://monitor.example.com',
    key: 'jeton-projet',
    release: 'a1b2c3d',
);

$reporter = new Reporter(
    config: $config,
    http: new CurlHttpClient(),
    scrubber: new Scrubber(['password', 'authorization', 'api_key']),
    builder: new ExceptionPayloadBuilder(
        traceLimit: $config->traceLimit,
        release: $config->release,
    ),
    logger: null, // Psr\Log\LoggerInterface optionnel
);

Envoyer des données

php
try {
    // votre code
} catch (\Throwable $e) {
    $reporter->reportException($e, ['user_id' => 42]);
}

Le Reporter expose trois méthodes d'envoi, chacune renvoyant un bool et ne levant jamais d'exception, la surveillance ne doit pas casser l'application hôte :

MéthodeEndpointNotes
reportException(Throwable $e, array $context = [])POST /api/v1/ingestConstruit le payload, masque le context, l'envoie.
sendLogs(array $logs)POST /api/v1/logsEnveloppe les logs en {"logs": [...]} et les masque. Renvoie true immédiatement si $logs est vide.
sendDependencies(array $packages)POST /api/v1/dependenciesEnveloppe en {"packages": [...]}. Renvoie false immédiatement si $packages est vide ; les dépendances ne sont pas masquées.

Un envoi ne renvoie true que sur un HTTP 2xx. Si le client n'est pas configuré (Config::isConfigured() est faux), les méthodes renvoient false sans toucher au réseau. Toute exception de transport est capturée, journalisée via le logger optionnel en avertissement, et transformée en false.

Le payload d'exception

Payload\ExceptionPayloadBuilder::build() transforme un Throwable en la forme attendue sur le fil :

php
[
    'exception' => [
        'class'   => $e::class,
        'message' => $e->getMessage(),
        'file'    => $e->getFile(),
        'line'    => $e->getLine(),
        'trace'   => [ // jusqu'à traceLimit frames
            ['class' => ..., 'type' => ..., 'function' => ..., 'file' => ..., 'line' => ...],
        ],
    ],
    'context' => [ /* release (si défini) fusionné avec votre contexte */ ],
]

La trace est tronquée à traceLimit frames, chacune réduite à class, type, function, file, line. Quand release est non nul, il est fusionné dans context (vos clés de contexte explicites l'emportent en cas de collision). Le contrat exact sur le fil est décrit par l'API d'ingestion (/docs/tool/1.0/ingestion-api).

L'abstraction de transport

Http\HttpClient est un contrat volontairement minimal :

php
interface HttpClient
{
    // renvoie le code de statut HTTP, ou 0 en cas d'échec de transport ; ne doit pas lever d'exception
    public function postJson(string $url, string $token, array $payload, int $timeout): int;
}

Le Http\CurlHttpClient fourni l'implémente avec ext-curl et sans aucune autre dépendance : il encode le corps en JSON et envoie Content-Type: application/json, Accept: application/json et Authorization: Bearer <token>. Il renvoie 0 (au lieu de lever une exception) quand l'encodage JSON ou curl_init() échoue.

Apporter votre propre client

Comme HttpClient est une simple interface, et non PSR-18 lui-même, vous pouvez encapsuler n'importe quel client (un client PSR-18, Guzzle, le client HTTP du framework) derrière elle. Les implémentations ne doivent pas lever d'exception et doivent renvoyer le code de statut (ou 0) :

php
use QuietGuard\Monitor\Http\HttpClient;
use Psr\Http\Client\ClientInterface;      // PSR-18
use Psr\Http\Message\RequestFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;

final class Psr18HttpClient implements HttpClient
{
    public function __construct(
        private ClientInterface $client,
        private RequestFactoryInterface $requests,
        private StreamFactoryInterface $streams,
    ) {}

    public function postJson(string $url, string $token, array $payload, int $timeout): int
    {
        try {
            $request = $this->requests->createRequest('POST', $url)
                ->withHeader('Content-Type', 'application/json')
                ->withHeader('Accept', 'application/json')
                ->withHeader('Authorization', 'Bearer '.$token)
                ->withBody($this->streams->createStream((string) json_encode($payload)));

            return $this->client->sendRequest($request)->getStatusCode();
        } catch (\Throwable) {
            return 0;
        }
    }
}

Passez votre implémentation comme argument http au Reporter. Pour capturer automatiquement les erreurs dans une appli PHP simple, voir le handler d'erreurs global ; pour garder les secrets hors des payloads, voir le masquage.

Vous lisez la documentation PHP Core v1.0.