Vous êtes ici

Créer des logs dans vos modules

Vous avez 30 secondes ?
S'abonner au flux d'actualités
Rubrique: 
Technique
Difficultée: 
Facile
Dans ce tutoriel, nous allons voir comment utiliser le loggeur de Drupal pour logger des informations lors de l'exécution de votre module. Nous verrons également quelques astuces intéressantes utilisant le logger.
Loggeur:
Un loggeur est un outil permettant de gérer des logs, c'est à dire de maintenanir un journal d'événements. La consultation de ce journal permet l'historisation et le debug.

Prérequis

Dans ce tutoriel, nous allons reprendre notre module helloworld et logger un message d'information à chaque fois qu'un utilisateur visitera la page /hello_world. Nous partirons donc du code obtenu à la fin du tutoriel "Création d'un module Hello World".

Logger un événement

Drupal dispose d'une API permettant de logger un événement. Cette API est étendue par divers loggeurs qui vont chacun enregistrer les événements d'une manière différente. Par défaut, c'est le dblog qui est utilisé : les logs seront enregistrés en base de données dans la table watchdog.
Dans Drupal 8, une interface de visualisation des logs est disponible dans la section Rapports à l'URL suivante : admin/reports/dblog. Ce journal regroupe l'ensemble des logs récents de votre site et permet l'usage de plusieurs filtres pour retrouver le log qui vous intéresse.
 
Le journal des événements récents regroupe les logs issus de tous les modules de votre site.
 
Le saviez-vous ?
Sur les anciennes versions de Drupal, cette fonctionnalités s'appelait le watchdog et était utilisable via la fonction watchdog(). Désormais, il s'agit du Logging Framework dont l'interface est Psr/Log/LoggerInterface. Plusieurs loggeurs peuvent implémenter cette interface afin d'enregistrer les logs de diverses manières. Database Logging (dblog) est le loggeur activé par défaut dans Drupal : il enregistre les logs en base de donnée dans la table watchdog. Le module Syslog est également disponible mais non activé par défaut. Celui-ci enregistre les logs directement dans le service syslog de votre serveur.
 
L'enregistrement d'un log s'effectue via la fonction \Drupal::logger($channel)->log($severity, $message, $context). Les paramètres sont les suivants :
  • channel : le nom du système émettant le log. En théorie, ce peut être n'importe quelle chaîne de caractères mais il est décidé par convention d'utiliser le nom machine du module responsable du log.
  • severity : la sévérité du log. Le choix de la sévérité doit être décidé en suivant la norme RFC 3164. Les valeurs possibles sont, par ordre de criticité :
    • WATCHDOG_EMERGENCY : une urgence, le site est désormais inutilisable.
    • WATCHDOG_ALERT : une alerte, une action doit être effectuée immédiatement.
    • WATCHDOG_CRITICAL
    • WATCHDOG_ERROR
    • WATCHDOG_WARNING
    • WATCHDOG_NOTICE : (par défaut) utilisé en situation normale pour un log classique.
    • WATCHDOG_INFO : un message d'information sans incidence.
    • WATCHDOG_DEBUG : un message utile au debug.
  • message : le message à logger.
  • context : un tableau associatif des variables de remplacement dans le message (voir exemple ci-dessous)
Il n'est pas conseillé d'utiliser directement la fonction log(), mais plutôt les différentes fonctions wrapper pour chacune des sévérités. Celles-ci sont de la forme severity($message, array $variables = array()). On retrouve ainsi :
  • emergency($message, array $context = array())
  • alert($message, array $context = array())
  • critical($message, array $context = array())
  • error($message, array $context = array())
  • warning($message, array $context = array())
  • notice($message, array $context = array())
  • info($message, array $context = array())
  • debug($message, array $context = array())
 
En pratique :
  • ouvrir le fichier HelloWorldController.php de votre module HelloWorld.
HelloWorldController est un contrôleur pour la page HelloWorld. Il se trouve dans src/Controller. Si vous ne le savez pas encore, vous voudrez probablement d'abord lire ce tuto : Mon premier module Drupal 8 : HelloWorld.
  • A l'intérieur de la méthode helloworld(), ajoutons quelques lignes de débug :
