Skip to content

Log broker availability events as String messages#36856

Open
kodacme wants to merge 1 commit into
spring-projects:mainfrom
kodacme:fix/cyclic-object-graph-event-log
Open

Log broker availability events as String messages#36856
kodacme wants to merge 1 commit into
spring-projects:mainfrom
kodacme:fix/cyclic-object-graph-event-log

Conversation

@kodacme
Copy link
Copy Markdown

@kodacme kodacme commented May 30, 2026

Overview

AbstractBrokerMessageHandler logs BrokerAvailabilityEvent directly at INFO level when the broker becomes (un)available:

logger.info(this.availableEvent);

With a structured JSON logging layout (e.g. Logstash/ECS encoders backed by Jackson), the logging framework may serialize the event object rather than its toString(). It then traverses BrokerAvailabilityEvent.getSource(), which is the SimpleBrokerMessageHandler itself.

That object graph contains a cyclic reference:

SimpleBrokerMessageHandler["clientInboundChannel"]
  -> ExecutorSubscribableChannel["executor"]
    -> ThreadPoolTaskExecutor["threadPoolExecutor"]
      -> ThreadPoolTaskExecutor$1["threadFactory"]   // anonymous inner class, holds outer ref
        -> ThreadPoolTaskExecutor["threadPoolExecutor"]
          -> ...

Because the thread factory is a non-static inner class, it keeps an implicit reference back to the enclosing ThreadPoolTaskExecutor, producing an effectively infinite graph. Jackson aborts at its maximum nesting depth:

com.fasterxml.jackson.databind.JsonMappingException:
Document nesting depth (1001) exceeds the maximum allowed (1000,
from `StreamWriteConstraints.getMaxNestingDepth()`)

The application keeps starting, but startup logs are flooded with a very large stack trace, which can hide real problems.

Change

Log the event's toString() representation instead of the event object, in both publishBrokerAvailableEvent() and publishBrokerUnavailableEvent():

logger.info(this.availableEvent.toString());

BrokerAvailabilityEvent.toString() already produces a concise, safe message, so the operational signal is preserved while structured logging layouts no longer traverse framework internals.

Notes

  • Behavior with plain text layouts is unchanged (they already called toString()).
  • Added a test verifying both lifecycle methods log the expected String messages.

Prior to this commit, AbstractBrokerMessageHandler logged the
BrokerAvailabilityEvent object directly at INFO level. With a structured
JSON logging layout, the event could be serialized as an object rather
than via toString(), causing the layout to traverse the event source
(a SimpleBrokerMessageHandler) object graph. That graph contains a
cyclic reference through the client inbound channel executor's thread
factory, which fails serialization at the maximum nesting depth.

This commit logs the event's toString() representation instead, keeping
the same operational signal while preventing structured logging layouts
from traversing framework internals.

Signed-off-by: kodacme <kodac.saito@kodac.me>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label May 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

status: waiting-for-triage An issue we've not yet triaged or decided on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants