Correlacionar una peticion entre servicios
Una sola acción de un comerciante puede tocar tres servicios antes de terminar: merchants llama a api-server, que llama a api-license, que llama a Statamic. Cuando uno de esos saltos falla, necesitas reconstruir la cadena entera, no sólo la hoja que lanzó.
La parte honesta: esta flota no te da un identificador único de petición que atraviese los tres. LogApiRequestsMiddleware envía user_id, method, endpoint, status y tiempos a un almacén compartido, una entrada por servicio y por salto. No hay campo trace_id en ninguna parte de esa carga.
Lo que sí tienes es el bearer. Cada salto interno de una operación lógica se autentica con el mismo bearer, llevando la misma identidad hasta el fondo. LogApiRequestsMiddleware escribe esa identidad como user_id, así que ese campo, acotado a una ventana temporal estrecha, es tu clave de unión real.
merchants request
↓
api-server (same bearer)
↓
api-license (same bearer)
↓
Statamic integration
Es más grosero de lo que sería un identificador de traza dedicado. Dos operaciones del mismo bearer dentro del mismo segundo son genuinamente difíciles de distinguir. Eso es una carencia real, no un diseño que debas copiar a ciegas: llámalo por su nombre en lugar de fingir que el bearer lo hace preciso.
Cuando lo que se rompe es el registro
Todos los mecanismos de este capítulo dependen de que algo esté en pie: Nightwatch, la cola, el almacén compartido. Ninguno está garantizado. La respuesta de la flota no es hacer el registro infalible, sino asegurarse de que si el registro falla no se lleve la petición por delante.
try {
// ...Nightwatch calls...
} catch (Throwable) {
// Nightwatch not available or container not initialized
}
Ese try/catch existe porque reportar una excepción puede lanzar a su vez, y un closure de report() que revienta mientras reporta un fallo es el peor resultado posible: el error original se pierde y ahora hay un segundo encima. Trágatelo. El cliente sigue recibiendo su ErrorResponse, sin verse afectado.
Y no es una protección hipotética frente a una caída futura. Vuelve al reparto de severidades de antes: Nightwatch::warning(), Nightwatch::info() y Nightwatch::captureException() lanzan en cada llamada, porque la facade instalada no las define. Este catch exacto es lo que se lo está comiendo calladamente. La petición no se ralentiza. El cliente no se entera. Pero la clasificación de severidad que ese closure se escribió para aportar no está ocurriendo, y nada saca ese hecho a la luz, porque lo único construido para atrapar un fallo silencioso es justo lo que está fallando en silencio.
SendToLogsJob recibe el mismo trato, sólo que antes en la tubería. LogApiRequestsMiddleware llama a $next($request) y devuelve la respuesta antes de que corra defer(). El registro se envía después de que la respuesta ya haya salido. Si la cola está atascada, si el almacén está caído, el llamante no lo ve: ya tiene su respuesta.
La regla que hay debajo de todo esto: la observabilidad es pasajera, no conductora. Si capturar lo que salió mal puede tumbar aquello que ya está mal, has construido exactamente el modo de fallo que intentabas atrapar.
Resumen
- [X] Los errores pasan por una sola puerta.
withExceptions()enbootstrap/app.php, no try/catch por controlador ni una claseHandlerque esta flota ya no tiene. - [X] La representación se comparte, no se copia.
ApiExceptionRendereryErrorResponseviven una vez enapi-infrastructure. - [X] Un código de estado no es un contrato. Dos
404distintos son idénticos sin un código legible por máquina junto al mensaje. - [X] El contexto vive en la excepción, no en la llamada de registro. Un booleano como
$outcomeUnknowndice más que cualquier traza. - [X] La correlación vale lo que valga tu clave de unión. Sin identificador de traza dedicado, el bearer y una ventana temporal estrecha son lo que realmente tienes.
- [X] El registro debe poder fallar sin hacer fallar la petición. Captura alrededor del reporte, difiere la escritura, encólala.
Un manejador de errores que sólo maneja al gemelo malvado del camino feliz no está terminado. Está terminado cuando sobrevive a la caída de sus propias dependencias.