Skip to content

Commit 2ede254

Browse files
committed
fix(FastLogger): enable SplitterChannel #5379
1 parent 7f269f2 commit 2ede254

6 files changed

Lines changed: 518 additions & 15 deletions

File tree

Foundation/include/Poco/SplitterChannel.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ class Foundation_API SplitterChannel: public Channel
6363
int count() const;
6464
/// Returns the number of channels in the SplitterChannel.
6565

66+
Channel::Ptr getChannel(int index) const;
67+
/// Returns the channel at the given (0-based) index, or null when index
68+
/// is out of range. Together with count() this lets callers enumerate the
69+
/// attached channels (e.g. FastLogger maps a SplitterChannel onto
70+
/// multiple Quill sinks).
71+
6672
protected:
6773
~SplitterChannel() override;
6874

Foundation/src/FastLogger.cpp

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "Poco/PatternFormatter.h"
3434
#include "Poco/AsyncChannel.h"
3535
#include "Poco/SplitterChannel.h"
36+
#include "Poco/EventChannel.h"
3637
#include "Poco/StreamChannel.h"
3738
#include "Poco/Environment.h"
3839
#if defined(POCO_OS_FAMILY_UNIX) && !defined(POCO_NO_SYSLOG_CHANNEL)
@@ -739,14 +740,16 @@ void collectSinksFromChannel(Channel::Ptr pChannel, SinkInfo& info)
739740
return;
740741
}
741742

742-
// SplitterChannel - the internal channel list is not accessible, so we fall back to console.
743-
// Users needing multiple sinks should configure them directly with FastLogger::addFileSink()
744-
// or by creating multiple FastLoggers.
745-
if (dynamic_cast<SplitterChannel*>(pChannel.get()))
743+
// SplitterChannel - recurse into each attached channel so a single
744+
// root.channel = splitter (e.g. console + file) maps to multiple Quill
745+
// sinks, and per-child PatternFormatter patterns are picked up. (Previously
746+
// this fell back to a lone console sink because SplitterChannel exposed no
747+
// accessor for its children, so file output and formatting were lost.)
748+
if (SplitterChannel* pSplitter = dynamic_cast<SplitterChannel*>(pChannel.get()))
746749
{
747-
// SplitterChannel's _channels vector is private with no accessor.
748-
// Fall back to console as a reasonable default.
749-
info.sinks.push_back(getConsoleSink());
750+
const int n = pSplitter->count();
751+
for (int i = 0; i < n; ++i)
752+
collectSinksFromChannel(pSplitter->getChannel(i), info);
750753
return;
751754
}
752755

@@ -894,6 +897,18 @@ void collectSinksFromChannel(Channel::Ptr pChannel, SinkInfo& info)
894897
}
895898
#endif
896899

900+
// EventChannel - dispatches Poco Messages to in-process listeners (e.g. a
901+
// web console). FastLogger/Quill bypasses Poco's Message/Channel pipeline
902+
// entirely, so it cannot feed an EventChannel. Skip it (rather than add a
903+
// spurious duplicate console sink via the fallback below) and warn.
904+
if (dynamic_cast<EventChannel*>(pChannel.get()))
905+
{
906+
std::cerr << "FastLogger warning: EventChannel is not supported by the fast "
907+
<< "logger; its output is omitted. Use a regular Logger for that "
908+
<< "channel if you need it." << std::endl;
909+
return;
910+
}
911+
897912
// Unknown channel type - fall back to console
898913
info.sinks.push_back(getConsoleSink());
899914
}
@@ -956,6 +971,21 @@ void FastLogger::setChannel(Channel::Ptr pChannel)
956971
SinkInfo info;
957972
collectSinksFromChannel(pChannel, info);
958973

