Problem/Motivation

On Drupal 11.4 with the core Syslog module enabled, the service container hits a circular reference during CLI container compilation. Any Drush command that eagerly resolves a logger channel fails, for example drush cex, drush cim and drush status:

In Container.php line 143:

  Circular reference detected for service "Drupal\Core\Logger\LoggerChannelFactoryInterface", path: "Drupal\Core\DefaultContent\Command\ContentExportCommand -> Drupal\Core\DefaultContent\Exporter -> logger.channel.default_content -> Drupal\Core\Logger\LoggerChannelFactoryInterface -> logger.syslog -> Drupal\menu_link_content\Hook\MenuLinkContentHooks -> plugin.manager.menu.link -> Drupal\Core\Menu\MenuTreeStorageInterface -> cache_tags.invalidator -> plugin.manager.block -> logger.channel.default".

The closing edge of the cycle is core's own service definition: plugin.manager.block takes @logger.channel.default as a plain constructor argument. Because BlockManager is collected as a cache-tags invalidator (service_collector), it is pulled into cache_tags.invalidator at compile time, which drags in the full logger graph. With Syslog enabled, logger.channel.default -> logger.factory -> logger.syslog reaches the autowired MenuLinkContentHooks (via the menu link plugin manager) and loops back through cache_tags.invalidator -> plugin.manager.block.

This is stock core: the only modules involved (syslog, menu_link_content, block) are all core. No contrib is required to reproduce.

Disabling Syslog makes the error disappear; re-enabling it brings it back. Which command sits at the head of the path varies (I have seen ContentExportCommand, and RecipeCommand which autowires logger.channel.default directly), but the cyclic tail is always the same: ... -> cache_tags.invalidator -> plugin.manager.block -> logger.channel.default -> logger.factory -> logger.syslog -> ...

The same cyclic tail is being worked around one-module-at-a-time across the contrib ecosystem, which strongly suggests the fix belongs in core:

  • Scheduler — #3595625 (identical logger.syslog -> menu_link_content hooks -> ... -> plugin.manager.block path, reported on 11.4.0-beta1)
  • Ultimenu — #3419031 ("Circular reference detected for service plugin.manager.block")
  • Modeler API — #3576308 (... -> cache_tags.invalidator -> plugin.manager.block -> logger.channel.default -> logger.factory -> logger.syslog)
  • Commerce — #3587604 (LoggerChannelFactoryInterface via plugin.manager.block -> logger.channel.default)

Related umbrella issue: #3103620 (Syslog logger being non-leaf because it depends on config storage). That issue is stalled in "Needs work" and frames the fix around config storage; this issue is narrower and directly addresses the plugin.manager.block edge.

Steps to reproduce

  1. Install Drupal 11.4.1.
  2. Enable the core Syslog module: drush en syslog -y
  3. Run any config command, e.g. drush cex -y (or drush status).

Expected: config is exported.
Actual: the command aborts with ServiceCircularReferenceException for LoggerChannelFactoryInterface (see path above).

Environment where this was observed: Drupal 11.4.1, Drush 13.7.5, PHP 8.3.31, MariaDB 10.11.

Proposed resolution

Pass the logger to BlockManager as a service closure so the container does not resolve logger.channel.default at compile time. The logger is only used at runtime, in handlePluginNotFound(), so lazy resolution is sufficient and there is no behavioural change.

Drupal's YAML loader supports the @> service-closure prefix (equivalent to !service_closure). Change the argument in core/core.services.yml:

  plugin.manager.block:
    class: Drupal\Core\Block\BlockManager
    parent: default_plugin_manager
    arguments: ['@>logger.channel.default']

and unwrap the closure at the single use site in core/lib/Drupal/Core/Block/BlockManager.php. The constructor accepts LoggerInterface|\Closure so existing direct instantiations (and subclasses) remain backwards compatible:

public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler, LoggerInterface|\Closure $logger) {
  ...
  $this->logger = $logger;
}

protected function handlePluginNotFound($plugin_id, array $configuration) {
  $logger = $this->logger instanceof \Closure ? ($this->logger)() : $this->logger;
  $logger->warning('The "%plugin_id" block plugin was not found', ['%plugin_id' => $plugin_id]);
  return parent::handlePluginNotFound($plugin_id, $configuration);
}

This breaks the cycle for every command at once, regardless of what else closes the loop (menu hooks, router, route_provider, etc.), because plugin.manager.block no longer forces the logger graph to resolve at compile time.

A full patch is attached. It has been verified on 11.4.1: after applying it, drush cex, drush cim and drush status all succeed, the block-not-found warning path still logs correctly (the closure resolves to a Drupal\Core\Logger\LoggerChannel), and web requests are unaffected.

Remaining tasks

  • Review the service-closure approach vs. removing the service_collector eagerness or fixing Syslog to be a leaf service (see #3103620).
  • Add a test that compiles the container with Syslog enabled and asserts no circular reference (a kernel test enabling syslog + menu_link_content and resolving cache_tags.invalidator).
  • Decide whether the same treatment is warranted for other plugin managers that inject @logger.channel.default directly.

User interface changes

None

Introduced terminology

None

API changes

BlockManager::__construct() now accepts \Psr\Log\LoggerInterface|\Closure for the $logger parameter (previously \Psr\Log\LoggerInterface). This is backwards compatible — a plain logger is still accepted.

Data model changes

None

Release notes snippet

None

Comments

doxigo created an issue. See original summary.

doxigo’s picture

Issue summary: View changes
doxigo’s picture

cilefen’s picture

Status: Active » Postponed (maintainer needs more info)
Issue tags: +Needs steps to reproduce, +Needs merge request

I am unable to reproduce the bug according to the "steps to reproduce".

longwave’s picture

markusa’s picture

I experienced the same issue, and my AI led me here, and this patch solved it.
Thanks @doxigo

markusa’s picture

i guess it was a little different for me to replicate.
I got the error doing an drush cr -y drush cache:warm -y in automated deployments.
The cache warming would fail consistently when ran immediately after the cache rebuild.
Syslog module enabled.

codebaboon’s picture

I too have received this error related to syslog and a similar patch that moves to a Closure() on the LoggerInterface injection fixed it.

knyshuk.vova’s picture

Status: Postponed (maintainer needs more info) » Needs review

While upgrading from version 11.3.13 to version 11.4.4.

drush cex

In Container.php line 143:
Circular reference detected for service "logger.channel.default", path: "module_installer -> logger.channel.default -> logger.factory -> logger.mylog -> Drupal\menu_link_content\Hook\MenuLinkContentHooks -> plugin.manager.menu.link -> Drupal\Core\Menu\MenuTreeStorageInterface -> cache_tags.invalidator -> plugin.manager.block".

The patch from #3 fixes the error.

smustgrave’s picture

Version: 11.4.x-dev » main
Status: Needs review » Needs work

Patches need to be in MRs please with test coverage included.

I've seen this before too when doing the 10 to 11 jump but for me the issue came from scheduler and acquia_connector

longwave’s picture

It would be good to see if the any of the fixes from #3103620: Dependency on config storage causes circular reference in service container solve the problem as well, because it's not clear which way to go here - solving it just for the block manager might not be conclusive for everyone else if the real problem is in another service.