Skip to content

Commit

Permalink
Provide temporary logging level change support using RAII
Browse files Browse the repository at this point in the history
  • Loading branch information
stephen-webb committed Nov 27, 2023
1 parent c4def54 commit 3ed54c3
Show file tree
Hide file tree
Showing 3 changed files with 228 additions and 0 deletions.
84 changes: 84 additions & 0 deletions src/main/include/log4cxx/levelchange.h
Original file line number Diff line number Diff line change
@@ -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_
1 change: 1 addition & 0 deletions src/test/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ set(ALL_LOG4CXX_TESTS
jsonlayouttest
l7dtestcase
leveltestcase
levelchangetestcase
loggertestcase
mdctestcase
minimumtestcase
Expand Down
143 changes: 143 additions & 0 deletions src/test/cpp/levelchangetestcase.cpp
Original file line number Diff line number Diff line change
@@ -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);

0 comments on commit 3ed54c3

Please sign in to comment.