Coding

Logging stuff in Drupal 7/8/9

Published by Alan Saunders on Sunday, January 24, 2021 - 22:00

Reading Time: 6 minutes

Sections

Title
Introduction

Body

This is just a quick blog post with some code that can be used to log messages to Drupals main log (Watchdog), as it is one of the common things that I seem to be looking up to make sure I have the syntax correct.

The way of logging something to the log is different between Drupal 7 and Drupal 8/9, so I will be covering both methods in this blog post.

Title
Drupal 7

Body

In Drupal 7, there is a function provided called watchdog, the function takes a number of different parameters.

watchdog($type, 
         $message, 
         $variables = array(), 
         $severity = WATCHDOG_NOTICE, 
         $link = NULL);

$type The category to which this message belongs. Can be any string, but the general practice is to use the name of the module that is calling the watchdog function.

$message The message to store in the log.

$variables Array of variables to replace in the message on display or NULL if message is already translated or not possible to translate.

$severity The severity of the message. Possible values are:

$link A link to associate with the message.

As shown in the code below, the minimum you need is the type and message.

watchdog('my_module', 'my error message');

The below example adds in the contents of a variable and sets the severity to the WATCHDOG_NOTICE constant.

watchdog('mail', 
         'Email injection exploit attempted in email form subject: ' .
         check_plain($form_state['values']['subject']), 
         WATCHDOG_NOTICE);

You can also output arrays and objects in a prettier/ more readable manner too, you just need to concatenate pre tags to your output.

watchdog('views_logging', '<pre>' . $output . '</pre>');

The below example is using placeholders that when rendered will be replaced with the value from an array that maps values to placeholders. Just to note, that the log entry is added to the database with the placeholders, it is only when the message is output into a UI or similar that the placeholders are rendered with the specified values.

watchdog('workflow', 
         'Attempt to go to nonexistent transition (from %old to %new)', 
         array('%old' => $old_sid, '%new' => $sid, WATCHDOG_ERROR));

Title
Drupal 8/9 functional implementation

Body

In Drupal 8/9, the watchdog function was replaced with the logger class. The logger class still contains the same severities as methods.

So to log a message with the different severities, you can use the following the logger calls, the my_module in the logger call should be replaced with the module name in which the call is being made, but realistically this can be anything, as it is just making it easier for you to find the entries in the log, as you can filter the list by type of my_module in this instance.

The below code shows you the different logging calls that we can make specifying the severity method that we want.

// Logs some information.
\Drupal::logger('my_module')->info($message);

// Logs an error
\Drupal::logger('my_module')->error($message);

// Logs an alert
\Drupal::logger('my_module')->alert($message);

// Logs some debug
\Drupal::logger('my_module')->debug($message);

// Logs a critical error
\Drupal::logger('my_module')->critical($message);

// Logs an emergency error
\Drupal::logger('my_module')->emergency($message);

// Logs a notice
\Drupal::logger('my_module')->notice($message);

// Logs a warning
\Drupal::logger('my_module')->warning($message);

The below example shows how you can log a notice using placeholders in the message, the values of which just like Drupal 7 are mapped to the placeholders in an array in the logger call.

\Drupal::logger('content_entity_example')->notice('@type: deleted %title.',
        array(
            '@type' => $this->entity->bundle(),
            '%title' => $this->entity->label(),
        ));

Title
Drupal 8/9 object oriented dependency injection implementation

Body

We've just gone over using the logger class, but the code used is mainly used for the likes of the .module file and anywhere else that uses a more functional programming style than an object oriented style. There is nothing stopping you from using the code we have just mentioned in your object oriented classes, but the better method is to use dependency injection to pull in the logger classes needed for us to log a message.

The below code is mostly the code of a controller from our blog post where we created a custom page.

<?php

namespace Drupal\custom_page\Controller;

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Class CustomPageController.
 */
class CustomPageController extends ControllerBase implements ContainerInjectionInterface {

  protected $loggerFactory;

  /**
   * CustomPageController constructor.
   * @param LoggerChannelFactoryInterface $logger_factory
   */
  public function __construct(LoggerChannelFactoryInterface $logger_factory)
  {
    $this->loggerFactory = $logger_factory->get('custom_page');
  }

  /**
   * @param ContainerInterface $container
   *
   * @return ContainerInjectionInterface|static
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('logger.factory')
    );
  }

  /**
   * Custompage.
   *
   * @param null $name
   * @return array
   *   Return Hello string.
   */
  public function CustomPage($name = NULL) {
    $message = "Hello World";
    $this->loggerFactory->info($message);
    $this->loggerFactory->error($message);
    $this->loggerFactory->alert($message);
    $this->loggerFactory->debug($message);
    $this->loggerFactory->critical($message);
    $this->loggerFactory->emergency($message);
    $this->loggerFactory->notice($message);
    $this->loggerFactory->warning($message);
    $this->loggerFactory->log('info', $message);

    return [
      '#theme' => 'custom_page_template',
      '#custom_text' => $this->t('Hello World'),
      '#name' => $name
    ];
  }

}

I have set the code to log a message each time the page is loaded, which isn't great in the long run, but for testing/ trying things out, it is perfectly fine. I aim to talk about dependency injection in a future blog post as it is a quite a tricky subject to get your head around.

First we need to implement the ContainerInjectionInterface and we initialise $this->loggerFactory with the logger channel that you want to use, the logger channel is essentially the $type parameter that we would use in Drupal 7 and this is usually the name of the module where the logger call is being made.

Using the create function, we then need to grab an instance of the logger factory from the container, so that we can actually get access to the logger methods and use the logger functionality.

We should then be able to call one of the logger methods as shown in our class above, which I have added below for ease:

$this->loggerFactory->info($message);
$this->loggerFactory->error($message);
$this->loggerFactory->alert($message);
$this->loggerFactory->debug($message);
$this->loggerFactory->critical($message);
$this->loggerFactory->emergency($message);
$this->loggerFactory->notice($message);
$this->loggerFactory->warning($message);
$this->loggerFactory->log('info', $message);

The last line in the above code is a generic log function where we can pass the type parameters ourselves, a bit like the watchdog function in Drupal 7, instead of calling the method for the type that we want. The former may be useful for some situations depending on how something has been built.

Title
[module name].services.yml

Body

For some classes in Drupal 8 like services that need to be registered with Drupal using a [module name].services.yml file in the custom module, before they can be used. This is so that Drupal knows that the class exists and is then able to use the class. If we want to use the logger functionality in one of these classes, then we need to pass logger.factory as an argument, so that Drupal knows that it needs to connect the dots, so that we can access the logger methods. If we don't pass the argument and we try to use the logger API functionality, then you will most likely get an error due to their not being enough arguments being passed to the class.

services:
  my_module.my_module_service:
    class: Drupal\my_moduel\MyModuleService
    arguments: ['@logger.factory']

The below images show our custom logging messages in the watchdog interface of Drupal.

Image
Watchdog entries using the different logger levels
Image
Watchdog entry showing our logged message.
Categories:

Related blog posts

Building a custom page in Drupal 8/9

Authored by Alan Saunders on Sunday, December 27, 2020 - 10:00
Reading Time: 12 minutes

Building a Drupal 8/9 custom block

Authored by Alan Saunders on Sunday, January 3, 2021 - 10:00
Reading Time: 5 minutes

Creating a custom form in Drupal 8/9

Authored by Alan Saunders on Sunday, January 10, 2021 - 10:00
Reading Time: 7 minutes