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.blockpath, 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 (
LoggerChannelFactoryInterfaceviaplugin.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
- Install Drupal 11.4.1.
- Enable the core Syslog module:
drush en syslog -y - Run any config command, e.g.
drush cex -y(ordrush 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_collectoreagerness 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_contentand resolvingcache_tags.invalidator). - Decide whether the same treatment is warranted for other plugin managers that inject
@logger.channel.defaultdirectly.
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
| Comment | File | Size | Author |
|---|---|---|---|
| #3 | core--block-manager-logger-circular-ref-11.4.patch | 3.33 KB | doxigo |
Comments
Comment #2
doxigo commentedComment #3
doxigo commentedComment #4
cilefen commentedI am unable to reproduce the bug according to the "steps to reproduce".
Comment #5
longwavePossible duplicate of #3103620: Dependency on config storage causes circular reference in service container
Also, if you used AI to generate this issue summary, please ensure you read and comply with the AI contribution policy: https://www.drupal.org/docs/develop/issues/issue-procedures-and-etiquett...
Comment #6
markusa commentedI experienced the same issue, and my AI led me here, and this patch solved it.
Thanks @doxigo
Comment #7
markusa commentedi guess it was a little different for me to replicate.
I got the error doing an
drush cr -y drush cache:warm -yin automated deployments.The cache warming would fail consistently when ran immediately after the cache rebuild.
Syslog module enabled.
Comment #8
codebaboon commentedI too have received this error related to syslog and a similar patch that moves to a Closure() on the LoggerInterface injection fixed it.
Comment #9
knyshuk.vova commentedWhile upgrading from version 11.3.13 to version 11.4.4.
drush cexThe patch from #3 fixes the error.
Comment #10
smustgrave commentedPatches 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
Comment #11
longwaveIt 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.