You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by sw...@apache.org on 2026/05/13 05:56:34 UTC

(logging-log4cxx) branch user_defined_socket_support created (now 952b1386)

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

swebb2066 pushed a change to branch user_defined_socket_support
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git


      at 952b1386 Support user provided Socket implementation in the next ABI version

This branch includes the following new commits:

     new 952b1386 Support user provided Socket implementation in the next ABI version

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(logging-log4cxx) 01/01: Support user provided Socket implementation in the next ABI version

Posted by sw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

swebb2066 pushed a commit to branch user_defined_socket_support
in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git

commit 952b138613b5d2f376266639702f316b32379c57
Author: Stephen Webb <sw...@gmail.com>
AuthorDate: Wed May 13 15:55:58 2026 +1000

    Support user provided Socket implementation in the next ABI version
---
 .github/workflows/log4cxx-windows.yml              |   9 +-
 src/main/cpp/CMakeLists.txt                        |   5 +-
 src/main/cpp/aprsocket.cpp                         |  34 +-
 src/main/cpp/exception.cpp                         |   4 +
 src/main/cpp/loglog.cpp                            |  43 +-
 src/main/cpp/socket.cpp                            |  42 +-
 src/main/cpp/socketappenderskeleton.cpp            |  22 +-
 src/main/include/log4cxx/helpers/exception.h       |   2 +
 src/main/include/log4cxx/helpers/loglog.h          |  90 +++-
 src/main/include/log4cxx/helpers/socket.h          |  21 +-
 .../include/log4cxx/net/socketappenderskeleton.h   |  13 +
 src/main/include/log4cxx/private/aprsocket.h       |  15 +-
 .../log4cxx/private/socketappenderskeleton_priv.h  |   2 +
 src/test/cpp/net/CMakeLists.txt                    |   7 +-
 src/test/cpp/net/bsdsocket.cpp                     | 470 +++++++++++++++++++++
 src/test/cpp/net/bsdsocket.h                       |  68 +++
 src/test/cpp/net/socketappendertestcase.cpp        |  32 +-
 17 files changed, 847 insertions(+), 32 deletions(-)

diff --git a/.github/workflows/log4cxx-windows.yml b/.github/workflows/log4cxx-windows.yml
index a93dd718..cc7e2a14 100644
--- a/.github/workflows/log4cxx-windows.yml
+++ b/.github/workflows/log4cxx-windows.yml
@@ -23,15 +23,21 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        name: [windows-2022, windows-2025-qt]
+        name: [windows-2022, windows-2025, windows-2025-qt]
         include:
           - name: windows-2022
             os: windows-2022
             qt: OFF
+            next_abi: OFF
+          - name: windows-2025
+            os: windows-2025
+            qt: OFF
+            next_abi: ON
           - name: windows-2025-qt
             os: windows-2025
             qt: ON
             qt_component: qtbase
+            next_abi: OFF
 
     steps:
     - uses: actions/checkout@v4
