Logging in Drupal 8
Buona parte dei siti Drupal in circolazione sono vere e proprie applicazioni, con una considerevole mole di funzionalità, integrazioni e logica. Spesso è necessario tenere traccia di quello che succede nell'applicazione, per capire cosa è andato storto in caso di errore o anche solo per monitorare l'attività degli utenti. Drupal ha da sempre un meccanismo di logging piuttosto limitato, che permette allo sviluppatore di identificare il tipo di messaggio da salvare, il messaggio stesso, un livello di attenzione (che va da "debug" a "emergency") e poco altro:
watchdog('content', '@type: added %title.', $watchdog_args, WATCHDOG_NOTICE, $node_link);
Il sistema si occupa poi di aggiungere qualche metadato e l'informazione che viene salvata nel database è:
L'unica cosa che possiamo fare è redirigere i log verso un'altra destinazione, e il core propone solo Syslog.
Chi viene da altri framework, Symfony ad esempio, è abituato a ben altra flessibilità. Li lo stato dell'arte è rappresentato da una libreria, Monolog, scritta dallo stesso sviluppatore che ha inventato Composer. Monolog supporta canali (Channels) di log differenti, ciascuno dei quali associati a degli Handler che sono in grado di scrivere un messaggio di log sulle destinazioni più disparate, da un semplice database passando per Syslog fino a soluzioni più evolute come postare un messaggio su Slack o Logstash. La cosa interessante di questi handler è che scrivono la riga di log solo se il livello di attenzione è superiore ad una certa soglia, possiamo quindi inviare a Logstash tutti i log ma a Slack (o per email) solo gli errori più gravi di "Critical". A chiudere questo elenco di funzionalità abbiamo i Formatter, per decidere la forma del messaggio di log, e i Processor, per aggiungere metadati al messaggio di log (come ad esempio l'indirizzo IP del client o i dati dell'utente).
Bello, ma possiamo avere tutta questa flessibilità anche in Drupal? Certo, "there's a module for that!". Il modulo Monolog esisteva già per Drupal 7, ma è con Drupal 8 e la sua nuova architettura che l'integrazione è perfetta. Vediamo come funziona.
Prima di tutto dobbiamo scaricare e installare il modulo Monolog e la sua dipendenza, Composer manager. Se lo facciamo con Drush alla fine del processo dovremmo ritrovarci con i due moduli installati e la libreria scaricata nella cartella vendor/monolog.
Il modulo è già preconfigurato con due canali, default e php. Il canale default salva i log su Syslog mentre il canale php salva i log sull'error_log standard.
Nella versione per Drupal 7 (e anche in una versione preliminare per Drupal 8) era possibile modificare la configurazione degli handler direttamente dall'interfaccia web del CMS. La versione attuale invece non ha interfaccia grafica, è stato scelto di "sacrificare" la semplicità di utlizzo a favore di un'architettura più efficiente e con meno dipendenze.
Immaginiamo ad esempio di voler salvare tutti i messaggi di log su di un file all'interno del filesystem privato di Drupal, quello che dobbiamo fare è creare un file dal nome services.yml all'interno della cartella sites/default del nostro sito:
services: monolog.handler.file: class: Monolog\Handler\StreamHandler arguments: ['private://monolog/debug.log', '200', false]
Il contenuto di questo file viene unito al Service Container, ossia il componente di Drupal che raccoglie tutti i servizi disponibili, in modo da consentire a ciascun sito di personalizzare i servizi da usare e i loro argomenti. In questo caso stiamo ridefinendo il servizio "monolog.handler.file" (originariamente presente in monolog.services.yml), passandogli come argomenti il file sui cui fare log, il livello di attenzione minimo da inserire nei log (200 corrisponde al livello INFO) e il bubbling, ossia se questo è l'ultimo handler della catena o se il messaggio deve continuare ad essere processato da successivi handler. Per fare in modo che Drupal si accorga del contenuto di questo file è sufficiente svuotare la cache.
Vediamo ora come estendere il modulo per definire nuovi canali e Processor.
Aggiungere un canale è piuttosto semplice, possiamo usare la chiave "parameters" del file services.yml:
parameters: monolog.channel_handlers: webprofiler: ['file'] monolog.processors: ['message_placeholder', 'current_user', 'request_uri', 'ip', 'referer']
In questo esempio abbiamo aggiunto un canale nuovo in modo da poter separare e gestire diversamente i log generati dal modulo Webprofiler. Per scrivere log su questo nuovo canale è sufficiente usare:
\Drupal::logger('webprofiler')->info('Info message');
Per definire un nuovo Processor è necessario usare uno dei nuovi concetti di Drupal 8: i servizi. Supponiamo di voler aggiungere a tutti i messaggi di log il tema attivo, iniziamo con il creare un nuovo modulo: monolog_theme_processor nel quale definiremo un servizio (nel file monolog_theme_processor.services.yml):
services: monolog.processor.theme: class: Drupal\monolog_theme_processor\Logger\Processor\ThemeProcessor arguments: ['@theme.manager']
Il nuovo servizio prende come argomento un altro servizio, il theme.manager. Vediamo ora il codice della classe ThemeProcessor:
<?php namespace Drupal\monolog_theme_processor\Logger\Processor; use Drupal\Core\StringTranslation\TranslatableMarkup; use Drupal\Core\Theme\ThemeManagerInterface; use Drupal\monolog\Logger\Processor\ProcessorInterface; ThemeProcessor { private $themeManager; public function __construct(ThemeManagerInterface $themeManager) { $this->themeManager = $themeManager; } public function __invoke(array $record) { $record['extra']['theme'] = $this->themeManager->getActiveTheme()->getName(); return $record; } }
La classe riceve nel costruttore un'istanza del servizio theme.manager. Durante il salvataggio dei messaggi di log il processor viene invocato e ha la possibilità di aggiungere elementi all'array $record, in questo caso usiamo il manager del tema per recuperare il nome del tema attivo. Per attivare questo processor è sufficiente aggiungere la chiave "theme" (ossia l'identificativo del servizio senza monolog.processor.) ad un canale definito in services.yml:
parameters: monolog.channel_handlers: default: ['file'] monolog.processors: ['message_placeholder', 'current_user', 'request_uri', 'ip', 'referer', 'theme'] services: monolog.handler.file: class: Monolog\Handler\StreamHandler arguments: ['public://monolog/debug.log', '200', false]
Il risultato che otteniamo è questo:
Nel messaggio, tra gli altri dati, possiamo vedere che il tema attivo era </strong>Bartik</strong>.
Mentre scrivo questo articolo il modulo Monolog non è ancora disponibile in una versione stabile per Drupal 8 ma è funzionante e usabile anche in ambienti di produzione.
Con questo tipo di integrazioni Drupal 8 si prospetta sempre più interessante in ambito enterprise dove questo tipo di funzionalità è indispensabile.