Avisos de excepciones de Laravel en tu teléfono
Laravel ya registra las excepciones en tu log y, con un canal como Slack, también en un chat. El problema llega cuando la misma excepción salta 300 veces en un minuto. Tú quieres enterarte una vez, con detalle suficiente para actuar, y que nadie te moleste hasta que se rompa otra cosa.
Esta guía lo configura con el paquete honk-me/honk-me y Laravel 13: las excepciones se convierten en notificaciones, las repeticiones de un mismo fallo se reúnen en un grupo y las tareas programadas te avisan cuando fallan y cuando vuelven a funcionar.
Antes de empezar
- Laravel 13 con PHP 8.3 o posterior.
- Una cuenta de Honk (por ahora solo con invitación: solicita acceso) y un proyecto con una clave de ingesta: en la app web, abre el proyecto y entra en Claves.
1. Instala el paquete
composer require honk-me/honk-me
# publishes config/honk.php and adds HONK_URL and HONK_KEY to .env.example
php artisan honk:installPon la URL del servidor y la clave en .env. Nunca hagas commit de la clave; en .env.example solo van valores vacíos.
HONK_URL=https://honk-me.app
HONK_KEY=honk_xxxxxxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxComprueba la conexión. honk:test envía un Bocinazo suave y muestra el id del mensaje:
php artisan honk:test # sends a light honk and prints the message id
php artisan about --only=honk # URL, key prefix (never the secret), queue, Context metadata2. Avisos de excepciones
Una línea en bootstrap/app.php:
// bootstrap/app.php
use HonkMe\Laravel\Facades\Honk;
->withExceptions(function (Exceptions $exceptions): void {
$exceptions->report(Honk::reportable());
})Desde ese momento, cada excepción que reporta Laravel se convierte en un Bocinazo largo, enviado como problema. Tres detalles evitan el ruido:
- Un grupo por cada punto del código. La clave de grupo es
exceptions/<Class>@<file>:<line>, así que 300 excepciones idénticas son un incidente con un recuento, y una excepción distinta es otro incidente. - Como mucho un bocinazo por grupo cada 5 minutos. El límite se guarda en tu caché, así que las 299 repeticiones ni siquiera salen de tu servidor.
- Se envía después de la respuesta. El modo predeterminado,
defer, envía cuando la respuesta HTTP ya ha salido, así que una red lenta nunca ralentiza una página.
Si falta HONK_URL o HONK_KEY, no hace nada, así que el desarrollo local y la CI siguen en silencio. Y nunca lanza excepciones.
Puedes elegir otro nivel u otra ventana por app; por ejemplo, Bocina sin parar con un límite de un minuto para un servicio de pagos:
$exceptions->report(Honk::reportable(severity: 'blast', throttleSeconds: 60));El resto se configura en config/honk.php: excluye las excepciones previstas, como un pago rechazado que la interfaz ya explica, y elige cómo se envían:
// config/honk.php (published by php artisan honk:install)
'exceptions' => [
'enabled' => (bool) env('HONK_EXCEPTIONS', true),
'severity' => 'long',
'channel' => 'exceptions',
'throttle' => 300, // seconds per group
'mode' => 'defer', // defer (after the response), sync or queue
'ignore' => [
\App\Exceptions\PaymentDeclined::class, // expected, already handled in the UI
],
],Los mensajes de las excepciones se envían tal cual, recortados para que quepan. No pongas datos personales en ellos: acabarían en una notificación.
3. Al instante, después de la respuesta o desde la cola
Para todo lo que envíes tú, tienes las mismas opciones:
use HonkMe\Laravel\Facades\Honk;
use HonkMe\Message;
Honk::defer()->beep('New order', "{$order->email} paid {$order->total} €"); // after the response
Honk::queue(Message::make('Imported 1 204 rows')->beep()->channel('imports')); // a queued job
Honk::loud('Disk 91%', '/var on app-01', ['groupKey' => 'disk/app-01/var']); // right nowHonk::defer()se ejecuta en este proceso después de la respuesta. No hace falta ningún worker. Si el proceso muere antes, ese bocinazo se pierde; para la mayoría de las alertas, no pasa nada.Honk::queue()se ejecuta en un worker de la cola, con reintentos y backoff, y sobrevive a los reinicios. Úsalo para lo que tiene que llegar sí o sí.- Los helpers de siempre envían al momento, con reintentos: lo ideal para comandos, jobs y el programador de tareas.
Tanto defer() como queue() validan el mensaje al instante, así que un campo incorrecto falla en tu código, no más tarde en un worker.
4. Tareas programadas
// routes/console.php
Schedule::command('backup:run')->daily()->honkOnFailure(); // problem on failure, recovery once it works again
Schedule::command('reports:send')->hourly()->honkOnSuccess(); // a beep after every successful runhonkOnFailure() envía un problema con el código de salida y las últimas líneas de la salida, agrupado por tarea. Cuando la tarea vuelve a terminar bien, envía una recuperación, pero solo si antes falló, así que una tarea sana nunca te molesta. honkOnSuccess() envía un Bip-bip tras cada ejecución correcta, para esos informes de los que sí quieres enterarte.
5. Pruébalo
Honk::fake() registra lo que se enviaría o se pondría en cola y nunca toca la red. Valida igual que el cliente real, así que un mensaje no válido sigue haciendo fallar la prueba:
use HonkMe\Laravel\Facades\Honk;
use HonkMe\Message;
use HonkMe\Severity;
Honk::fake();
$this->post(route('quote.store'), $data);
Honk::assertSent(fn (Message $m) => $m->groupKey === 'requests/1' && $m->severity === Severity::Light);
Honk::assertSentTimes(1);
Honk::assertNotSent(fn (Message $m) => $m->severity === Severity::Blast);
Honk::assertQueued(fn (Message $m, string $idempotencyKey) => $m->channel === 'imports');
Honk::assertNothingSent(); // also assertNothingQueued(), assertNothingOutgoing()Qué añade Honk a los avisos de Laravel
Tu log sigue recibiendo todas las excepciones. Honk añade una notificación por cada fallo nuevo, en tu iPhone, tu Apple Watch o tu navegador, agrupa las repeticiones con un recuento y las reúne en la misma bandeja de entrada que tus tareas cron y tus servidores. Si quieres tratar algunas de otra forma sin tocar el código, añade una regla al proyecto; por ejemplo, para mandar al resumen las exceptions/ de una integración ruidosa.
Por ahora, Honk funciona solo con invitación: solicita acceso. Todo lo que explica esta guía funciona con el plan Free.