@@ -85,6 +91,7 @@ jobs:
            "-DCMAKE_TOOLCHAIN_FILE=$ROOT/vcpkg/scripts/buildsystems/vcpkg.cmake" `
            "-DVCPKG_TARGET_TRIPLET=x64-windows" `
            "-DCMAKE_INSTALL_PREFIX=$ROOT/Libraries" `
+           "-DLOG4CXX_BUILD_NEXT_ABI=${{ matrix.next_abi }}" `
            -S "$ROOT/main" `
            -B "$ROOT/build"
 
diff --git a/src/main/cpp/CMakeLists.txt b/src/main/cpp/CMakeLists.txt
index 5273700f..634c5dd7 100644
--- a/src/main/cpp/CMakeLists.txt
+++ b/src/main/cpp/CMakeLists.txt
@@ -32,7 +32,10 @@ else()
     list(APPEND PUBLIC_COMPILE_DEFINITIONS LOG4CXX_STATIC)
 endif()
 if(LOG4CXX_TEST_ONLY_BUILD)
-    list(APPEND PRIVATE_COMPILE_DEFINITIONS "ENABLE_FAILING_APPENDER_SIMULATION_TESTING=1")
+    list(APPEND PRIVATE_COMPILE_DEFINITIONS
+      "ENABLE_FAILING_APPENDER_SIMULATION_TESTING=1"
+      "LOGLOG_THRESHOLD=1000"
+    )
 endif()
 target_compile_definitions(log4cxx
     PUBLIC ${PUBLIC_COMPILE_DEFINITIONS}
diff --git a/src/main/cpp/aprsocket.cpp b/src/main/cpp/aprsocket.cpp
index 8115b7f7..ac06322c 100644
--- a/src/main/cpp/aprsocket.cpp
+++ b/src/main/cpp/aprsocket.cpp
@@ -29,7 +29,7 @@ namespace helpers
 {
 
 struct APRSocket::APRSocketPriv : public Socket::SocketPrivate {
-	APRSocketPriv(InetAddressPtr& address, int port)
+	APRSocketPriv(LOG4CXX_16_CONST InetAddressPtr& address, int port)
 		: Socket::SocketPrivate(address, port)
 		, socket(nullptr)
 	{}
@@ -40,13 +40,15 @@ struct APRSocket::APRSocketPriv : public Socket::SocketPrivate {
 	{}
 
 	Pool pool;
-	apr_socket_t* socket;
+	apr_socket_t* socket{ NULL };
+	bool connected{ false };
 };
 
 #define _priv static_cast<APRSocketPriv*>(m_priv.get())
 
-APRSocket::APRSocket(InetAddressPtr& address, int port) :
-	Socket(std::make_unique<APRSocketPriv>(address, port)){
+APRSocket::APRSocket(LOG4CXX_16_CONST InetAddressPtr& address, int port)
+	: Socket(std::make_unique<APRSocketPriv>(address, port))
+{
 	apr_status_t status =
 		apr_socket_create(&_priv->socket, APR_INET, SOCK_STREAM,
 			APR_PROTO_TCP, _priv->pool.getAPRPool());
@@ -55,14 +57,23 @@ APRSocket::APRSocket(InetAddressPtr& address, int port) :
 	{
 		throw SocketException(status);
 	}
+	open();
+}
 
-	LOG4CXX_ENCODE_CHAR(host, address->getHostAddress());
+void APRSocket::open()
+{
+	LOG4CXX_ENCODE_CHAR(host, _priv->address->getHostAddress());
 
 	// create socket address (including port)
 	apr_sockaddr_t* client_addr;
-	status =
-		apr_sockaddr_info_get(&client_addr, host.c_str(), APR_INET,
-			port, 0, _priv->pool.getAPRPool());
+	apr_status_t status = apr_sockaddr_info_get
+		( &client_addr
+		, host.c_str()
+		, APR_INET
+		, _priv->port
+		, 0
+		, _priv->pool.getAPRPool()
+		);
 
 	if (status != APR_SUCCESS)
 	{
@@ -76,6 +87,13 @@ APRSocket::APRSocket(InetAddressPtr& address, int port) :
 	{
 		throw ConnectException(status);
 	}
+	_priv->connected = true;
+}
+
+// Is the socket available for use?
+bool APRSocket::is_open()
+{
+	return _priv->socket && _priv->connected;
 }
 
 APRSocket::APRSocket(apr_socket_t* s, apr_pool_t* pool) :
diff --git a/src/main/cpp/exception.cpp b/src/main/cpp/exception.cpp
index e0edabdc..264f96c2 100644
--- a/src/main/cpp/exception.cpp
+++ b/src/main/cpp/exception.cpp
@@ -433,6 +433,10 @@ SocketException& SocketException::operator=(const SocketException& src)
 	return *this;
 }
 
+ConnectException::ConnectException(const LogString& msg) : SocketException(msg)
+{
+}
+
 ConnectException::ConnectException(log4cxx_status_t status) : SocketException(status)
 {
 }
diff --git a/src/main/cpp/loglog.cpp b/src/main/cpp/loglog.cpp
index df5b7e71..1f8875fe 100644
--- a/src/main/cpp/loglog.cpp
+++ b/src/main/cpp/loglog.cpp
@@ -24,6 +24,8 @@
 #endif
 #include <log4cxx/private/log4cxx_private.h>
 #include <log4cxx/helpers/aprinitializer.h>
+#include <log4cxx/helpers/date.h>
+#include <log4cxx/helpers/stringhelper.h>
 #include <log4cxx/helpers/systemerrwriter.h>
 #include <log4cxx/helpers/optionconverter.h>
 #include <mutex>
@@ -69,6 +71,13 @@ struct LogLog::LogLogPrivate {
 			this->suffix.clear();
 		}
 	}
+	LogString elapsedMicroseconds()
+	{
+		LogString result;
+		auto microsecondInterval = Date::currentTime() - APRInitializer::getStartTime();
+		StringHelper::toString(microsecondInterval, result);
+		return result;
+	}
 };
 
 LogLog::LogLog() :
@@ -97,6 +106,16 @@ bool LogLog::isDebugEnabled()
 			 && p->debugEnabled;
 }
 
+bool LogLog::isDebugEnabledFor(const LoggerPtr& category)
+{
+	return isDebugEnabled() && category && category->isDebugEnabled();
+}
+
+bool LogLog::isTraceEnabledFor(const LoggerPtr& category)
+{
+	return isDebugEnabled() && category && category->isTraceEnabled();
+}
+
 void LogLog::setInternalDebugging(bool debugEnabled1)
 {
 	auto p = getInstance().m_priv.get();
@@ -127,7 +146,6 @@ void LogLog::debug(const LogString& msg)
 		}
 
 		std::lock_guard<std::mutex> lock(p->mutex);
-
 		emit_log(p->debugPrefix, msg, p->suffix);
 	}
 }
@@ -169,6 +187,29 @@ void LogLog::error(const LogString& msg, const std::exception& e)
 	}
 }
 
+#if !LOG4CXX_LOGCHAR_IS_UTF8
+void LogLog::trace(const LoggerPtr& category, const std::string& msg)
+{
+	LOG4CXX_DECODE_CHAR(lsMsg, msg);
+	trace(category, lsMsg);
+}
+#endif
+
+void LogLog::trace(const LoggerPtr& category, const LogString& msg)
+{
+	auto p = getInstance().m_priv.get();
+	if (p && !p->quietMode) // Not deleted by onexit processing?
+	{
+		if (!p->debugEnabled)
+		{
+			return;
+		}
+
+		std::lock_guard<std::mutex> lock(p->mutex);
+		emit_log(p->debugPrefix, p->elapsedMicroseconds() + LOG4CXX_STR(" ") + category->getName() + LOG4CXX_STR("::") + msg, p->suffix);
+	}
+}
+
 void LogLog::setQuietMode(bool quietMode1)
 {
 	auto p = getInstance().m_priv.get();
diff --git a/src/main/cpp/socket.cpp b/src/main/cpp/socket.cpp
index 4df85cbd..4b000f9c 100644
--- a/src/main/cpp/socket.cpp
+++ b/src/main/cpp/socket.cpp
@@ -15,11 +15,10 @@
  * limitations under the License.
  */
 #include <log4cxx/helpers/socket.h>
-#include <log4cxx/helpers/bytebuffer.h>
-#include <log4cxx/helpers/transcoder.h>
-
 #include <log4cxx/private/socket_priv.h>
 #include <log4cxx/private/aprsocket.h>
+#include <log4cxx/helpers/loader.h>
+#include <log4cxx/helpers/loglog.h>
 
 using namespace LOG4CXX_NS;
 using namespace LOG4CXX_NS::helpers;
@@ -45,8 +44,45 @@ int Socket::getPort() const
 	return m_priv->port;
 }
 
+void Socket::setAttributes(const InetAddressPtr& newAddress, int newPort)
+{
+	m_priv->address = newAddress;
+	m_priv->port = newPort;
+}
+
+#if LOG4CXX_ABI_VERSION <= 15
 SocketUniquePtr Socket::create(InetAddressPtr& address, int port){
 	return std::make_unique<APRSocket>(address, port);
 }
+#endif
 
+SocketUniquePtr Socket::create(LOG4CXX_16_CONST InetAddressPtr& address, int port, const LogString& concreteClassName)
+{
+#if 15 < LOG4CXX_ABI_VERSION
+	if (!concreteClassName.empty())
+	{
+		if (LogLog::isDebugEnabled())
+		{
+			LogLog::debug(LOG4CXX_STR("Desired ") + Socket::getStaticClass().getName()
+				+ LOG4CXX_STR(" sub-class: [") + concreteClassName + LOG4CXX_STR("]"));
+		}
+		auto& classObj = Loader::loadClass(concreteClassName);
+		auto newObject = classObj.newInstance();
+		auto pSocket = dynamic_cast<Socket*>(newObject);
+		if (!pSocket)
+		{
+			LogLog::error(concreteClassName + LOG4CXX_STR(" is not a ") + Socket::getStaticClass().getName() + LOG4CXX_STR(" sub-class"));
+			delete newObject;
+		}
+		else
+		{
+			auto result = std::unique_ptr<Socket>(pSocket);
+			pSocket->setAttributes(address, port);
+			pSocket->open();
+			return result;
+		}
+	}
+#endif
+	return std::make_unique<APRSocket>(address, port);
+}
 
diff --git a/src/main/cpp/socketappenderskeleton.cpp b/src/main/cpp/socketappenderskeleton.cpp
index 00ca2aae..bceaf47e 100644
--- a/src/main/cpp/socketappenderskeleton.cpp
+++ b/src/main/cpp/socketappenderskeleton.cpp
@@ -98,7 +98,7 @@ void SocketAppenderSkeleton::SocketAppenderSkeletonPriv::connect()
 				msg += LOG4CXX_STR("].");
 				LogLog::debug(msg);
 			}
-			this->setOutputSink(Socket::create(this->address, this->port));
+			this->setOutputSink(Socket::create(this->address, this->port, this->socketSubclass));
 		}
 		catch (SocketException& e)
 		{
@@ -131,6 +131,12 @@ void SocketAppenderSkeleton::setOption(const LogString& option, const LogString&
 	{
 		setReconnectionDelay(OptionConverter::toInt(value, getDefaultDelay()));
 	}
+#if 15 < LOG4CXX_ABI_VERSION
+	else if (StringHelper::equalsIgnoreCase(option, LOG4CXX_STR("SOCKETSUBCLASS"), LOG4CXX_STR("socketsubclass")))
+	{
+		setSocketSubclass(value);
+	}
+#endif
 	else
 	{
 		AppenderSkeleton::setOption(option, value);
@@ -193,7 +199,7 @@ void SocketAppenderSkeleton::SocketAppenderSkeletonPriv::retryConnect()
 				msg += LOG4CXX_STR("].");
 				LogLog::debug(msg);
 			}
-			this->setOutputSink(Socket::create(this->address, this->port));
+			this->setOutputSink(Socket::create(this->address, this->port, this->socketSubclass));
 			if (LogLog::isDebugEnabled())
 			{
 				LogString msg(LOG4CXX_STR("Connection established to [")
@@ -314,3 +320,15 @@ int SocketAppenderSkeleton::getReconnectionDelay() const
 {
 	return _priv->reconnectionDelay;
 }
+
+#if 15 < LOG4CXX_ABI_VERSION
+void SocketAppenderSkeleton::setSocketSubclass(const LogString& newValue)
+{
+	_priv->socketSubclass = newValue;
+}
+
+const LogString& SocketAppenderSkeleton::getSocketSubclass() const
+{
+	return _priv->socketSubclass;
+}
+#endif
\ No newline at end of file
diff --git a/src/main/include/log4cxx/helpers/exception.h b/src/main/include/log4cxx/helpers/exception.h
index 143e00c0..ee799aae 100644
--- a/src/main/include/log4cxx/helpers/exception.h
+++ b/src/main/include/log4cxx/helpers/exception.h
@@ -26,6 +26,7 @@
 #ifdef _MSC_VER
 	#pragma warning ( push )
 	#pragma warning (disable : 4275) // ::std::exception needs to have dll-interface
+	#pragma warning (disable : 4251) // ::std::basic_string needs to have dll-interface
 #endif
 
 namespace LOG4CXX_NS
@@ -234,6 +235,7 @@ remotely (e.g., no process is listening on the remote address/port).
 class LOG4CXX_EXPORT ConnectException : public SocketException
 {
 	public:
+		ConnectException(const LogString& msg);
 		ConnectException(log4cxx_status_t status);
 		ConnectException(const ConnectException& src);
 		ConnectException& operator=(const ConnectException&);
diff --git a/src/main/include/log4cxx/helpers/loglog.h b/src/main/include/log4cxx/helpers/loglog.h
index f163b36c..2b110b25 100644
--- a/src/main/include/log4cxx/helpers/loglog.h
+++ b/src/main/include/log4cxx/helpers/loglog.h
@@ -19,6 +19,7 @@
 #define _LOG4CXX_HELPERS_LOG_LOG_H
 
 #include <log4cxx/logstring.h>
+#include <log4cxx/logger.h>
 #include <log4cxx/helpers/widelife.h>
 #include <exception>
 #include <mutex>
@@ -57,6 +58,16 @@ class LOG4CXX_EXPORT LogLog
 		 **/
 		static bool isDebugEnabled();
 
+		/**
+		 *  Are \c category debug messages sent to SystemErrWriter?
+		 **/
+		static bool isDebugEnabledFor(const LoggerPtr& category);
+
+		/**
+		 *  Are \c category trace messages sent to SystemErrWriter?
+		 **/
+		static bool isTraceEnabledFor(const LoggerPtr& category);
+
 		/**
 		Start/stop outputing debug messages if \c newValue is true/false respectively.
 		*/
@@ -91,7 +102,17 @@ class LOG4CXX_EXPORT LogLog
 		*/
 		static void error(const LogString& msg, const std::exception& ex);
 
+		/**
+		Output \c msg to SystemErrWriter if internal debug logging is enabled.
+		*/
+		static void trace(const LoggerPtr& category, const LogString& msg);
 
+#if !LOG4CXX_LOGCHAR_IS_UTF8
+		/**
+		Output \c msg to SystemErrWriter if internal debug logging is enabled.
+		*/
+		static void trace(const LoggerPtr& category, const std::string& msg);
+#endif
 		/**
 		Change quiet mode to \c newValue.
 
@@ -118,14 +139,69 @@ class LOG4CXX_EXPORT LogLog
 }  // namespace helpers
 } // namespace log4cxx
 
-#define LOGLOG_DEBUG(log) { \
-		if (LogLog::isDebugEnabled()) \
-			LOG4CXX_NS::helpers::LogLog::debug(log) ; }
+#if defined(LOGLOG_THRESHOLD) || LOGLOG_THRESHOLD <= 10000
+/**
+Send \c message to helpers::SystemErrWriter if \c logger is enabled for <code>DEBUG</code> events.
+
+\usage
+~~~{.cpp}
+LOGLOG_DEBUG(m_log, "setRemote:"
+	<< " hostAddress " << address->getHostAddress()
+	<< " port " << port
+	<< " inetType " << m_inetType
+	<< " ipVersion " << m_inetFamily
+	);
+~~~
+
+@param logger the logger that has the enabled status.
+@param message a valid parameter to an <code>operator<<(std::ostream&. ...)</code> overload.
+*/
+#define LOGLOG_DEBUG(logger, message) { \
+		if (LOG4CXX_NS::helpers::LogLog::isDebugEnabledFor(logger)) { \
+			LOG4CXX_NS::helpers::MessageBuffer buf; \
+			LOG4CXX_NS::helpers::LogLog::trace(logger, buf.str(buf << message)); } }
+#else // !defined(LOGLOG_THRESHOLD) || 10000 < LOGLOG_THRESHOLD
+#define LOGLOG_DEBUG(logger, message)
+#endif
+
+#if defined(LOGLOG_THRESHOLD) && LOGLOG_THRESHOLD <= 5000
+/**
+Send \c message to helpers::SystemErrWriter if \c logger is enabled for <code>TRACE</code> events.
+
+\usage
+~~~{.cpp}
+LOGLOG_TRACE(m_log, "write:"
+	<< " byteCount " << byteCount
+	<< " to " << *m_remoteAddress
+	<< "\n" << MessageData(pMessage, byteCount)
+	);
+~~~
+
+@param logger the logger that has the enabled status.
+@param message a valid parameter to an <code>operator<<(std::ostream&. ...)</code> overload.
+*/
+#define LOGLOG_TRACE(logger, message) { \
+		if (LOG4CXX_NS::helpers::LogLog::isTraceEnabledFor(logger)) { \
+			LOG4CXX_NS::helpers::MessageBuffer buf; \
+			LOG4CXX_NS::helpers::LogLog::trace(logger, buf.str(buf << message)); } }
+#else // !defined(LOGLOG_THRESHOLD) || 5000 < LOGLOG_THRESHOLD
+#define LOGLOG_TRACE(logger, message)
+#endif
 
-#define LOGLOG_WARN(log) { \
-		LOG4CXX_NS::helpers::LogLog::warn(log) ; }
+/**
+Send \c message to helpers::SystemErrWriter using the configured warning output style.
+@param message a valid parameter to an <code>operator<<(std::ostream&. ...)</code> overload.
+*/
+#define LOGLOG_WARN(message) { \
+		LOG4CXX_NS::helpers::MessageBuffer buf; \
+		LOG4CXX_NS::helpers::LogLog::warn(buf.str(buf << message)); }
 
-#define LOGLOG_ERROR(log) { \
-		LOG4CXX_NS::helpers::LogLog::warn(log); }
+/**
+Send \c message to helpers::SystemErrWriter using the configured error output style.
+@param message a valid parameter to an <code>operator<<(std::ostream&. ...)</code> overload.
+*/
+#define LOGLOG_ERROR(message) { \
+		LOG4CXX_NS::helpers::MessageBuffer buf; \
+		LOG4CXX_NS::helpers::LogLog::error(buf.str(buf << message)); }
 
 #endif //_LOG4CXX_HELPERS_LOG_LOG_H
diff --git a/src/main/include/log4cxx/helpers/socket.h b/src/main/include/log4cxx/helpers/socket.h
index 36fd7afe..a46af82d 100644
--- a/src/main/include/log4cxx/helpers/socket.h
+++ b/src/main/include/log4cxx/helpers/socket.h
@@ -21,6 +21,12 @@
 #include <log4cxx/helpers/inetaddress.h>
 #include <log4cxx/helpers/pool.h>
 
+#if 15 < LOG4CXX_ABI_VERSION
+#define LOG4CXX_16_CONST const
+#else
+#define LOG4CXX_16_CONST
+#endif
+
 
 namespace LOG4CXX_NS
 {
@@ -60,6 +66,12 @@ class LOG4CXX_EXPORT Socket : public helpers::Object
 		When true, an exception is thrown if the write would block.
 		*/
 		virtual void setNonBlocking(bool newValue) = 0;
+
+		/// Open this socket.
+		virtual void open() = 0;
+
+		/// Is this available for use?
+		virtual bool is_open() = 0;
 #endif
 
 		/** Close this socket. */
@@ -71,10 +83,17 @@ class LOG4CXX_EXPORT Socket : public helpers::Object
 		/** Returns the value of this socket's port field. */
 		int getPort() const;
 
+		/** Use \c newAddress and \c newPort as the destination. */
+		void setAttributes(const InetAddressPtr& newAddress, int newPort);
+
 		/** Create a concrete instance of this class
 		*/
+#if LOG4CXX_ABI_VERSION <= 15
 		static SocketUniquePtr create(InetAddressPtr& address, int port);
-
+		static SocketUniquePtr create(LOG4CXX_16_CONST InetAddressPtr& address, int port, const LogString& concreteClassName);
+#else
+		static SocketUniquePtr create(const InetAddressPtr& address, int port, const LogString& concreteClassName = {});
+#endif
 	private:
 		Socket(const Socket&);
 		Socket& operator=(const Socket&);
diff --git a/src/main/include/log4cxx/net/socketappenderskeleton.h b/src/main/include/log4cxx/net/socketappenderskeleton.h
index c96f50ec..b9eb19ed 100644
--- a/src/main/include/log4cxx/net/socketappenderskeleton.h
+++ b/src/main/include/log4cxx/net/socketappenderskeleton.h
@@ -179,6 +179,16 @@ class LOG4CXX_EXPORT SocketAppenderSkeleton : public AppenderSkeleton
 		@deprecated This method will be removed in a future version.
 		*/
 		void fireConnector();
+#else
+		/**
+		* Use \c newSubclass as the helpers::Socket interface instead of the default implementation.
+		* */
+		void setSocketSubclass(const LogString& newSubclass);
+
+		/**
+		The class name used for the helpers::Socket interface implemention.
+		*/
+		const LogString& getSocketSubclass() const;
 #endif
 
 		/**
@@ -189,11 +199,14 @@ class LOG4CXX_EXPORT SocketAppenderSkeleton : public AppenderSkeleton
 		RemoteHost |  (\ref inetAddress "1") | - |
 		Port | {int} | (\ref defaultPort "2") |
 		LocationInfo | True,False | False |
+		SocketSubclass |  (\ref socketSubclass "3") | APRSocket |
 
 		\anchor inetAddress (1) A valid internet address.
 
 		\anchor defaultPort (2) Provided by the derived class.
 
+		\anchor socketSubclass (3) A registered class derived from helpers::Socket.
+
 		\sa AppenderSkeleton::setOption()
 		*/
 		void setOption(const LogString& option, const LogString& value) override;
diff --git a/src/main/include/log4cxx/private/aprsocket.h b/src/main/include/log4cxx/private/aprsocket.h
index addb3898..98195d3e 100644
--- a/src/main/include/log4cxx/private/aprsocket.h
+++ b/src/main/include/log4cxx/private/aprsocket.h
@@ -30,17 +30,26 @@ namespace helpers
 class LOG4CXX_EXPORT APRSocket : public helpers::Socket
 {
 	public:
+		/** An uninitialised socket
+		*/
+		APRSocket();
+
 		/** Creates a stream socket and connects it to the specified port
 		number at the specified IP address.
 		*/
-		APRSocket(InetAddressPtr& address, int port);
+		APRSocket(LOG4CXX_16_CONST InetAddressPtr& address, int port);
 		APRSocket(apr_socket_t*, apr_pool_t* pool);
-
 		size_t write(ByteBuffer&) override;
 
 		void setNonBlocking(bool newValue) LOG4CXX_16_VIRTUAL_SPECIFIER;
 
-		/** Closes this socket. */
+		/// Is this available for use?
+		bool is_open() LOG4CXX_16_VIRTUAL_SPECIFIER;
+
+		/// Establish a connection on this socket.
+		void open() LOG4CXX_16_VIRTUAL_SPECIFIER;
+
+		/// Disconnect this socket.
 		void close() override;
 
 		apr_socket_t* getSocketPtr() const;
diff --git a/src/main/include/log4cxx/private/socketappenderskeleton_priv.h b/src/main/include/log4cxx/private/socketappenderskeleton_priv.h
index e92f0f4e..fcf62616 100644
--- a/src/main/include/log4cxx/private/socketappenderskeleton_priv.h
+++ b/src/main/include/log4cxx/private/socketappenderskeleton_priv.h
@@ -76,6 +76,7 @@ struct SocketAppenderSkeleton::SocketAppenderSkeletonPriv : public AppenderSkele
 	int port;
 	int reconnectionDelay;
 	bool locationInfo;
+
 	void close();
 
 	/**
@@ -101,6 +102,7 @@ struct SocketAppenderSkeleton::SocketAppenderSkeletonPriv : public AppenderSkele
 
 	void retryConnect();
 
+	LogString socketSubclass;
 };
 
 } // namespace net
diff --git a/src/test/cpp/net/CMakeLists.txt b/src/test/cpp/net/CMakeLists.txt
index 7d43f790..9c9e87c9 100644
--- a/src/test/cpp/net/CMakeLists.txt
+++ b/src/test/cpp/net/CMakeLists.txt
@@ -34,8 +34,13 @@ endif()
 if(HAS_LIBESMTP)
     list(APPEND NET_TESTS smtpappendertestcase)
 endif(HAS_LIBESMTP)
+
+if(${log4cxx_ABI_VER} GREATER 15)
+set(OTHER_SOCKET_IMPL bsdsocket.cpp)
+endif()
+
 foreach(fileName IN LISTS NET_TESTS)
-    add_executable(${fileName} "${fileName}.cpp")
+    add_executable(${fileName} "${fileName}.cpp" ${OTHER_SOCKET_IMPL})
 endforeach()
 
 set(ALL_LOG4CXX_TESTS ${ALL_LOG4CXX_TESTS} ${NET_TESTS} PARENT_SCOPE)
diff --git a/src/test/cpp/net/bsdsocket.cpp b/src/test/cpp/net/bsdsocket.cpp
new file mode 100644
index 00000000..fbe182e3
--- /dev/null
+++ b/src/test/cpp/net/bsdsocket.cpp
@@ -0,0 +1,470 @@
+/*
+ * 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.
+ */
+ #include "bsdsocket.h"
+#include <log4cxx/helpers/bytebuffer.h>
+#include <log4cxx/private/socket_priv.h>
+#include <log4cxx/helpers/exception.h>
+#include <log4cxx/helpers/loglog.h>
+#include <log4cxx/helpers/transcoder.h>
+#include <string>
+#include <memory>
+#ifdef WIN32
+#undef UNICODE
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#pragma comment(lib, "Ws2_32.lib")
+#else
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <net/if.h>
+#include <netdb.h>
+#include <cstring>
+#endif
+#include <sstream>
+#undef min
+#include <algorithm>
+
+// Types
+using HandleType = int;
+using InfoType = struct addrinfo;
+using SockAddrType = struct sockaddr_storage;
+#ifdef WIN32
+using SockSizeType = int;
+#else
+using SockSizeType = unsigned int;
+#endif
+using SockAddrPtr = std::unique_ptr<SockAddrType>;
+
+/// Streamable array elements
+template <typename T, typename S, typename D = T>
+class SeparatedArray
+{
+	const T *m_vec;
+	size_t m_len;
+	S m_separator;
+	size_t m_perLine;
+public:
+	SeparatedArray(const T *vec, size_t len, S separ, size_t perLine = 10)
+		: m_vec(vec)
+		, m_len(len)
+		, m_separator(separ)
+		, m_perLine(perLine)
+	{}
+	void Write(std::ostream& os) const
+	{
+		if (0 < m_perLine && m_perLine <= m_len)
+			os << std::endl;
+		for (size_t i = 0; i < m_len; ++i)
+		{
+			if (0 < i)
+			{
+				if (0 < m_perLine && 0 == (i % m_perLine))
+					os << std::endl;
+				else
+					os << m_separator;
+			}
+			os << (D)m_vec[i];
+		}
+	}
+};
+
+/// Put \c S separated type \c D elements of \c v onto \c os
+	template <typename T, typename S, typename D>
+	std::ostream&
+ operator<<(std::ostream& os, const SeparatedArray<T, S, D>& v)
+{ v.Write(os); return os;  }
+
+/// An overload that puts the internet address in \c data onto \c os
+	inline std::ostream&
+operator<<(std::ostream& os, const struct sockaddr& data)
+{
+	if (AF_INET == data.sa_family)
+	{
+		using Formatter = SeparatedArray<unsigned char, unsigned char, unsigned>;
+		const struct sockaddr_in& ipv4Data = reinterpret_cast<const struct sockaddr_in&>(data);
+		const unsigned char* octet = (const unsigned char*)&ipv4Data.sin_addr;
+		os << std::dec << Formatter(octet, 4, '.') << ':' << ntohs(ipv4Data.sin_port);
+	}
+	else
+	{
+		using Formatter = SeparatedArray<unsigned short, unsigned char, unsigned>;
+		const struct sockaddr_in6& ipv6Data = reinterpret_cast<const struct sockaddr_in6&>(data);
+		const unsigned short* hextet = (const unsigned short*)&ipv6Data.sin6_addr;
+		os << '[' << std::hex << Formatter(hextet, 8, ':') << ']';
+		os << ':' << std::dec << ntohs(ipv6Data.sin6_port);
+	}
+	return os;
+}
+
+/// An overload that puts the internet address in \c data onto \c os
+	inline std::ostream&
+operator<<(std::ostream& os, const struct sockaddr_storage& data)
+{
+	os << reinterpret_cast<const struct sockaddr&>(data);
+	return os;
+}
+
+namespace
+{
+
+class UnexpectedSystemError : public LOG4CXX_NS::helpers::SocketException
+{
+	using BaseType = LOG4CXX_NS::helpers::SocketException;
+public: // ...stuctors
+	UnexpectedSystemError(const char* context, const std::string& data = {})
+#ifdef WIN32
+		: BaseType(MakeMessage(::GetLastError(), context, data))
+#else
+		: BaseType(MakeMessage(errno, context, data))
+#endif
+	{ }
+	UnexpectedSystemError(int id, const char* context, const std::string& data = {})
+		: BaseType(MakeMessage(id, context, data))
+	{ }
+public: // Class methods
+	static LOG4CXX_NS::LogString MakeConnectMessage()
+	{
+#ifdef WIN32
+		return MakeMessage(WSAGetLastError(), "connect");
+#else
+		return MakeMessage(errno, "connect");
+#endif
+	}
+	static LOG4CXX_NS::LogString MakeMessage(int id, const char* context, const std::string& data = {})
+	{
+		std::stringstream ss;
+		ss << "System error 0x" << std::hex << id << ": " << context;
+		if (!data.empty())
+			ss << ' ' << data;
+		LOG4CXX_DECODE_CHAR(result, ss.str());
+		return result;
+	}
+};
+
+/// An adaptor of message data for printing
+class MessageData
+{
+	const unsigned char *m_vec;
+	size_t m_len;
+public:
+	/// A streamable version of the \c len bytes in \c vec
+	MessageData(const void *vec, size_t len)
+		: m_vec(reinterpret_cast<const unsigned char *>(vec))
+		, m_len(len)
+		{}
+	void Write(std::ostream& os) const
+	{
+		for (size_t i = 0; i < m_len; ++i)
+		{
+			if ('&' == m_vec[i])
+				os << "&amp;";
+			else if (isprint(m_vec[i]))
+				os << m_vec[i];
+			else
+				os << "&#" << std::hex << int(m_vec[i]) << ';';
+		}
+	}
+};
+	std::ostream&
+operator<<(std::ostream& s, const MessageData& v)
+{ v.Write(s); return s;  }
+
+#ifdef WIN32
+class SystemInitialiser
+{
+public:
+	SystemInitialiser()
+	{
+		WORD version = MAKEWORD(2, 2);
+		WSADATA data;
+		if (WSAStartup(version, &data) != 0)
+		{
+			throw UnexpectedSystemError(WSAGetLastError(), "initialising WSA");
+		}
+	}
+};
+void CheckWSA()
+{
+	static SystemInitialiser _;
+}
+#endif
+
+} // namespace
+
+namespace LOG4CXX_NS::helpers
+{
+
+struct BSDSocket::Data : public Socket::SocketPrivate
+{
+	int m_inetType;                 //!< SOCK_DGRAM or SOCK_STREAM
+	int m_inetFamily;               //!< AF_INET or AF_INET6
+	InfoType* m_remote{ 0 };        //!< A chain of interfaces on which data may be sent and received
+	HandleType m_handle{ -1 };      //!< The handle for send and receive
+	SockAddrPtr m_remoteAddress{ std::make_unique<SockAddrType>() };
+	SockSizeType m_remoteAddressSize{ 0 };
+	LoggerPtr m_log{ Logger::getLogger("BSDSocket") };
+
+	Data(bool isDatagramType = false, bool ipV6 = false)
+		: m_inetType{ isDatagramType ? SOCK_DGRAM : SOCK_STREAM }
+		, m_inetFamily{ ipV6 ? AF_INET6 : AF_INET }
+	{
+#ifdef WIN32
+		CheckWSA();
+#endif
+	}
+
+	Data(const InetAddressPtr& address, int port, bool isDatagramType = false, bool ipV6 = false)
+		: Socket::SocketPrivate(address, port)
+		, m_inetType{ isDatagramType ? SOCK_DGRAM : SOCK_STREAM }
+		, m_inetFamily{ ipV6 ? AF_INET6 : AF_INET }
+	{
+#ifdef WIN32
+		CheckWSA();
+#endif
+	}
+
+	/// Prepare the socket for use.
+	void open();
+
+	/// Release resources required by an open socket.
+	void close();
+
+	/// Send the bytes in \c data.
+	size_t write(ByteBuffer& data);
+
+	/// Initialise the port field of \c addr to \c port
+	void setInternetPort(struct sockaddr* addr, int port);
+
+	/// Set up \c m_remote for a \c m_inetType connection on \c port to \c node using \c m_inetFamily
+	void setRemote(const InetAddressPtr& address, int port);
+
+	/// Initialise \c m_remoteAddress from \c data
+	void setRemoteAddress(const struct sockaddr* data, size_t dataSize);
+};
+
+// Prepare the channel for use.
+	void
+BSDSocket::Data::open()
+{
+	LOGLOG_DEBUG(m_log, "open:");
+	setRemote(this->address, this->port);
+	m_handle = (HandleType)socket(m_inetFamily, m_inetType, SOCK_DGRAM == m_inetType ? IPPROTO_UDP : IPPROTO_TCP);
+	if (m_handle < 0)
+	   throw UnexpectedSystemError("creating socket");
+
+#ifdef WIN32
+	// Prevent a WSAECONNRESET when a previous send operation reported a ICMP Port Unreachable message
+	// to allow sending when the destination port is not yet receiving data
+	static const int SIO_UDP_CONNRESET = _WSAIOW(IOC_VENDOR, 12);
+	BOOL bNewBehavior = FALSE;
+	DWORD dwBytesReturned = 0;
+	WSAIoctl
+		( m_handle
+		, SIO_UDP_CONNRESET // dwIoControlCode
+		, &bNewBehavior // lpvInBuffer
+		, sizeof (bNewBehavior) // cbInBuffer
+		, NULL // lpvOutBuffer
+		, 0 // cbOutBuffer
+		, &dwBytesReturned // lpcbBytesReturned
+		, NULL // lpOverlapped
+		, NULL // lpCompletionRoutine
+		);
+#endif
+	LOGLOG_DEBUG(m_log, "open:"
+		<< " connect " << m_handle
+		<< " to address " << *m_remoteAddress
+		);
+	auto rs = connect(m_handle, (const struct sockaddr *)m_remoteAddress.get(), m_remoteAddressSize);
+	if (rs < 0)
+		throw ConnectException(UnexpectedSystemError::MakeConnectMessage());
+	LOGLOG_DEBUG(m_log, "open:" << " handle " << m_handle);
+}
+
+// Release resources required by an open channel.
+	void
+BSDSocket::Data::close()
+{
+	LOGLOG_DEBUG(m_log, "close:" << " handle " << m_handle);
+#ifdef WIN32
+	closesocket(m_handle);
+#else
+	close(m_handle);
+#endif
+	m_handle = -1;
+ }
+
+// Send the \c size bytes at \c data.
+	size_t
+BSDSocket::Data::write(ByteBuffer& data)
+{
+	auto pMessage = data.current();
+	auto byteCount = static_cast<int>(data.remaining());
+	LOGLOG_TRACE(m_log, "write:"
+		<< " byteCount " << byteCount
+		<< " to " << *m_remoteAddress
+		<< "\n" << MessageData(pMessage, byteCount)
+		);
+	int rs = sendto
+		( m_handle
+		, pMessage
+		, byteCount
+		, 0
+		, reinterpret_cast<struct sockaddr*>(m_remoteAddress.get())
+		, (int)m_remoteAddressSize
+		);
+	if (rs < 0)
+		throw UnexpectedSystemError("send");
+	data.increment_position(byteCount);
+	return byteCount;
+}
+
+// Set up \c m_remote for a \c m_inetType connection on \c port to \c address
+	void
+BSDSocket::Data::setRemote(const InetAddressPtr& address, int port)
+{
+	LOG4CXX_ENCODE_CHAR(hostAddress, address->getHostAddress());
+	LOGLOG_DEBUG(m_log, "setRemote:"
+		<< " hostAddress " << hostAddress
+		<< " port " << port
+		<< " inetType " << m_inetType
+		<< " ipVersion " << m_inetFamily
+		);
+	struct addrinfo hints;
+	memset(&hints, 0, sizeof (hints));
+	hints.ai_family = m_inetFamily;
+	hints.ai_socktype = m_inetType;
+	auto service = std::to_string(port);
+	int rs = getaddrinfo(hostAddress.c_str(), service.c_str(), &hints, &m_remote);
+#ifdef WIN32
+	if (0 != rs)
+		throw UnexpectedSystemError(rs, "getaddrinfo");
+#else
+	if (0 == rs)
+		;
+	else if (EAI_SYSTEM == rs)
+		throw UnexpectedSystemError("getaddrinfo");
+	else
+		throw UnexpectedSystemError(rs, gai_strerror(rs), "getaddrinfo");
+#endif
+	setRemoteAddress(m_remote->ai_addr, m_remote->ai_addrlen);
+	setInternetPort(m_remote->ai_addr, port);
+}
+
+// Initialise \c m_remoteAddress from \c data
+	void
+BSDSocket::Data::setRemoteAddress(const struct sockaddr* data, size_t dataSize)
+{
+	LOGLOG_DEBUG(m_log, "setRemoteAddress:" << " size " << dataSize << " address " << *data);
+	m_remoteAddressSize = SockSizeType(std::min(dataSize, sizeof (SockAddrType)));
+	memcpy(m_remoteAddress.get(), data, m_remoteAddressSize);
+}
+
+// Initialise the port field of \c addr to \c port
+	void
+BSDSocket::Data::setInternetPort(struct sockaddr* addr, int port)
+{
+	LOGLOG_DEBUG(m_log, "setInternetPort: " << port);
+	if (AF_INET == addr->sa_family)
+	{
+		struct sockaddr_in* ipv4Data = reinterpret_cast<struct sockaddr_in*>(addr);
+		ipv4Data->sin_port = htons(port);
+	}
+	else
+	{
+		struct sockaddr_in6* ipv6Data = reinterpret_cast<struct sockaddr_in6*>(addr);
+		ipv6Data->sin6_port = htons(port);
+	}
+}
+
+IMPLEMENT_LOG4CXX_OBJECT(BSDSocket)
+
+	const int
+BSDSocket::DefaultPort{ 4560 };
+
+#define _priv static_cast<Data*>(m_priv.get())
+
+// A \c isDatagramType connection
+BSDSocket::BSDSocket(bool isDatagramType, bool ipV6)
+	: Socket{ std::make_unique<Data>(isDatagramType, ipV6) }
+{
+}
+
+// A \c inetType connection on \c port to \c address (or as a server if 0)
+BSDSocket::BSDSocket(const InetAddressPtr& address, int port, bool isDatagramType, bool ipV6)
+	: Socket{ std::make_unique<Data>(address, port, isDatagramType, ipV6) }
+{
+}
+
+BSDSocket::~BSDSocket()
+{
+}
+
+	void
+BSDSocket::setNonBlocking(bool newValue)
+{
+#ifdef WIN32
+	u_long ulValue = newValue;
+	if (ioctlsocket(_priv->m_handle, FIONBIO, &ulValue) == SOCKET_ERROR)
+		throw UnexpectedSystemError(WSAGetLastError(), "ioctlsocket" );
+#else
+	int fd_flags = fcntl(_priv->m_handle, F_GETFL, 0);
+#if defined(O_NONBLOCK)
+	fd_flags |= O_NONBLOCK;
+#elif defined(O_NDELAY)
+	fd_flags |= O_NDELAY;
+#elif defined(FNDELAY)
+	fd_flags |= FNDELAY;
+#else
+#error Making sockets non-blocking not supported on your platform.
+#endif
+	if (fcntl(_priv->m_handle, F_SETFL, fd_flags) == -1)
+		throw UnexpectedSystemError("fcntl");
+#endif
+}
+
+// Prepare the channel for use.
+	void
+BSDSocket::open()
+{
+	_priv->open();
+}
+
+// Is the port available for use?
+	bool
+BSDSocket::is_open()
+{
+	return 0 <= _priv->m_handle;
+}
+
+// Release resources required by an open channel.
+	void
+BSDSocket::close()
+{
+	_priv->close();
+}
+
+// Send the \c size bytes at \c data.
+	size_t
+BSDSocket::write(ByteBuffer& data)
+{
+	return _priv->write(data);
+}
+
+} // namespace LOG4CXX_NS::helpers
diff --git a/src/test/cpp/net/bsdsocket.h b/src/test/cpp/net/bsdsocket.h
new file mode 100644
index 00000000..6da5757a
--- /dev/null
+++ b/src/test/cpp/net/bsdsocket.h
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+#ifndef LOG4CXX_BSD_SOCKET_H_
+#define LOG4CXX_BSD_SOCKET_H_
+#include <log4cxx/helpers/socket.h>
+
+namespace LOG4CXX_NS::helpers
+{
+
+/// A class for writing to a network socket.
+class BSDSocket : public Socket
+{
+public: // Class attributes
+	static const int DefaultPort;
+
+public:
+	DECLARE_LOG4CXX_OBJECT(BSDSocket)
+	BEGIN_LOG4CXX_CAST_MAP()
+	LOG4CXX_CAST_ENTRY(Socket)
+	LOG4CXX_CAST_ENTRY(BSDSocket)
+	END_LOG4CXX_CAST_MAP()
+
+public: // ...structors
+	/// A \c isDatagramType connection
+	BSDSocket(bool isDatagramType = false, bool ipV6 = false);
+	/// A \c isDatagramType connection on \c port to \c address
+	BSDSocket(const InetAddressPtr& address, int port = DefaultPort, bool isDatagramType = false, bool ipV6 = false);
+
+	/// Release resources
+	~BSDSocket();
+
+public: // Hooked methods
+	/// Is this available for use?
+	bool is_open() override;
+
+	/// Prepare the socket for use.
+	void open() override;
+
+	/// Release resources required by an open socket.
+	void close() override;
+
+	/// Send the bytes in \c data.
+	size_t write(ByteBuffer& data) override;
+
+	/// Use \c newValue for the behaviour when the network buffer (on an accepted socket connection) is full.
+	void setNonBlocking(bool newValue) override;
+
+private: // Class methods
+	struct Data;
+};
+
+} // namespace LOG4CXX_NS::helpers
+
+#endif // LOG4CXX_BSD_SOCKET_H_
diff --git a/src/test/cpp/net/socketappendertestcase.cpp b/src/test/cpp/net/socketappendertestcase.cpp
index aed44689..c52fe33a 100644
--- a/src/test/cpp/net/socketappendertestcase.cpp
+++ b/src/test/cpp/net/socketappendertestcase.cpp
@@ -16,6 +16,7 @@
  */
 
 #include "../appenderskeletontestcase.h"
+#include <log4cxx/logmanager.h>
 #include <log4cxx/patternlayout.h>
 #include <log4cxx/basicconfigurator.h>
 #include <log4cxx/net/xmlsocketappender.h>
@@ -44,8 +45,10 @@ class SocketAppenderTestCase : public AppenderSkeletonTestCase
 		//
 		LOGUNIT_TEST(testDefaultThreshold);
 		LOGUNIT_TEST(testSetOptionThreshold);
-		LOGUNIT_TEST(testRetryConnect);
-
+		LOGUNIT_TEST(testRetryConnectDefault);
+#if 15 < LOG4CXX_ABI_VERSION
+		LOGUNIT_TEST(testRetryConnectBSD);
+#endif
 		LOGUNIT_TEST_SUITE_END();
 
 #ifdef _DEBUG
@@ -59,20 +62,29 @@ class SocketAppenderTestCase : public AppenderSkeletonTestCase
 
 
 	public:
+		void tearDown() override
+		{
+			LogManager::shutdown();
+		}
 
 		AppenderSkeleton* createAppenderSkeleton() const
 		{
 			return new log4cxx::net::SocketAppender();
 		}
 
-		void testRetryConnect()
+		void testRetryConnect(const LogString& socketImpl = {})
 		{
 			int tcpPort = 44445;
+			int millisecondDelay = 50;
 			auto appender = std::make_shared<net::SocketAppender>();
 			appender->setLayout(std::make_shared<log4cxx::PatternLayout>(LOG4CXX_STR("%d [%T] %m%n")));
 			appender->setRemoteHost(LOG4CXX_STR("localhost"));
-			appender->setReconnectionDelay(50); // milliseconds
+			appender->setReconnectionDelay(millisecondDelay);
 			appender->setPort(tcpPort);
+#if 15 < LOG4CXX_ABI_VERSION
+			if (!socketImpl.empty())
+				appender->setSocketSubclass(socketImpl);
+#endif
 			appender->activateOptions();
 
 			BasicConfigurator::configure(appender);
@@ -179,6 +191,18 @@ class SocketAppenderTestCase : public AppenderSkeletonTestCase
 			}
 			LOGUNIT_ASSERT_EQUAL(logEventCount, (int)messageCount.size());
 		}
+
+		void testRetryConnectDefault()
+		{
+			testRetryConnect();
+		}
+
+#if 15 < LOG4CXX_ABI_VERSION
+		void testRetryConnectBSD()
+		{
+			testRetryConnect(LOG4CXX_STR("BSDSocket"));
+		}
+#endif
 };
 
 LOGUNIT_TEST_SUITE_REGISTRATION(SocketAppenderTestCase);