15 mins Davide Lobascio
Loggare Errori su Discord da Laravel
Notifiche LOG direttamente su un server Discord. Con un canale per ogni progetto, permette di centralizzare il monitoraggio dei progetti da me gestiti su uno strumento, Discord, che di default è sempre open, e quindi di facile visione.
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!