You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2019/07/04 15:42:29 UTC

[httpcomponents-core] 02/02: Redesign of fatal exception handling by the i/o reactors

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

olegk pushed a commit to branch bug-fixes
in repository https://gitbox.apache.org/repos/asf/httpcomponents-core.git

commit 40ea1cd9e63d2ad8cb3daf597af0d9786c027ba8
Author: Oleg Kalnichevski <ol...@apache.org>
AuthorDate: Thu Jul 4 15:31:58 2019 +0200

    Redesign of fatal exception handling by the i/o reactors
---
 .../impl/nio/bootstrap/H2RequesterBootstrap.java   | 11 ++++
 .../impl/nio/bootstrap/H2ServerBootstrap.java      | 13 +++-
 .../impl/nio/bootstrap/Http2AsyncRequester.java    |  4 +-
 .../nio/bootstrap/Http2MultiplexingRequester.java  |  4 +-
 .../Http2MultiplexingRequesterBootstrap.java       | 11 ++++
 .../hc/core5/testing/nio/AsyncRequester.java       | 10 ++-
 .../apache/hc/core5/testing/nio/AsyncServer.java   | 11 +++-
 .../hc/core5/testing/nio/IOReactorExecutor.java    |  8 ---
 .../testing/nio/LoggingExceptionCallback.java      | 35 ++++------
 .../hc/core5/testing/nio/Http1IntegrationTest.java | 16 ++---
 .../testing/nio/Http1ServerAndRequesterTest.java   | 38 +++--------
 .../apache/hc/core5/testing/nio/Http2AlpnTest.java | 42 +++---------
 .../hc/core5/testing/nio/Http2IntegrationTest.java | 20 ++----
 .../testing/nio/Http2ProtocolNegotiationTest.java  | 38 +++--------
 .../Http2ServerAndMultiplexingRequesterTest.java   | 30 ++-------
 .../testing/nio/Http2ServerAndRequesterTest.java   | 34 ++--------
 .../testing/nio/InternalHttp1ServerTestBase.java   | 20 +-----
 .../testing/nio/InternalHttp2ServerTestBase.java   | 20 +-----
 .../core5/testing/reactive/ReactiveClientTest.java | 36 +++-------
 .../core5/http/impl/bootstrap/AsyncRequester.java  |  9 +--
 .../impl/bootstrap/AsyncRequesterBootstrap.java    | 11 ++++
 .../hc/core5/http/impl/bootstrap/AsyncServer.java  |  9 +--
 .../http/impl/bootstrap/AsyncServerBootstrap.java  | 13 +++-
 .../http/impl/bootstrap/HttpAsyncRequester.java    |  4 +-
 .../core5/http/impl/bootstrap/HttpAsyncServer.java |  4 +-
 .../core5/reactor/AbstractSingleCoreIOReactor.java | 23 +++----
 .../core5/reactor/DefaultConnectingIOReactor.java  | 17 +----
 .../core5/reactor/DefaultListeningIOReactor.java   | 19 ++----
 .../apache/hc/core5/reactor/ExceptionEvent.java    | 77 ----------------------
 .../apache/hc/core5/reactor/IOReactorService.java  |  9 ---
 .../hc/core5/reactor/SingleCoreIOReactor.java      | 10 +--
 .../reactor/SingleCoreListeningIOReactor.java      |  4 +-
 32 files changed, 187 insertions(+), 423 deletions(-)

diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java
index 79d5d49..754e2dd 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2RequesterBootstrap.java
@@ -30,6 +30,7 @@ import java.util.ArrayList;
 import java.util.List;
 
 import org.apache.hc.core5.annotation.Experimental;
