diff --git a/src/core/config/logging.h b/src/core/config/logging.h index d5a73d244..2e090df44 100644 --- a/src/core/config/logging.h +++ b/src/core/config/logging.h @@ -197,6 +197,20 @@ #define OPENTHREAD_CONFIG_LOG_MAX_SIZE 150 #endif +/** + * @def OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE + * + * Define to 1 to enable the log level override feature and its associated APIs. + * + * This feature is used when `OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE` is also enabled. + * + * When enabled, new mechanism is added to allow temporary override of the current log level (e.g., to increase the + * level to capture more detailed information) and subsequently restore it to the original user-specified level. + */ +#ifndef OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE +#define OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE 0 +#endif + /** * @} */ diff --git a/src/core/instance/instance.cpp b/src/core/instance/instance.cpp index 5a02b7321..4f6b28623 100644 --- a/src/core/instance/instance.cpp +++ b/src/core/instance/instance.cpp @@ -320,6 +320,11 @@ Instance::Instance(void) #endif #if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE , mLogLevel(static_cast(OPENTHREAD_CONFIG_LOG_LEVEL_INIT)) +#if OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE + , mOriginalLogLevel(kLogLevelNone) + , mOverrideLogLevel(kLogLevelNone) + , mIsLogLevelOverriden(false) +#endif #if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE , mIsLogLevelSet(false) #else @@ -591,21 +596,67 @@ Error Instance::SetLogLevel(LogLevel aLogLevel) #if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && !OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE ExitNow(error = kErrorNotCapable); #else +#if OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE + if (mIsLogLevelOverriden) + { + mOriginalLogLevel = aLogLevel; + aLogLevel = Max(aLogLevel, mOverrideLogLevel); + } +#endif VerifyOrExit(mLogLevel != aLogLevel); mLogLevel = aLogLevel; - -#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE - mIsLogLevelSet = true; -#else - otPlatLogHandleLevelChanged(mLogLevel); -#endif - otPlatLogHandleLogLevelChanged(this, mLogLevel); + SignalLogLevelChange(); #endif exit: return error; } +void Instance::SignalLogLevelChange(void) +{ +#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE + mIsLogLevelSet = true; +#else + otPlatLogHandleLevelChanged(mLogLevel); +#endif + + otPlatLogHandleLogLevelChanged(this, mLogLevel); +} + +#if OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE +void Instance::OverrideLogLevel(LogLevel aLogLevel) +{ + LogLevel logLevel; + + if (!mIsLogLevelOverriden) + { + mOriginalLogLevel = GetLogLevel(); + mIsLogLevelOverriden = true; + } + + mOverrideLogLevel = aLogLevel; + + logLevel = Max(mOverrideLogLevel, mOriginalLogLevel); + + VerifyOrExit(mLogLevel != logLevel); + mLogLevel = logLevel; + SignalLogLevelChange(); + +exit: + return; +} + +void Instance::RestoreLogLevel(void) +{ + VerifyOrExit(mIsLogLevelOverriden); + mIsLogLevelOverriden = false; + IgnoreError(SetLogLevel(mOriginalLogLevel)); + +exit: + return; +} +#endif // OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE + #if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE Error Instance::SetGlobalLogLevel(LogLevel aLogLevel) { diff --git a/src/core/instance/instance.hpp b/src/core/instance/instance.hpp index a658e4318..e459e7137 100644 --- a/src/core/instance/instance.hpp +++ b/src/core/instance/instance.hpp @@ -356,6 +356,31 @@ public: */ Error SetLogLevel(LogLevel aLogLevel); +#if OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE + /** + * Overrides the current log level with a new one. + * + * The active log level will be the maximum of the original (user-set) log level and the override log level. + * If this is the first time an override is requested, the current log level is saved as the original log level. + * + * Subsequent calls to `SetLogLevel()` will update the original log level, and the active log level will be + * updated accordingly (again as the maximum of the new original and the override level). + * + * This method can be called multiple times to change the override log level. + * + * @param[in] aLogLevel The override log level. + */ + void OverrideLogLevel(LogLevel aLogLevel); + + /** + * Restores the log level to its original (user-set) value. + * + * This clears the log level override state. If the log level is not currently overridden, calling this + * method has no effect. + */ + void RestoreLogLevel(void); +#endif // OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE + #if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE /** * Sets the global log level. @@ -523,6 +548,9 @@ private: Instance(void); void AfterInit(void); #endif +#if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE + void SignalLogLevelChange(void); +#endif //----------------------------------------------------------------------------------------------------------------- // `static` variables @@ -866,6 +894,11 @@ private: #if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE LogLevel mLogLevel; +#if OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE + LogLevel mOriginalLogLevel; + LogLevel mOverrideLogLevel; + bool mIsLogLevelOverriden; +#endif #if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE bool mIsLogLevelSet; #endif diff --git a/tests/nexus/CMakeLists.txt b/tests/nexus/CMakeLists.txt index 1e421c198..09b38e60c 100644 --- a/tests/nexus/CMakeLists.txt +++ b/tests/nexus/CMakeLists.txt @@ -382,6 +382,7 @@ ot_nexus_test(1_4_CS_TC_3 "cert;nexus") ot_nexus_test(border_admitter "core;nexus") ot_nexus_test(border_agent "core;nexus") ot_nexus_test(border_agent_tracker "core;nexus") +ot_nexus_test(log_override "core;nexus") ot_nexus_test(discover_scan "core;nexus") ot_nexus_test(dtls "core;nexus") ot_nexus_test(form_join "core;nexus") diff --git a/tests/nexus/openthread-core-nexus-config.h b/tests/nexus/openthread-core-nexus-config.h index e5975ab2b..6aa87472a 100644 --- a/tests/nexus/openthread-core-nexus-config.h +++ b/tests/nexus/openthread-core-nexus-config.h @@ -89,6 +89,7 @@ #define OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE 1 #define OPENTHREAD_CONFIG_LOG_LEVEL_INIT OT_LOG_LEVEL_CRIT #define OPENTHREAD_CONFIG_LOG_OUTPUT OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED +#define OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE 1 #define OPENTHREAD_CONFIG_LOG_PLATFORM 0 #define OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL 1 #define OPENTHREAD_CONFIG_LOG_PREPEND_UPTIME 0 diff --git a/tests/nexus/test_log_override.cpp b/tests/nexus/test_log_override.cpp new file mode 100644 index 000000000..85082c135 --- /dev/null +++ b/tests/nexus/test_log_override.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2026, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +#include "platform/nexus_core.hpp" +#include "platform/nexus_node.hpp" + +namespace ot { +namespace Nexus { + +void TestLogOverride(void) +{ + Core nexus; + Node &node = nexus.CreateNode(); + + Log("---------------------------------------------------------------------------------------"); + Log("TestLogOverride"); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check initial log level"); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelCrit); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check OverrideLogLevel to a higher level"); + + node.Get().OverrideLogLevel(kLogLevelInfo); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelInfo); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check RestoreLogLevel"); + + node.Get().RestoreLogLevel(); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelCrit); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check OverrideLogLevel with a level lower than the current level"); + + SuccessOrQuit(node.SetLogLevel(kLogLevelWarn)); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelWarn); + + node.Get().OverrideLogLevel(kLogLevelCrit); + + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelWarn); + + node.Get().RestoreLogLevel(); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelWarn); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check user SetLogLevel while an override is active"); + + node.Get().OverrideLogLevel(kLogLevelInfo); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelInfo); + + SuccessOrQuit(node.SetLogLevel(kLogLevelCrit)); + + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelInfo); + + node.Get().RestoreLogLevel(); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelCrit); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check multiple OverrideLogLevel calls"); + + node.Get().OverrideLogLevel(kLogLevelWarn); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelWarn); + + node.Get().OverrideLogLevel(kLogLevelInfo); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelInfo); + + node.Get().RestoreLogLevel(); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelCrit); + + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + Log("Check OverrideLogLevel and then SetLogLevel to a higher level than override"); + + node.Get().OverrideLogLevel(kLogLevelWarn); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelWarn); + + SuccessOrQuit(node.SetLogLevel(kLogLevelInfo)); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelInfo); + + node.Get().RestoreLogLevel(); + VerifyOrQuit(node.Get().GetLogLevel() == kLogLevelInfo); +} + +} // namespace Nexus +} // namespace ot + +int main(void) +{ + ot::Nexus::TestLogOverride(); + printf("All tests passed\n"); + return 0; +} diff --git a/tests/toranj/openthread-core-toranj-config.h b/tests/toranj/openthread-core-toranj-config.h index cacbe729b..3b1495a1f 100644 --- a/tests/toranj/openthread-core-toranj-config.h +++ b/tests/toranj/openthread-core-toranj-config.h @@ -147,6 +147,8 @@ #define OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE 1 +#define OPENTHREAD_CONFIG_LOG_LEVEL_OVERRIDE_ENABLE 1 + #define OPENTHREAD_CONFIG_LOG_PREPEND_UPTIME 1 #define OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL 1