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.

smustgrave’s picture

Status: Needs work » Closed (duplicate)

I hit the issue here and tried the patch mentioned in #11 and it immediately solved the issue (did have to tweak the MR to not use \Closure). Moving over credit for the patch.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

mfb’s picture

Status: Closed (duplicate) » Needs work

Hmm, if there is a workable fix to inject the logger as a closure, wouldn't it make sense to resolve this issue, while we work separately (very slowly) on the logger dependencies issue? If not, feel free to re-close.

longwave’s picture

Status: Needs work » Closed (duplicate)

I still think this is a duplicate and suspect the fix is not quite enough, it might just move the circular reference to later when the logger is actually used. Let's deal with this properly in the other issue.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

mfb’s picture

Agree that it's "not enough" - however, it also seems reasonable to lazily load the logger, when possible, regardless of how logger modules are eventually (soon?) fixed.