diff --git a/Foundation/include/Poco/Logger.h b/Foundation/include/Poco/Logger.h index 6cac05db05..132e683ad8 100644 --- a/Foundation/include/Poco/Logger.h +++ b/Foundation/include/Poco/Logger.h @@ -434,8 +434,15 @@ class Foundation_API Logger: public Channel /// become invalid. static void shutdown(); - /// Shuts down the logging framework and releases all - /// Loggers. + /// Shuts down the logging framework: detaches and releases the + /// channels of all existing Loggers, so that channel resources + /// (open files, sockets, ...) are freed. The Logger instances + /// themselves remain alive, so any cached Logger references held + /// by singletons stay valid; logging through them becomes a + /// silent no-op. + /// + /// Intended to be called once, as the last logging-related action + /// before the process exits. static void names(std::vector& names); /// Fills the given vector with the names diff --git a/Foundation/src/Logger.cpp b/Foundation/src/Logger.cpp index 801bc9d6f4..7bbddd3974 100644 --- a/Foundation/src/Logger.cpp +++ b/Foundation/src/Logger.cpp @@ -345,7 +345,18 @@ void Logger::shutdown() { Mutex::ScopedLock lock(_mapMtx); - _pLoggerMap.reset(); + // Detach channels from all loggers instead of destroying the logger + // objects. Singletons and other components may still hold Logger + // references obtained via Logger::get() during their lifetime. + // Destroying the loggers would leave those references dangling, + // causing use-after-free during static destruction. + if (_pLoggerMap) + { + for (auto& [name, pLogger] : *_pLoggerMap) + { + if (pLogger) pLogger->setChannel(nullptr); + } + } } diff --git a/Foundation/testsuite/src/LoggerTest.cpp b/Foundation/testsuite/src/LoggerTest.cpp index 1350eb8b74..8d46102bdf 100644 --- a/Foundation/testsuite/src/LoggerTest.cpp +++ b/Foundation/testsuite/src/LoggerTest.cpp @@ -385,6 +385,32 @@ void LoggerTest::testFormatStdThreadName() #endif } +void LoggerTest::testLoggerRefSurvivesShutdown() +{ + // Simulate a singleton caching a Logger& obtained before shutdown. + Logger& cached = Logger::get("TestLogger.Cached"); + AutoPtr pChannel = new TestChannel; + cached.setChannel(pChannel); + cached.setLevel(Message::PRIO_INFORMATION); + cached.information("before shutdown"); + assertTrue (pChannel->list().size() == 1); + + Logger::shutdown(); + + // The cached reference must still be valid; logging through it + // must be a safe no-op (channel detached). + cached.information("post-shutdown message"); + cached.log(Message("x", "y", Message::PRIO_ERROR)); + assertTrue (cached.getChannel().isNull()); + assertTrue (pChannel->list().size() == 1); + + // get() with the same name returns the same (muted) instance. + Logger& again = Logger::get("TestLogger.Cached"); + assertTrue (&again == &cached); + assertTrue (again.getChannel().isNull()); +} + + void LoggerTest::setUp() { Logger::shutdown(); @@ -406,6 +432,7 @@ CppUnit::Test* LoggerTest::suite() CppUnit_addTest(pSuite, LoggerTest, testDump); CppUnit_addTest(pSuite, LoggerTest, testFormatThreadName); CppUnit_addTest(pSuite, LoggerTest, testFormatStdThreadName); + CppUnit_addTest(pSuite, LoggerTest, testLoggerRefSurvivesShutdown); return pSuite; } diff --git a/Foundation/testsuite/src/LoggerTest.h b/Foundation/testsuite/src/LoggerTest.h index 209aa083b9..c454dd9138 100644 --- a/Foundation/testsuite/src/LoggerTest.h +++ b/Foundation/testsuite/src/LoggerTest.h @@ -30,6 +30,7 @@ class LoggerTest: public CppUnit::TestCase void testDump(); void testFormatThreadName(); void testFormatStdThreadName(); + void testLoggerRefSurvivesShutdown(); void setUp(); void tearDown();