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 9eafcdd4 SyslogAppender packet splitting for large chunk counts (#682)
9eafcdd4 is described below
commit 9eafcdd4601d44225e65f4a9af4403934b5b3980
Author: jmestwa-coder <[email protected]>
AuthorDate: Sun Jul 12 08:23:19 2026 +0530
SyslogAppender packet splitting for large chunk counts (#682)
---
src/main/cpp/syslogappender.cpp | 181 ++++++++++++++++-----
src/main/include/log4cxx/helpers/syslogwriter.h | 2 +
.../include/log4cxx/private/syslogappender_priv.h | 6 +
src/test/cpp/net/syslogappendertestcase.cpp | 71 +++++++-
4 files changed, 218 insertions(+), 42 deletions(-)
diff --git a/src/main/cpp/syslogappender.cpp b/src/main/cpp/syslogappender.cpp
index ea9bd450..9d944b00 100644
--- a/src/main/cpp/syslogappender.cpp
+++ b/src/main/cpp/syslogappender.cpp
@@ -26,8 +26,8 @@
#if !defined(LOG4CXX)
#define LOG4CXX 1
#endif
-#include <apr_strings.h>
#include <log4cxx/private/syslogappender_priv.h>
+#include <algorithm>
#define LOG_UNDEF -1
@@ -35,6 +35,142 @@ using namespace LOG4CXX_NS;
using namespace LOG4CXX_NS::helpers;
using namespace LOG4CXX_NS::net;
+namespace
+{
+ constexpr size_t SYSLOG_PACKET_SUFFIX_RESERVED_CHARS = 12;
+
+ void appendPacketSuffix(LogString& item, size_t current, size_t total)
+ {
+ item.append(LOG4CXX_STR("("));
+ StringHelper::toString(current, item);
+ item.append(LOG4CXX_STR("/"));
+ StringHelper::toString(total, item);
+ item.append(LOG4CXX_STR(")"));
+ }
+}
+
+namespace LOG4CXX_NS
+{
+namespace net
+{
+namespace detail
+{
+LOG4CXX_EXPORT std::vector<LogString> splitSyslogPackets(const LogString& msg,
size_t maxMessageLength)
+{
+ std::vector<LogString> packets;
+ auto digitCount = [](size_t value) {
+ size_t digits = 1u;
+ while (value >= 10u)
+ {
+ value /= 10u;
+ ++digits;
+ }
+ return digits;
+ };
+ auto reservePackets = [&](size_t count) -> bool
+ {
+ if (count > packets.max_size())
+ {
+ LogLog::error(LOG4CXX_STR("SyslogAppender cannot
reserve memory for packet splitting; message too large."));
+ return false;
+ }
+
+ try
+ {
+ packets.reserve(count);
+ return true;
+ }
+ catch (const std::length_error&)
+ {
+ LogLog::error(LOG4CXX_STR("SyslogAppender cannot
reserve memory for packet splitting; message too large."));
+ return false;
+ }
+ };
+ auto splitByChunkSize = [&](size_t chunkSize, bool appendSuffix) -> bool
+ {
+ const size_t nChunks = msg.size() / chunkSize + ((msg.size() %
chunkSize) != 0u ? 1u : 0u);
+ if (!reservePackets(nChunks))
+ {
+ return false;
+ }
+
+ for (size_t start = 0u; start < msg.size();)
+ {
+ const size_t remaining = msg.size() - start;
+ const size_t count = std::min(chunkSize, remaining);
+ packets.push_back(msg.substr(start, count));
+ start += count;
+ }
+
+ if (appendSuffix)
+ {
+ size_t current = 1u;
+ for (auto& item : packets)
+ {
+ appendPacketSuffix(item, current,
packets.size());
+ ++current;
+ }
+ }
+
+ return true;
+ };
+
+ if (maxMessageLength == 0u)
+ {
+ return packets;
+ }
+
+ if (msg.size() <= maxMessageLength)
+ {
+ packets.push_back(msg);
+ return packets;
+ }
+
+ size_t chunkSize = maxMessageLength >
SYSLOG_PACKET_SUFFIX_RESERVED_CHARS
+ ? maxMessageLength - SYSLOG_PACKET_SUFFIX_RESERVED_CHARS
+ : 1u;
+
+ const size_t maxIterations = 10u;
+ for (size_t iter = 0u; iter < maxIterations; ++iter)
+ {
+ const size_t nChunks = msg.size() / chunkSize + ((msg.size() %
chunkSize) != 0u ? 1u : 0u);
+ const size_t suffixLen = 2u * digitCount(nChunks) + 3u;
+
+ if (suffixLen <= SYSLOG_PACKET_SUFFIX_RESERVED_CHARS)
+ {
+ splitByChunkSize(chunkSize, true);
+ return packets;
+ }
+
+ if (suffixLen >= maxMessageLength)
+ {
+ LogLog::warn(LOG4CXX_STR("SyslogAppender: suffix does
not fit in MaxMessageLength; omitting packet suffix."));
+ splitByChunkSize(maxMessageLength, false);
+ return packets;
+ }
+
+ size_t newChunkSize = maxMessageLength - suffixLen;
+ if (newChunkSize == 0u)
+ {
+ newChunkSize = 1u;
+ }
+
+ if (newChunkSize >= chunkSize)
+ {
+ splitByChunkSize(chunkSize, true);
+ return packets;
+ }
+
+ chunkSize = newChunkSize;
+ }
+
+ splitByChunkSize(chunkSize, true);
+ return packets;
+}
+}
+}
+}
+
IMPLEMENT_LOG4CXX_OBJECT(SyslogAppender)
#define _priv static_cast<SyslogAppenderPriv*>(m_priv.get())
@@ -282,46 +418,10 @@ void SyslogAppender::append(
LOG4CXX_APPEND_FORMAL_PARAMETERS )
_priv->layout->format(msg, event);
Transcoder::encode(msg, encoded);
-
- // Split up the message if it is over maxMessageLength in size.
- // According to RFC 3164, the max message length is 1024, however
- // newer systems(such as syslog-ng) can go up to 8k in size for their
- // messages. We will append (x/y) at the end of each message
- // to indicate how far through the message we are
- std::vector<LogString> packets;
-
- if ( msg.size() > _priv->maxMessageLength )
- {
- LogString::iterator start = msg.begin();
-
- while ( start != msg.end() )
- {
- LogString::iterator end = start +
_priv->maxMessageLength - 12;
-
- if ( end > msg.end() )
- {
- end = msg.end();
- }
-
- LogString newMsg = LogString( start, end );
- packets.push_back( newMsg );
- start = end;
- }
-
- int current = 1;
-
- for (auto& item : packets)
- {
- char buf[12];
- apr_snprintf( buf, sizeof(buf), "(%d/%d)", current,
(int)packets.size() );
- LOG4CXX_DECODE_CHAR(str, buf);
- item.append( str );
- ++current;
- }
- }
- else
+ auto packets = detail::splitSyslogPackets(msg,
static_cast<size_t>(_priv->maxMessageLength));
+ if (packets.empty() && !msg.empty())
{
- packets.push_back( msg );
+ return;
}
// On the local host, we can directly use the system function 'syslog'
@@ -493,4 +593,3 @@ int SyslogAppender::getMaxMessageLength() const
{
return _priv->maxMessageLength;
}
-
diff --git a/src/main/include/log4cxx/helpers/syslogwriter.h
b/src/main/include/log4cxx/helpers/syslogwriter.h
index 12ec39cd..47829afe 100644
--- a/src/main/include/log4cxx/helpers/syslogwriter.h
+++ b/src/main/include/log4cxx/helpers/syslogwriter.h
@@ -21,6 +21,7 @@
#include <log4cxx/helpers/object.h>
#include <log4cxx/helpers/inetaddress.h>
#include <log4cxx/helpers/datagramsocket.h>
+#include <vector>
namespace LOG4CXX_NS
{
@@ -38,6 +39,7 @@ class LOG4CXX_EXPORT SyslogWriter
~SyslogWriter();
void write(const LogString& string);
+
private:
LOG4CXX_DECLARE_PRIVATE_MEMBER_PTR(SyslogWriterPrivate, m_priv)
};
diff --git a/src/main/include/log4cxx/private/syslogappender_priv.h
b/src/main/include/log4cxx/private/syslogappender_priv.h
index 3865eba9..431e7389 100644
--- a/src/main/include/log4cxx/private/syslogappender_priv.h
+++ b/src/main/include/log4cxx/private/syslogappender_priv.h
@@ -16,6 +16,7 @@
*/
#include <log4cxx/helpers/syslogwriter.h>
+#include <vector>
#include "appenderskeleton_priv.h"
@@ -93,5 +94,10 @@ struct SyslogAppender::SyslogAppenderPriv : public
AppenderSkeleton::AppenderSke
int maxMessageLength;
};
+namespace detail
+{
+LOG4CXX_EXPORT std::vector<LogString> splitSyslogPackets(const LogString& msg,
size_t maxMessageLength);
+}
+
}
}
diff --git a/src/test/cpp/net/syslogappendertestcase.cpp
b/src/test/cpp/net/syslogappendertestcase.cpp
index 697055f6..30dc70a1 100644
--- a/src/test/cpp/net/syslogappendertestcase.cpp
+++ b/src/test/cpp/net/syslogappendertestcase.cpp
@@ -15,8 +15,10 @@
* limitations under the License.
*/
-#include <log4cxx/helpers/datagramsocket.h>
+#define LOG4CXX_TEST 1
+#include <log4cxx/helpers/stringhelper.h>
#include <log4cxx/net/syslogappender.h>
+#include <log4cxx/private/syslogappender_priv.h>
#include "../appenderskeletontestcase.h"
using namespace log4cxx;
@@ -37,6 +39,9 @@ class SyslogAppenderTestCase : public AppenderSkeletonTestCase
LOGUNIT_TEST(testSetMaxMessageLengthNegativeFallsBack);
LOGUNIT_TEST(testMaxMessageLengthOptionBelowSuffixSizeFallsBack);
LOGUNIT_TEST(testMaxMessageLengthOptionValid);
+ LOGUNIT_TEST(testSplitMessageOneByteRemainderBoundary);
+ LOGUNIT_TEST(testImpossibleSuffixFitHandling);
+ LOGUNIT_TEST(testDigitGrowthBoundaryCase);
LOGUNIT_TEST_SUITE_END();
@@ -48,6 +53,18 @@ class SyslogAppenderTestCase : public
AppenderSkeletonTestCase
return new log4cxx::net::SyslogAppender();
}
+ private:
+ static LogString makePacket(size_t payloadLength, size_t
current, size_t total)
+ {
+ LogString packet(payloadLength,
static_cast<logchar>('A'));
+ packet.append(LOG4CXX_STR("("));
+ StringHelper::toString(current, packet);
+ packet.append(LOG4CXX_STR("/"));
+ StringHelper::toString(total, packet);
+ packet.append(LOG4CXX_STR(")"));
+ return packet;
+ }
+
void testSetMaxMessageLengthBelowSuffixSizeFallsBack()
{
log4cxx::net::SyslogAppender appender;
@@ -75,6 +92,58 @@ class SyslogAppenderTestCase : public
AppenderSkeletonTestCase
appender.setOption(LOG4CXX_STR("MAXMESSAGELENGTH"),
LOG4CXX_STR("2048"));
LOGUNIT_ASSERT_EQUAL(2048,
appender.getMaxMessageLength());
}
+
+ void testSplitMessageOneByteRemainderBoundary()
+ {
+ const size_t maxMessageLength = 16u;
+ const LogString message(17, static_cast<logchar>('A'));
+ const auto packets =
log4cxx::net::detail::splitSyslogPackets(message, maxMessageLength);
+
+ LOGUNIT_ASSERT_EQUAL((size_t) 5, packets.size());
+ LOGUNIT_ASSERT_EQUAL(makePacket(4u, 1u, 5u),
packets[0]);
+ LOGUNIT_ASSERT_EQUAL(makePacket(4u, 2u, 5u),
packets[1]);
+ LOGUNIT_ASSERT_EQUAL(makePacket(4u, 3u, 5u),
packets[2]);
+ LOGUNIT_ASSERT_EQUAL(makePacket(4u, 4u, 5u),
packets[3]);
+ LOGUNIT_ASSERT_EQUAL(makePacket(1u, 5u, 5u),
packets[4]);
+ for (const auto& packet : packets)
+ {
+ LOGUNIT_ASSERT(packet.size() <=
maxMessageLength);
+ }
+ }
+
+ void testDigitGrowthBoundaryCase()
+ {
+ const size_t maxMessageLength = 14u;
+ const LogString message(19999,
static_cast<logchar>('A'));
+ const auto packets =
log4cxx::net::detail::splitSyslogPackets(message, maxMessageLength);
+
+ LOGUNIT_ASSERT_EQUAL((size_t) 19999, packets.size());
+ LOGUNIT_ASSERT_EQUAL(makePacket(1u, 1u, 19999u),
packets.front());
+ LOGUNIT_ASSERT_EQUAL(makePacket(1u, 9999u, 19999u),
packets[9998]);
+ LOGUNIT_ASSERT_EQUAL(makePacket(1u, 10000u, 19999u),
packets[9999]);
+ LOGUNIT_ASSERT_EQUAL(makePacket(1u, 19999u, 19999u),
packets.back());
+ LOGUNIT_ASSERT(packets.front().size() <=
maxMessageLength);
+ LOGUNIT_ASSERT(packets[9998].size() <=
maxMessageLength);
+ LOGUNIT_ASSERT(packets[9999].size() <=
maxMessageLength);
+ LOGUNIT_ASSERT(packets.back().size() <=
maxMessageLength);
+ }
+
+ void testImpossibleSuffixFitHandling()
+ {
+ const size_t maxMessageLength = 13u;
+ const LogString message(10000,
static_cast<logchar>('A'));
+ const auto packets =
log4cxx::net::detail::splitSyslogPackets(message, maxMessageLength);
+
+ LOGUNIT_ASSERT_EQUAL((size_t) 770, packets.size());
+ LOGUNIT_ASSERT_EQUAL(LogString(13,
static_cast<logchar>('A')), packets.front());
+ LOGUNIT_ASSERT_EQUAL(LogString(3,
static_cast<logchar>('A')), packets.back());
+ for (const auto& packet : packets)
+ {
+ LOGUNIT_ASSERT(packet.size() <=
maxMessageLength);
+ LOGUNIT_ASSERT(packet.find(LOG4CXX_STR("(")) ==
LogString::npos);
+ }
+ }
+
};
LOGUNIT_TEST_SUITE_REGISTRATION(SyslogAppenderTestCase);