Defining a custom logger channelfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Think back to the last lesson's single "my_module" channel — fine for a small module, but what happens when your module grows to handle payments, imports, and user notifications all at once? Dumping everything into one channel is like a company routing every email — sales, support, security alerts — into a single shared inbox. A custom log channel is how you give each concern its own dedicated inbox, filterable independently.

What you'll learn in this lesson

  • Why you'd want more than one logging channel per module
  • How to register a dedicated channel via a service definition
  • How to inject and use a specific named channel instead of your module's default one
  • How channels show up (and get filtered) in the log report

Declaring a custom channel as a service

A logger channel isn't something you configure in a settings form — it's registered as a service, tagged so Drupal's logger factory knows to expose it under a specific name. In my_module.services.yml:

services:
  logger.channel.my_module_imports:
    parent: logger.channel_base
    arguments: ['my_module_imports']

This is the standard pattern Drupal core itself uses for every one of its own channels (logger.channel.cron, logger.channel.php, and so on). parent: logger.channel_base reuses the base channel service's wiring, and the single argument, 'my_module_imports', is the channel name that will show up in the log report's Type column — distinct from your module's default channel.

Using the dedicated channel

class ImportService {

  public function __construct(
    protected LoggerInterface $importsLogger,
  ) {}

  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('logger.channel.my_module_imports'),
    );
  }

  public function runImport(): void {
    $this->importsLogger->notice('Import job started.');
    // ... import logic ...
    $this->importsLogger->notice('Import job finished: @count records.', ['@count' => 42]);
  }

}

Nothing about calling ->notice() changes from the last lesson — the only difference is which service you injected. Your import-related noise now lives entirely under its own channel name, completely separate from anything else my_module logs.

Naming convention: prefix every custom channel with your module's machine name (my_module_imports, not just imports) — channel names share one global namespace across every module on the site, and an unprefixed name like imports could collide with another module's channel of the same name.

See it for yourself

Clear caches after adding the service definition (new services require a cache rebuild to register), trigger some code that logs through your new channel, then visit Reports → Recent log messages (/admin/reports/dblog). Your custom channel name will appear as its own distinct value in the Type filter, completely separate from your module's default channel.

Quick check: you forget to run drush cr after adding a new logger.channel.* service definition, then try to inject it. What happens? You'd get a "Service not found" container exception — service definitions are collected into the compiled container at cache-rebuild time, not read live from the YAML file on every request, so a new or changed service simply doesn't exist yet until the container is rebuilt.

Key takeaways

  • A custom log channel is a service, declared with parent: logger.channel_base and one argument — the channel name.
  • Prefix every custom channel with your module's machine name to avoid colliding with another module's channel.
  • Inject the specific logger.channel.* service you want rather than the generic factory when a class should only ever log to one particular channel.
  • New or changed service definitions require a cache rebuild before they're available to inject.
  • Splitting channels by concern (imports, security, performance) makes the log report dramatically easier to filter on a busy site.

Coming up next

Drupal's own logging system is one thing — but Drupal also has to interoperate with the classic watchdog hook and legacy logging expectations some older code still assumes. Next, we'll look at how Drupal's modern logger system relates to and replaces the old watchdog model.