Ir al contenido

Reporte de excepciones

El reporte de excepciones es el corazón del SDK. Una vez configurado, cada excepción que Laravel reporta se transmite a su dashboard.

Captura automática

Al arrancar, el SDK registra un callback reportable() aditivo en el handler de excepciones de la aplicación:

php
$handler->reportable(function (Throwable $e): void {
    app(Monitor::class)->report($e);
});

Como el callback es aditivo, no sustituye ni elimina su logging existente ni los demás handlers reportable: se limita a transmitir una copia a Quiet Guard. Toda excepción que llega al handler (excepciones no capturadas, o cualquier cosa que pase a report()) queda capturada, excepto la que se produce al servir una petición que coincide con ignore_paths: consulte Configuración.

La captura es fail-safe: un fallo de red, una mala configuración o un error de servidor dentro del SDK se absorbe en silencio y nunca se propaga a la aplicación anfitriona.

Fail-safe no significa mudo. Cuando el servidor rechaza un reporte, dice por qué, y esa frase se escribe en su propio log como advertencia, con la forma HTTP <estado> from <url>: <mensaje>: un 402 mientras hay un pago pendiente, un 403 por un límite de plan, un 422 por un campo mal formado. La línea lleva un marcador interno, así que nunca se captura y se transmite a su vez.

Reporte manual

Utilice la fachada Monitor para reportar explícitamente una excepción gestionada:

php
use QuietGuard\LaravelMonitor\Facades\Monitor;

try {
    $this->chargeCustomer($order);
} catch (\Throwable $e) {
    Monitor::report($e);

    // Relance o recupere como prefiera.
    throw $e;
}

Monitor::report() nunca lanza excepciones. No hace nada, silenciosamente, cuando el reporte está desactivado, cuando falta la clave del proyecto, cuando el entorno actual no figura en la lista blanca, o cuando la petición coincide con ignore_paths. A partir de la versión 0.3.1, una MONITOR_URL vacía no lo detiene: equivale al servicio alojado, vea Configuración.

Puede resolver el mismo objeto desde el contenedor:

php
app(\QuietGuard\LaravelMonitor\Monitor::class)->report($e);

Qué se envía

Cada reporte se envía mediante POST al endpoint /api/v1/ingest del servidor y contiene:

La excepción

  • clase, mensaje, código, archivo y línea;
  • la pila de llamadas completa por defecto (MONITOR_TRACE_LIMIT=0): todos los frames, para que los errores profundos conserven su origen; fije un número de frames para aligerar los payloads. Cada frame se reduce a file, line, function, class y type (los argumentos no se envían nunca, pueden contener secretos);
  • a partir de la versión 0.3, el punto de lanzamiento al principio de la pila: el archivo y la línea de la propia excepción viajan como frame cero, ya que la traza de PHP empieza en el llamador y nunca contuvo la línea que falló. MONITOR_TRACE_LIMIT cuenta ese frame (1 = solo el punto de lanzamiento);
  • un fragmento de código (code) en los frames de su aplicación: las cinco líneas a cada lado de la línea del frame, leídas en el archivo que se ejecutó, cada una cortada a 500 caracteres, en como máximo diez frames por informe. Los frames de una dependencia (cualquier ruta que contenga /vendor/) nunca lo llevan. Una línea que nombra una clave de la lista scrub de más abajo (password, secret, token, passphrase...) y le da un valor ($secret = ..., 'api_key' => ..., setApiKey('...')) se sustituye entera por [scrubbed], porque el código fuente es donde vive un secreto escrito en duro; las palabras key, auth, credential, salt, private, dsn, bearer, sk_ y pk_ cuentan también, y un literal que no nombra ninguna viaja tal cual. A partir de la versión 0.3.1, una llamada estática (Auth::user()) ya no se toma por un valor, de modo que su línea sigue siendo legible; la regla completa está en la página de enmascaramiento del núcleo. MONITOR_CODE_SNIPPETS=false envía solo el archivo y la línea.

El contexto

  • environment, release (desde MONITOR_RELEASE, o detectada desde el checkout cuando está vacía), php_version, laravel_version, occurred_at (ISO 8601);
  • source: console en CLI/cola, o http para las peticiones web.

Para las peticiones HTTP, el contexto incluye además:

  • url: la dirección completa de la petición. A partir de la versión 0.3.1 pasa por el enmascaramiento: el valor de cada parámetro de su cadena de consulta nombrado en la lista scrub (el token y la signature de un enlace de restablecimiento firmado), y todo segmento de ruta formado por cuarenta letras o cifras seguidas (el token que Laravel coloca en /reset-password/{token}), pasan a ser %5Bscrubbed%5D; un identificador numérico, un slug, un UUID o un ULID se quedan;
  • method;
  • request: cabeceras, query string y cuerpo, con el password / password_confirmation del cuerpo eliminados y el conjunto pasado por el enmascaramiento. A partir de la versión 0.3.1, el enmascaramiento entra también en un valor que es un objeto JSON escrito como cadena, como la instantánea (snapshot) que un componente Livewire devuelve con cada petición, y una cabecera escrita con guiones se enmascara como su nombre con guiones bajos: php_auth_pw enmascara php-auth-pw, la contraseña de una petición HTTP Basic;
  • user: únicamente el identificador del usuario autenticado (id), nunca sus atributos.