public function helloWorld() {
   $output = array();
   $account = \Drupal::currentUser();
   
   if ($account->id() == 1) {
     \Drupal::logger('hello_world')->debug("Site owner came here !");
   }
   else {
     \Drupal::logger('hello_world')->error(
         '%username (id=%id) came here : No one should be here but site owner.',
         array('%username' => $account->getUsername(), '%id' => $account->id())
     );
   }
   
   $output['hello_world'] = array(
     '#markup' => $this->t('Hello World!'),
   );
   
   return $output;
}
Dans le code ci-dessous, nous récupérons les données de l'utilisateur visitant la page via : \Drupal::currentUser(). Ensuite, si l'utilisateur a l'ID 1, c'est que nous avons affaire à l'administrateur du site : c'est le premier utilisateur créé lors de l'installation de votre site Drupal. Nous affichons alors un log destiné simplement au débug, via la fonction wrapper \Drupal::logger('hello_world')->debug().
 
Information de debug : le propriétaire du site a visité la page hello_world.
 
Si l'utilisateur n'est pas un administrateur, le message affiché est une erreur. Notez l'utilisation des placeholders %username et %id qui sont remplacés par les valeurs indiquées dans le tableau associatif des paramètres.
 
Log d'erreur : un visiteur anoyme a vu la page hello_world.
Remarque :
Les visiteurs anonymes possèdent l'ID 0.
Il existe la fonction \Drupal::currentUser()->isAnonymous() qui renvoit TRUE si l'utilisateur est un visiteur anonyme.
Au contraire, la fonction \Drupal::currentUser()->isAuthenticated() renvoit TRUE si l'utilisateur est authentifié.
 
Attention !
Le test sur l'ID de l'utilisateur effectué dans ce tutoriel est donné purement à titre d'exemple. Ce n'est pas la bonne approche si vous souhaitiez interdire l'accès à vos pages à certains utilisateurs ! Pour savoir comment restreindre l'accès à vos pages ou aux fonctionnalités de vos modules, lisez le tutoriel : Gestion des permissions dans vos modules.

Création d'un nouveau loggeur

Nous avons vu précédement dans le tutoriel que le Logging Framework définit l'interface Psr/Log/LoggerInterface. En d'autres mots plusieurs loggeurs peuvent coexister pour stocker / exposer les logs. Nous avons ainsi évoqué Database Logging et Syslog, deux loggeurs intégrés à Drupal core. Nous allons voir -brièvement- comment créer un nouveau loggeur pour notre module.
 
Théorie (niveau avancé, pour ceux qui veulent tout comprendre uniquement) :
D'un point de vue théorique, sachez que Drupal définit un service nommé logger.factory (dans core.services.yml) associé à la classe LoggerChannelFactory. Cette factory est capable de retourner l'ensemble des loggers associés à un channel. Les services sont une notion venue de l'intégration de Symfony 2 dans Drupal 8 et très bien expliqué dans leur documentation. Nous aborderons la création et l'utilisation des services dans Drupal 8 dans un prochain tutoriel. Pour l'instant, sachez ceci :
Lorsque vous souhaitez logger un message, vous utilisez la fonction \Drupal::logger($channel)->log(). La méthode logger() est un wrapper appelant la méthode get($channel) du service logger.factoy : une fonction renvoyant une instance de la classe LoggerChannel. Nous l'avons vu, en pratique le channel demandé est le nom du module et les modules peuvent utiliser l'ensemble des loggers disponibles. Il est toutefois possible de restreindre un channel en particulier à un seul logger. Quoi qu'il en soit la méthode log() de la classe LoggerChannel est ensuite appelée. Cette méthode va alors itérer sur chacun des loggers déclarés du site et appeler leur méthode log(). En conséquence, votre message sera loggué via l'ensemble des méthodes définis par les loggers actifs de votre site ! Voilà, j'avais prévenu que cette partie théorique serait un peu corsée : reprenons en pratique la création d'un nouveau logger.
 