974+
// Some sinks are process-wide singletons (e.g. the shared console sink), so a
975+
// channel tree can surface the same sink more than once; de-duplicate so Quill
976+
// doesn't emit each line multiple times.
977+
if (info.sinks.size() > 1)
978+
{
979+
std::vector<std::shared_ptr<quill::Sink>> uniqueSinks;
980+
for (auto& s: info.sinks)
981+
{
982+
bool dup = false;
983+
for (auto& u: uniqueSinks) { if (u == s) { dup = true; break; } }
984+
if (!dup) uniqueSinks.push_back(s);
985+
}
986+
info.sinks.swap(uniqueSinks);
987+
}
988+
959989
if (!info.sinks.empty())
960990
{
961991
// Quill doesn't support changing sinks on an existing logger.

Foundation/src/SplitterChannel.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,14 @@ int SplitterChannel::count() const
105105
}
106106

107107

108+
Channel::Ptr SplitterChannel::getChannel(int index) const
109+
{
110+
FastMutex::ScopedLock lock(_mutex);
111+
112+
if (index >= 0 && index < (int) _channels.size())
113+
return _channels[index];
114+
return Channel::Ptr();
115+
}
116+
117+
108118
} // namespace Poco

Foundation/testsuite/src/FastLoggerChannelsTest.cpp

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include "Poco/PatternFormatter.h"
3333
#include "Poco/AsyncChannel.h"
3434
#include "Poco/SplitterChannel.h"
35+
#include "Poco/EventChannel.h"
3536
#include "Poco/File.h"
3637
#include "Poco/TemporaryFile.h"
3738
#include "Poco/FileStream.h"
@@ -53,6 +54,7 @@ using Poco::FormattingChannel;
5354
using Poco::PatternFormatter;
5455
using Poco::AsyncChannel;
5556
using Poco::SplitterChannel;
57+
using Poco::EventChannel;
5658
using Poco::Channel;
5759
using Poco::Message;
5860
using Poco::AutoPtr;
@@ -281,21 +283,82 @@ void FastLoggerChannelsTest::testAsyncChannel()
281283

282284
void FastLoggerChannelsTest::testSplitterChannel()
283285
{
284-
// Test SplitterChannel handling
285-
// Note: SplitterChannel's internal channels are private, so FastLogger
286-
// falls back to console output. Use NullChannel for test.
287-
AutoPtr<NullChannel> pNullChannel(new NullChannel);
286+
// A SplitterChannel must fan out to ALL its children. Previously FastLogger
287+
// could not see inside a SplitterChannel and fell back to a single console
288+
// sink, losing file output and per-child patterns. Verify both children of
289+
// the splitter receive the (formatted) output.
290+
std::string file1 = TemporaryFile::tempName() + "_split1.log";
291+
std::string file2 = TemporaryFile::tempName() + "_split2.log";
292+
293+
AutoPtr<FileChannel> pFile1(new FileChannel(file1));
294+
// Wrap the second file in a FormattingChannel so we also exercise pattern
295+
// extraction through the splitter.
296+
AutoPtr<FileChannel> pFile2(new FileChannel(file2));
297+
AutoPtr<PatternFormatter> pFormatter(new PatternFormatter("[%p] %t"));
298+
AutoPtr<FormattingChannel> pFmt2(new FormattingChannel(pFormatter, pFile2));
299+
300+
AutoPtr<SplitterChannel> pSplitter(new SplitterChannel);
301+
pSplitter->addChannel(pFile1);
302+
pSplitter->addChannel(pFmt2);
303+
304+
// SplitterChannel introspection API (used by FastLogger to map a splitter
305+
// onto multiple sinks).
306+
assertTrue(pSplitter->count() == 2);
307+
assertTrue(pSplitter->getChannel(0).get() == pFile1.get());
308+
assertTrue(pSplitter->getChannel(1).get() == pFmt2.get());
309+
assertTrue(pSplitter->getChannel(2).isNull());
310+
assertTrue(pSplitter->getChannel(-1).isNull());
288311

289312
FastLogger& logger = FastLogger::get("TestAdapters.SplitterChannel");
290-
logger.setChannel(pNullChannel); // Use null to avoid console spam
313+
logger.setChannel(pSplitter);
291314
logger.setLevel(Message::PRIO_TRACE);
315+
logger.information("splitter routed message");
316+
logger.flush();
317+
318+
// Both children received output - not just a single console fallback.
319+
File f1(file1);
320+
File f2(file2);
321+
assertTrue(f1.exists() && f1.getSize() > 0);
322+
assertTrue(f2.exists() && f2.getSize() > 0);
323+
324+
// The message reached the file sinks (pattern was extracted via the splitter).
325+
Poco::FileInputStream fis(file2);
326+
std::string content;
327+
std::getline(fis, content);
328+
fis.close();
329+
assertTrue(content.find("splitter routed message") != std::string::npos);
330+
331+
try { f1.remove(); } catch (...) {}
332+
try { f2.remove(); } catch (...) {}
333+
}
292334

