This is an automated email from the ASF dual-hosted git repository.

dakirily pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/qpid-broker-j.git


The following commit(s) were added to refs/heads/main by this push:
     new 30a6e6357b QPID-8768: [Broker-J] Remove obsolete Logback 
circular-exception workaround (#449)
30a6e6357b is described below

commit 30a6e6357bbe1f9e5b57452de0134cbc0cea1272
Author: usugumo <[email protected]>
AuthorDate: Mon Oct 19 11:19:02 2026 +0200

    QPID-8768: [Broker-J] Remove obsolete Logback circular-exception workaround 
(#449)
---
 .../logback/Logback1027WorkaroundTurboFilter.java  | 119 --------
 .../LogbackLoggingSystemLauncherListener.java      |   5 -
 .../Logback1027WorkaroundTurboFilterTest.java      | 280 -----------------
 .../logback/LogbackExceptionHandlingTest.java      | 332 +++++++++++++++++++++
 4 files changed, 332 insertions(+), 404 deletions(-)

diff --git 
a/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/Logback1027WorkaroundTurboFilter.java
 
b/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/Logback1027WorkaroundTurboFilter.java
deleted file mode 100644
index 5180f31a2e..0000000000
--- 
a/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/Logback1027WorkaroundTurboFilter.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *
- */
-
-package org.apache.qpid.server.logging.logback;
-
-import java.io.IOException;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.Collections;
-import java.util.IdentityHashMap;
-import java.util.Set;
-
-import ch.qos.logback.classic.Level;
-import ch.qos.logback.classic.Logger;
-import ch.qos.logback.classic.turbo.TurboFilter;
-import ch.qos.logback.core.spi.FilterReply;
-import org.slf4j.Marker;
-
-public class Logback1027WorkaroundTurboFilter extends TurboFilter
-{
-    @Override
-    public FilterReply decide(final Marker marker,
-                              final Logger logger,
-                              final Level level,
-                              final String format,
-                              final Object[] params,
-                              final Throwable t)
-    {
-        if (t != null && hasRecursiveThrowableReference(t, null))
-        {
-            final int locationAwareLoggerInteger = 
Level.toLocationAwareLoggerInteger(level);
-            logger.log(marker, logger.getName(), locationAwareLoggerInteger, 
format, params, new StringifiedException(t));
-            return FilterReply.DENY;
-        }
-
-        return FilterReply.NEUTRAL;
-    }
-
-    private boolean hasRecursiveThrowableReference(Throwable t, Set<Throwable> 
seen)
-    {
-        if (t == null)
-        {
-            return false;
-        }
-
-        if (seen == null)
-        {
-            seen = Collections.newSetFromMap(new IdentityHashMap<>());
-        }
-
-        if (!seen.add(t))
-        {
-            return true;
-        }
-        else
-        {
-            final Throwable[] allSuppressed = t.getSuppressed();
-            int allSuppressedLength = allSuppressed.length;
-            if (allSuppressedLength > 0)
-            {
-                Set<Throwable> seenCopy = Collections.newSetFromMap(new 
IdentityHashMap<>(seen.size()));
-                seenCopy.addAll(seen);
-                for (int i = 0; i < allSuppressedLength; ++i)
-                {
-                    if (hasRecursiveThrowableReference(allSuppressed[i], 
seenCopy))
-                    {
-                        return true;
-                    }
-                }
-            }
-            if (hasRecursiveThrowableReference(t.getCause(), seen))
-            {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    static class StringifiedException extends RuntimeException
-    {
-        public StringifiedException(final Throwable t)
-        {
-            super(stringifyStacktrace(t));
-        }
-
-        private static String stringifyStacktrace(final Throwable e)
-        {
-            try (StringWriter sw = new StringWriter();
-                 PrintWriter pw = new PrintWriter(sw))
-            {
-                e.printStackTrace(pw);
-                pw.println("End of stringified Stacktrace");
-                return sw.toString();
-            }
-            catch (IOException e1)
-            {
-                throw new RuntimeException("Unexpected exception stringifying 
stacktrace", e1);
-            }
-        }
-    }
-
-}
diff --git 
a/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/LogbackLoggingSystemLauncherListener.java
 
b/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/LogbackLoggingSystemLauncherListener.java
index a56e2702e1..90b781c784 100644
--- 
a/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/LogbackLoggingSystemLauncherListener.java
+++ 
b/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/LogbackLoggingSystemLauncherListener.java
@@ -32,7 +32,6 @@ public class LogbackLoggingSystemLauncherListener implements 
SystemLauncherListe
 {
     private StartupAppender _startupAppender;
     private ch.qos.logback.classic.Logger _logger;
-    private Logback1027WorkaroundTurboFilter _logback1027WorkaroundTurboFilter;
 
     @Override
     public void beforeStartup()
@@ -45,9 +44,6 @@ public class LogbackLoggingSystemLauncherListener implements 
SystemLauncherListe
         }
 
         final LoggerContext loggerContext = _logger.getLoggerContext();
-        _logback1027WorkaroundTurboFilter = new 
Logback1027WorkaroundTurboFilter();
-        loggerContext.addTurboFilter(_logback1027WorkaroundTurboFilter);
-
         _startupAppender = new StartupAppender();
         _startupAppender.setContext(loggerContext);
         _startupAppender.start();
@@ -85,7 +81,6 @@ public class LogbackLoggingSystemLauncherListener implements 
SystemLauncherListe
     public void onContainerClose(final SystemConfig<?> systemConfig)
     {
         QpidLoggerTurboFilter.uninstallFromRootContext();
-        
_logger.getLoggerContext().getTurboFilterList().remove(_logback1027WorkaroundTurboFilter);
     }
 
 
diff --git 
a/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/Logback1027WorkaroundTurboFilterTest.java
 
b/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/Logback1027WorkaroundTurboFilterTest.java
deleted file mode 100644
index 5d42f26141..0000000000
--- 
a/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/Logback1027WorkaroundTurboFilterTest.java
+++ /dev/null
@@ -1,280 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- *
- */
-
-package org.apache.qpid.server.logging.logback;
-
-import static org.apache.qpid.test.utils.JvmVendor.IBM;
-import static org.apache.qpid.test.utils.JvmVendor.OPENJDK;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assumptions.assumeFalse;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-import ch.qos.logback.classic.Level;
-import ch.qos.logback.classic.Logger;
-import ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.classic.spi.IThrowableProxy;
-import ch.qos.logback.core.Appender;
-import ch.qos.logback.core.Context;
-import ch.qos.logback.core.LogbackException;
-import ch.qos.logback.core.filter.Filter;
-import ch.qos.logback.core.spi.FilterReply;
-import ch.qos.logback.core.status.Status;
-
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import org.apache.qpid.test.utils.UnitTestBase;
-
-public class Logback1027WorkaroundTurboFilterTest extends UnitTestBase
-{
-    private static final String TEST_LOG_MESSAGE = "hello";
-    private final Logback1027WorkaroundTurboFilter _filter = new 
Logback1027WorkaroundTurboFilter();
-    private Logger _logger;
-    private SnoopingAppender _snoopingAppender;
-
-    @BeforeEach
-    public void setUp() throws Exception
-    {
-        final LoggerContext context = new LoggerContext();
-        _logger = 
context.getLogger(Logback1027WorkaroundTurboFilterTest.class);
-        _snoopingAppender = new SnoopingAppender();
-        _logger.addAppender(_snoopingAppender);
-    }
-
-    @Test
-    public void testOneException()
-    {
-        final Exception e = new Exception();
-        final FilterReply reply = doDecide(e);
-        assertEquals(FilterReply.NEUTRAL, reply);
-
-        assertEquals(0, (long) _snoopingAppender.getEvents().size());
-    }
-
-    @Test
-    public void testSuppressedExceptionRecursion()
-    {
-        // 
https://www.ibm.com/developerworks/community/forums/html/topic?id=8482d948-665c-47a2-862e-457e49ac71a4&ps=25
-        assumeFalse(Objects.equals(getJvmVendor(), OPENJDK) || 
Objects.equals(getJvmVendor(), IBM),
-                "(QPID-7955) Behavioural difference between the IBM JDK and 
the Open JDK");
-
-        final Exception e1 = new Exception();
-        final Exception e2 = new Exception();
-        e2.addSuppressed(e1);
-        e1.addSuppressed(e2);
-
-        final FilterReply reply = doDecide(e1);
-        assertEquals(FilterReply.DENY, reply);
-
-        final List<ILoggingEvent> events = _snoopingAppender.getEvents();
-        assertEquals(1, (long) events.size());
-
-        assertLoggingEvent(events.get(0));
-    }
-
-    private void assertLoggingEvent(final ILoggingEvent loggingEvent)
-    {
-        assertEquals(Level.INFO, loggingEvent.getLevel());
-        assertEquals(TEST_LOG_MESSAGE, loggingEvent.getMessage());
-        assertNull(loggingEvent.getArgumentArray());
-        final IThrowableProxy thing = loggingEvent.getThrowableProxy();
-        
assertEquals(Logback1027WorkaroundTurboFilter.StringifiedException.class.getName(),
 thing.getClassName());
-    }
-
-    @Test
-    public void testInitCauseRecursion()
-    {
-        // 
https://www.ibm.com/developerworks/community/forums/html/topic?id=8482d948-665c-47a2-862e-457e49ac71a4&ps=25
-        assumeFalse(Objects.equals(getJvmVendor(), OPENJDK) || 
Objects.equals(getJvmVendor(), IBM),
-                "(QPID-7955) Behavioural difference between the IBM JDK and 
the Open JDK");
-
-        final Exception e1 = new Exception();
-        final Exception e2 = new Exception();
-        e2.initCause(e1);
-        e1.initCause(e2);
-
-        final FilterReply reply = doDecide(e1);
-        assertEquals(FilterReply.DENY, reply);
-        assertEquals(1, (long) _snoopingAppender.getEvents().size());
-    }
-
-    @Test
-    public void testNoRecursion()
-    {
-        final Exception e1 = new Exception();
-        final Exception e2 = new Exception();
-        final Exception e3 = new Exception();
-
-        e2.addSuppressed(e3);
-        e1.addSuppressed(e2);
-        e1.initCause(e3);
-
-        final FilterReply reply = doDecide(e1);
-        assertEquals(FilterReply.NEUTRAL, reply);
-        assertEquals(0, (long) _snoopingAppender.getEvents().size());
-    }
-
-    @Test
-    public void testNoRecursion2()
-    {
-        final Exception e1 = new Exception();
-        final Exception e2 = new Exception();
-        final Exception e3 = new Exception();
-
-        e2.initCause(e3);
-        e1.initCause(e2);
-        e1.addSuppressed(e3);
-
-        final FilterReply reply = doDecide(e1);
-        assertEquals(FilterReply.NEUTRAL, reply);
-        assertEquals(0, (long) _snoopingAppender.getEvents().size());
-    }
-
-    private FilterReply doDecide(final Exception e1)
-    {
-        return _filter.decide(null, _logger, Level.INFO, "hello", null, e1);
-    }
-
-    private static class SnoopingAppender implements Appender<ILoggingEvent>
-    {
-
-        private final List<ILoggingEvent> _events = new ArrayList<>();
-
-        List<ILoggingEvent> getEvents()
-        {
-            return _events;
-        }
-
-        @Override
-        public void doAppend(final ILoggingEvent event) throws LogbackException
-        {
-            _events.add(event);
-        }
-
-        @Override
-        public String getName()
-        {
-            return null;
-        }
-
-        @Override
-        public void setName(final String name)
-        {
-        }
-
-        @Override
-        public void setContext(final Context context)
-        {
-        }
-
-        @Override
-        public Context getContext()
-        {
-            return null;
-        }
-
-        @Override
-        public void addStatus(final Status status)
-        {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void addInfo(final String msg)
-        {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void addInfo(final String msg, final Throwable ex)
-        {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void addWarn(final String msg)
-        {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void addWarn(final String msg, final Throwable ex)
-        {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void addError(final String msg)
-        {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void addError(final String msg, final Throwable ex)
-        {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void addFilter(final Filter<ILoggingEvent> newFilter)
-        {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void clearAllFilters()
-        {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public List<Filter<ILoggingEvent>> getCopyOfAttachedFiltersList()
-        {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public FilterReply getFilterChainDecision(final ILoggingEvent event)
-        {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void start()
-        {
-        }
-
-        @Override
-        public void stop()
-        {
-        }
-
-        @Override
-        public boolean isStarted()
-        {
-            return true;
-        }
-    }
-}
\ No newline at end of file
diff --git 
a/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/LogbackExceptionHandlingTest.java
 
b/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/LogbackExceptionHandlingTest.java
new file mode 100644
index 0000000000..618b2e59f1
--- /dev/null
+++ 
b/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/LogbackExceptionHandlingTest.java
@@ -0,0 +1,332 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.logging.logback;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verifyNoInteractions;
+
+import java.util.Arrays;
+import java.util.List;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.PatternLayout;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.StackTraceElementProxy;
+import ch.qos.logback.classic.spi.ThrowableProxy;
+import ch.qos.logback.classic.util.LogbackMDCAdapter;
+import ch.qos.logback.core.read.ListAppender;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.MockedStatic;
+import org.slf4j.LoggerFactory;
+import org.slf4j.Marker;
+import org.slf4j.MarkerFactory;
+
+import org.apache.qpid.test.utils.UnitTestBase;
+
+public class LogbackExceptionHandlingTest extends UnitTestBase
+{
+    private static final String TEST_LOG_MESSAGE = "hello";
+    private static final Marker TEST_MARKER = 
MarkerFactory.getMarker("exception-handling-test");
+
+    private LoggerContext _loggerContext;
+    private Logger _logger;
+    private ListAppender<ILoggingEvent> _appender;
+    private PatternLayout _layout;
+
+    @BeforeEach
+    public void setUp()
+    {
+        _loggerContext = new LoggerContext();
+        _loggerContext.setMDCAdapter(new LogbackMDCAdapter());
+        _loggerContext.start();
+        _logger = _loggerContext.getLogger(getTestClassName());
+        _logger.setLevel(Level.INFO);
+        _logger.setAdditive(false);
+
+        _appender = new ListAppender<>();
+        _appender.setContext(_loggerContext);
+        _appender.start();
+        _logger.addAppender(_appender);
+
+        _layout = new PatternLayout();
+        _layout.setContext(_loggerContext);
+        _layout.setPattern("%msg%n%ex");
+        _layout.start();
+    }
+
+    @AfterEach
+    public void tearDown()
+    {
+        _layout.stop();
+        _loggerContext.stop();
+    }
+
+    @Test
+    public void testSingleException()
+    {
+        final Exception exception = new IllegalStateException("outer");
+        final ILoggingEvent event = logException(exception, false);
+
+        assertNull(event.getThrowableProxy().getCause());
+        assertEquals(0, event.getThrowableProxy().getSuppressed().length);
+        assertFalse(_layout.doLayout(event).contains("CIRCULAR REFERENCE"));
+    }
+
+    @Test
+    public void testCauseAndSuppressedException()
+    {
+        final Exception cause = new IllegalArgumentException("cause");
+        final Exception suppressed = new 
UnsupportedOperationException("suppressed");
+        final Exception exception = new IllegalStateException("outer", cause);
+        exception.addSuppressed(suppressed);
+
+        final ILoggingEvent event = logException(exception, false);
+        assertThrowable(cause, event.getThrowableProxy().getCause());
+        assertEquals(1, event.getThrowableProxy().getSuppressed().length);
+        assertThrowable(suppressed, 
event.getThrowableProxy().getSuppressed()[0]);
+        assertRendered(event, "Caused by: " + cause);
+        assertRendered(event, "Suppressed: " + suppressed);
+        assertFalse(_layout.doLayout(event).contains("CIRCULAR REFERENCE"));
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testCircularCauses(final boolean parameterized)
+    {
+        final Exception exception = new IllegalStateException("outer");
+        final Exception cause = new IllegalArgumentException("cause", 
exception);
+        exception.initCause(cause);
+
+        final ILoggingEvent event = logException(exception, parameterized);
+        final IThrowableProxy causeProxy = 
event.getThrowableProxy().getCause();
+        assertThrowable(cause, causeProxy);
+        assertCyclicReference(exception, causeProxy.getCause());
+        assertRendered(event, "Caused by: " + cause);
+        assertRendered(event, "CIRCULAR REFERENCE");
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testCircularSuppressedExceptions(final boolean parameterized)
+    {
+        final Exception exception = new IllegalStateException("outer");
+        final Exception suppressed = new 
IllegalArgumentException("suppressed");
+        exception.addSuppressed(suppressed);
+        suppressed.addSuppressed(exception);
+
+        final ILoggingEvent event = logException(exception, parameterized);
+        assertEquals(1, event.getThrowableProxy().getSuppressed().length);
+        final IThrowableProxy suppressedProxy = 
event.getThrowableProxy().getSuppressed()[0];
+        assertThrowable(suppressed, suppressedProxy);
+        assertEquals(1, suppressedProxy.getSuppressed().length);
+        assertCyclicReference(exception, suppressedProxy.getSuppressed()[0]);
+        assertRendered(event, "Suppressed: " + suppressed);
+        assertRendered(event, "CIRCULAR REFERENCE");
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    public void testMixedCauseAndSuppressedCycle(final boolean parameterized)
+    {
+        final Exception cause = new IllegalArgumentException("cause");
+        final Exception exception = new IllegalStateException("outer", cause);
+        cause.addSuppressed(exception);
+
+        final ILoggingEvent event = logException(exception, parameterized);
+        final IThrowableProxy causeProxy = 
event.getThrowableProxy().getCause();
+        assertThrowable(cause, causeProxy);
+        assertEquals(1, causeProxy.getSuppressed().length);
+        assertCyclicReference(exception, causeProxy.getSuppressed()[0]);
+        assertRendered(event, "Caused by: " + cause);
+        assertRendered(event, "CIRCULAR REFERENCE");
+    }
+
+    @Test
+    public void testCauseSharedWithNestedSuppressedException()
+    {
+        final Exception shared = new IllegalArgumentException("shared");
+        final Exception suppressed = new 
UnsupportedOperationException("suppressed");
+        final Exception exception = new IllegalStateException("outer", shared);
+        exception.addSuppressed(suppressed);
+        suppressed.addSuppressed(shared);
+
+        final ILoggingEvent event = logException(exception, false);
+        assertThrowable(shared, event.getThrowableProxy().getCause());
+        assertEquals(1, event.getThrowableProxy().getSuppressed().length);
+        final IThrowableProxy suppressedProxy = 
event.getThrowableProxy().getSuppressed()[0];
+        assertThrowable(suppressed, suppressedProxy);
+        assertEquals(1, suppressedProxy.getSuppressed().length);
+        // Logback also uses a cyclic reference for a shared throwable in an 
otherwise acyclic graph.
+        assertCyclicReference(shared, suppressedProxy.getSuppressed()[0]);
+        assertRendered(event, "CIRCULAR REFERENCE");
+    }
+
+    @Test
+    public void testSuppressedExceptionSharedWithNestedCause()
+    {
+        final Exception shared = new UnsupportedOperationException("shared");
+        final Exception cause = new IllegalArgumentException("cause", shared);
+        final Exception exception = new IllegalStateException("outer", cause);
+        exception.addSuppressed(shared);
+
+        final ILoggingEvent event = logException(exception, false);
+        assertThrowable(cause, event.getThrowableProxy().getCause());
+        assertThrowable(shared, 
event.getThrowableProxy().getCause().getCause());
+        assertEquals(1, event.getThrowableProxy().getSuppressed().length);
+        assertCyclicReference(shared, 
event.getThrowableProxy().getSuppressed()[0]);
+        assertRendered(event, "CIRCULAR REFERENCE");
+    }
+
+    @Test
+    public void testCircularExceptionAfterBrokerStartup()
+    {
+        final Logger rootLogger = 
_loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
+        _logger.setAdditive(true);
+        final LogbackLoggingSystemLauncherListener listener = beforeStartup();
+        try
+        {
+            final Exception exception = new IllegalStateException("outer");
+            exception.initCause(new IllegalArgumentException("cause", 
exception));
+
+            final ILoggingEvent event = logException(exception, false);
+            assertCyclicReference(exception, 
event.getThrowableProxy().getCause().getCause());
+            assertRendered(event, "CIRCULAR REFERENCE");
+
+            final StartupAppender startupAppender =
+                    assertInstanceOf(StartupAppender.class, 
rootLogger.getAppender(StartupAppender.class.getName()));
+            startupAppender.replayAccumulatedEvents(_appender);
+            assertEquals(2, _appender.list.size());
+            assertSame(event, _appender.list.get(1));
+        }
+        finally
+        {
+            listener.afterStartup();
+        }
+    }
+
+    @Test
+    public void testDisabledLevelDoesNotInspectExceptionAfterBrokerStartup()
+    {
+        final LogbackLoggingSystemLauncherListener listener = beforeStartup();
+        try
+        {
+            _logger.setLevel(Level.WARN);
+            final Throwable exception = mock(Throwable.class);
+
+            _logger.info(TEST_LOG_MESSAGE, exception);
+
+            assertTrue(_appender.list.isEmpty());
+            verifyNoInteractions(exception);
+        }
+        finally
+        {
+            listener.afterStartup();
+        }
+    }
+
+    private LogbackLoggingSystemLauncherListener beforeStartup()
+    {
+        final Logger rootLogger = 
_loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
+        final LogbackLoggingSystemLauncherListener listener = new 
LogbackLoggingSystemLauncherListener();
+        try (final MockedStatic<LoggerFactory> loggerFactory = 
mockStatic(LoggerFactory.class))
+        {
+            loggerFactory.when(() -> 
LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME)).thenReturn(rootLogger);
+            listener.beforeStartup();
+        }
+        return listener;
+    }
+
+    private ILoggingEvent logException(final Throwable exception, final 
boolean parameterized)
+    {
+        if (parameterized)
+        {
+            _logger.info(TEST_MARKER, TEST_LOG_MESSAGE + " {}", "world", 
exception);
+        }
+        else
+        {
+            _logger.info(TEST_MARKER, TEST_LOG_MESSAGE, exception);
+        }
+
+        assertEquals(1, _appender.list.size(), "Exactly one logging event 
should be emitted");
+        final ILoggingEvent event = _appender.list.get(0);
+        assertEquals(Level.INFO, event.getLevel());
+        assertEquals(_logger.getName(), event.getLoggerName());
+        assertEquals(List.of(TEST_MARKER), event.getMarkerList());
+        assertEquals(parameterized ? TEST_LOG_MESSAGE + " {}" : 
TEST_LOG_MESSAGE, event.getMessage());
+        assertEquals(parameterized ? TEST_LOG_MESSAGE + " world" : 
TEST_LOG_MESSAGE, event.getFormattedMessage());
+        if (parameterized)
+        {
+            assertArrayEquals(new Object[] {"world"}, 
event.getArgumentArray());
+        }
+        else
+        {
+            assertNull(event.getArgumentArray());
+        }
+        assertThrowable(exception, event.getThrowableProxy());
+        assertRendered(event, exception.toString());
+        return event;
+    }
+
+    private void assertThrowable(final Throwable expected, final 
IThrowableProxy actual)
+    {
+        final ThrowableProxy proxy = assertInstanceOf(ThrowableProxy.class, 
actual);
+        assertSame(expected, proxy.getThrowable());
+        assertEquals(expected.getClass().getName(), actual.getClassName());
+        assertEquals(expected.getMessage(), actual.getMessage());
+        assertFalse(actual.isCyclic());
+        assertArrayEquals(expected.getStackTrace(), 
Arrays.stream(actual.getStackTraceElementProxyArray())
+                
.map(StackTraceElementProxy::getStackTraceElement).toArray(StackTraceElement[]::new));
+    }
+
+    private void assertCyclicReference(final Throwable expected, final 
IThrowableProxy actual)
+    {
+        final ThrowableProxy proxy = assertInstanceOf(ThrowableProxy.class, 
actual);
+        assertSame(expected, proxy.getThrowable());
+        assertEquals(expected.getClass().getName(), actual.getClassName());
+        assertEquals(expected.getMessage(), actual.getMessage());
+        assertTrue(actual.isCyclic());
+        assertNull(actual.getCause());
+        assertEquals(0, actual.getSuppressed().length);
+        assertEquals(0, actual.getStackTraceElementProxyArray().length);
+    }
+
+    private void assertRendered(final ILoggingEvent event, final String 
expected)
+    {
+        final String rendered = _layout.doLayout(event);
+        assertTrue(rendered.contains(expected), rendered);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to