Límites aplicados antes del envío

El endpoint de ingesta valida cuatro de estos campos y responde 422 para el reporte entero en cuanto uno de ellos es demasiado largo. Por eso el SDK los recorta él mismo en lugar de perder el reporte, y marca el corte con … [truncated]:

CampoLímite
exception.message8192 caracteres
exception.class255 caracteres
exception.file1024 caracteres
context.release255 caracteres

Estos límites los alcanzan precisamente los reportes que más quiere leer: una QueryException lleva toda la consulta SQL y sus bindings dentro de su mensaje, y una excepción lanzada desde una clase anónima tiene la ruta absoluta del archivo dentro de su nombre de clase, que en un directorio de release consume por sí sola casi los 255. Recortar es una respuesta con pérdida, y aquí es la correcta: los primeros 8192 caracteres de una consulta valen incomparablemente más que un reporte que desaparece en silencio. El corte cuenta caracteres, no bytes, de modo que un carácter multibyte nunca se parte en dos, lo que produciría un payload que el servidor rechazaría como UTF-8 inválido.

Enmascaramiento

Antes de que cualquier payload salga de la aplicación, los datos de petición y de contexto se enmascaran de forma recursiva: toda clave cuyo nombre contenga uno de los términos configurados (sin distinción de mayúsculas y minúsculas) ve su valor sustituido por [scrubbed]. A partir de la versión 0.3.1, el enmascaramiento lee también las cadenas que llevan valores con nombre propios: una cadena JSON se abre y se enmascara por clave, y una URL pierde el valor de sus parámetros nombrados en la lista así como sus segmentos de ruta con forma de token (vea la página de enmascaramiento del núcleo).

Los términos por defecto son:

password, password_confirmation, passphrase, token, signature, secret,
authorization, cookie, php_auth_pw, api_key, access_token,
referer, referrer, x-forwarded-for, x-real-ip, cf-connecting-ip,
true-client-ip, x-client-ip, forwarded

Para añadir los suyos, publique la configuración (php artisan vendor:publish --tag=monitor-config) y amplíe el array scrub en config/monitor.php:

php
'scrub' => [
    ...config('monitor.scrub'),
    // los suyos:
    'credit_card',
    'iban',
],

Conserve cada término entregado, salvo que quiera quitarlo. Reescribir el array a mano es como passphrase y las cabeceras de IP del visitante salen de la lista sin ruido.

La correspondencia se hace por subcadena: api_key enmascara por tanto también stripe_api_key, y token enmascara csrf_token. A partir de la versión 0.3.1, un guion cuenta como un guion bajo: api_key enmascara también la cabecera x-api-key.

Enmascarado por la forma del valor

La lista scrub anterior mira el nombre del campo. No ve una dirección escrita en mitad de un mensaje de error, en un segmento de URL, ni en un campo que alguien llamó reference.

La lista redact mira el valor en sí, en su servidor, antes de cualquier envío:

php
'redact' => ['email', 'iban', 'nir', 'card', 'phone'],

Las formas que llevan dígito de control se verifican en lugar de solo reconocerse: una referencia de pedido de dieciséis dígitos no se toma por un número de tarjeta, y un IBAN debe pasar el módulo 97. Cada valor enmascarado indica qué se ocultó, por ejemplo Usuario [redacted:email] no encontrado, de modo que el mensaje sigue siendo legible.

Elimine los patrones que produzcan falsos positivos con sus datos, o ponga 'redact' => [] para desactivarlo. Para sus propias formas:

php
'redact_custom' => ['customer_ref' => '/CUST-\d{6}/'],

Envío en segundo plano

Cuando MONITOR_QUEUE está definido, las excepciones se despachan como job en cola SendExceptionToMonitor (2 intentos) en lugar de enviarse en línea. Esto saca el reporte del camino crítico de la petición. Consulte Configuración.

El payload se enmascara antes de encolarse, de modo que las formas que el enmascaramiento anterior existe para detener nunca quedan en claro en sus propias tablas jobs y failed_jobs. Un job cuyo envío ha fallado lanza, llevando lo que dijo el servidor: el segundo intento ocurre de verdad y un fallo definitivo queda registrado en failed_jobs en lugar de marcarse como correcto.

Agrupación en el lado del servidor

El SDK se limita a enviar los datos; el servidor agrupa las ocurrencias en incidencias por huella (fingerprint), reabre las regresiones y correlaciona las releases con los commits. Consulte la documentación del servidor para ver cómo se muestran y gestionan las incidencias.

Está leyendo la documentación Laravel SDK v1.0.