Prérequis :
Considérons que nous ayons créé un module à l'image de notre Hello World mais qui s'appellerait EmailLogger et dont le but serait d'envoyer par mail tous les logs de niveau "erreur" ou "critique". Le nom machine de ce module est emailogger.
 
En pratique :
  • Dans un premier temps, nous allons enregister notre loggeur comme nouveau service. Pour cela, créez un fichier nommé emailogger.services.yml à la racine de votre module. Il contiendra le code suivant :
services:
  logger.emailogger:
    class: Drupal\emailogger\Logger\EmailLogger
    arguments: ['@logger.log_message_parser']
    tags:
      - { name: logger }
Dans ce fichier de configuration (nom_du_module.services.yml), nous retrouvons la structure :
  • services : nous allons ajouter un service à la liste des services.
    • logger.emailogger : nous déclarons un nouveau service nommé logger.emaillogger (plus généralement logger.nom_du_module).
      • class : namespace de la classe implémentant ce service.
      • arguments : tableau représentant dans l'ordre les arguments passés dans le constructeur de notre classe EmailLogger.
      • tags : nous tagguons notre loggeur par le nom "logger" afin qu'il soit regroupé avec les autres loggeurs et utilisé par le bon bundle (voir la doc).
Nous allons maintenant implémenter la classe EmailLogger. En tant que loggeur, cette classe doit implémenter l'interface LoggerInterface. Par convention, cette classe doit être placée dans le dossier src/Logger de votre module.
  • Créez le fichier src/Logger/EmailLogger.php dans votre module. Il contient le code suivant :
/**
 * @file
 * Contains \Drupal\emailogger\Logger\EmailLogger.
 */

namespace Drupal\emailogger\Logger;

use Drupal\Core\Logger\LogMessageParserInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LoggerTrait;

/**
 * Implemente l'interface LoggerInterface : redirige les logs vers EmailLogger.
 */

class EmailLogger implements LoggerInterface {
  // LoggerTrait permet de rediriger les fonctions emergency(), error(), debug(),
  // etc.. vers la méthode log() avec le bon niveau de criticité.
  // Risque de changer prochainement, voir :
  // https://drupal.org/node/2267545
  use LoggerTrait;

  /**
   * Le parser pour le remplacement des placeholders dans le message.
   *
   * @var \Drupal\Core\Logger\LogMessageParserInterface
   */

  protected $parser;

  /**
   * Construit l'objet EmailLogger.
   *
   * @param \Drupal\Core\Logger\LogMessageParserInterface $parser
   *   Le parser à utiliser pour le remplacement des variables dans le message
   *   de log.
   */

  public function __construct(LogMessageParserInterface $parser) {
    $this->parser = $parser;
  }

  /**
   * {@inheritdoc}
   */

  public function log($level, $message, array $context = array()) {

    // Remplace les placeholders du message par leur bonne valeur.
    $message_placeholders = $this->parser->parseMessagePlaceholders($message, $context);
    $message = empty($message_placeholders) ? $message :
      strtr($message, $message_placeholders);

    // A partir de maintenant :
    // $level : un chiffre de 0 à 7 suivant le niveau de criticité du message
                ce chiffre respecte la norme RFC3164.
    // $message : le texte du message à logger.

    // Si la criticité est "error" ou plus :
    if ($level <= 3) {

      // Récupère l'email du site depuis la configuration system.
      $from = \Drupal::config('system.site')->get('mail');
      // $to = ...
      //
      // @TODO : envoyer un email.
      // L'envoi d'un email sera l'objet d'un tutoriel ultérieur.
    }

  }
}

Les commentaires dans ce code très simple devraient suffir à l'expliquer ! Nous avons simplement implémenté la méthode log() de l'interface LoggerInterface qu'implémente notre classe EmailLogger.
 
Les points suivont feront l'objet d'un tutoriel dédié :
- création et utilisation des services
- envoi d'un email via un module
- enregistrement et utilisation d'options de configuration pour votre module
Notation: 
Average: 4.3 (3 votes)
Vous avez aimé: