How to Log Messages in Drupal 8

Drupal 8 tutorial

Note: For related topics to this tutorial that you may need to understand, you may be interested in tutorials about using dependency injection, which is needed to get the logger service into your code. Understand the Service Container and Get a Service from the Container are good places to start with that. Additionally the Entity API Hooks tutorial uses logging as an example when working with entities.

Developers familiar with Drupal 7 will also be familiar with watchdog(), an API function that allows you to create a log message that appears on the Reports page in the Drupal administrative UI. Implementing D7’s hook_watchdog allows module developers to customize the destination of these log messages. In Drupal 8, both functions are replaced by a PSR-3-compatible logging interface (change notice).

In this tutorial, you’ll learn how to create the D8 equivalent of D7’s watchdog() function: creating a log message that appears on the Reports administrative page. In a future tutorial, we’ll cover how to create your own logger, and use it in your custom module. However, I've left you some hints and pointers at the end of this tutorial if you care to dive in before then.

Before and after

There is a straight-up D8 procedural equivalent of D7’s watchdog function, as we learn from the change notice:

Drupal 7 watchdog()

// Logs a notice
watchdog('my_module', $message, array());
// Logs an error
watchdog('my_module', $message, array(), WATCHDOG_ERROR);

Drupal 8 logger class

// Logs a notice
// Logs an error

Learn from Examples

I downloaded the Examples for Developers module (version 8.x-1.x, but please consult the project page for the latest version) and looked for an example of this in action. I found a good one in the content_entity_example module within the Examples project.

After downloading Examples, open the project in your code editor or IDE of choice. (With Drupal 8’s object-oriented framework, I highly recommend using an IDE such as PhpStorm, simply because it makes navigating classes, methods, and their implementations so much easier. If you’re new to using an IDE, we have a nice set of PhpStorm IDE how-to videos from the creators, JetBrains, available on Drupalize.Me.)

Let’s take a look at the ContactDeleteForm class in examples/content_entity_example.

In your code editor, navigate to:

If you have Examples downloaded to a Drupal 8 installation, that path is:

In the submitform() method, we see the following:

