Reportar excepciones
QuietGuard\Monitor\Reporter es el cliente neutro respecto al framework. Los adaptadores lo conectan a los hooks de excepciones y de logs de un host; usted también puede pilotarlo a mano.
Cablear un Reporter
El constructor del Reporter recibe un Config, un Http\HttpClient, un Support\Scrubber, un Payload\ExceptionPayloadBuilder y un LoggerInterface PSR opcional:
Enviar datos
El Reporter expone tres métodos de envío, cada uno de los cuales devuelve un bool y nunca lanza excepciones: la supervisión no debe romper la aplicación anfitriona:
| Método | Endpoint | Notas |
|---|---|---|
reportException(Throwable $e, array $context = []) | POST /api/v1/ingest | Construye el payload, enmascara el context, lo envía. |
sendLogs(array $logs) | POST /api/v1/logs | Envuelve los logs en {"logs": [...]} y los enmascara. Devuelve true inmediatamente si $logs está vacío. |
sendDependencies(array $packages) | POST /api/v1/dependencies | Envuelve en {"packages": [...]}. Devuelve false inmediatamente si $packages está vacío; las dependencias no se enmascaran. |
Un envío solo devuelve true con un HTTP 2xx. Si el cliente no está configurado (Config::isConfigured() es falso), los métodos devuelven false sin tocar la red. Toda excepción de transporte se captura, se registra como advertencia a través del logger opcional, y se convierte en false.
Un rechazo también se registra, y para eso está el logger. Una respuesta que no es 2xx se escribe como advertencia, nombrando el estado y la dirección: un 401 por una clave mal pegada, un 402 mientras hay un pago pendiente, un 403 por un límite de plan. CurlHttpClient nunca lanza, devuelve 0: el catch anterior no podía activarse en esos casos, y cada uno llegaba a quien llamaba como un único false mudo. Pase un logger en un anfitrión que no tiene consola propia, o el cliente no tendrá forma alguna de decirle que nunca ha funcionado.
El payload de excepción
Payload\ExceptionPayloadBuilder::build() transforma un Throwable en la forma esperada en el cable:
Desde la versión 0.3, la traza empieza en el punto de lanzamiento: el archivo y la línea de la propia excepción forman el frame cero, ya que getTrace() empieza en el llamador y nunca contuvo la línea que falló. La traza se trunca a traceLimit frames, ese frame incluido (1 = solo el punto de lanzamiento), cada uno reducido a class, type, function, file, line. Los frames de la aplicación llevan además un objeto code, producido por Payload\SourceSnippet: start, el número de la primera línea, y lines, las cinco líneas a cada lado de la línea del frame, leídas en el archivo que se ejecutó y cortadas a 500 caracteres, en como máximo diez frames por informe (ExceptionPayloadBuilder::MAX_SNIPPET_FRAMES). Un frame cuya ruta contiene /vendor/ nunca lo lleva, y cualquier lectura que falle responde null en lugar de perder el informe. codeSnippets: false en Config (y en el builder, que lo lee como tercer argumento) envía solo el archivo y la línea. El Reporter pasa los fragmentos por Scrubber::scrubSnippets(): una línea que nombra una clave de la lista de enmascaramiento, o una palabra de Scrubber::LINE_NEEDLES, y le da un valor se sustituye entera por [scrubbed], vea Enmascaramiento. Cuando release no es nulo, se fusiona en context (sus claves de contexto explícitas prevalecen en caso de colisión). El contrato exacto en el cable está descrito por la API de ingesta (/docs/tool/1.0/ingestion-api).
Cuatro campos se recortan a la longitud que acepta el servidor, mediante Support\Cut, marcando el corte con … [truncated]: message a 8192 caracteres, class a 255, file a 1024 y release a 255. El endpoint los valida y responde 422 para el reporte entero: una QueryException que lleva toda su consulta SQL, o una excepción de una clase anónima cuyo nombre contiene una ruta absoluta, eran precisamente los reportes que desaparecían. El corte cuenta caracteres y no bytes, de modo que un carácter multibyte nunca se parte en dos, lo que produciría un payload rechazado como UTF-8 inválido.
La abstracción de transporte
Http\HttpClient es un contrato deliberadamente mínimo:
El Http\CurlHttpClient incluido lo implementa con ext-curl y sin ninguna otra dependencia: codifica el cuerpo en JSON y envía Content-Type: application/json, Accept: application/json y Authorization: Bearer <token>. Devuelve 0 (en lugar de lanzar una excepción) cuando la codificación JSON o curl_init() falla.
Traiga su propio cliente
Como HttpClient es una interfaz simple, y no PSR-18 en sí misma, puede encapsular cualquier cliente (un cliente PSR-18, Guzzle, el cliente HTTP del framework) detrás de ella. Las implementaciones no deben lanzar excepciones y deben devolver el código de estado (o 0):
Pase su implementación como argumento http al Reporter. Para capturar automáticamente los errores en una aplicación PHP simple, consulte el handler de errores global; para mantener los secretos fuera de los payloads, consulte el enmascaramiento.
Está leyendo la documentación PHP Core v1.0.