Problem/Motivation
On Drupal 11.4, any container rebuild that logs — drush cim, drush cr, config import during deploy — fails with a circular reference:
Circular reference detected for service "Drupal\Core\Logger\LoggerChannelFactoryInterface", path: LoggerChannelFactoryInterface -> logger.raven -> Drupal\menu_link_content\Hook\MenuLinkContentHooks -> plugin.manager.menu.link -> Drupal\Core\Menu\MenuTreeStorageInterface -> cache_tags.invalidator -> plugin.manager.block -> logger.channel.default
Drupal 11.4 converted core hooks into OOP service classes (e.g. MenuLinkContentHooks) that are registered on the event dispatcher. Raven::__construct() eagerly injects @event_dispatcher and calls $this->getClient() (which dispatches OptionsAlter). Constructing logger.raven therefore instantiates the dispatcher and, transitively, the menu-link and block plugin managers, which need logger.channel.default — while the logger channel factory that owns logger.raven is still being built. That closes the loop.
This is the same class of 11.4 regression as Scheduler #3595625; it is being addressed per-module by minimizing eager dependencies on early-instantiated logger services. It is distinct from the previously fixed raven circular references (#3584527, #3593734, #3457197), which are different service paths.
Steps to reproduce
- Drupal core 11.4.x, Raven 7.4.1, with a configured DSN (
client_key). - Run
drush cim(or anydrush crthat triggers a container rebuild while logging). - Observe the
Circular reference detectedfatal above.
Proposed resolution
Stop resolving the event dispatcher while logger.raven is being constructed:
- Remove the
event_dispatcherconstructor injection. - Defer
getClient()out of the constructor — it is already invoked at the top oflog(), so the client still initializes on first use. - Resolve the dispatcher lazily at its two
dispatch()call sites (OptionsAlter,AttributesAlter).
Verified: with this change drush cim completes and Sentry still captures events (SDK captureMessage returns SUCCESS; the logger path forwards without error). MR attached.
Open question for the maintainer: the eager getClient() in the constructor is intentional ("scope immediately available"), but it is precisely what triggers the cycle under 11.4. If a different approach is preferred for preserving early scope availability, happy to adjust.
Remaining tasks
- Maintainer review of the deferred-init approach vs. an alternative that preserves eager scope setup.
- Automated test coverage for container compilation under the 11.4 OOP-hook service graph.
User interface changes
None.
API changes
Behavioral: the Sentry client now initializes lazily on the first log()/getClient() call rather than in the logger constructor. No signature or public API changes.
Data model changes
None.
Issue fork raven-3612284
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #3
talishkhan commentedMR !73 opened against 7.x.
Root cause: On Drupal 11.4, core hooks were converted to OOP service classes (e.g. MenuLinkContentHooks) registered on the event dispatcher. Raven::__construct() eagerly injects @event_dispatcher and calls getClient(), so building logger.raven resolves the dispatcher — and transitively the menu-link and block plugin managers — while the logger channel factory is still being built, creating a circular reference back to the factory:
LoggerChannelFactoryInterface -> logger.raven
-> Drupal\menu_link_content\Hook\MenuLinkContentHooks
-> plugin.manager.menu.link -> Drupal\Core\Menu\MenuTreeStorageInterface
-> cache_tags.invalidator -> plugin.manager.block -> logger.channel.default
Any container rebuild that logs (drush cim, drush cr, config import on deploy) fails. It's the same 11.4 class of regression as Scheduler #3595625, and distinct from the previously-fixed raven circular refs (#3584527, #3593734), which are different service paths.
Fix: stop resolving the dispatcher during logger.raven construction — drop the event_dispatcher constructor injection, defer getClient() to its first real call (already invoked at the top of log()), and resolve the dispatcher lazily at its two dispatch() call sites.
Verified on Drupal 11.4.4 / raven 7.4.1: drush cim now completes, and Sentry still captures events (\Sentry\captureMessage() → SUCCESS, and the \Drupal::logger()->error() path forwards without error).
Open question: the eager getClient() in the constructor is intentional (scope immediately available), but it's exactly what triggers the cycle under 11.4. Deferring it to the first log() is the smallest change that breaks the cycle; happy to adjust if you'd prefer a different way to preserve early scope setup.
Comment #4
mfbI was wondering if something is missing from your steps to reproduce, like some other contrib or custom module? Because I didn't see a problem after installing Raven and running drush cr or drush cim.
So far, we have been initializing Sentry as soon as reasonably possible, rather than lazily loading it, to make the scope available to handle fatal errors, such as memory limit exceeded (Drupal doesn't - yet - handle these via logging), and any calls to Sentry methods in custom code (e.g. adding tags, which would otherwise be lost).
Comment #5
talishkhan commentedThanks , good questions on both.
Reproduction: you're right that plain drush cr doesn't trigger it , it didn't for me either.
Environment is Drupal 11.4.4 (upgraded from 11.3.12), Standard profile plus ~70 contrib modules, with menu_link_content and block enabled. It surfaced on drush cim while importing a config backlog that installed modules i.e. an in-process container rebuild happening while Drupal was logging "module installed" messages. Full path:
It's all core services, so I don't think a contrib module is the cause but something in our set makes logger.raven resolve during that mid-import rebuild in a way a no-op cim doesn't. I'm happy to bisect and hand you a minimal repro if that helps.
Design: understood, and agreed — eager init matters for fatals and early scope. The trouble is that the eager getClient() in the constructor dispatches OptionsAlter, and under 11.4's service-based hooks that resolves the dispatcher's listener graph while the logger channel factory is still building logger.raven and that's the loop. Making only the dispatcher lazy breaks compile-time detection but still cycles at runtime once the constructor calls getClient(), so deferral was the only way I found to fully break it.
Would you be open to keeping eager initialization but moving it out of the service constructor — e.g. from a very-early kernel.request subscriber / early bootstrap hook so it still fires "as soon as reasonably possible" for scope and fatals, but after the container is built? Or if you have a preferred approach, I'll rework the MR to match.
Comment #6
mfbIt sounds like you might be close to figuring out the actual steps to reproduce. Then it would be good to add a failing test, that way we can ensure that any solution works.
We do have a kernel.request subscriber already, we would just have to find out what breaks if we were to only rely on that to initialize Sentry. For example, I think currently the "dr" CLI command only initializes Sentry because we forcefully initialize it from the logger constructor.
Comment #7
rosk0Noting that I do not see this problem on the Drupal 11.4.3 and PHP 8.3, and Raven 7.4.1...
Comment #8
mfbI didn't find that constructing logger.raven necessarily instantiates the menu-link and block plugin managers. So I'm guessing there is something else going on in your case and the issue summary should be clarified.
Comment #9
mfbPostponing this until we have a failing test or steps to reproduce.
Comment #10
mfb#3609333: Circular reference for logger.channel.default via plugin.manager.block when syslog is enabled (breaks drush cex/cim/status on 11.4) appears to be a similar (or even the same) issue.
Comment #11
longwaveAlso running into this when upgrading a site to Drupal 11.4. It happens when the discovery bin is emptied and another drush command is run:
Comment #12
longwaveJust removing
$this->getClient();from the constructor is enough to fix this for me. The lazy service initialisation in the MR is not necessary (and incorrectly handled in Drupal, as the discussion states).Comment #13
mfbdoes not throw a circular reference for me, on a fresh drupal install. Can you verify the steps to reproduce - maybe some particular module or config is needed?
So, would be excellent if we can write a failing test for this or at least clarify the steps to reproduce. Otherwise it's not easy for me to review and/or work on.
Also worth double-checking if this issue is unique to raven module or also triggered by core syslog module.
Comment #14
longwaveHm it was reproducible on my setup, but I don't know what's different; this is a complex install with a bunch of contrib and a few custom modules. I don't have syslog installed.
Comment #15
longwaveClaude Code thinks the required combination is Custom Menu Links and Layout Builder from core, plus Symfony Mailer from contrib. Symfony Mailer has a config factory override that also performs plugin discovery. Will upload a test shortly, although it's a bit contrived.
Comment #16
longwaveComment #17
longwaveAlso opened #3622391: Use tagged iterators in CacheTagsInvalidator in core which would solve this from the other side.
Comment #18
mfbSolving in core would be excellent, if possible. Since core syslog module also fails the test, possibly it could be added to core.
Meanwhile, we should at least work on lazier initializing of Sentry in the next major version - if not sooner.
Comment #22
mfbWent ahead and merged this - hopefully side effects are minimal - we should be initializing Sentry reasonably early in the code paths that I know about. Thanks!