This is an automated email from the ASF dual-hosted git repository. swebb2066 pushed a commit to branch scoped_level_change in repository https://gitbox.apache.org/repos/asf/logging-log4cxx.git
commit 3ed54c34ec395fca198b9f3705b562347bcc9dfa Author: Stephen Webb <[email protected]> AuthorDate: Mon Nov 27 13:18:01 2023 +1100 Provide temporary logging level change support using RAII --- src/main/include/log4cxx/levelchange.h | 84 +++++++++++++++++++ src/test/cpp/CMakeLists.txt | 1 + src/test/cpp/levelchangetestcase.cpp | 143 +++++++++++++++++++++++++++++++++ 3 files changed, 228 insertions(+) diff --git a/src/main/include/log4cxx/levelchange.h b/src/main/include/log4cxx/levelchange.h new file mode 100644 index 00000000..df2f6f96 --- /dev/null +++ b/src/main/include/log4cxx/levelchange.h @@ -0,0 +1,84 @@ +/* + * 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_LEVEL_CHANGE_HDR_ +#define LOG4CXX_LEVEL_CHANGE_HDR_ + +#include <log4cxx/logmanager.h> +#include <log4cxx/logger.h> + +namespace LOG4CXX_NS +{ + +/** + * Changes a verbosity level for the instance variable's lifetime. + + * Create a LevelChange variable on the stack + * to temporarily (e.g. for a single method) + * increase the quantity of information logged. + + * Typically used to propagate the locally used logger's level (e.g. DEBUG or TRACE) + * to another named (e.g. the name of the class of the method invoked next) logger. + + * The LevelChange variable does not need to be removed from the code (e.g. for a release build) + * as it has no impact when the local and other logger point to the same level + * (e.g. null_ptr, implying their level is inherited). + */ + class LevelChange +{ + LoggerPtr m_otherCategory; + LevelPtr m_savedLevel; +public: // ...structors + /// Set \c otherCategory to \c level + LevelChange(const LoggerPtr& otherCategory, const LevelPtr& level) + : m_otherCategory(otherCategory) + , m_savedLevel(otherCategory->getLevel()) + { + m_otherCategory->setLevel(level); + } + /// Set \c otherCategory to the level of \c thisCategory + LevelChange(const LoggerPtr& otherCategory, const LoggerPtr& thisCategory) + : LevelChange(otherCategory, m_otherCategory->getLevel()) + { + } + /// Set the logger named \c otherCategory to \c level + template <class StringType> + LevelChange(const StringType& otherCategory, const LevelPtr& level) + : LevelChange(LogManager::getLogger(otherCategory), level) + { + } + /// Set the logger named \c otherCategory to the level of \c thisCategory + template <class StringType> + LevelChange(const StringType& otherCategory, const LoggerPtr& thisCategory) + : LevelChange(LogManager::getLogger(otherCategory), thisCategory->getLevel()) + { + } + /// Restore the verbosity level of the other logger + ~LevelChange() + { + m_otherCategory->setLevel(m_savedLevel); + } +private: // Prevent copies and assignment + LevelChange(const LevelChange&) = delete; + LevelChange(LevelChange&&) = delete; + LevelChange& operator=(const LevelChange&) = delete; + LevelChange& operator=(LevelChange&&) = delete; +}; + +} // namespace LOG4CXX_NS + +#endif // LOG4CXX_LEVEL_CHANGE_HDR_ diff --git a/src/test/cpp/CMakeLists.txt b/src/test/cpp/CMakeLists.txt index 049aad7e..f015e416 100644 --- a/src/test/cpp/CMakeLists.txt +++ b/src/test/cpp/CMakeLists.txt @@ -50,6 +50,7 @@ set(ALL_LOG4CXX_TESTS jsonlayouttest l7dtestcase leveltestcase + levelchangetestcase loggertestcase mdctestcase minimumtestcase diff --git a/src/test/cpp/levelchangetestcase.cpp b/src/test/cpp/levelchangetestcase.cpp new file mode 100644 index 00000000..c19e4e53 --- /dev/null +++ b/src/test/cpp/levelchangetestcase.cpp @@ -0,0 +1,143 @@ + +/* + * 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 <log4cxx/levelchange.h> +#include <log4cxx/appenderskeleton.h> +#include <log4cxx/helpers/loglog.h> +#include "logunit.h" + +using namespace log4cxx; + +namespace +{ + +class CountingAppender : public AppenderSkeleton +{ + public: + int count; + + CountingAppender() : count(0) + {} + + void close() override + {} + + void append(const spi::LoggingEventPtr& /*event*/, helpers::Pool& /*p*/) override + { + count++; + } + + bool requiresLayout() const override + { + return true; + } + + LogString getName() const override + { + return LOG4CXX_STR("counter"); + } +}; + +LoggerPtr getLogger(const std::string& name) +{ + static struct initializer { + initializer() { + // Prevent default configuration + LogManager::getLoggerRepository()->ensureIsConfigured([](){}); + // Install my appender + LogManager::getRootLogger()->addAppender( std::make_shared<CountingAppender>() ); + } + } init; + return LogManager::getLogger(name); +} + +} // anonymous namespace + +// A mocked worker +class ComplexProcessing +{ +public: + LoggerPtr logger = getLogger("ComplexProcessing"); + void DoStep1() + { + LOG4CXX_DEBUG(logger, "Step 1 message"); + } + void DoStep2() + { + LOG4CXX_DEBUG(logger, "Step 2 message"); + } + void DoStep3() + { + LOG4CXX_DEBUG(logger, "Step 3 message"); + } +}; +static ComplexProcessing processor; + +LOGUNIT_CLASS(LevelChangeTestCase) +{ + LOGUNIT_TEST_SUITE(LevelChangeTestCase); + LOGUNIT_TEST(testLevelChange); + LOGUNIT_TEST_SUITE_END(); + +#ifdef _DEBUG + struct Fixture + { + Fixture() { + helpers::LogLog::setInternalDebugging(true); + } + } suiteFixture; +#endif + +public: + void setUp() + { + // Disable DEBUG output from ComplexProcessing + processor.logger->setLevel(Level::getInfo()); + } + + void testLevelChange() + { + auto appender = dynamic_cast<CountingAppender*>(LogManager::getRootLogger()->getAppender(LOG4CXX_STR("counter")).get()); + LOGUNIT_ASSERT(appender); + + auto myLogger = getLogger("Controller"); + myLogger->setLevel(Level::getDebug()); + + // Check this debug request is sent to the appender + LOG4CXX_DEBUG(myLogger, "Start test"); + auto initialCount = appender->count; + LOGUNIT_ASSERT_EQUAL(initialCount, 1); + + // Check the ComplexProcessing debug request is not sent to the appender + processor.DoStep1(); + LOGUNIT_ASSERT_EQUAL(appender->count, initialCount); + { + LevelChange x(LOG4CXX_STR("ComplexProcessing"), myLogger); + processor.DoStep2(); + // Check the ComplexProcessing debug request was sent to the appender + LOGUNIT_ASSERT_EQUAL(appender->count, initialCount + 1); + } + + // Check the ComplexProcessing debug request is no longer sent to the appender + auto finalCount = appender->count; + processor.DoStep3(); + LOGUNIT_ASSERT_EQUAL(appender->count, finalCount); + } +}; + +LOGUNIT_TEST_SUITE_REGISTRATION(LevelChangeTestCase);