293-
// Log messages
294-
logger.information("Test message to splitter channel (falls back to console)");
295335

336+
void FastLoggerChannelsTest::testEventChannelSkipped()
337+
{
338+
// EventChannel cannot be mapped to a Quill sink (FastLogger bypasses Poco's
339+
// Message/Channel pipeline). FastLogger must skip it gracefully - no crash,
340+
// no spurious duplicate console sink - while still serving the splitter's
341+
// other children. (A warning is printed to stderr; that is expected.)
342+
std::string tempFile = TemporaryFile::tempName() + "_evt.log";
343+
344+
AutoPtr<FileChannel> pFile(new FileChannel(tempFile));
345+
AutoPtr<EventChannel> pEvent(new EventChannel);
346+
347+
AutoPtr<SplitterChannel> pSplitter(new SplitterChannel);
348+
pSplitter->addChannel(pEvent);
349+
pSplitter->addChannel(pFile);
350+
351+
FastLogger& logger = FastLogger::get("TestAdapters.SplitterEventChannel");
352+
logger.setChannel(pSplitter);
353+
logger.setLevel(Message::PRIO_TRACE);
354+
logger.information("message with an EventChannel sibling");
296355
logger.flush();
297356

298-
// Test passes if no crash occurs
357+
// The FileChannel sibling still works despite the unsupported EventChannel.
358+
File file(tempFile);
359+
assertTrue(file.exists() && file.getSize() > 0);
360+
361+
try { file.remove(); } catch (...) {}
299362
}
300363

301364

@@ -346,6 +409,7 @@ CppUnit::Test* FastLoggerChannelsTest::suite()
346409
CppUnit_addTest(pSuite, FastLoggerChannelsTest, testFormattingChannel);
347410
CppUnit_addTest(pSuite, FastLoggerChannelsTest, testAsyncChannel);
348411
CppUnit_addTest(pSuite, FastLoggerChannelsTest, testSplitterChannel);
412+
CppUnit_addTest(pSuite, FastLoggerChannelsTest, testEventChannelSkipped);
349413
#if defined(POCO_OS_FAMILY_UNIX) && !defined(POCO_NO_SYSLOG_CHANNEL)
350414
CppUnit_addTest(pSuite, FastLoggerChannelsTest, testSyslogChannel);
351415
#endif
@@ -426,6 +490,12 @@ void FastLoggerChannelsTest::testSplitterChannel()
426490
}
427491

428492

493+
void FastLoggerChannelsTest::testEventChannelSkipped()
494+
{
495+
// FastLogger not enabled
496+
}
497+
498+
429499
#if defined(POCO_OS_FAMILY_UNIX) && !defined(POCO_NO_SYSLOG_CHANNEL)
430500
void FastLoggerChannelsTest::testSyslogChannel()
431501
{

Foundation/testsuite/src/FastLoggerChannelsTest.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class FastLoggerChannelsTest: public CppUnit::TestCase
3434
void testFormattingChannel();
3535
void testAsyncChannel();
3636
void testSplitterChannel();
37+
void testEventChannelSkipped();
3738
#if defined(POCO_OS_FAMILY_UNIX) && !defined(POCO_NO_SYSLOG_CHANNEL)
3839
void testSyslogChannel();
3940
#endif

0 commit comments

Comments
 (0)