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 <[email protected]> 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 << "&"; + 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);