+import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.HttpHost;
@@ -89,6 +90,7 @@ public class H2RequesterBootstrap {
     private TlsStrategy tlsStrategy;
     private Timeout handshakeTimeout;
     private Decorator<ProtocolIOSession> ioSessionDecorator;
+    private Callback<Exception> exceptionCallback;
     private IOSessionListener sessionListener;
     private Http2StreamListener streamListener;
     private Http1StreamListener http1StreamListener;
@@ -204,6 +206,14 @@ public class H2RequesterBootstrap {
     }
 
     /**
+     * Assigns {@link Exception} {@link Callback} instance.
+     */
+    public final H2RequesterBootstrap setExceptionCallback(final Callback<Exception> exceptionCallback) {
+        this.exceptionCallback = exceptionCallback;
+        return this;
+    }
+
+    /**
      * Assigns {@link IOSessionListener} instance.
      */
     public final H2RequesterBootstrap setIOSessionListener(final IOSessionListener sessionListener) {
@@ -319,6 +329,7 @@ public class H2RequesterBootstrap {
                 ioReactorConfig,
                 ioEventHandlerFactory,
                 ioSessionDecorator,
+                exceptionCallback,
                 sessionListener,
                 connPool);
     }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java
index 649b775..33ac4af 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/H2ServerBootstrap.java
@@ -29,6 +29,7 @@ package org.apache.hc.core5.http2.impl.nio.bootstrap;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.config.CharCodingConfig;
@@ -94,6 +95,7 @@ public class H2ServerBootstrap {
     private TlsStrategy tlsStrategy;
     private Timeout handshakeTimeout;
     private Decorator<ProtocolIOSession> ioSessionDecorator;
+    private Callback<Exception> exceptionCallback;
     private IOSessionListener sessionListener;
     private Http2StreamListener http2StreamListener;
     private Http1StreamListener http1StreamListener;
@@ -187,6 +189,14 @@ public class H2ServerBootstrap {
     }
 
     /**
+     * Assigns {@link Exception} {@link Callback} instance.
+     */
+    public final H2ServerBootstrap setExceptionCallback(final Callback<Exception> exceptionCallback) {
+        this.exceptionCallback = exceptionCallback;
+        return this;
+    }
+
+    /**
      * Assigns {@link IOSessionListener} instance.
      */
     public final H2ServerBootstrap setIOSessionListener(final IOSessionListener sessionListener) {
@@ -435,7 +445,8 @@ public class H2ServerBootstrap {
                 versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE,
                 tlsStrategy != null ? tlsStrategy : new H2ServerTlsStrategy(443, 8443),
                 handshakeTimeout);
-        return new HttpAsyncServer(ioEventHandlerFactory, ioReactorConfig, ioSessionDecorator, sessionListener);
+        return new HttpAsyncServer(ioEventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback,
+                sessionListener);
     }
 
 }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2AsyncRequester.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2AsyncRequester.java
index 128c100..58af7ee 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2AsyncRequester.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2AsyncRequester.java
@@ -31,6 +31,7 @@ import java.util.concurrent.Future;
 
 import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.http.HttpHost;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncRequester;
@@ -63,9 +64,10 @@ public class Http2AsyncRequester extends HttpAsyncRequester {
             final IOReactorConfig ioReactorConfig,
             final IOEventHandlerFactory eventHandlerFactory,
             final Decorator<ProtocolIOSession> ioSessionDecorator,
+            final Callback<Exception> exceptionCallback,
             final IOSessionListener sessionListener,
             final ManagedConnPool<HttpHost, IOSession> connPool) {
-        super(ioReactorConfig, eventHandlerFactory, ioSessionDecorator, sessionListener, connPool);
+        super(ioReactorConfig, eventHandlerFactory, ioSessionDecorator, exceptionCallback, sessionListener, connPool);
         this.versionPolicy = versionPolicy != null ? versionPolicy : HttpVersionPolicy.NEGOTIATE;
     }
 
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequester.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequester.java
index 63bfbbf..3ba70ff 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequester.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequester.java
@@ -39,6 +39,7 @@ import org.apache.hc.core5.concurrent.Cancellable;
 import org.apache.hc.core5.concurrent.CancellableDependency;
 import org.apache.hc.core5.concurrent.ComplexFuture;
 import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.function.Resolver;
 import org.apache.hc.core5.http.EntityDetails;
@@ -93,10 +94,11 @@ public class Http2MultiplexingRequester extends AsyncRequester{
             final IOReactorConfig ioReactorConfig,
             final IOEventHandlerFactory eventHandlerFactory,
             final Decorator<ProtocolIOSession> ioSessionDecorator,
+            final Callback<Exception> exceptionCallback,
             final IOSessionListener sessionListener,
             final Resolver<HttpHost, InetSocketAddress> addressResolver,
             final TlsStrategy tlsStrategy) {
-        super(eventHandlerFactory, ioReactorConfig, ioSessionDecorator, sessionListener,
+        super(eventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback, sessionListener,
                         ShutdownCommand.GRACEFUL_IMMEDIATE_CALLBACK, DefaultAddressResolver.INSTANCE);
         this.connPool = new H2ConnPool(this, addressResolver, tlsStrategy);
     }
diff --git a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequesterBootstrap.java b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequesterBootstrap.java
index afd7fc7..9ab396a 100644
--- a/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequesterBootstrap.java
+++ b/httpcore5-h2/src/main/java/org/apache/hc/core5/http2/impl/nio/bootstrap/Http2MultiplexingRequesterBootstrap.java
@@ -29,6 +29,7 @@ package org.apache.hc.core5.http2.impl.nio.bootstrap;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.config.CharCodingConfig;
@@ -68,6 +69,7 @@ public class Http2MultiplexingRequesterBootstrap {
     private TlsStrategy tlsStrategy;
     private boolean strictALPNHandshake;
     private Decorator<ProtocolIOSession> ioSessionDecorator;
+    private Callback<Exception> exceptionCallback;
     private IOSessionListener sessionListener;
     private Http2StreamListener streamListener;
 
@@ -133,6 +135,14 @@ public class Http2MultiplexingRequesterBootstrap {
     }
 
     /**
+     * Assigns {@link Exception} {@link Callback} instance.
+     */
+    public final Http2MultiplexingRequesterBootstrap setExceptionCallback(final Callback<Exception> exceptionCallback) {
+        this.exceptionCallback = exceptionCallback;
+        return this;
+    }
+
+    /**
      * Assigns {@link IOSessionListener} instance.
      */
     public final Http2MultiplexingRequesterBootstrap setIOSessionListener(final IOSessionListener sessionListener) {
@@ -208,6 +218,7 @@ public class Http2MultiplexingRequesterBootstrap {
 
                 },
                 ioSessionDecorator,
+                exceptionCallback,
                 sessionListener,
                 DefaultAddressResolver.INSTANCE,
                 tlsStrategy != null ? tlsStrategy : new H2ClientTlsStrategy());
diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/AsyncRequester.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/AsyncRequester.java
index a62c7d6..3c22903 100644
--- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/AsyncRequester.java
+++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/AsyncRequester.java
@@ -58,8 +58,14 @@ public class AsyncRequester extends IOReactorExecutor<DefaultConnectingIOReactor
             final IOReactorConfig ioReactorConfig,
             final ThreadFactory threadFactory,
             final Callback<IOSession> sessionShutdownCallback) throws IOException {
-        return new DefaultConnectingIOReactor(ioEventHandlerFactory, ioReactorConfig, threadFactory,
-                LoggingIOSessionDecorator.INSTANCE, LoggingIOSessionListener.INSTANCE, sessionShutdownCallback);
+        return new DefaultConnectingIOReactor(
+                ioEventHandlerFactory,
+                ioReactorConfig,
+                threadFactory,
+                LoggingIOSessionDecorator.INSTANCE,
+                LoggingExceptionCallback.INSTANCE,
+                LoggingIOSessionListener.INSTANCE,
+                sessionShutdownCallback);
     }
 
     private InetSocketAddress toSocketAddress(final HttpHost host) {
diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/AsyncServer.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/AsyncServer.java
index 76b2135..562d33e 100644
--- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/AsyncServer.java
+++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/AsyncServer.java
@@ -52,8 +52,15 @@ public class AsyncServer extends IOReactorExecutor<DefaultListeningIOReactor> {
             final IOReactorConfig ioReactorConfig,
             final ThreadFactory threadFactory,
             final Callback<IOSession> sessionShutdownCallback) throws IOException {
-        return new DefaultListeningIOReactor(ioEventHandlerFactory, ioReactorConfig, threadFactory, threadFactory,
-                LoggingIOSessionDecorator.INSTANCE, LoggingIOSessionListener.INSTANCE, sessionShutdownCallback);
+        return new DefaultListeningIOReactor(
+                ioEventHandlerFactory,
+                ioReactorConfig,
+                threadFactory,
+                threadFactory,
+                LoggingIOSessionDecorator.INSTANCE,
+                LoggingExceptionCallback.INSTANCE,
+                LoggingIOSessionListener.INSTANCE,
+                sessionShutdownCallback);
     }
 
     public Future<ListenerEndpoint> listen(final InetSocketAddress address) {
diff --git a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/IOReactorExecutor.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/IOReactorExecutor.java
index 1b739c6..22d1db3 100644
--- a/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/IOReactorExecutor.java
+++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/IOReactorExecutor.java
@@ -28,15 +28,12 @@
 package org.apache.hc.core5.testing.nio;
 
 import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.http.nio.command.ShutdownCommand;
 import org.apache.hc.core5.io.CloseMode;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.IOReactorService;
@@ -99,11 +96,6 @@ abstract class IOReactorExecutor<T extends IOReactorService> implements AutoClos
         return ioReactor != null ? ioReactor.getStatus() : IOReactorStatus.INACTIVE;
     }
 
-    public List<ExceptionEvent> getExceptionLog() {
-        final T ioReactor = ioReactorRef.get();
-        return ioReactor != null ? ioReactor.getExceptionLog() : Collections.<ExceptionEvent>emptyList();
-    }
-
     public void awaitShutdown(final TimeValue waitTime) throws InterruptedException {
         Args.notNull(waitTime, "Wait time");
         final T ioReactor = ioReactorRef.get();
diff --git a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/ExceptionEventTest.java b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/LoggingExceptionCallback.java
similarity index 54%
rename from httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/ExceptionEventTest.java
rename to httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/LoggingExceptionCallback.java
index 6561ca6..da097e9 100644
--- a/httpcore5/src/test/java/org/apache/hc/core5/http/impl/nio/ExceptionEventTest.java
+++ b/httpcore5-testing/src/main/java/org/apache/hc/core5/testing/nio/LoggingExceptionCallback.java
@@ -25,37 +25,24 @@
  *
  */
 
-package org.apache.hc.core5.http.impl.nio;
+package org.apache.hc.core5.testing.nio;
 
-import java.util.Date;
+import org.apache.hc.core5.function.Callback;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import org.apache.hc.core5.reactor.ExceptionEvent;
-import org.junit.Assert;
-import org.junit.Test;
+public class LoggingExceptionCallback implements Callback<Exception> {
 
-public class ExceptionEventTest {
+    public final static LoggingExceptionCallback INSTANCE = new LoggingExceptionCallback();
 
-    @Test
-    public void testGetCause() {
-        final NullPointerException npe = new NullPointerException("npe");
-        ExceptionEvent ee = new ExceptionEvent(npe);
-        Assert.assertSame(npe, ee.getCause());
-        ee = new ExceptionEvent(npe, new Date());
-        Assert.assertSame(npe, ee.getCause());
-    }
+    private final Logger log = LoggerFactory.getLogger("org.apache.hc.core5.reactor");
 
-    @Test
-    public void testGetTimestamp() {
-        final NullPointerException npe = new NullPointerException("npe");
-        ExceptionEvent ee = new ExceptionEvent(npe);
-        Assert.assertNotNull(ee.getTimestamp());
-        ee = new ExceptionEvent(npe, new Date(1234567890L));
-        Assert.assertEquals(new Date(1234567890L), ee.getTimestamp());
+    private LoggingExceptionCallback() {
     }
 
-    @Test
-    public void testToString() {
-        Assert.assertNotNull(new ExceptionEvent(new NullPointerException()));
+    @Override
+    public void execute(final Exception ex) {
+        log.error(ex.getMessage(), ex);
     }
 
 }
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java
index 1d5f613..ec355d3 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1IntegrationTest.java
@@ -94,10 +94,6 @@ import org.apache.hc.core5.http.nio.AsyncRequestProducer;
 import org.apache.hc.core5.http.nio.AsyncResponseProducer;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.AsyncServerRequestHandler;
-import org.apache.hc.core5.http.nio.support.BasicRequestConsumer;
-import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
-import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
-import org.apache.hc.core5.http.nio.support.BasicResponseProducer;
 import org.apache.hc.core5.http.nio.CapacityChannel;
 import org.apache.hc.core5.http.nio.ContentEncoder;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
@@ -113,6 +109,10 @@ import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
 import org.apache.hc.core5.http.nio.support.AbstractServerExchangeHandler;
 import org.apache.hc.core5.http.nio.support.BasicAsyncServerExpectationDecorator;
+import org.apache.hc.core5.http.nio.support.BasicRequestConsumer;
+import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
+import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
+import org.apache.hc.core5.http.nio.support.BasicResponseProducer;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicEntityConsumer;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicEntityProducer;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicServerExchangeHandler;
@@ -122,7 +122,6 @@ import org.apache.hc.core5.http.protocol.HttpProcessor;
 import org.apache.hc.core5.http.protocol.RequestConnControl;
 import org.apache.hc.core5.http.protocol.RequestContent;
 import org.apache.hc.core5.http.protocol.RequestValidateHost;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.IOSession;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
@@ -179,13 +178,6 @@ public class Http1IntegrationTest extends InternalHttp1ServerTestBase {
         log.debug("Shutting down test client");
         if (client != null) {
             client.shutdown(TimeValue.ofSeconds(5));
-            final List<ExceptionEvent> exceptionLog = client.getExceptionLog();
-            if (!exceptionLog.isEmpty()) {
-                for (final ExceptionEvent event: exceptionLog) {
-                    final Throwable cause = event.getCause();
-                    log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                }
-            }
         }
     }
 
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
index 5a15180..04d8f5e 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http1ServerAndRequesterTest.java
@@ -32,7 +32,6 @@ import java.net.InetSocketAddress;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Queue;
 import java.util.concurrent.Future;
 
@@ -61,16 +60,15 @@ import org.apache.hc.core5.http.nio.AsyncFilterChain;
 import org.apache.hc.core5.http.nio.AsyncFilterHandler;
 import org.apache.hc.core5.http.nio.AsyncPushProducer;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
-import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
-import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
 import org.apache.hc.core5.http.nio.ssl.BasicClientTlsStrategy;
 import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy;
+import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
+import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
 import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http.protocol.UriPatternMatcher;
 import org.apache.hc.core5.io.CloseMode;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.ListenerEndpoint;
 import org.apache.hc.core5.testing.SSLTestContexts;
@@ -168,9 +166,10 @@ public class Http1ServerAndRequesterTest {
                     .setTlsStrategy(scheme == URIScheme.HTTPS  ? new BasicServerTlsStrategy(
                             SSLTestContexts.createServerSSLContext(),
                             SecureAllPortsStrategy.INSTANCE) : null)
-                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .create();
         }
 
@@ -178,18 +177,7 @@ public class Http1ServerAndRequesterTest {
         protected void after() {
             log.debug("Shutting down test server");
             if (server != null) {
-                try {
-                    server.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = server.getExceptionLog();
-                    server = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                server.close(CloseMode.GRACEFUL);
             }
         }
 
@@ -208,10 +196,11 @@ public class Http1ServerAndRequesterTest {
                             .setSoTimeout(TIMEOUT)
                             .build())
                     .setTlsStrategy(new BasicClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
-                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
                     .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .create();
         }
 
@@ -219,18 +208,7 @@ public class Http1ServerAndRequesterTest {
         protected void after() {
             log.debug("Shutting down test client");
             if (requester != null) {
-                try {
-                    requester.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = requester.getExceptionLog();
-                    requester = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                requester.close(CloseMode.GRACEFUL);
             }
         }
 
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2AlpnTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2AlpnTest.java
index fbea0f3..23b28e8 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2AlpnTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2AlpnTest.java
@@ -28,7 +28,6 @@
 package org.apache.hc.core5.testing.nio;
 
 import java.net.InetSocketAddress;
-import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
 
@@ -43,17 +42,16 @@ import org.apache.hc.core5.http.Methods;
 import org.apache.hc.core5.http.URIScheme;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
-import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
-import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
 import org.apache.hc.core5.http.nio.ssl.BasicServerTlsStrategy;
+import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
+import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
 import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap;
 import org.apache.hc.core5.http2.impl.nio.bootstrap.Http2MultiplexingRequester;
 import org.apache.hc.core5.http2.impl.nio.bootstrap.Http2MultiplexingRequesterBootstrap;
 import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
 import org.apache.hc.core5.io.CloseMode;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.ListenerEndpoint;
 import org.apache.hc.core5.testing.SSLTestContexts;
@@ -93,9 +91,10 @@ public class Http2AlpnTest {
                     .setTlsStrategy(new BasicServerTlsStrategy(
                             SSLTestContexts.createServerSSLContext(),
                             SecureAllPortsStrategy.INSTANCE))
-                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
-                    .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
                     .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
+                    .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .register("*", new Supplier<AsyncServerExchangeHandler>() {
 
                         @Override
@@ -111,18 +110,7 @@ public class Http2AlpnTest {
         protected void after() {
             log.debug("Shutting down test server");
             if (server != null) {
-                try {
-                    server.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = server.getExceptionLog();
-                    server = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                server.close(CloseMode.GRACEFUL);
             }
         }
 
@@ -137,18 +125,7 @@ public class Http2AlpnTest {
         protected void after() {
             log.debug("Shutting down test client");
             if (requester != null) {
-                try {
-                    requester.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = requester.getExceptionLog();
-                    requester = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                requester.close(CloseMode.GRACEFUL);
             }
         }
 
@@ -178,9 +155,10 @@ public class Http2AlpnTest {
                         .build())
                 .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
                 .setStrictALPNHandshake(false)
-                .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
-                .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
                 .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
+                .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                 .create();
 
         server.start();
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2IntegrationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2IntegrationTest.java
index df1aee9..a2b448d 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2IntegrationTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2IntegrationTest.java
@@ -85,12 +85,6 @@ import org.apache.hc.core5.http.nio.AsyncRequestConsumer;
 import org.apache.hc.core5.http.nio.AsyncResponseProducer;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
 import org.apache.hc.core5.http.nio.AsyncServerRequestHandler;
-import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder;
-import org.apache.hc.core5.http.nio.support.BasicPushProducer;
-import org.apache.hc.core5.http.nio.support.BasicRequestConsumer;
-import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
-import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
-import org.apache.hc.core5.http.nio.support.BasicResponseProducer;
 import org.apache.hc.core5.http.nio.CapacityChannel;
 import org.apache.hc.core5.http.nio.DataStreamChannel;
 import org.apache.hc.core5.http.nio.HandlerFactory;
@@ -103,7 +97,13 @@ import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
 import org.apache.hc.core5.http.nio.support.AbstractAsyncPushHandler;
 import org.apache.hc.core5.http.nio.support.AbstractServerExchangeHandler;
+import org.apache.hc.core5.http.nio.support.AsyncResponseBuilder;
 import org.apache.hc.core5.http.nio.support.BasicAsyncServerExpectationDecorator;
+import org.apache.hc.core5.http.nio.support.BasicPushProducer;
+import org.apache.hc.core5.http.nio.support.BasicRequestConsumer;
+import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
+import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
+import org.apache.hc.core5.http.nio.support.BasicResponseProducer;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicEntityConsumer;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicEntityProducer;
 import org.apache.hc.core5.http.nio.support.classic.AbstractClassicServerExchangeHandler;
@@ -114,7 +114,6 @@ import org.apache.hc.core5.http2.config.H2Config;
 import org.apache.hc.core5.http2.nio.command.PingCommand;
 import org.apache.hc.core5.http2.nio.support.BasicPingHandler;
 import org.apache.hc.core5.reactor.Command;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.IOSession;
 import org.apache.hc.core5.reactor.ProtocolIOSession;
@@ -170,13 +169,6 @@ public class Http2IntegrationTest extends InternalHttp2ServerTestBase {
         log.debug("Shutting down test client");
         if (client != null) {
             client.shutdown(TimeValue.ofSeconds(5));
-            final List<ExceptionEvent> exceptionLog = client.getExceptionLog();
-            if (!exceptionLog.isEmpty()) {
-                for (final ExceptionEvent event: exceptionLog) {
-                    final Throwable cause = event.getCause();
-                    log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                }
-            }
         }
     }
 
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ProtocolNegotiationTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ProtocolNegotiationTest.java
index 2391cb4..72af361 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ProtocolNegotiationTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ProtocolNegotiationTest.java
@@ -28,7 +28,6 @@
 package org.apache.hc.core5.testing.nio;
 
 import java.net.InetSocketAddress;
-import java.util.List;
 import java.util.concurrent.Future;
 
 import org.apache.hc.core5.function.Supplier;
@@ -43,10 +42,10 @@ import org.apache.hc.core5.http.ProtocolVersion;
 import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
 import org.apache.hc.core5.http.nio.AsyncClientEndpoint;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
-import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
-import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityConsumer;
 import org.apache.hc.core5.http.nio.entity.StringAsyncEntityProducer;
+import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
+import org.apache.hc.core5.http.nio.support.BasicResponseConsumer;
 import org.apache.hc.core5.http2.HttpVersionPolicy;
 import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
 import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap;
@@ -54,7 +53,6 @@ import org.apache.hc.core5.http2.impl.nio.bootstrap.Http2AsyncRequester;
 import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
 import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy;
 import org.apache.hc.core5.io.CloseMode;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.ListenerEndpoint;
 import org.apache.hc.core5.testing.SSLTestContexts;
@@ -104,10 +102,11 @@ public class Http2ProtocolNegotiationTest {
                         }
 
                     })
-                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .create();
         }
 
@@ -115,18 +114,7 @@ public class Http2ProtocolNegotiationTest {
         protected void after() {
             log.debug("Shutting down test server");
             if (server != null) {
-                try {
-                    server.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = server.getExceptionLog();
-                    server = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                server.close(CloseMode.GRACEFUL);
             }
         }
 
@@ -147,11 +135,12 @@ public class Http2ProtocolNegotiationTest {
                             .setSoTimeout(TIMEOUT)
                             .build())
                     .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
-                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
                     .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .create();
         }
 
@@ -159,18 +148,7 @@ public class Http2ProtocolNegotiationTest {
         protected void after() {
             log.debug("Shutting down test client");
             if (requester != null) {
-                try {
-                    requester.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = requester.getExceptionLog();
-                    requester = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                requester.close(CloseMode.GRACEFUL);
             }
         }
 
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ServerAndMultiplexingRequesterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ServerAndMultiplexingRequesterTest.java
index 4cc704b..d20abed 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ServerAndMultiplexingRequesterTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ServerAndMultiplexingRequesterTest.java
@@ -31,7 +31,6 @@ import java.net.InetSocketAddress;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Queue;
 import java.util.Random;
 import java.util.concurrent.CountDownLatch;
@@ -61,7 +60,6 @@ import org.apache.hc.core5.http2.impl.nio.bootstrap.Http2MultiplexingRequesterBo
 import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
 import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy;
 import org.apache.hc.core5.io.CloseMode;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.ListenerEndpoint;
 import org.apache.hc.core5.testing.SSLTestContexts;
@@ -119,6 +117,7 @@ public class Http2ServerAndMultiplexingRequesterTest {
                             SecureAllPortsStrategy.INSTANCE) : null)
                     .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
                     .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
                     .register("*", new Supplier<AsyncServerExchangeHandler>() {
 
@@ -135,18 +134,7 @@ public class Http2ServerAndMultiplexingRequesterTest {
         protected void after() {
             log.debug("Shutting down test server");
             if (server != null) {
-                try {
-                    server.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = server.getExceptionLog();
-                    server = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                server.close(CloseMode.GRACEFUL);
             }
         }
 
@@ -167,6 +155,7 @@ public class Http2ServerAndMultiplexingRequesterTest {
                     .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
                     .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
                     .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
                     .create();
         }
@@ -175,18 +164,7 @@ public class Http2ServerAndMultiplexingRequesterTest {
         protected void after() {
             log.debug("Shutting down test client");
             if (requester != null) {
-                try {
-                    requester.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = requester.getExceptionLog();
-                    requester = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                requester.close(CloseMode.GRACEFUL);
             }
         }
 
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ServerAndRequesterTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ServerAndRequesterTest.java
index f0f6b0f..1fec54c 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ServerAndRequesterTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/Http2ServerAndRequesterTest.java
@@ -31,7 +31,6 @@ import java.net.InetSocketAddress;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.LinkedList;
-import java.util.List;
 import java.util.Queue;
 import java.util.concurrent.Future;
 
@@ -58,7 +57,6 @@ import org.apache.hc.core5.http2.impl.nio.bootstrap.H2ServerBootstrap;
 import org.apache.hc.core5.http2.ssl.H2ClientTlsStrategy;
 import org.apache.hc.core5.http2.ssl.H2ServerTlsStrategy;
 import org.apache.hc.core5.io.CloseMode;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.ListenerEndpoint;
 import org.apache.hc.core5.testing.SSLTestContexts;
@@ -116,10 +114,11 @@ public class Http2ServerAndRequesterTest {
                     .setTlsStrategy(scheme == URIScheme.HTTPS  ? new H2ServerTlsStrategy(
                             SSLTestContexts.createServerSSLContext(),
                             SecureAllPortsStrategy.INSTANCE) : null)
-                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
                     .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .register("*", new Supplier<AsyncServerExchangeHandler>() {
 
                         @Override
@@ -135,18 +134,7 @@ public class Http2ServerAndRequesterTest {
         protected void after() {
             log.debug("Shutting down test server");
             if (server != null) {
-                try {
-                    server.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = server.getExceptionLog();
-                    server = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                server.close(CloseMode.GRACEFUL);
             }
         }
 
@@ -166,11 +154,12 @@ public class Http2ServerAndRequesterTest {
                             .setSoTimeout(TIMEOUT)
                             .build())
                     .setTlsStrategy(new H2ClientTlsStrategy(SSLTestContexts.createClientSSLContext()))
-                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
                     .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
                     .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
                     .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                    .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                    .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                     .create();
         }
 
@@ -178,18 +167,7 @@ public class Http2ServerAndRequesterTest {
         protected void after() {
             log.debug("Shutting down test client");
             if (requester != null) {
-                try {
-                    requester.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = requester.getExceptionLog();
-                    requester = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                requester.close(CloseMode.GRACEFUL);
             }
         }
 
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/InternalHttp1ServerTestBase.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/InternalHttp1ServerTestBase.java
index 3bbcb50..06e4210 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/InternalHttp1ServerTestBase.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/InternalHttp1ServerTestBase.java
@@ -27,17 +27,14 @@
 
 package org.apache.hc.core5.testing.nio;
 
-import java.util.List;
-
 import org.apache.hc.core5.http.URIScheme;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.testing.SSLTestContexts;
 import org.apache.hc.core5.util.TimeValue;
-import org.slf4j.LoggerFactory;
-import org.slf4j.Logger;
 import org.junit.Rule;
 import org.junit.rules.ExternalResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public abstract class InternalHttp1ServerTestBase {
 
@@ -70,18 +67,7 @@ public abstract class InternalHttp1ServerTestBase {
         protected void after() {
             log.debug("Shutting down test server");
             if (server != null) {
-                try {
-                    server.shutdown(TimeValue.ofSeconds(5));
-                    final List<ExceptionEvent> exceptionLog = server.getExceptionLog();
-                    server = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                server.shutdown(TimeValue.ofSeconds(5));
             }
         }
 
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/InternalHttp2ServerTestBase.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/InternalHttp2ServerTestBase.java
index a6dc728..bb5c6ca 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/InternalHttp2ServerTestBase.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/nio/InternalHttp2ServerTestBase.java
@@ -27,17 +27,14 @@
 
 package org.apache.hc.core5.testing.nio;
 
-import java.util.List;
-
 import org.apache.hc.core5.http.URIScheme;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.testing.SSLTestContexts;
 import org.apache.hc.core5.util.TimeValue;
-import org.slf4j.LoggerFactory;
-import org.slf4j.Logger;
 import org.junit.Rule;
 import org.junit.rules.ExternalResource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public abstract class InternalHttp2ServerTestBase {
 
@@ -69,18 +66,7 @@ public abstract class InternalHttp2ServerTestBase {
         protected void after() {
             log.debug("Shutting down test server");
             if (server != null) {
-                try {
-                    server.shutdown(TimeValue.ofSeconds(5));
-                    final List<ExceptionEvent> exceptionLog = server.getExceptionLog();
-                    server = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                server.shutdown(TimeValue.ofSeconds(5));
             }
         }
 
diff --git a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/ReactiveClientTest.java b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/ReactiveClientTest.java
index 9128648..0a28f1d 100644
--- a/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/ReactiveClientTest.java
+++ b/httpcore5-testing/src/test/java/org/apache/hc/core5/testing/reactive/ReactiveClientTest.java
@@ -67,8 +67,8 @@ import org.apache.hc.core5.http.impl.bootstrap.HttpAsyncServer;
 import org.apache.hc.core5.http.message.BasicHeader;
 import org.apache.hc.core5.http.message.BasicHttpResponse;
 import org.apache.hc.core5.http.nio.AsyncServerExchangeHandler;
-import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
 import org.apache.hc.core5.http.nio.ResponseChannel;
+import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
 import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http2.HttpVersionPolicy;
 import org.apache.hc.core5.http2.impl.nio.bootstrap.H2RequesterBootstrap;
@@ -78,10 +78,10 @@ import org.apache.hc.core5.reactive.ReactiveEntityProducer;
 import org.apache.hc.core5.reactive.ReactiveRequestProcessor;
 import org.apache.hc.core5.reactive.ReactiveResponseConsumer;
 import org.apache.hc.core5.reactive.ReactiveServerExchangeHandler;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.ListenerEndpoint;
 import org.apache.hc.core5.testing.classic.LoggingConnPoolListener;
+import org.apache.hc.core5.testing.nio.LoggingExceptionCallback;
 import org.apache.hc.core5.testing.nio.LoggingHttp1StreamListener;
 import org.apache.hc.core5.testing.nio.LoggingHttp2StreamListener;
 import org.apache.hc.core5.testing.nio.LoggingIOSessionDecorator;
@@ -162,10 +162,11 @@ public class ReactiveClientTest {
                     IOReactorConfig.custom()
                         .setSoTimeout(SOCKET_TIMEOUT)
                         .build())
-                .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_SERVER)
                 .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                 .register("*", new Supplier<AsyncServerExchangeHandler>() {
 
                     @Override
@@ -181,18 +182,7 @@ public class ReactiveClientTest {
         protected void after() {
             log.debug("Shutting down test server");
             if (server != null) {
-                try {
-                    server.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = server.getExceptionLog();
-                    server = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                server.close(CloseMode.GRACEFUL);
             }
         }
 
@@ -211,11 +201,12 @@ public class ReactiveClientTest {
                 .setIOReactorConfig(IOReactorConfig.custom()
                     .setSoTimeout(SOCKET_TIMEOUT)
                     .build())
-                .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                 .setStreamListener(LoggingHttp1StreamListener.INSTANCE_CLIENT)
                 .setStreamListener(LoggingHttp2StreamListener.INSTANCE)
                 .setConnPoolListener(LoggingConnPoolListener.INSTANCE)
                 .setIOSessionDecorator(LoggingIOSessionDecorator.INSTANCE)
+                .setExceptionCallback(LoggingExceptionCallback.INSTANCE)
+                .setIOSessionListener(LoggingIOSessionListener.INSTANCE)
                 .create();
         }
 
@@ -223,18 +214,7 @@ public class ReactiveClientTest {
         protected void after() {
             log.debug("Shutting down test client");
             if (requester != null) {
-                try {
-                    requester.close(CloseMode.GRACEFUL);
-                    final List<ExceptionEvent> exceptionLog = requester.getExceptionLog();
-                    requester = null;
-                    if (!exceptionLog.isEmpty()) {
-                        for (final ExceptionEvent event: exceptionLog) {
-                            final Throwable cause = event.getCause();
-                            log.error("Unexpected " + cause.getClass() + " at " + event.getTimestamp(), cause);
-                        }
-                    }
-                } catch (final Exception ignore) {
-                }
+                requester.close(CloseMode.GRACEFUL);
             }
         }
 
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequester.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequester.java
index 29a2983..b52ae46 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequester.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequester.java
@@ -29,7 +29,6 @@ package org.apache.hc.core5.http.impl.bootstrap;
 
 import java.io.IOException;
 import java.net.InetSocketAddress;
-import java.util.List;
 import java.util.concurrent.Future;
 
 import org.apache.hc.core5.annotation.Internal;
@@ -43,7 +42,6 @@ import org.apache.hc.core5.http.impl.DefaultAddressResolver;
 import org.apache.hc.core5.io.CloseMode;
 import org.apache.hc.core5.reactor.ConnectionInitiator;
 import org.apache.hc.core5.reactor.DefaultConnectingIOReactor;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.IOReactorService;
@@ -70,6 +68,7 @@ public class AsyncRequester extends AbstractConnectionInitiatorBase implements I
             final IOEventHandlerFactory eventHandlerFactory,
             final IOReactorConfig ioReactorConfig,
             final Decorator<ProtocolIOSession> ioSessionDecorator,
+            final Callback<Exception> exceptionCallback,
             final IOSessionListener sessionListener,
             final Callback<IOSession> sessionShutdownCallback,
             final Resolver<HttpHost, InetSocketAddress> addressResolver) {
@@ -78,6 +77,7 @@ public class AsyncRequester extends AbstractConnectionInitiatorBase implements I
                 ioReactorConfig,
                 new DefaultThreadFactory("requester-dispatch", true),
                 ioSessionDecorator,
+                exceptionCallback,
                 sessionListener,
                 sessionShutdownCallback);
         this.addressResolver = addressResolver != null ? addressResolver : DefaultAddressResolver.INSTANCE;
@@ -109,11 +109,6 @@ public class AsyncRequester extends AbstractConnectionInitiatorBase implements I
     }
 
     @Override
-    public List<ExceptionEvent> getExceptionLog() {
-        return ioReactor.getExceptionLog();
-    }
-
-    @Override
     public void initiateShutdown() {
         ioReactor.initiateShutdown();
     }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java
index 06b9e49..9e104e2 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncRequesterBootstrap.java
@@ -27,6 +27,7 @@
 package org.apache.hc.core5.http.impl.bootstrap;
 
 import org.apache.hc.core5.annotation.Experimental;
+import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.http.ConnectionReuseStrategy;
 import org.apache.hc.core5.http.HttpHost;
@@ -72,6 +73,7 @@ public class AsyncRequesterBootstrap {
     private TlsStrategy tlsStrategy;
     private Timeout handshakeTimeout;
     private Decorator<ProtocolIOSession> ioSessionDecorator;
+    private Callback<Exception> exceptionCallback;
     private IOSessionListener sessionListener;
     private Http1StreamListener streamListener;
     private ConnPoolListener<HttpHost> connPoolListener;
@@ -177,6 +179,14 @@ public class AsyncRequesterBootstrap {
     }
 
     /**
+     * Assigns {@link Exception} {@link Callback} instance.
+     */
+    public final AsyncRequesterBootstrap setExceptionCallback(final Callback<Exception> exceptionCallback) {
+        this.exceptionCallback = exceptionCallback;
+        return this;
+    }
+
+    /**
      * Assigns {@link IOSessionListener} instance.
      */
     public final AsyncRequesterBootstrap setIOSessionListener(final IOSessionListener sessionListener) {
@@ -236,6 +246,7 @@ public class AsyncRequesterBootstrap {
                 ioReactorConfig,
                 ioEventHandlerFactory,
                 ioSessionDecorator,
+                exceptionCallback,
                 sessionListener,
                 connPool);
     }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServer.java
index 4eb09c5..0c4db17 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServer.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServer.java
@@ -29,7 +29,6 @@ package org.apache.hc.core5.http.impl.bootstrap;
 
 import java.io.IOException;
 import java.net.SocketAddress;
-import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Future;
 
@@ -42,7 +41,6 @@ import org.apache.hc.core5.io.CloseMode;
 import org.apache.hc.core5.reactor.ConnectionAcceptor;
 import org.apache.hc.core5.reactor.ConnectionInitiator;
 import org.apache.hc.core5.reactor.DefaultListeningIOReactor;
-import org.apache.hc.core5.reactor.ExceptionEvent;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
 import org.apache.hc.core5.reactor.IOReactorConfig;
 import org.apache.hc.core5.reactor.IOReactorService;
@@ -65,6 +63,7 @@ public class AsyncServer extends AbstractConnectionInitiatorBase implements IORe
             final IOEventHandlerFactory eventHandlerFactory,
             final IOReactorConfig ioReactorConfig,
             final Decorator<ProtocolIOSession> ioSessionDecorator,
+            final Callback<Exception> exceptionCallback,
             final IOSessionListener sessionListener,
             final Callback<IOSession> sessionShutdownCallback) {
         this.ioReactor = new DefaultListeningIOReactor(
@@ -73,6 +72,7 @@ public class AsyncServer extends AbstractConnectionInitiatorBase implements IORe
                 new DefaultThreadFactory("server-dispatch", true),
                 new DefaultThreadFactory("server-listener", true),
                 ioSessionDecorator,
+                exceptionCallback,
                 sessionListener,
                 sessionShutdownCallback);
     }
@@ -117,11 +117,6 @@ public class AsyncServer extends AbstractConnectionInitiatorBase implements IORe
     }
 
     @Override
-    public List<ExceptionEvent> getExceptionLog() {
-        return ioReactor.getExceptionLog();
-    }
-
-    @Override
     public void initiateShutdown() {
         ioReactor.initiateShutdown();
     }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java
index 509ea98..9da2720 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/AsyncServerBootstrap.java
@@ -29,6 +29,7 @@ package org.apache.hc.core5.http.impl.bootstrap;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.function.Supplier;
 import org.apache.hc.core5.http.ConnectionReuseStrategy;
@@ -86,6 +87,7 @@ public class AsyncServerBootstrap {
     private TlsStrategy tlsStrategy;
     private Timeout handshakeTimeout;
     private Decorator<ProtocolIOSession> ioSessionDecorator;
+    private Callback<Exception> exceptionCallback;
     private IOSessionListener sessionListener;
     private Http1StreamListener streamListener;
 
@@ -171,6 +173,14 @@ public class AsyncServerBootstrap {
     }
 
     /**
+     * Assigns {@link Exception} {@link Callback} instance.
+     */
+    public final AsyncServerBootstrap setExceptionCallback(final Callback<Exception> exceptionCallback) {
+        this.exceptionCallback = exceptionCallback;
+        return this;
+    }
+
+    /**
      * Assigns {@link IOSessionListener} instance.
      */
     public final AsyncServerBootstrap setIOSessionListener(final IOSessionListener sessionListener) {
@@ -405,7 +415,8 @@ public class AsyncServerBootstrap {
                 streamHandlerFactory,
                 tlsStrategy,
                 handshakeTimeout);
-        return new HttpAsyncServer(ioEventHandlerFactory, ioReactorConfig, ioSessionDecorator, sessionListener);
+        return new HttpAsyncServer(ioEventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback,
+                sessionListener);
     }
 
 }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncRequester.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncRequester.java
index 0dff2f7..3d1bca1 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncRequester.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncRequester.java
@@ -38,6 +38,7 @@ import org.apache.hc.core5.annotation.Internal;
 import org.apache.hc.core5.concurrent.BasicFuture;
 import org.apache.hc.core5.concurrent.ComplexFuture;
 import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.http.EntityDetails;
 import org.apache.hc.core5.http.Header;
@@ -94,9 +95,10 @@ public class HttpAsyncRequester extends AsyncRequester implements ConnPoolContro
             final IOReactorConfig ioReactorConfig,
             final IOEventHandlerFactory eventHandlerFactory,
             final Decorator<ProtocolIOSession> ioSessionDecorator,
+            final Callback<Exception> exceptionCallback,
             final IOSessionListener sessionListener,
             final ManagedConnPool<HttpHost, IOSession> connPool) {
-        super(eventHandlerFactory, ioReactorConfig, ioSessionDecorator, sessionListener,
+        super(eventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback, sessionListener,
                         ShutdownCommand.GRACEFUL_IMMEDIATE_CALLBACK, DefaultAddressResolver.INSTANCE);
         this.connPool = Args.notNull(connPool, "Connection pool");
     }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncServer.java b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncServer.java
index e965742..d288716 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncServer.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/http/impl/bootstrap/HttpAsyncServer.java
@@ -27,6 +27,7 @@
 package org.apache.hc.core5.http.impl.bootstrap;
 
 import org.apache.hc.core5.annotation.Internal;
+import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.function.Decorator;
 import org.apache.hc.core5.http.nio.command.ShutdownCommand;
 import org.apache.hc.core5.reactor.IOEventHandlerFactory;
@@ -49,8 +50,9 @@ public class HttpAsyncServer extends AsyncServer {
             final IOEventHandlerFactory eventHandlerFactory,
             final IOReactorConfig ioReactorConfig,
             final Decorator<ProtocolIOSession> ioSessionDecorator,
+            final Callback<Exception> exceptionCallback,
             final IOSessionListener sessionListener) {
-        super(eventHandlerFactory, ioReactorConfig, ioSessionDecorator, sessionListener,
+        super(eventHandlerFactory, ioReactorConfig, ioSessionDecorator, exceptionCallback, sessionListener,
                         ShutdownCommand.GRACEFUL_NORMAL_CALLBACK);
     }
 
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractSingleCoreIOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractSingleCoreIOReactor.java
index bcce91b..62db777 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractSingleCoreIOReactor.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/AbstractSingleCoreIOReactor.java
@@ -32,11 +32,10 @@ import java.io.IOException;
 import java.nio.channels.ClosedSelectorException;
 import java.nio.channels.SelectionKey;
 import java.nio.channels.Selector;
-import java.util.Date;
-import java.util.Queue;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicReference;
 
+import org.apache.hc.core5.function.Callback;
 import org.apache.hc.core5.io.CloseMode;
 import org.apache.hc.core5.io.Closer;
 import org.apache.hc.core5.util.Args;
@@ -44,15 +43,15 @@ import org.apache.hc.core5.util.TimeValue;
 
 abstract class AbstractSingleCoreIOReactor implements IOReactor {
 
-    private final Queue<ExceptionEvent> auditLog;
+    private final Callback<Exception> exceptionCallback;
     private final AtomicReference<IOReactorStatus> status;
     private final Object shutdownMutex;
 
     final Selector selector;
 
-    AbstractSingleCoreIOReactor(final Queue<ExceptionEvent> auditLog) {
+    AbstractSingleCoreIOReactor(final Callback<Exception> exceptionCallback) {
         super();
-        this.auditLog = auditLog;
+        this.exceptionCallback = exceptionCallback;
         this.shutdownMutex = new Object();
         this.status = new AtomicReference<>(IOReactorStatus.INACTIVE);
         try {
@@ -67,8 +66,10 @@ abstract class AbstractSingleCoreIOReactor implements IOReactor {
         return this.status.get();
     }
 
-    void addExceptionEvent(final Throwable ex) {
-        this.auditLog.add(new ExceptionEvent(ex, new Date()));
+    void logException(final Exception ex) {
+        if (exceptionCallback != null) {
+            exceptionCallback.execute(ex);
+        }
     }
 
     abstract void doExecute() throws IOException;
@@ -82,7 +83,7 @@ abstract class AbstractSingleCoreIOReactor implements IOReactor {
             } catch (final ClosedSelectorException ignore) {
                 // ignore
             } catch (final Exception ex) {
-                addExceptionEvent(ex);
+                logException(ex);
             } finally {
                 try {
                     doTerminate();
@@ -91,17 +92,17 @@ abstract class AbstractSingleCoreIOReactor implements IOReactor {
                         try {
                             Closer.close((Closeable) key.attachment());
                         } catch (final IOException ex) {
-                            addExceptionEvent(ex);
+                            logException(ex);
                         }
                         key.channel().close();
                     }
                     try {
                         this.selector.close();
                     } catch (final IOException ex) {
-                        addExceptionEvent(ex);
+                        logException(ex);
                     }
                 } catch (final Exception ex) {
-                    addExceptionEvent(ex);
+                    logException(ex);
                 } finally {
                     this.status.set(IOReactorStatus.SHUT_DOWN);
                     synchronized (this.shutdownMutex) {
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultConnectingIOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultConnectingIOReactor.java
index 1b10cde..ac10193 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultConnectingIOReactor.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultConnectingIOReactor.java
@@ -28,11 +28,6 @@
 package org.apache.hc.core5.reactor;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.List;
-import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.ThreadFactory;
 
 import org.apache.hc.core5.concurrent.DefaultThreadFactory;
@@ -53,7 +48,6 @@ import org.apache.hc.core5.util.TimeValue;
  */
 public class DefaultConnectingIOReactor extends AbstractIOReactorBase {
 
-    private final Deque<ExceptionEvent> auditLog;
     private final int workerCount;
     private final SingleCoreIOReactor[] workers;
     private final MultiCoreIOReactor ioReactor;
@@ -66,16 +60,16 @@ public class DefaultConnectingIOReactor extends AbstractIOReactorBase {
             final IOReactorConfig ioReactorConfig,
             final ThreadFactory threadFactory,
             final Decorator<ProtocolIOSession> ioSessionDecorator,
+            final Callback<Exception> exceptionCallback,
             final IOSessionListener sessionListener,
             final Callback<IOSession> sessionShutdownCallback) {
         Args.notNull(eventHandlerFactory, "Event handler factory");
-        this.auditLog = new ConcurrentLinkedDeque<>();
         this.workerCount = ioReactorConfig != null ? ioReactorConfig.getIoThreadCount() : IOReactorConfig.DEFAULT.getIoThreadCount();
         this.workers = new SingleCoreIOReactor[workerCount];
         final Thread[] threads = new Thread[workerCount];
         for (int i = 0; i < this.workers.length; i++) {
             final SingleCoreIOReactor dispatcher = new SingleCoreIOReactor(
-                    auditLog,
+                    exceptionCallback,
                     eventHandlerFactory,
                     ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT,
                     ioSessionDecorator,
@@ -92,7 +86,7 @@ public class DefaultConnectingIOReactor extends AbstractIOReactorBase {
             final IOEventHandlerFactory eventHandlerFactory,
             final IOReactorConfig config,
             final Callback<IOSession> sessionShutdownCallback) {
-        this(eventHandlerFactory, config, null, null, null, sessionShutdownCallback);
+        this(eventHandlerFactory, config, null, null, null, null, sessionShutdownCallback);
     }
 
     /**
@@ -120,11 +114,6 @@ public class DefaultConnectingIOReactor extends AbstractIOReactorBase {
     }
 
     @Override
-    public List<ExceptionEvent> getExceptionLog() {
-        return auditLog.isEmpty() ? Collections.<ExceptionEvent>emptyList() : new ArrayList<>(auditLog);
-    }
-
-    @Override
     public void initiateShutdown() {
         ioReactor.initiateShutdown();
     }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultListeningIOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultListeningIOReactor.java
index aad2664..edf1fa6 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultListeningIOReactor.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/DefaultListeningIOReactor.java
@@ -30,12 +30,7 @@ package org.apache.hc.core5.reactor;
 import java.io.IOException;
 import java.net.SocketAddress;
 import java.nio.channels.SocketChannel;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.List;
 import java.util.Set;
-import java.util.concurrent.ConcurrentLinkedDeque;
 import java.util.concurrent.Future;
 import java.util.concurrent.ThreadFactory;
 
@@ -61,7 +56,6 @@ public class DefaultListeningIOReactor extends AbstractIOReactorBase implements
     private final static ThreadFactory DISPATCH_THREAD_FACTORY = new DefaultThreadFactory("I/O server dispatch", true);
     private final static ThreadFactory LISTENER_THREAD_FACTORY = new DefaultThreadFactory("I/O listener", true);
 
-    private final Deque<ExceptionEvent> auditLog;
     private final int workerCount;
     private final SingleCoreIOReactor[] workers;
     private final SingleCoreListeningIOReactor listener;
@@ -84,16 +78,16 @@ public class DefaultListeningIOReactor extends AbstractIOReactorBase implements
             final ThreadFactory dispatchThreadFactory,
             final ThreadFactory listenerThreadFactory,
             final Decorator<ProtocolIOSession> ioSessionDecorator,
+            final Callback<Exception> exceptionCallback,
             final IOSessionListener sessionListener,
             final Callback<IOSession> sessionShutdownCallback) {
         Args.notNull(eventHandlerFactory, "Event handler factory");
-        this.auditLog = new ConcurrentLinkedDeque<>();
         this.workerCount = ioReactorConfig != null ? ioReactorConfig.getIoThreadCount() : IOReactorConfig.DEFAULT.getIoThreadCount();
         this.workers = new SingleCoreIOReactor[workerCount];
         final Thread[] threads = new Thread[workerCount + 1];
         for (int i = 0; i < this.workers.length; i++) {
             final SingleCoreIOReactor dispatcher = new SingleCoreIOReactor(
-                    auditLog,
+                    exceptionCallback,
                     eventHandlerFactory,
                     ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT,
                     ioSessionDecorator,
@@ -104,7 +98,7 @@ public class DefaultListeningIOReactor extends AbstractIOReactorBase implements
         }
         final IOReactor[] ioReactors = new IOReactor[this.workerCount + 1];
         System.arraycopy(this.workers, 0, ioReactors, 1, this.workerCount);
-        this.listener = new SingleCoreListeningIOReactor(auditLog, ioReactorConfig, new Callback<SocketChannel>() {
+        this.listener = new SingleCoreListeningIOReactor(exceptionCallback, ioReactorConfig, new Callback<SocketChannel>() {
 
             @Override
             public void execute(final SocketChannel channel) {
@@ -133,7 +127,7 @@ public class DefaultListeningIOReactor extends AbstractIOReactorBase implements
             final IOEventHandlerFactory eventHandlerFactory,
             final IOReactorConfig config,
             final Callback<IOSession> sessionShutdownCallback) {
-        this(eventHandlerFactory, config, null, null, null, null, sessionShutdownCallback);
+        this(eventHandlerFactory, config, null, null, null, null, null, sessionShutdownCallback);
     }
 
     /**
@@ -186,11 +180,6 @@ public class DefaultListeningIOReactor extends AbstractIOReactorBase implements
         return workerSelector;
     }
 
-    @Override
-    public List<ExceptionEvent> getExceptionLog() {
-        return auditLog.isEmpty() ? Collections.<ExceptionEvent>emptyList() : new ArrayList<>(auditLog);
-    }
-
     private void enqueueChannel(final SocketChannel socketChannel) {
         try {
             workerSelector.next().enqueueChannel(socketChannel);
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ExceptionEvent.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/ExceptionEvent.java
deleted file mode 100644
index 5374dc4..0000000
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/ExceptionEvent.java
+++ /dev/null
@@ -1,77 +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.
- * ====================================================================
- *
- * This software consists of voluntary contributions made by many
- * individuals on behalf of the Apache Software Foundation.  For more
- * information on the Apache Software Foundation, please see
- * <http://www.apache.org/>.
- *
- */
-
-package org.apache.hc.core5.reactor;
-
-import java.util.Date;
-
-import org.apache.hc.core5.annotation.Contract;
-import org.apache.hc.core5.annotation.ThreadingBehavior;
-
-/**
- * A {@link Throwable} instance along with a time stamp.
- *
- * @since 4.0
- */
-@Contract(threading = ThreadingBehavior.IMMUTABLE)
-public class ExceptionEvent {
-
-    private final Throwable ex;
-    private final long time;
-
-    public ExceptionEvent(final Throwable ex, final Date timestamp) {
-        super();
-        this.ex = ex;
-        if (timestamp != null) {
-            this.time = timestamp.getTime();
-        } else {
-            this.time = 0;
-        }
-    }
-
-    public ExceptionEvent(final Exception ex) {
-        this(ex, new Date());
-    }
-
-    public Throwable getCause() {
-        return ex;
-    }
-
-    public Date getTimestamp() {
-        return new Date(this.time);
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder buffer = new StringBuilder();
-        buffer.append(new Date(this.time));
-        buffer.append(" ");
-        buffer.append(this.ex);
-        return buffer.toString();
-    }
-
-}
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorService.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorService.java
index 9c1f1e1..e711e95 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorService.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/IOReactorService.java
@@ -27,8 +27,6 @@
 
 package org.apache.hc.core5.reactor;
 
-import java.util.List;
-
 /**
  * {@link IOReactor} running as a service.
  *
@@ -41,11 +39,4 @@ public interface IOReactorService extends IOReactor {
      */
     void start();
 
-    /**
-     * Returns the list of internal exceptions occurred during
-     * I/O reactor shutdown process.
-     * @return list of internal exceptions.
-     */
-    List<ExceptionEvent> getExceptionLog();
-
 }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java
index b36f16a..cd65bc7 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreIOReactor.java
@@ -67,13 +67,13 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
     private volatile long lastTimeoutCheckMillis;
 
     SingleCoreIOReactor(
-            final Queue<ExceptionEvent> auditLog,
+            final Callback<Exception> exceptionCallback,
             final IOEventHandlerFactory eventHandlerFactory,
             final IOReactorConfig reactorConfig,
             final Decorator<ProtocolIOSession> ioSessionDecorator,
             final IOSessionListener sessionListener,
             final Callback<IOSession> sessionShutdownCallback) {
-        super(auditLog);
+        super(exceptionCallback);
         this.eventHandlerFactory = Args.notNull(eventHandlerFactory, "Event handler factory");
         this.reactorConfig = Args.notNull(reactorConfig, "I/O reactor config");
         this.ioSessionDecorator = ioSessionDecorator;
@@ -181,11 +181,11 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
                 prepareSocket(socketChannel.socket());
                 socketChannel.configureBlocking(false);
             } catch (final IOException ex) {
-                addExceptionEvent(ex);
+                logException(ex);
                 try {
                     socketChannel.close();
                 } catch (final IOException ex2) {
-                    addExceptionEvent(ex2);
+                    logException(ex2);
                 }
                 throw ex;
             }
@@ -353,7 +353,7 @@ class SingleCoreIOReactor extends AbstractSingleCoreIOReactor implements Connect
             try {
                 socketChannel.close();
             } catch (final IOException ex) {
-                addExceptionEvent(ex);
+                logException(ex);
             }
         }
     }
diff --git a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreListeningIOReactor.java b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreListeningIOReactor.java
index 7d56233..1b8577f 100644
--- a/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreListeningIOReactor.java
+++ b/httpcore5/src/main/java/org/apache/hc/core5/reactor/SingleCoreListeningIOReactor.java
@@ -60,10 +60,10 @@ class SingleCoreListeningIOReactor extends AbstractSingleCoreIOReactor implement
     private final long selectTimeoutMillis;
 
     SingleCoreListeningIOReactor(
-            final Queue<ExceptionEvent> auditLog,
+            final Callback<Exception> exceptionCallback,
             final IOReactorConfig ioReactorConfig,
             final Callback<SocketChannel> callback) {
-        super(auditLog);
+        super(exceptionCallback);
         this.reactorConfig = ioReactorConfig != null ? ioReactorConfig : IOReactorConfig.DEFAULT;
         this.callback = callback;
         this.requestQueue = new ConcurrentLinkedQueue<>();