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 ef9735807b QPID-8771: [Broker-J] Isolate socket log MDC and capture 
caller data (#454)
ef9735807b is described below

commit ef9735807b2de691ad3e99de8f8904b7bca8b1e2
Author: usugumo <[email protected]>
AuthorDate: Tue Oct 20 19:13:57 2026 +0200

    QPID-8771: [Broker-J] Isolate socket log MDC and capture caller data (#454)
---
 .../logback/BrokerLogbackSocketLoggerImpl.java     |  37 +-
 .../logback/MdcEnrichingSocketAppender.java        |  72 ++++
 .../server/logging/logback/MdcLoggingEvent.java    | 165 ++++++++
 .../logback/MdcEnrichingSocketAppenderTest.java    | 436 +++++++++++++++++++++
 .../logging/logback/MdcLoggingEventTest.java       |  71 ++++
 5 files changed, 748 insertions(+), 33 deletions(-)

diff --git 
a/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/BrokerLogbackSocketLoggerImpl.java
 
b/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/BrokerLogbackSocketLoggerImpl.java
index ccaa7f8446..b4ced2569d 100644
--- 
a/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/BrokerLogbackSocketLoggerImpl.java
+++ 
b/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/BrokerLogbackSocketLoggerImpl.java
@@ -21,11 +21,9 @@
 
 package org.apache.qpid.server.logging.logback;
 
-import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
 
 import ch.qos.logback.classic.net.SocketAppender;
@@ -35,7 +33,6 @@ import ch.qos.logback.core.Context;
 import ch.qos.logback.core.util.Duration;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.slf4j.MDC;
 
 import org.apache.qpid.server.model.Broker;
 import org.apache.qpid.server.model.ManagedAttributeField;
@@ -62,7 +59,7 @@ public class BrokerLogbackSocketLoggerImpl
     private boolean _includeCallerData;
 
     @ManagedAttributeField
-    private Map<String,String> _mappedDiagnosticContext;
+    private volatile Map<String, String> _mappedDiagnosticContext;
 
     @ManagedAttributeField
     private Map<String, String> _contextProperties;
@@ -110,11 +107,11 @@ public class BrokerLogbackSocketLoggerImpl
     }
 
     @Override
-    protected Appender<ILoggingEvent> createAppenderInstance(Context 
loggerContext)
+    protected Appender<ILoggingEvent> createAppenderInstance(final Context 
loggerContext)
     {
         if (_contextProperties != null && !_contextProperties.isEmpty())
         {
-            for (Map.Entry<String, String> property : 
_contextProperties.entrySet())
+            for (final Map.Entry<String, String> property : 
_contextProperties.entrySet())
             {
                 final String key = property.getKey();
                 final String value = property.getValue();
@@ -132,33 +129,7 @@ public class BrokerLogbackSocketLoggerImpl
             }
         }
 
-        SocketAppender socketAppender = new SocketAppender()
-                                        {
-                                            @Override
-                                            protected void append(final 
ILoggingEvent event)
-                                            {
-                                                Set<String> keys = new 
HashSet<>();
-                                                try
-                                                {
-                                                    for (Map.Entry<String, 
String> entry : _mappedDiagnosticContext.entrySet())
-                                                    {
-                                                        
MDC.put(entry.getKey(), entry.getValue());
-                                                        
keys.add(entry.getKey());
-                                                    }
-
-                                                    // Workaround for 
suspected Logback defect LOGBACK-1088
-                                                    
event.prepareForDeferredProcessing();
-                                                    super.append(event);
-                                                }
-                                                finally
-                                                {
-                                                    for (String key : keys)
-                                                    {
-                                                        MDC.remove(key);
-                                                    }
-                                                }
-                                            }
-                                        };
+        final SocketAppender socketAppender = new 
MdcEnrichingSocketAppender(this::getMappedDiagnosticContext);
         socketAppender.setPort(_port);
         socketAppender.setRemoteHost(_remoteHost);
         socketAppender.setIncludeCallerData(_includeCallerData);
diff --git 
a/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/MdcEnrichingSocketAppender.java
 
b/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/MdcEnrichingSocketAppender.java
new file mode 100644
index 0000000000..565a799e82
--- /dev/null
+++ 
b/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/MdcEnrichingSocketAppender.java
@@ -0,0 +1,72 @@
+/*
+ *
+ * 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.Serializable;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import ch.qos.logback.classic.net.LoggingEventPreSerializationTransformer;
+import ch.qos.logback.classic.net.SocketAppender;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggingEventVO;
+import ch.qos.logback.core.spi.PreSerializationTransformer;
+
+class MdcEnrichingSocketAppender extends SocketAppender
+{
+    private static final PreSerializationTransformer<ILoggingEvent> 
EVENT_TRANSFORMER =
+            new LoggingEventPreSerializationTransformer()
+            {
+                @Override
+                public Serializable transform(final ILoggingEvent event)
+                {
+                    return event instanceof MdcLoggingEvent ? 
LoggingEventVO.build(event) : super.transform(event);
+                }
+            };
+
+    private final Supplier<Map<String, String>> _mdcSupplier;
+
+    MdcEnrichingSocketAppender(final Supplier<Map<String, String>> mdcSupplier)
+    {
+        _mdcSupplier = Objects.requireNonNull(mdcSupplier, "MDC supplier must 
not be null");
+    }
+
+    @Override
+    protected void append(final ILoggingEvent event)
+    {
+        if (event == null)
+        {
+            return;
+        }
+
+        super.postProcessEvent(event);
+        final Map<String, String> additions = _mdcSupplier.get();
+        super.append(additions == null || additions.isEmpty() ? event : new 
MdcLoggingEvent(event, additions));
+    }
+
+    @Override
+    public PreSerializationTransformer<ILoggingEvent> getPST()
+    {
+        return EVENT_TRANSFORMER;
+    }
+}
diff --git 
a/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/MdcLoggingEvent.java
 
b/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/MdcLoggingEvent.java
new file mode 100644
index 0000000000..a2e8b96375
--- /dev/null
+++ 
b/broker-plugins/logging-logback/src/main/java/org/apache/qpid/server/logging/logback/MdcLoggingEvent.java
@@ -0,0 +1,165 @@
+/*
+ *
+ * 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.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.IThrowableProxy;
+import ch.qos.logback.classic.spi.LoggerContextVO;
+import org.slf4j.Marker;
+import org.slf4j.event.KeyValuePair;
+
+final class MdcLoggingEvent implements ILoggingEvent
+{
+    private final ILoggingEvent _event;
+    private final Map<String, String> _mdc;
+
+    MdcLoggingEvent(final ILoggingEvent event, final Map<String, String> 
additions)
+    {
+        _event = Objects.requireNonNull(event, "Event must not be null");
+        final Map<String, String> mdc = new 
HashMap<>(event.getMDCPropertyMap());
+        mdc.putAll(Objects.requireNonNull(additions, "MDC additions must not 
be null"));
+        _mdc = Collections.unmodifiableMap(mdc);
+    }
+
+    @Override
+    public String getThreadName()
+    {
+        return _event.getThreadName();
+    }
+
+    @Override
+    public Level getLevel()
+    {
+        return _event.getLevel();
+    }
+
+    @Override
+    public String getMessage()
+    {
+        return _event.getMessage();
+    }
+
+    @Override
+    public Object[] getArgumentArray()
+    {
+        return _event.getArgumentArray();
+    }
+
+    @Override
+    public String getFormattedMessage()
+    {
+        return _event.getFormattedMessage();
+    }
+
+    @Override
+    public String getLoggerName()
+    {
+        return _event.getLoggerName();
+    }
+
+    @Override
+    public LoggerContextVO getLoggerContextVO()
+    {
+        return _event.getLoggerContextVO();
+    }
+
+    @Override
+    public IThrowableProxy getThrowableProxy()
+    {
+        return _event.getThrowableProxy();
+    }
+
+    @Override
+    public StackTraceElement[] getCallerData()
+    {
+        return _event.getCallerData();
+    }
+
+    @Override
+    public boolean hasCallerData()
+    {
+        return _event.hasCallerData();
+    }
+
+    @Override
+    public List<Marker> getMarkerList()
+    {
+        return _event.getMarkerList();
+    }
+
+    @Override
+    public Map<String, String> getMDCPropertyMap()
+    {
+        return _mdc;
+    }
+
+    @Deprecated
+    @Override
+    public Map<String, String> getMdc()
+    {
+        return _mdc;
+    }
+
+    @Override
+    public long getTimeStamp()
+    {
+        return _event.getTimeStamp();
+    }
+
+    @Override
+    public Instant getInstant()
+    {
+        return _event.getInstant();
+    }
+
+    @Override
+    public int getNanoseconds()
+    {
+        return _event.getNanoseconds();
+    }
+
+    @Override
+    public long getSequenceNumber()
+    {
+        return _event.getSequenceNumber();
+    }
+
+    @Override
+    public List<KeyValuePair> getKeyValuePairs()
+    {
+        return _event.getKeyValuePairs();
+    }
+
+    @Override
+    public void prepareForDeferredProcessing()
+    {
+        _event.prepareForDeferredProcessing();
+    }
+}
diff --git 
a/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/MdcEnrichingSocketAppenderTest.java
 
b/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/MdcEnrichingSocketAppenderTest.java
new file mode 100644
index 0000000000..05027e165b
--- /dev/null
+++ 
b/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/MdcEnrichingSocketAppenderTest.java
@@ -0,0 +1,436 @@
+/*
+ *
+ * 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.verifyNoInteractions;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.Logger;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.net.server.HardenedLoggingEventInputStream;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.classic.spi.LoggingEvent;
+import ch.qos.logback.classic.spi.LoggingEventVO;
+import ch.qos.logback.classic.util.LogbackMDCAdapter;
+import ch.qos.logback.core.filter.Filter;
+import ch.qos.logback.core.read.ListAppender;
+import ch.qos.logback.core.spi.FilterReply;
+import ch.qos.logback.core.spi.PreSerializationTransformer;
+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.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.slf4j.MarkerFactory;
+import org.slf4j.event.KeyValuePair;
+
+import org.apache.qpid.test.utils.UnitTestBase;
+
+class MdcEnrichingSocketAppenderTest extends UnitTestBase
+{
+    private static final int TIMEOUT_SECONDS = 5;
+    private static final Map<String, String> PRODUCER_MDC = Map.of("trace", 
"request", "common", "producer");
+
+    private final AtomicReference<Map<String, String>> _additions = new 
AtomicReference<>();
+    private final List<MdcEnrichingSocketAppender> _appenders = new 
ArrayList<>();
+
+    private LoggerContext _loggerContext;
+    private Logger _logger;
+    private CountDownLatch _serializationAllowed;
+
+    @BeforeEach
+    void setUp()
+    {
+        _additions.set(Map.of());
+        _serializationAllowed = new CountDownLatch(1);
+        _loggerContext = new LoggerContext();
+        _loggerContext.setMDCAdapter(new LogbackMDCAdapter());
+        _loggerContext.start();
+        _logger = _loggerContext.getLogger(getTestClassName());
+        _logger.setLevel(Level.INFO);
+        _logger.setAdditive(false);
+    }
+
+    @AfterEach
+    void tearDown()
+    {
+        _serializationAllowed.countDown();
+        for (final MdcEnrichingSocketAppender appender : _appenders)
+        {
+            appender.stop();
+        }
+        _appenders.clear();
+        _loggerContext.stop();
+    }
+
+    @ParameterizedTest
+    @ValueSource(booleans = {false, true})
+    void enrichmentPreservesOriginalEventAndProducerMdc(final boolean 
prepareEarlier) throws Exception
+    {
+        final ListAppender<ILoggingEvent> earlier = 
addRecordingAppender(prepareEarlier);
+        _additions.set(Map.of("common", "socket", "broker", "node"));
+        try (final SocketReceiver receiver = new SocketReceiver())
+        {
+            addSocketAppender(receiver, _additions::get, false);
+            final ListAppender<ILoggingEvent> later = 
addRecordingAppender(false);
+            logOnProducer(() -> _logger.info("event"));
+
+            _loggerContext.getMDCAdapter().put("trace", "reader");
+            _serializationAllowed.countDown();
+            final LoggingEventVO received = receiver.readEvent();
+
+            assertEquals(Map.of("trace", "request", "common", "socket", 
"broker", "node"),
+                    received.getMDCPropertyMap());
+            assertEquals("producer-thread", received.getThreadName());
+            assertEquals(1, earlier.list.size());
+            assertEquals(1, later.list.size());
+            assertSame(earlier.list.get(0), later.list.get(0));
+            assertEquals(PRODUCER_MDC, later.list.get(0).getMDCPropertyMap());
+        }
+    }
+
+    @Test
+    void eachSocketHasIndependentMdc() throws Exception
+    {
+        try (final SocketReceiver first = new SocketReceiver();
+             final SocketReceiver second = new SocketReceiver())
+        {
+            addSocketAppender(first, () -> Map.of("common", "first", 
"first-only", "value"), false);
+            addSocketAppender(second, () -> Map.of("common", "second"), false);
+            logOnProducer(() -> _logger.info("event"));
+            _serializationAllowed.countDown();
+
+            assertEquals(Map.of("trace", "request", "common", "first", 
"first-only", "value"),
+                    first.readEvent().getMDCPropertyMap());
+            assertEquals(Map.of("trace", "request", "common", "second"), 
second.readEvent().getMDCPropertyMap());
+        }
+    }
+
+    @Test
+    void queuedEventsRetainTheirMdcAfterConfigurationChanges() throws Exception
+    {
+        final Map<String, String> additions = new HashMap<>(Map.of("common", 
"first"));
+        _additions.set(additions);
+        try (final SocketReceiver receiver = new SocketReceiver())
+        {
+            addSocketAppender(receiver, _additions::get, false);
+            logOnProducer(() -> _logger.info("first"));
+            additions.put("common", "mutated");
+            _additions.set(Map.of("common", "second"));
+            logOnProducer(() -> _logger.info("second"));
+            _additions.set(Map.of());
+            logOnProducer(() -> _logger.info("third"));
+            _serializationAllowed.countDown();
+
+            final LoggingEventVO first = receiver.readEvent();
+            final LoggingEventVO second = receiver.readEvent();
+            final LoggingEventVO third = receiver.readEvent();
+            assertEquals("first", first.getMessage());
+            assertEquals("first", first.getMDCPropertyMap().get("common"));
+            assertEquals("second", second.getMessage());
+            assertEquals("second", second.getMDCPropertyMap().get("common"));
+            assertEquals("third", third.getMessage());
+            assertEquals(PRODUCER_MDC, third.getMDCPropertyMap());
+        }
+    }
+
+    @ParameterizedTest
+    @CsvSource({"false,false", "false,true", "true,false", "true,true"})
+    void callerCaptureOnProducer(final boolean includeCallerData, final 
boolean enrich) throws Exception
+    {
+        _additions.set(enrich ? Map.of("broker", "node") : Map.of());
+        final ListAppender<ILoggingEvent> recording = 
addRecordingAppender(false);
+        try (final SocketReceiver receiver = new SocketReceiver())
+        {
+            addSocketAppender(receiver, _additions::get, includeCallerData);
+            logOnProducer(this::logWithCaller);
+            assertEquals(includeCallerData, 
recording.list.get(0).hasCallerData());
+            _serializationAllowed.countDown();
+            final LoggingEventVO received = receiver.readEvent();
+
+            assertEquals(includeCallerData, received.hasCallerData());
+            if (includeCallerData)
+            {
+                assertTrue(received.getCallerData().length > 0);
+                assertEquals(getClass().getName(), 
received.getCallerData()[0].getClassName());
+                assertEquals("logWithCaller", 
received.getCallerData()[0].getMethodName());
+            }
+            else
+            {
+                assertNull(received.getCallerData());
+            }
+        }
+    }
+
+    @Test
+    void nativeSerializationPreservesEventData() throws Exception
+    {
+        final Map<String, String> additions = new HashMap<>();
+        additions.put("broker", "node");
+        additions.put("nullable", null);
+        _additions.set(additions);
+        _loggerContext.putProperty("context-property", "context-value");
+        final IllegalStateException exception = new 
IllegalStateException("outer");
+        final IllegalArgumentException cause = new 
IllegalArgumentException("cause", exception);
+        exception.initCause(cause);
+        exception.addSuppressed(new 
UnsupportedOperationException("suppressed"));
+        final LoggingEvent original = new LoggingEvent(Logger.class.getName(), 
_logger, Level.WARN,
+                "value={}", exception, new Object[]{"argument"});
+        original.setInstant(Instant.parse("2026-09-11T12:34:56.123456789Z"));
+        original.setSequenceNumber(42);
+        original.addMarker(MarkerFactory.getMarker("first"));
+        original.addMarker(MarkerFactory.getMarker("second"));
+
+        try (final SocketReceiver receiver = new SocketReceiver())
+        {
+            final MdcEnrichingSocketAppender appender = 
addSocketAppender(receiver, _additions::get, false);
+            logOnProducer(() -> appender.doAppend(original));
+            _serializationAllowed.countDown();
+            final LoggingEventVO received = receiver.readEvent();
+
+            assertEquals("producer-thread", received.getThreadName());
+            assertEquals(original.getLoggerName(), received.getLoggerName());
+            assertEquals(original.getLevel(), received.getLevel());
+            assertEquals(original.getMessage(), received.getMessage());
+            assertEquals(original.getFormattedMessage(), 
received.getFormattedMessage());
+            assertArrayEquals(original.getArgumentArray(), 
received.getArgumentArray());
+            assertEquals(original.getTimeStamp(), received.getTimeStamp());
+            assertEquals(original.getNanoseconds(), received.getNanoseconds());
+            assertEquals(original.getSequenceNumber(), 
received.getSequenceNumber());
+            assertEquals(original.getMarkerList(), received.getMarkerList());
+            assertEquals("context-value", 
received.getLoggerContextVO().getPropertyMap().get("context-property"));
+            assertEquals("node", received.getMDCPropertyMap().get("broker"));
+            assertTrue(received.getMDCPropertyMap().containsKey("nullable"));
+            assertNull(received.getMDCPropertyMap().get("nullable"));
+            assertEquals(IllegalStateException.class.getName(), 
received.getThrowableProxy().getClassName());
+            assertEquals("outer", received.getThrowableProxy().getMessage());
+            assertEquals("cause", 
received.getThrowableProxy().getCause().getMessage());
+            
assertTrue(received.getThrowableProxy().getCause().getCause().isCyclic());
+            assertEquals("suppressed", 
received.getThrowableProxy().getSuppressed()[0].getMessage());
+        }
+    }
+
+    @Test
+    void transformerPreservesStructuredArgumentsAndNativeValueObjects()
+    {
+        final LoggingEvent original = new LoggingEvent(Logger.class.getName(), 
_logger, Level.INFO,
+                "value={}", null, new Object[]{"argument"});
+        original.setKeyValuePairs(List.of(new KeyValuePair("key", "value")));
+        original.prepareForDeferredProcessing();
+        final MdcEnrichingSocketAppender appender = new 
MdcEnrichingSocketAppender(_additions::get);
+        final PreSerializationTransformer<ILoggingEvent> transformer = 
appender.getPST();
+        final MdcLoggingEvent enriched = new MdcLoggingEvent(original, 
Map.of("broker", "node"));
+        final LoggingEventVO transformed = 
assertInstanceOf(LoggingEventVO.class, transformer.transform(enriched));
+
+        assertEquals(original.getKeyValuePairs(), 
transformed.getKeyValuePairs());
+        assertEquals(original.getInstant(), enriched.getInstant());
+        assertEquals("node", transformed.getMDCPropertyMap().get("broker"));
+        final LoggingEventVO enrichedValueObject = 
assertInstanceOf(LoggingEventVO.class,
+                transformer.transform(new MdcLoggingEvent(transformed, 
Map.of("broker", "another-node"))));
+        assertEquals("another-node", 
enrichedValueObject.getMDCPropertyMap().get("broker"));
+        assertEquals("node", transformed.getMDCPropertyMap().get("broker"));
+        assertEquals(original.getKeyValuePairs(), 
enrichedValueObject.getKeyValuePairs());
+        assertSame(transformed, transformer.transform(transformed));
+        assertNull(transformer.transform(null));
+    }
+
+    @Test
+    void rejectedEventsAreNotEnriched() throws Exception
+    {
+        final Supplier<Map<String, String>> supplier = mock(Supplier.class);
+        final ILoggingEvent event = mock(ILoggingEvent.class);
+        try (final SocketReceiver receiver = new SocketReceiver())
+        {
+            final MdcEnrichingSocketAppender appender = 
addSocketAppender(receiver, supplier, true);
+            appender.addFilter(new Filter<>()
+            {
+                @Override
+                public FilterReply decide(final ILoggingEvent loggingEvent)
+                {
+                    return FilterReply.DENY;
+                }
+            });
+            appender.doAppend(event);
+            appender.stop();
+            appender.clearAllFilters();
+            appender.doAppend(event);
+
+            verifyNoInteractions(supplier, event);
+        }
+    }
+
+    private void logWithCaller()
+    {
+        _logger.info("caller");
+    }
+
+    private void logOnProducer(final Runnable log) throws Exception
+    {
+        final FutureTask<Void> task = new FutureTask<>(() ->
+        {
+            _loggerContext.getMDCAdapter().setContextMap(PRODUCER_MDC);
+            try
+            {
+                log.run();
+                assertEquals(PRODUCER_MDC, 
_loggerContext.getMDCAdapter().getCopyOfContextMap());
+            }
+            finally
+            {
+                _loggerContext.getMDCAdapter().clear();
+            }
+        }, null);
+        final Thread producer = new Thread(task, "producer-thread");
+        producer.start();
+        try
+        {
+            task.get(TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        }
+        finally
+        {
+            producer.join(TimeUnit.SECONDS.toMillis(TIMEOUT_SECONDS));
+        }
+        assertFalse(producer.isAlive());
+    }
+
+    private ListAppender<ILoggingEvent> addRecordingAppender(final boolean 
prepare)
+    {
+        final ListAppender<ILoggingEvent> appender = new ListAppender<>()
+        {
+            @Override
+            protected void append(final ILoggingEvent event)
+            {
+                if (prepare)
+                {
+                    event.prepareForDeferredProcessing();
+                }
+                super.append(event);
+            }
+        };
+        appender.setContext(_loggerContext);
+        appender.start();
+        _logger.addAppender(appender);
+        return appender;
+    }
+
+    private MdcEnrichingSocketAppender addSocketAppender(final SocketReceiver 
receiver,
+                                                         final 
Supplier<Map<String, String>> supplier,
+                                                         final boolean 
includeCallerData)
+    {
+        final MdcEnrichingSocketAppender appender = new 
MdcEnrichingSocketAppender(supplier)
+        {
+            @Override
+            public PreSerializationTransformer<ILoggingEvent> getPST()
+            {
+                final PreSerializationTransformer<ILoggingEvent> transformer = 
super.getPST();
+                return event ->
+                {
+                    try
+                    {
+                        if (!_serializationAllowed.await(TIMEOUT_SECONDS, 
TimeUnit.SECONDS))
+                        {
+                            throw new IllegalStateException("Serialization was 
not released");
+                        }
+                    }
+                    catch (InterruptedException e)
+                    {
+                        Thread.currentThread().interrupt();
+                        throw new IllegalStateException("Serialization 
interrupted", e);
+                    }
+                    return transformer.transform(event);
+                };
+            }
+        };
+        appender.setContext(_loggerContext);
+        appender.setName("socket-" + _appenders.size());
+        
appender.setRemoteHost(InetAddress.getLoopbackAddress().getHostAddress());
+        appender.setPort(receiver._serverSocket.getLocalPort());
+        appender.setIncludeCallerData(includeCallerData);
+        _appenders.add(appender);
+        appender.start();
+        assertTrue(appender.isStarted());
+        _logger.addAppender(appender);
+        return appender;
+    }
+
+    private class SocketReceiver implements AutoCloseable
+    {
+        private final ServerSocket _serverSocket = new ServerSocket(0, 1, 
InetAddress.getLoopbackAddress());
+        private Socket _socket;
+        private HardenedLoggingEventInputStream _input;
+
+        private SocketReceiver() throws IOException
+        {
+            _serverSocket.setSoTimeout((int) 
TimeUnit.SECONDS.toMillis(TIMEOUT_SECONDS));
+        }
+
+        private LoggingEventVO readEvent() throws IOException, 
ClassNotFoundException
+        {
+            if (_socket == null)
+            {
+                _socket = _serverSocket.accept();
+                _socket.setSoTimeout((int) 
TimeUnit.SECONDS.toMillis(TIMEOUT_SECONDS));
+                _input = new HardenedLoggingEventInputStream(_loggerContext, 
_socket.getInputStream());
+            }
+            return assertInstanceOf(LoggingEventVO.class, _input.readObject());
+        }
+
+        @Override
+        public void close() throws IOException
+        {
+            try
+            {
+                if (_socket != null)
+                {
+                    _socket.close();
+                }
+            }
+            finally
+            {
+                _serverSocket.close();
+            }
+        }
+    }
+}
diff --git 
a/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/MdcLoggingEventTest.java
 
b/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/MdcLoggingEventTest.java
new file mode 100644
index 0000000000..78cff0c66e
--- /dev/null
+++ 
b/broker-plugins/logging-logback/src/test/java/org/apache/qpid/server/logging/logback/MdcLoggingEventTest.java
@@ -0,0 +1,71 @@
+/*
+ *
+ * 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.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import ch.qos.logback.classic.spi.LoggingEvent;
+import org.junit.jupiter.api.Test;
+
+import org.apache.qpid.test.utils.UnitTestBase;
+
+class MdcLoggingEventTest extends UnitTestBase
+{
+    @Test
+    @SuppressWarnings("deprecation")
+    void mdcIsAnUnmodifiableSnapshot()
+    {
+        final Map<String, String> originalMdc = new HashMap<>(Map.of("trace", 
"request", "common", "original"));
+        final LoggingEvent original = new LoggingEvent();
+        original.setMDCPropertyMap(originalMdc);
+        final Map<String, String> additions = new HashMap<>(Map.of("common", 
"socket", "broker", "node"));
+        final MdcLoggingEvent event = new MdcLoggingEvent(original, additions);
+        final Map<String, String> expected = Map.of("trace", "request", 
"common", "socket", "broker", "node");
+
+        assertEquals(Map.of("trace", "request", "common", "original"), 
originalMdc);
+        originalMdc.clear();
+        additions.clear();
+
+        assertEquals(expected, event.getMDCPropertyMap());
+        assertSame(event.getMDCPropertyMap(), event.getMdc());
+        assertThrows(UnsupportedOperationException.class, () -> 
event.getMDCPropertyMap().put("common", "changed"));
+        assertThrows(UnsupportedOperationException.class, () ->
+                
event.getMDCPropertyMap().entrySet().iterator().next().setValue("changed"));
+    }
+
+    @Test
+    void checkingForCallerDataDoesNotCaptureIt()
+    {
+        final LoggingEvent original = new LoggingEvent();
+        original.setMDCPropertyMap(Map.of());
+        final MdcLoggingEvent event = new MdcLoggingEvent(original, 
Map.of("broker", "node"));
+
+        assertFalse(event.hasCallerData());
+        assertFalse(original.hasCallerData());
+    }
+}


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

Reply via email to