[logging] introduce instance-aware platform logging API (#12737)

This commit adds `otPlatLogOutput()`, a new platform logging API that
provides the `otInstance` pointer along with a pre-formatted log
string. This addresses the limitation of the existing `otPlatLog()` in
multi-instance builds, where the function cannot reliably determine
which OpenThread instance generated the log.

`OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE`, is introduced to
enable this behavior. When enabled, `Logger::Log()` resolves the
active instance (via a tracked global pointer `gActiveInstance`)
and passes it to `otPlatLogOutput()`.

To support tracking the active instance context,
`UpdateActiveInstance()` is added and called during standard
instance retrieval paths, such as `Locator::GetInstance()` and
`Message::GetInstance()`. The TCP endpoints and listeners are also
updated to track the active instance when their `GetInstance()` methods
are invoked.

The Nexus testing platform is updated to enable this configuration
and implement `otPlatLogOutput()` to print the instance ID alongside
the log line, simplifying log tracing in multi-node simulations.
This commit is contained in:
Abtin Keshavarzian
2026-03-24 13:30:08 -07:00
committed by GitHub
parent 52551d8ff0
commit b35cb137d5
11 changed files with 110 additions and 29 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ extern "C" {
*
* @note This number versions both OpenThread platform and user APIs.
*/
#define OPENTHREAD_API_VERSION (583)
#define OPENTHREAD_API_VERSION (584)
/**
* @addtogroup api-instance
+21
View File
@@ -35,6 +35,7 @@
#ifndef OPENTHREAD_PLATFORM_LOGGING_H_
#define OPENTHREAD_PLATFORM_LOGGING_H_
#include <openthread/instance.h>
#include <openthread/platform/toolchain.h>
#ifdef __cplusplus
@@ -140,6 +141,9 @@ typedef enum otLogRegion
/**
* Outputs logs.
*
* This platform API is used to output logs when the configuration `OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE`
* is disabled. When the configuration is enabled, `otPlatLogOutput()` is used instead.
*
* Note that the support for log region is removed. The OT core will always emit all logs with `OT_LOG_REGION_CORE`
* as @p aLogRegion.
*
@@ -151,6 +155,23 @@ typedef enum otLogRegion
void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...)
OT_TOOL_PRINTF_STYLE_FORMAT_ARG_CHECK(3, 4);
/**
* Outputs a log line.
*
* This platform API is an alternative to `otPlatLog()` and is used when the configuration
* `OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE` is enabled.
*
* Unlike `otPlatLog()`, this API also provides a pointer to the OpenThread instance (`otInstance*`) from which the
* log is generated. This is particularly helpful in a multi-instance setup to distinguish logs from different
* instances. Additionally, it provides the log line as a fully formatted null-terminated string instead of
* a format string and variable arguments.
*
* @param[in] aInstance A pointer to the OpenThread instance.
* @param[in] aLogLevel The log level.
* @param[in] aLogLine A pointer to the null-terminated string containing the log line.
*/
void otPlatLogOutput(otInstance *aInstance, otLogLevel aLogLevel, const char *aLogLine);
/**
* Handles OpenThread log level changes.
*
+8 -1
View File
@@ -48,6 +48,13 @@ class Instance;
extern uint64_t gInstanceRaw[];
#endif
#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE
extern Instance *gActiveInstance;
inline Instance *UpdateActiveInstance(Instance *aInstance) { return gActiveInstance = aInstance; }
#else
inline Instance *UpdateActiveInstance(Instance *aInstance) { return aInstance; }
#endif
/**
* @addtogroup core-locator
*
@@ -117,7 +124,7 @@ public:
* @returns A reference to the parent otInstance.
*/
#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
Instance &GetInstance(void) const { return *mInstance; }
Instance &GetInstance(void) const { return *UpdateActiveInstance(mInstance); }
#else
Instance &GetInstance(void) const { return GetSingleInstance(); }
#endif
+16
View File
@@ -166,7 +166,23 @@ void Logger::Log(const char *aModuleName, LogLevel aLogLevel, Error aError, cons
}
logString.Append("%s", OPENTHREAD_CONFIG_LOG_SUFFIX);
#if OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE
{
Instance *instance;
#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
instance = Instance::GetActiveInstance();
VerifyOrExit(instance != nullptr);
#else
instance = &Instance::Get();
#endif
otPlatLogOutput(instance, aLogLevel, logString.AsCString());
}
#else
otPlatLog(aLogLevel, OT_LOG_REGION_CORE, "%s", logString.AsCString());
#endif
ExitNow();
+1 -1
View File
@@ -476,7 +476,7 @@ public:
* @returns A reference to the `Instance`.
*/
#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
Instance &GetInstance(void) const { return *GetMetadata().mInstance; }
Instance &GetInstance(void) const { return *UpdateActiveInstance(GetMetadata().mInstance); }
#else
Instance &GetInstance(void) const { return GetSingleInstance(); }
#endif
+14
View File
@@ -77,6 +77,20 @@
/** Log output is handled by a platform defined function */
#define OPENTHREAD_CONFIG_LOG_OUTPUT_PLATFORM_DEFINED 3
/**
* @def OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE
*
* Define to 1 to enable the instance-aware platform logging API.
*
* When this configuration is enabled, OpenThread logging will track the OpenThread instance (`otInstance`) from which
* a log is generated. The core will use the `otPlatLogOutput()` platform API instead of `otPlatLog()`. The new
* platform API provides the `otInstance` pointer along with the log as a fully formatted string, which is particularly
* useful in multi-instance setups to distinguish logs from different OpenThread instances.
*/
#ifndef OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE
#define OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE 0
#endif
/**
* @def OPENTHREAD_CONFIG_LOG_LEVEL
*
+9
View File
@@ -51,6 +51,11 @@ OT_DEFINE_ALIGNED_VAR(gInstanceRaw, sizeof(Instance), uint64_t);
#endif
#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE
// The currently active instance
Instance *gActiveInstance = nullptr;
#endif
#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE
#define INSTANCE_SIZE_ALIGNED OT_ALIGNED_VAR_SIZE(sizeof(ot::Instance), uint64_t)
@@ -411,6 +416,10 @@ exit:
return instance;
}
#if OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE
Instance *Instance::GetActiveInstance(void) { return gActiveInstance; }
#endif
#endif // OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE
void Instance::Reset(void) { otPlatReset(this); }
+11
View File
@@ -429,6 +429,17 @@ public:
*/
template <typename Type> inline Type &Get(void);
#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE
/**
* Gets the currently active OpenThread instance.
*
* It is used to determine the active instance primarily for logging purposes.
*
* @returns A pointer to the active OpenThread instance, or `nullptr` if not known.
*/
static Instance *GetActiveInstance(void);
#endif
#if OPENTHREAD_PLATFORM_NEXUS
/**
* Constructor to initialize an `Instance`
+8 -2
View File
@@ -109,7 +109,10 @@ exit:
return error;
}
Instance &Tcp::Endpoint::GetInstance(void) const { return AsNonConst(AsCoreType(GetTcb().instance)); }
Instance &Tcp::Endpoint::GetInstance(void) const
{
return *UpdateActiveInstance(&AsNonConst(AsCoreType(GetTcb().instance)));
}
const SockAddr &Tcp::Endpoint::GetLocalAddress(void) const
{
@@ -547,7 +550,10 @@ exit:
return error;
}
Instance &Tcp::Listener::GetInstance(void) const { return AsNonConst(AsCoreType(GetTcbListen().instance)); }
Instance &Tcp::Listener::GetInstance(void) const
{
return *UpdateActiveInstance(&AsNonConst(AsCoreType(GetTcbListen().instance)));
}
Error Tcp::Listener::Listen(const SockAddr &aSockName)
{
+1 -1
View File
@@ -83,7 +83,6 @@
#define OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE 1
#define OPENTHREAD_CONFIG_JOINER_ENABLE 1
#define OPENTHREAD_CONFIG_LOG_LEVEL OT_LOG_LEVEL_INFO
#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
@@ -91,6 +90,7 @@
#define OPENTHREAD_CONFIG_LOG_PREPEND_LEVEL 1
#define OPENTHREAD_CONFIG_LOG_PREPEND_UPTIME 0
#define OPENTHREAD_CONFIG_LOG_SUFFIX ""
#define OPENTHREAD_CONFIG_LOG_INSTANCE_AWARE_API_ENABLE 1
#define OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE 1
#define OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE 1
#define OPENTHREAD_CONFIG_MAC_FILTER_ENABLE 1
+20 -23
View File
@@ -39,8 +39,7 @@
namespace ot {
namespace Nexus {
static void LogVarArgs(Node *aActiveNode, const char *aFormat, va_list aArgs)
OT_TOOL_PRINTF_STYLE_FORMAT_ARG_CHECK(2, 0);
static void LogTime(void);
extern "C" {
@@ -57,16 +56,18 @@ void otTaskletsSignalPending(otInstance *aInstance)
//---------------------------------------------------------------------------------------------------------------------
// otPlatLog
void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...)
void otPlatLogOutput(otInstance *aInstance, otLogLevel aLogLevel, const char *aLogLine)
{
OT_UNUSED_VARIABLE(aLogLevel);
OT_UNUSED_VARIABLE(aLogRegion);
va_list args;
VerifyOrExit(aInstance != nullptr);
va_start(args, aFormat);
LogVarArgs(Core::Get().GetActiveNode(), aFormat, args);
va_end(args);
LogTime();
printf("%03u %s\n", AsNode(aInstance).GetId(), aLogLine);
fflush(stdout);
exit:
return;
}
//---------------------------------------------------------------------------------------------------------------------
@@ -136,29 +137,25 @@ void otPlatWakeHost(void) {}
//---------------------------------------------------------------------------------------------------------------------
// Log related function
static void LogTime(void)
{
uint32_t now = Core::Get().GetNow().GetValue();
printf("%02u:%02u:%02u.%03u ", now / 3600000, (now / 60000) % 60, (now / 1000) % 60, now % 1000);
}
void Log(const char *aFormat, ...)
{
va_list args;
va_start(args, aFormat);
LogVarArgs(nullptr, aFormat, args);
va_end(args);
}
static void LogVarArgs(Node *aActiveNode, const char *aFormat, va_list aArgs)
{
uint32_t now = Core::Get().GetNow().GetValue();
printf("%02u:%02u:%02u.%03u ", now / 3600000, (now / 60000) % 60, (now / 1000) % 60, now % 1000);
if (aActiveNode != nullptr)
{
printf("%03u ", aActiveNode->GetInstance().GetId());
}
vprintf(aFormat, aArgs);
LogTime();
vprintf(aFormat, args);
printf("\n");
fflush(stdout);
va_end(args);
}
} // namespace Nexus