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

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


The following commit(s) were added to refs/heads/master by this push:
     new 7ed16a26 Support user provided Socket implementation in the next ABI 
version (#663)
7ed16a26 is described below

commit 7ed16a2697e96466bc6ca88f18f9b1f60a9f55a0
Author: Stephen Webb <[email protected]>
AuthorDate: Fri May 15 11:13:17 2026 +1000

    Support user provided Socket implementation in the next ABI version (#663)
    
    * Introduce enhanced internal logging support
    
    * Only expand LOGLOG_DEBUG and LOGLOG_TRACE macros when building with 
LOG4CXX_TEST_ONLY_BUILD
    
    * Test the next ABI on MacOS
    
    * Exceptions must be exported using the new ABI
---
 .github/workflows/log4cxx-macos.yml                |  14 +-
 .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/cpp/stringtokenizer.cpp                   |   7 +-
 src/main/include/log4cxx/helpers/exception.h       |  18 +-
 src/main/include/log4cxx/helpers/loglog.h          |  90 +++-
 src/main/include/log4cxx/helpers/socket.h          |  21 +-
 src/main/include/log4cxx/helpers/stringtokenizer.h |   1 -
 src/main/include/log4cxx/log4cxx.h.in              |  14 +-
 .../include/log4cxx/net/socketappenderskeleton.h   |  13 +
 src/main/include/log4cxx/private/aprsocket.h       |  15 +-
 .../log4cxx/private/socketappenderskeleton_priv.h  |   2 +
 src/test/cpp/helpers/stringtokenizertestcase.cpp   |  31 +-
 src/test/cpp/net/CMakeLists.txt                    |   7 +-
 src/test/cpp/net/bsdsocket.cpp                     | 472 +++++++++++++++++++++
 src/test/cpp/net/bsdsocket.h                       |  68 +++
 src/test/cpp/net/socketappendertestcase.cpp        |  32 +-
 22 files changed, 884 insertions(+), 80 deletions(-)

diff --git a/.github/workflows/log4cxx-macos.yml 
b/.github/workflows/log4cxx-macos.yml
index b0f823bd..79f426a5 100644
--- a/.github/workflows/log4cxx-macos.yml
+++ b/.github/workflows/log4cxx-macos.yml
@@ -24,7 +24,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        name: [osx-14-clang, osx-15-clang, osx-g++]
+        name: [osx-14-clang, osx-15-clang, osx-26-clang, osx-g++]
         include:
           - name: osx-14-clang
             os: macos-14
@@ -33,6 +33,7 @@ jobs:
             qt: OFF
             multiprocess: ON
             cfstring: OFF
+            next_abi: OFF
           - name: osx-15-clang
             os: macos-15
             cxx: clang++
@@ -40,6 +41,15 @@ jobs:
             qt: ON
             multiprocess: OFF
             cfstring: ON
+            next_abi: OFF
+          - name: osx-26-clang
+            os: macos-26
+            cxx: clang++
+            odbc: ON
+            qt: ON
+            multiprocess: OFF
+            cfstring: ON
+            next_abi: ON
           - name: osx-g++
             os: macos-latest
             cxx: g++-14
@@ -47,6 +57,7 @@ jobs:
             qt: OFF
             multiprocess: OFF
             cfstring: ON
+            next_abi: OFF
 
     steps:
     - uses: actions/checkout@v4
@@ -76,6 +87,7 @@ jobs:
           -DLOG4CXX_ENABLE_ODBC=${{ matrix.odbc }} \
           -DLOG4CXX_MULTIPROCESS_ROLLING_FILE_APPENDER=${{ matrix.multiprocess 
}} \
           -DLOG4CXX_CFSTRING=${{ matrix.cfstring }} \
+          -DLOG4CXX_BUILD_NEXT_ABI=${{ matrix.next_abi }} \
           -DCMAKE_BUILD_TYPE=Debug \
           ..
         cmake --build .
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..a672f9b0 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=1"
+    )
 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/cpp/stringtokenizer.cpp b/src/main/cpp/stringtokenizer.cpp
index fe846cc3..ad3fd9e3 100644
--- a/src/main/cpp/stringtokenizer.cpp
+++ b/src/main/cpp/stringtokenizer.cpp
@@ -15,13 +15,8 @@
  * limitations under the License.
  */
 
-#include <log4cxx/logstring.h>
 #include <log4cxx/helpers/stringtokenizer.h>
 #include <log4cxx/helpers/exception.h>
-#if !defined(LOG4CXX)
-       #define LOG4CXX 1
-#endif
-#include <log4cxx/private/log4cxx_private.h>
 
 using namespace LOG4CXX_NS;
 using namespace LOG4CXX_NS::helpers;
@@ -30,7 +25,7 @@ struct StringTokenizer::StringTokenizerPrivate{
        StringTokenizerPrivate(const LogString& str, const LogString& delim1) : 
src(str), delim(delim1), pos(0){}
        LogString src;
        LogString delim;
-       size_t pos;
+       LogString::size_type pos;
 };
 
 
diff --git a/src/main/include/log4cxx/helpers/exception.h 
b/src/main/include/log4cxx/helpers/exception.h
index 143e00c0..c5215ee7 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
@@ -97,14 +98,14 @@ class LOG4CXX_EXPORT IOException : public Exception
                IOException(log4cxx_status_t stat);
                IOException(const LogString& msg);
                IOException(const char* msg);
-       IOException(const LogString& type, log4cxx_status_t stat);
+               IOException(const LogString& type, log4cxx_status_t stat);
 #if !LOG4CXX_LOGCHAR_IS_UTF8
-       IOException(const char* msg, log4cxx_status_t stat);
+               IOException(const char* msg, log4cxx_status_t stat);
 #endif
-       IOException(const IOException& src);
-       IOException& operator=(const IOException&);
-    private:
-       static LogString formatMessage(log4cxx_status_t stat);
+               IOException(const IOException& src);
+               IOException& operator=(const IOException&);
+       private:
+               static LogString formatMessage(log4cxx_status_t stat);
 };
 
 class LOG4CXX_EXPORT MissingResourceException : public Exception
