Score:5

Extending contrib module filelog service class displays PSR implementation error

nl flag

I wanted to override the logger.filelog service from filelog module to make use of my own parser.

An error occurred through drush cim and drush cr when the new custom module that overrides the service is enabled.

The website encountered an unexpected error. Please try again later.
Symfony\Component\DependencyInjection\Exception\LogicException: Service 'logger.filelog' for consumer 'logger.factory' does not implement Psr\Log\LoggerInterface. in Drupal\Core\DependencyInjection\Compiler\TaggedHandlersPass->processServiceCollectorPass() (line 182 of /app/docroot/core/lib/Drupal/Core/DependencyInjection/Compiler/TaggedHandlersPass.php).

My custom modules structure looks like this

custom_module
  - src
    - Logger
      - TestFilelog.php
    CustomModuleServiceProvider.php
  custom_module.info.yml
  custom_module.module

Currently the service provider class has implemented ServiceModifierInterface and altered the original logger.filelog service by setting its class to Drupal\custom_module\Logger\TestFilelog.

/**
 * Class to override contrib module logger.filelog service.
 */
class CustomModuleServiceProvider implements ServiceModifierInterface {

  /**
   * {@inheritdoc}
   */
  public function alter(ContainerBuilder $container) {
    if ($container->has('logger.filelog')) {
      $definition = $container->getDefinition('logger.filelog');
      $definition->setClass('Drupal\custom_module\Logger\TestFilelog');
    }
  }

}

TestFilelog.php

namespace Drupal\custom_module\Logger;

use Drupal\filelog\Logger\FileLog;

/**
 * File-based logger.
 */
class TestFileLog extends FileLog {

  /**
   * Renders a message to a string.
   *
   * @param mixed $level
   *   Severity level of the log message.
   * @param string $message
   *   Content of the log message.
   * @param array $context
   *   Context of the log message.
   *
   * @return string
   *   The formatted message.
   */
  protected function render($level, $message, array $context = []): string {

    $plainString = parent::render($level, $message, $context);

    $custom_channel = [
      'filter_custom_channel'
    ];

    if (in_array($context['channel'], $custom_channel)) {
      $plainString = $this->parseStrMasking($plainString);
    }
    return $plainString;
  }

  /**
   * Mask personal details from strings to logs.
   *
   * @param string $pureString
   *   The string to be parsed.
   *
   * @return string
   *   The string that has been masked.
   */
  protected function parseStrMasking(string $pureString) {
    // Default to return finalString.
    $finalString = $pureString;
    
    // Custom parsing goes here.
    
    return $finalString;
  }

}

I see that Drupal\filelog\Logger\FileLog; has use RfcLoggerTrait; which does implement the Psr\Log\LoggerInterface, what am I missing here??

cn flag
Your code works fine for me - have you tried restarting Apache/PHP-FPM?
cn flag
You do have a small discrepancy though - you're mixing the casing of `TestFileLog` (it's `Testfilelog` in the filename and service alter). Might make a difference if you're on a case-sensitive file system
Michael Chen avatar
nl flag
OMG @Clive you are absolutely right! Kudos for you mate!
Michael Chen avatar
nl flag
FYI, while struggling last night, I'd discovered another way to override the contrib module's service through custom_module.services.yml. It works, so I made use of it instead. Thanks for your effort on testing and reading through, thanks!
Score:2
nl flag

As pointed out by @Clive in the comment above, I had a discrepancy in the casing of TestFilelog and TestFileLog. Thanks again!

This should make a difference on why the implementation of interface wasn't read by Drupal on service handling.

On the other hand, I discovered another approach to override filelog module's service, which I took instead.

  1. I deleted the CustomModuleServiceProvider.php file
  2. Created custom_module.services.yml
  3. service machine name should be the same as the overridden service. (e.g. logger.filelog)
    Check docroot/modules/contrib/filelog/filelog.services.yml

Content of custom_module.services.yml

services:
  logger.filelog:
    class: Drupal\custom_module\Logger\TestFileLog
    arguments:
      - '@config.factory'
      - '@state'
      - '@token'
      - '@datetime.time'
      - '@logger.log_message_parser'
      - '@filelog.file_manager'
    tags:
      - { name: logger }

Ref:

  1. How to decorate Services - Symfony
  2. Altering existing services - Drupal
  3. Overriding services in Drupal - PreviousNext
fr flag
Redefining the service like that would be my last choice. Better choices are the way you did it in the original question, or by decoration. The reason is that defining your own logger.filelog in the services.yml file does nothing to indicate that this is an overridden service - the intention of the programmer is obscured (was it just a mistake that a previously-named service was used?). Also, what happens if the Filelog module is loaded *after* your module - which service actually gets defined, yours or the Filelog service? And what happens if another module overrides logger.filelog?
Michael Chen avatar
nl flag
Oh dear! Your concern do have a point, maybe I should refine it back to the original format and check that it works. On the other hand, is there other resources that I may reference in decoration of service? I only read that decoration extends the original service, but while implementing it, I don't actually know how I should override the original service with a decorate service. Cheers!
fr flag
This is a pretty good article which should help: https://www.phase2technology.com/blog/using-symfony-service-decorators-drupal-8
Michael Chen avatar
nl flag
Thanks for the info, this article has been really helpful as it written every detail of their consideration and thinking process.
mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.