Guida Completa: Inviare i Log di Errore di Laravel su Discord
Avere un sistema di monitoraggio degli errori in tempo reale è fondamentale per qualsiasi applicazione in produzione. Invece di controllare manualmente i file di log, ho pensato di ricevere notifiche istantanee direttamente su un'applicazione che di default ho sempre aperta: Discord!
In questa guida, vi guiderò nell'installazione di un sistema di logging per Laravel che invia automaticamente gli errori critici a un canale Discord, senza sacrificare il logging tradizionale su file.
📋 Indice
- Guida Completa: Inviare i Log di Errore di Laravel su Discord
- 📋 Indice
- A Cosa Serve e Quali Vantaggi Offre?
- 🚀 Implementazione Passo-Passo
- Step 1: Creare un Webhook su Discord
- Step 2: Configurare le Variabili d'Ambiente (.env)
- Step 3: Definire il Canale di Log Personalizzato
- Step 4: Creare il
DiscordWebhookHandler - Step 5: Creare la Factory
DiscordLogger - Step 6: Pulire la Cache della Configurazione
- Step 7: Verificare i Prerequisiti
- Step 8: Testare il Sistema
- 🔧 Troubleshooting
- 🎉 Conclusione e Best Practices
A Cosa Serve e Quali Vantaggi Offre?
- Notifiche Immediate e Leggibili: Ricevi un avviso su Discord nel momento esatto in cui si verifica un errore, formattato con colori e campi strutturati per una facile lettura.
- Monitoraggio Centralizzato: Se gestisci più progetti, puoi centralizzare i log di errore di tutti in un unico server Discord, usando canali diversi.
- Sistema Ridondante e Robusto: I log continueranno a essere salvati anche nel file
storage/logs/laravel.log. Se Discord non fosse raggiungibile, il fallimento della notifica verrebbe registrato nel file di log locale, senza mai bloccare l'applicazione. - Facilità di Implementazione: Con pochi passaggi, avrai un sistema di logging robusto e professionale.
Pronto? Iniziamo!
🚀 Implementazione Passo-Passo
Step 1: Creare un Webhook su Discord
Il primo passo è dire a Discord di "ascoltare" i messaggi in arrivo. Lo facciamo creando un Webhook.
- Apri Discord, vai sul server e scegli il canale dove vuoi ricevere le notifiche (es.
#error-logs). - Clicca sull'icona a forma di ingranaggio (⚙️) accanto al nome del canale per aprire le Impostazioni del canale.
- Vai alla sezione Integrazioni e clicca su Webhook.
- Clicca sul pulsante Nuovo Webhook.
- Assegna un nome riconoscibile al Webhook (es. "Laravel Logs - Progetto X") e, se vuoi, un avatar.
- Infine, clicca su Copia l'URL del Webhook. Tienilo a portata di mano, ci servirà tra poco.
L'URL sarà simile a questo: https://discord.com/api/webhooks/1234567890/abcdefg...
⚠️ Nota di Sicurezza: Tratta l'URL del Webhook come se fosse una password. Chiunque lo possieda può inviare messaggi al tuo canale Discord. Non condividerlo pubblicamente e non esporlo in repository pubblici!
Step 2: Configurare le Variabili d'Ambiente (.env)
Ora configuriamo Laravel per utilizzare il nostro nuovo sistema di logging. Apri il file .env del tuo progetto e aggiungi queste righe:
# modifica
LOG_CHANNEL=stack
LOG_STACK=single,discord
#aggiungi
DISCORD_WEBHOOK_URL=IL_TUO_URL_WEBHOOK_COPIATO_PRIMA
DISCORD_LOG_LEVEL=error
Cosa significano queste variabili?
LOG_CHANNEL=stack: Questa è la chiave di tutto. Stiamo dicendo a Laravel di non usare un singolo canale di log, ma uno "stack" (una pila) di canali. Questo ci permette di inviare lo stesso log a più destinazioni contemporaneamente.LOG_STACK=single,discord: Qui definiamo quali canali fanno parte del nostro stack.singleè il canale predefinito che scrive su file, mentrediscordè il nome del nostro nuovo canale personalizzato.DISCORD_WEBHOOK_URL: L'URL del Webhook che hai appena creato.DISCORD_LOG_LEVEL: Il livello minimo di log da inviare a Discord.errorè una scelta sicura per evitare spam, ma puoi usare anchewarning,info, etc.
Step 3: Definire il Canale di Log Personalizzato
Laravel ha bisogno di sapere cosa significa discord. Apri il file config/logging.php e aggiungi il nostro canale all'array channels:
'channels' => [
'discord' => [
'driver' => 'custom',
'via' => App\Logging\DiscordLogger::class,
'level' => env('DISCORD_LOG_LEVEL', 'error'),
'url' => env('DISCORD_WEBHOOK_URL'),
],
// ... altri canali come 'single', 'daily', etc.
],
Assicurati anche che il canale stack sia configurato per leggere dinamicamente i canali dal file .env:
'stack' => [
'driver' => 'stack',
// Questa riga legge LOG_STACK e divide la stringa in un array
'channels' => explode(',', (string) env('LOG_STACK', 'single')),
'ignore_exceptions' => false,
],
Step 4: Creare il DiscordWebhookHandler
Questa è la classe che si occuperà del lavoro sporco: formattare il messaggio di log in un ricco "Embed" colorato e inviarlo a Discord.
Crea il file app/Logging/DiscordWebhookHandler.php e inserisci il seguente codice:
<?php
namespace App\Logging;
use Illuminate\Support\Facades\Http;
use Monolog\Formatter\FormatterInterface;
use Monolog\Formatter\LineFormatter;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Level;
use Monolog\LogRecord;
class DiscordWebhookHandler extends AbstractProcessingHandler
{
protected string $webhookUrl;
public function __construct(string $webhookUrl, Level|int $level = Level::Error, bool $bubble = true)
{
$this->webhookUrl = $webhookUrl;
parent::__construct($level, $bubble);
}
/**
* Scrive il record di log sulla destinazione (Discord) usando Embeds.
*/
protected function write(LogRecord $record): void
{
try {
Http::asJson()->post($this->webhookUrl, [
'embeds' => [
[
'title' => $record->level->getName() . ': ' . $record->message,
'description' => '```json' . PHP_EOL . json_encode($record->context, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL . '```',
'color' => $this->getColorForLevel($record->level),
'timestamp' => $record->datetime->format('c'),
'footer' => [
'text' => config('app.name'),
],
],
],
]);
} catch (\Throwable $e) {
// Se l'invio a Discord fallisce, logga l'errore nel canale 'single' (file di log locale).
// Questo evita loop infiniti e informa sul problema di logging.
logger()->channel('single')->warning(
'Impossibile inviare il log a Discord.',
[
'exception' => $e->getMessage(),
]
);
}
}
/**
* Restituisce un colore esadecimale (in formato decimale) per il livello di log.
*/
private function getColorForLevel(Level $level): int
{
return match ($level) {
Level::Debug => 0x808080, // Grigio
Level::Info => 0x0000FF, // Blu
Level::Notice => 0x00FFFF, // Ciano
Level::Warning => 0xFFD700, // Giallo Oro
Level::Error => 0xFF0000, // Rosso
Level::Critical => 0xDC143C, // Rosso Cremisi
Level::Alert => 0xFF4500, // Rosso Arancio
Level::Emergency => 0x8B0000, // Rosso Scuro
default => 0x000000, // Nero
};
}
/**
* Definisce il formato di default per i messaggi di log (non più usato direttamente per l\embed).
*/
protected function getDefaultFormatter(): FormatterInterface
{
return new LineFormatter(
"[%channel%] %level_name%: %message% %context% %extra%\n",
null,
true,
true
);
}
}
Step 5: Creare la Factory DiscordLogger
Questa classe "costruisce" il nostro logger, collegandolo all'handler.
Crea il file app/Logging/DiscordLogger.php con il seguente contenuto:
<?php
namespace App\Logging;
use Monolog\Level;
use Monolog\Logger;
class DiscordLogger
{
/**
* Crea un'istanza Monolog personalizzata.
*
* @param array<string, mixed> $config
*/
public function __invoke(array $config): Logger
{
$logger = new Logger('discord');
$url = '';
if (app()->environment('production')) {
$url = $config['url'] ?? env('DISCORD_WEBHOOK_URL');
}
// Se l'URL non è configurato (o non siamo in produzione), restituiamo un logger vuoto.
if (!$url) {
return $logger;
}
// Logica corretta per determinare il livello, risolvendo il bug.
try {
// Prova a convertire il livello dalla configurazione (es. 'warning' -> Level::Warning).
$level = Level::fromName(strtoupper($config['level'] ?? 'error'));
} catch (\Throwable $e) {
// Se il livello specificato non è valido, usa 'Error' come fallback sicuro.
$level = Level::Error;
}
$logger->pushHandler(new DiscordWebhookHandler($url, $level));
return $logger;
}
}
⚠️ Nota: Qui c'è un controllo sull'ambiente per evitare che vengano spammate notifiche in fase di sviluppo. Per testare il Discord Logger, commentare l'if di controllo dell'env, o spostare momentaneamente fuori dall'if la riga $url = $config['url'] ?? env('DISCORD_WEBHOOK_URL');
Step 6: Pulire la Cache della Configurazione
Ogni volta che modifichi un file nella cartella config, è una buona pratica pulire la cache di configurazione per assicurarti che Laravel carichi le nuove impostazioni.
Esegui questo comando nel terminale:
php artisan config:clear
Step 7: Verificare i Prerequisiti
Prima di procedere con il test, assicurati che tutto sia configurato correttamente:
- ✅ Il Webhook Discord sia attivo e l'URL sia stato copiato correttamente
- ✅ La variabile
DISCORD_WEBHOOK_URLsia presente nel file.env - ✅ Non ci siano errori di sintassi nei file PHP creati (
DiscordWebhookHandler.phpeDiscordLogger.php) - ✅ La cache di configurazione sia stata pulita con
php artisan config:clear
Step 8: Testare il Sistema
È il momento della verità! Il modo più rapido per testare è usare artisan tinker:
php artisan tinker --execute="Log::error('Questo è un test di logging da Bauhouse! Qualcuno ha rotto qualcosa.', ['user_id' => 123, 'details' => 'Action failed']);"
Cosa dovrebbe succedere?
- Il messaggio di errore apparirà immediatamente nel tuo file
storage/logs/laravel.log. - Se la tua variabile
APP_ENVnel file.envè impostata suproduction, nel canale Discord apparirà un messaggio "Embed" rosso contenente il titolo dell'errore e i dati contestuali formattati in un blocco di codice JSON.
Nota per i test in locale: Il nostro codice invia log a Discord solo in produzione (
APP_ENV=production). Per testare in locale, puoi temporaneamente cambiareAPP_ENVinproduction, eseguire il test e poi ricordarti di rimetterlo alocal.
🔧 Troubleshooting
Se qualcosa non funziona come previsto, ecco le soluzioni ai problemi più comuni:
❌ Problema: Non ricevo messaggi su Discord
Possibili cause e soluzioni:
-
Ambiente non corretto
- Verifica che
APP_ENV=productionnel file.env - Se stai testando in locale, ricorda di cambiare temporaneamente l'ambiente
- Verifica che
-
URL del Webhook errato
- Controlla che
DISCORD_WEBHOOK_URLnel.envsia esattamente quello copiato da Discord - Verifica che non ci siano spazi prima o dopo l'URL
- Controlla che
-
Cache di configurazione
- Esegui
php artisan config:clearper pulire la cache - Riavvia il server se usi
php artisan serve
- Esegui
-
Errori di connessione
- Controlla
storage/logs/laravel.logper messaggi del tipo "Impossibile inviare il log a Discord" - Verifica la connessione internet del server
- Controlla
⚠️ Problema: Ricevo troppi messaggi su Discord
Soluzione:
Aumenta il livello di log minimo nel file .env:
# Invece di 'error', usa un livello più restrittivo
DISCORD_LOG_LEVEL=critical
I livelli disponibili in ordine crescente di gravità sono:
debug→info→notice→warning→error→critical→alert→emergency
🎨 Problema: L'embed non è formattato correttamente
Possibili cause:
-
Context array non serializzabile
- Assicurati che l'array
contextpassato al log contenga solo dati serializzabili in JSON - Evita di passare oggetti complessi o risorse
- Assicurati che l'array
-
Caratteri speciali
- I caratteri speciali vengono automaticamente escaped, ma verifica che non ci siano problemi di encoding
Esempio di log corretto:
Log::error('Errore durante il pagamento', [
'user_id' => $user->id,
'amount' => 49.99,
'error_code' => 'PAYMENT_DECLINED',
]);
🔄 Problema: Loop infiniti di logging
Se noti che i log si moltiplicano all'infinito, verifica che:
- Il canale
discordnon sia incluso nello stack quando loggi errori di Discord stesso - La gestione degli errori nel
try-catchdiDiscordWebhookHandlerusi specificamentelogger()->channel('single')e nonLog::error()
🎉 Conclusione e Best Practices
Congratulazioni! Hai appena implementato un sistema di monitoraggio degli errori potente, leggibile e professionale per la tua applicazione Laravel.
💡 Suggerimenti per l'Uso Ottimale
1. Personalizza gli Embeds
Sentiti libero di modificare la struttura dell'embed in DiscordWebhookHandler per aggiungere informazioni utili:
'embeds' => [
[
'title' => $record->level->getName() . ': ' . $record->message,
'description' => '```json' . PHP_EOL . json_encode($record->context, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL . '```',
'color' => $this->getColorForLevel($record->level),
'timestamp' => $record->datetime->format('c'),
'fields' => [
[
'name' => 'Environment',
'value' => config('app.env'),
'inline' => true,
],
[
'name' => 'URL',
'value' => request()->fullUrl() ?? 'N/A',
'inline' => true,
],
],
'footer' => [
'text' => config('app.name') . ' v' . config('app.version', '1.0'),
],
],
],
2. Anti-Spam: Scegli il Livello Giusto
Resisti alla tentazione di abbassare DISCORD_LOG_LEVEL a info o debug in produzione. Rischieresti di inondare il canale con centinaia di notifiche irrilevanti.
Livelli consigliati per ambiente:
- Production:
errorocritical - Staging:
warningoerror - Development: usa solo il file di log, disabilita Discord
3. Separa gli Ambienti
Usa Webhook e canali Discord diversi per ogni ambiente:
# Production (.env.production)
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/xxx/prod-webhook
# Staging (.env.staging)
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/xxx/staging-webhook
In questo modo potrai:
- Distinguere visivamente i messaggi (usa colori/icone diverse)
- Applicare livelli di log diversi per ambiente
- Gestire i permessi del canale in modo granulare
4. Monitora Più Progetti
Se gestisci più applicazioni Laravel, centralizza tutti i log in un unico server Discord usando canali diversi:
#logs-sito-X#logs-sito-Y#logs-crm-sito-Z
5. Integra con il Flusso di Lavoro del Team
- Mention automatiche: Modifica l'embed per menzionare utenti specifici su errori critici
- Thread automatici: Discord permette di creare thread da webhook per organizzare le discussioni
- Emoji reactions: Usa le reactions per segnare gli errori come "risolti" o "in lavorazione"
🔗 Risorse Utili
Ti è stata utile? Se questa guida ti è stata utile, non esitare a farmelo sapere!
Galleria Immagini _