@@ -199,7 +200,7 @@ class LOG4CXX_EXPORT ClassNotFoundException : public 
Exception
 };
 
 
-class NoSuchElementException : public Exception
+class LOG4CXX_EXPORT NoSuchElementException : public Exception
 {
        public:
                NoSuchElementException();
@@ -207,7 +208,7 @@ class NoSuchElementException : public Exception
                NoSuchElementException& operator=(const 
NoSuchElementException&);
 };
 
-class IllegalStateException : public Exception
+class LOG4CXX_EXPORT IllegalStateException : public Exception
 {
        public:
                IllegalStateException();
@@ -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..82fde47d 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/helpers/stringtokenizer.h 
b/src/main/include/log4cxx/helpers/stringtokenizer.h
index 8e406dec..23e3241a 100644
--- a/src/main/include/log4cxx/helpers/stringtokenizer.h
+++ b/src/main/include/log4cxx/helpers/stringtokenizer.h
@@ -19,7 +19,6 @@
 #define _LOG4CXX_HELPERS_STRING_TOKENIZER_H
 
 #include <log4cxx/logstring.h>
-#include <log4cxx/helpers/exception.h>
 
 namespace LOG4CXX_NS
 {
diff --git a/src/main/include/log4cxx/log4cxx.h.in 
b/src/main/include/log4cxx/log4cxx.h.in
index 4e6c2931..12602d4b 100644
--- a/src/main/include/log4cxx/log4cxx.h.in
+++ b/src/main/include/log4cxx/log4cxx.h.in
@@ -90,16 +90,16 @@ __pragma( warning( pop ) )
 
 #if defined(_WIN32) && defined(_MSC_VER)
 #if defined(LOG4CXX_STATIC)     // Linking a static library?
-#define LOG4CXX_EXPORT
+#  define LOG4CXX_EXPORT
 #elif defined(LOG4CXX)          // Building a DLL?
-#define LOG4CXX_EXPORT __declspec(dllexport)
+#  define LOG4CXX_EXPORT __declspec(dllexport)
 #else                          // Linking against a DLL?
-#define LOG4CXX_EXPORT __declspec(dllimport)
+#  define LOG4CXX_EXPORT __declspec(dllimport)
 #endif // !LOG4CXX_STATIC
-#elif defined(__GNUC__) && 4 <= __GNUC__ && 15 < LOG4CXX_ABI_VERSION
-  #define LOG4CXX_EXPORT __attribute__ ((visibility ("default")))
-#else // !(defined(_WIN32) && defined(_MSC_VER)) || LOG4CXX_ABI_VERSION <= 15 
|| __GNUC__ < 4
-  #define LOG4CXX_EXPORT
+#elif defined(__has_attribute) && __has_attribute(visibility)
+#  define LOG4CXX_EXPORT __attribute__ ((visibility ("default")))
+#else // !defined(__has_attribute)
+#  define LOG4CXX_EXPORT
 #endif
 
 #define LOG4CXX_NS @LOG4CXX_NS@
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/helpers/stringtokenizertestcase.cpp 
b/src/test/cpp/helpers/stringtokenizertestcase.cpp
index b0b9bf42..0a940eb3 100644
--- a/src/test/cpp/helpers/stringtokenizertestcase.cpp
+++ b/src/test/cpp/helpers/stringtokenizertestcase.cpp
@@ -16,6 +16,7 @@
  */
 #include <log4cxx/logstring.h>
 #include <log4cxx/helpers/stringtokenizer.h>
+#include <log4cxx/helpers/exception.h>
 #include "../logunit.h"
 #include "../insertwide.h"
 
@@ -26,9 +27,9 @@ using namespace log4cxx::helpers;
 LOGUNIT_CLASS(StringTokenizerTestCase)
 {
        LOGUNIT_TEST_SUITE(StringTokenizerTestCase);
-       LOGUNIT_TEST(testNextTokenEmptyString);
+       LOGUNIT_TEST_EXCEPTION(testNextTokenEmptyString, 
NoSuchElementException);
        LOGUNIT_TEST(testHasMoreTokensEmptyString);
-       LOGUNIT_TEST(testNextTokenAllDelim);
+       LOGUNIT_TEST_EXCEPTION(testNextTokenAllDelim, NoSuchElementException);
        LOGUNIT_TEST(testHasMoreTokensAllDelim);
        LOGUNIT_TEST(test1);
        LOGUNIT_TEST(test2);
@@ -44,17 +45,7 @@ public:
                LogString src;
                LogString delim(LOG4CXX_STR(" "));
                StringTokenizer tokenizer(src, delim);
-
-               try
-               {
-                       LogString token(tokenizer.nextToken());
-               }
-               catch (NoSuchElementException& ex)
-               {
-                       return;
-               }
-
-               LOGUNIT_ASSERT(false);
+               LogString token(tokenizer.nextToken());
        }
 
        void testHasMoreTokensEmptyString()
@@ -70,17 +61,7 @@ public:
                LogString src(LOG4CXX_STR("==="));
                LogString delim(LOG4CXX_STR("="));
                StringTokenizer tokenizer(src, delim);
-
-               try
-               {
-                       LogString token(tokenizer.nextToken());
-               }
-               catch (NoSuchElementException& ex)
-               {
-                       return;
-               }
-
-               LOGUNIT_ASSERT(false);
+               LogString token(tokenizer.nextToken());
        }
 
        void testHasMoreTokensAllDelim()
@@ -106,7 +87,7 @@ public:
                {
                        LogString token(tokenizer.nextToken());
                }
-               catch (NoSuchElementException& ex)
+               catch (const NoSuchElementException&)
                {
                        return;
                }
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..8ae9391b
--- /dev/null
+++ b/src/test/cpp/net/bsdsocket.cpp
@@ -0,0 +1,472 @@
+/*
+ * 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 <fcntl.h>
+#include <cstring>
+#include <unistd.h>
+#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);


Reply via email to