public function submitForm(array &$form, FormStateInterface $form_state) {
    $entity = $this->getEntity();

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

Let’s break this down.

Annotated logger

a. \Drupal::

In this example, the submitForm method relies on the Drupal service container wrapper, a class which smoothes the transition from procedural to object-oriented code in the Drupal code base. The double colon means that we’re accessing a class from “elsewhere,” or outside of the class that it’s called from. It’s kind of like using an absolute path instead of relative one.

b. logger

A helper method that quickly creates a copy of the logger service.

In core/lib/Drupal.php, we find:

public static function logger($channel) {
    return static::getContainer()->get('logger.factory')->get($channel);

So, from this, it looks like the logger method is way to kick-start the logger factory and get a copy of the logger to use in our code.

c. module name

The name of the module, in this case “content_entity_example.”

d. severity-level method

The name of the method in the \Drupal::logger service (in this case “alert”), which will correspond to a severity level, i.e. debug, info, notice, warning, error, critical, alert, emergency. These can be also found in the \PSR\Log\LoggerInterface in the file DRUPALROOT/vendor/psr/log/Psr/Log/LoggerInterface.php

e. log message

This can be a simple quoted PHP string, or a string that utilizes placeholders. In the latter case, pass in an associative array of specially formatted placeholder keys and values into the second parameter.

f. placeholders (array)

An array of placeholder keys and values. The key is the placeholder name with the appropriate prefix (see below for explanation), and the value is what should be used as a value. In this case, a method is called that supplies the value. Placeholders are provided so that values from context may be provided in the message. The logger will transform any placeholder messages to a safe format for output to HTML. For more information, see:

Placeholder types

@variable — Use this style of placeholder for most use-cases. Special characters in the text will be converted to HTML entities.

%variable — Use this style of placeholder to pass text through drupal_placeholder() which will result in HTML escaped text, then wrapped with <em> tags.

:variable — Use this style of placeholder when substituting the value of an href attribute. Values will be HTML escaped, and filtered for dangerous protocols.

How to view log messages as administrator

Log messages created with the \Drupal::logger service can be viewed and filtered on the Reports page of the administrative UI. In the Administrative menu, go to Reports > Recent log messages. On this page is a list of recent log messages which you can filter by type and severity.

Recent log messages page

Why log messages?

Why would you want to use logging in your modules? You know how debug messages are really valuable during development? Well, when a site is in production and debug messages are no longer an option on a public site, log messages are a way to keep tabs on important things that are happening on your site—from notices about users signing in, to major errors that signal a problem that needs immediate attention. They can help you troubleshoot problems and find errors—both large and small—that need attention.

What should I be logging?

You can and should log anything that will help you keep tabs on what your users are doing, how the system is performing, and behind-the-scenes, automatic notifications, which can help you troubleshoot various errors. Things like:

  • Actions performed by users
  • Recoverable errors
  • "Recurly notification processed"

As a developer, visit your Reports page often to learn about notices and recoverable errors that can let you know about places in your code that need attention. They may not be bringing down the system, but they are having some kind of impact and may point to one or more bugs.

Exercise: Implement your own logger

In Drupal 7, you could implement hook_watchdog in your module if you wanted to log messages to custom locations. In Drupal 8, hook_watchdog has been removed, and you now register your logger as a service in your module’s, and then in your logger class, implement the \Psr\Log\LoggerInterface and customize the logging destinations as you wish.

Several examples and variations of this can be found in core; for example, in contact, aggregator, and syslog core modules.

In a future tutorial, we’ll walk you through how to implement your own custom logger. For now, start using Drupal 8's logger to create log messages in your fresh new Drupal 8 modules and keep an eye on that Reports page!

Happy logging!

Related Topics: 


For anything related to forms, keep in mind if your form class extends FormBase in any way, you automatically have a logger() method available that should be used instead of \Drupal::logger():

Thanks Dave! Excellent point.

...and this will also be true for any Controller that has the service container injected into it. If you're extending a ControllerBase of some kind, be sure and check that parent class to see what other goodies you already have. See also

Another wheel reinvented. What was wrong with watchdog? Now we have two loggers? Crazy.

Near the end it says " In Drupal 8, hook_watchdog has been removed, ".

No, I was talking about the FormBase logger.

The logger called by FormBase will be the same one, but accessed through dependency injection (i.e is passed into the original form constructor as a parameter), so that if it some other kind of logging was wanted for forms, it could be done seamlessly. It's not creating a new logger at all, but allows other modules to do so if they wanted. Then you could have form errors logged & listed somewhere completely different if you wanted :-)

Ok. Makes sense. Thank you James.

To make mocking easier in testing, it's better to create a simple service that instantiates a LoggerChannel and to pass this service into your class's constructor (if it doesn't extend ControllerBase or FormBase already).

The change record explains how to do that:

tl;dr: Avoid calling \Drupal::anything() unless you're inside a procedural function and you haven't passed the service as an argument (e.g. it's a hook), in which case you can't really do better.

I am learning Drupal as a Drupal novice but with lots of years of experience in software and hardware. I am finding Drupal awkward and frustrating, with a lack of clear, straightforward examples. I have to say that this article, though, was very helpful to me. I often find a blog post which aims to demystify a Drupal subject, but it will be written by someone assuming the reader has familiarity with certain aspects of Drupal which the author (incorrectly) considers trivial and omits. As a Drupal novice, you have, by definition, no familiarity with Drupal. In particular, you do not necessarily know the location, naming conventions, contents format etc of the php source being described. And if these things are not absolutely correct, it does not work. I am finding that there is almost no step in Drupal which does not require familiarity with these things. In this article at least, Drupal structures are clearly described which makes it possible for novices like me to use it without getting more frustrated. Thank You.


Can I use print or kint on logs

Sure, but logs can be more conveniently accessed by logging in as an administrator and navigating to Reports > Recent log messages. All logs are saved there and can be filtered by type and/or severity.

The documentation for the use of ':variable' as a placeholder doesn't match the current behaviour (still present in 8.5), because of

Until this is fixed, you will need to use '!variable' instead of ':variable'.

If doing some logging inside a controller, I feel that it is worth pointing out that the ControllerBase class comes with a bunch of useful Traits. One of which is the LoggerChannelTrait. With this instead of writing \Drupal::logger('my_module')->error($message), one could refactor this to $this->getLogger('my_module')->error($message). The code is now not tightly coupled and not dependent on a global method which helping with testing.
In addition to this there is no need to use 'my_module' as an argument for getLogger. Anything will do and the word you decide on will conveniently appear 'Type' column of the Recent Log Messages display

Add new comment