Nordic 日志

Nordic 日志

本文为本人粗略研究Nordic所作笔记,只研究了在GUNC中的代码

编译器环境为 GUNC

本文中用到的知识点:

  • C语言中# 和##的用法
  • 字符串的拼接
  • ANSI颜色控制码
  • C语言中attribute属性的定义与用法

1、日志中的定义

1.1、日志信息结构体

typedef struct
{
    //模块名字,字符串,由##方式拼接而成
    const char *       p_module_name;    ///< Module or instance name.
    //模块颜色,ANSI控制码
    uint8_t            info_color_id;    ///< Color code of info messages.
    uint8_t            debug_color_id;   ///< Color code of debug messages.
    //编译等级
    nrf_log_severity_t compiled_lvl;     ///< Compiled highest severity level.
    nrf_log_severity_t initial_lvl;      ///< Severity level for given module or instance set on backend initialization.
} nrf_log_module_const_data_t;

1.2、通用宏

#define NRF_LOG_ITEM_DATA(_name)         CONCAT_3(m_nrf_log_,_name,_logs_data)
//宏拼接
#define CONCAT_2(p1, p2)      CONCAT_2_(p1, p2)
#define CONCAT_2_(p1, p2)     p1##p2
//转字符串
#define STRINGIFY_(val) #val
#define STRINGIFY(val)  STRINGIFY_(val)

#define NRF_LOG_CONST_SECTION_NAME(_module_name) CONCAT_2(log_const_data_,_module_name)

#define _CONST const

1.3、日志注册宏

//定义了一个const修饰的结构体变量
#define NRF_LOG_INTERNAL_CONST_ITEM_REGISTER(                                             \
            _name, _str_name, _info_color, _debug_color, _initial_lvl, _compiled_lvl)     \
            NRF_SECTION_ITEM_REGISTER(NRF_LOG_CONST_SECTION_NAME(_name),                  \
            _CONST nrf_log_module_const_data_t NRF_LOG_ITEM_DATA_CONST(_name)) = {        \
                .p_module_name   = _str_name,                                             \
                .info_color_id   = (_info_color),                                         \
                .debug_color_id  = (_debug_color),                                        \
                .compiled_lvl    = (nrf_log_severity_t)(_compiled_lvl),                   \
                .initial_lvl     = (nrf_log_severity_t)(_initial_lvl),                    \
            }
            
#define NRF_LOG_INTERNAL_ITEM_REGISTER( \
                         _name, _str_name, _info_color, _debug_color, _initial_lvl, _compiled_lvl) \
    NRF_LOG_INTERNAL_CONST_ITEM_REGISTER(_name,                                                    \
                                         _str_name,                                                \
                                         _info_color,                                              \
                                         _debug_color,                                             \
                                         _initial_lvl,                                             \
                                         _compiled_lvl)
#define NRF_LOG_INTERNAL_MODULE_REGISTER() \
                   NRF_LOG_INTERNAL_ITEM_REGISTER(NRF_LOG_MODULE_NAME,                 \
                                                  STRINGIFY(NRF_LOG_MODULE_NAME),      \
                                                  NRF_LOG_INFO_COLOR,                  \
                                                  NRF_LOG_DEBUG_COLOR,                 \
                                                  NRF_LOG_INITIAL_LEVEL,               \
                                                  COMPILED_LOG_LEVEL)   


#define NRF_LOG_MODULE_REGISTER() NRF_LOG_INTERNAL_MODULE_REGISTER()    

1.4、打印宏

//日志打印的最终走向,
#define LOG_INTERNAL_X(N, ...)          CONCAT_2(LOG_INTERNAL_, N) (__VA_ARGS__)     
//日志的打印接入口    
// NUM_VA_ARGS_LESS_1宏为一个数值,该数值为可变参数目减一
#define LOG_INTERNAL(type, ...)         LOG_INTERNAL_X(NUM_VA_ARGS_LESS_1( __VA_ARGS__), type, __VA_ARGS__)   

#define NRF_LOG_INTERNAL_MODULE(level, level_id, ...)                                    \
    if (NRF_LOG_ENABLED && (NRF_LOG_LEVEL >= level) &&                                   \
        (level <= NRF_LOG_DEFAULT_LEVEL))                                                \
    {                                                                                    \
        if (NRF_LOG_FILTER >= level)                                                     \
        {                                                                                \
            LOG_INTERNAL(LOG_SEVERITY_MOD_ID(level_id), __VA_ARGS__);                    \
        }                                                                                \
    }

#define NRF_LOG_INTERNAL_INFO(...) \
        NRF_LOG_INTERNAL_MODULE(NRF_LOG_SEVERITY_INFO, NRF_LOG_SEVERITY_INFO, __VA_ARGS__)


#define NRF_LOG_INFO(...)                      NRF_LOG_INTERNAL_INFO( __VA_ARGS__) 

1.5、其他宏

#define NRF_SECTION_START_ADDR(section_name)       &CONCAT_2(__start_, section_name)
//该宏找到了变量的起始地址,来确定该模块具体的信息
#define NRF_LOG_MODULE_ID_POS      16
#define NRF_LOG_MODULE_ID_GET_CONST(addr) (((uint32_t)(addr) -                                     \
                                   (uint32_t)NRF_SECTION_START_ADDR(log_const_data)) /             \
                                    sizeof(nrf_log_module_const_data_t))

#define NRF_LOG_ITEM_DATA_CONST(_name)   CONCAT_2(NRF_LOG_ITEM_DATA(_name),_const)

#define NRF_LOG_MODULE_ID        NRF_LOG_MODULE_ID_GET_CONST(&NRF_LOG_ITEM_DATA_CONST(NRF_LOG_MODULE_NAME))
                               
#define LOG_SEVERITY_MOD_ID(severity) ((severity) | NRF_LOG_MODULE_ID << NRF_LOG_MODULE_ID_POS)

//ANSI控制码中的颜色
// <0=> Default 
// <1=> Black 
// <2=> Red 
// <3=> Green 
// <4=> Yellow 
// <5=> Blue 
// <6=> Magenta 
// <7=> Cyan 
// <8=> White 

#ifndef NRF_LOG_COLOR_DEFAULT
#define NRF_LOG_COLOR_DEFAULT 0    //默认颜色
#endif  

//日志等级
// <0=> Off 
// <1=> Error 
// <2=> Warning 
// <3=> Info 
// <4=> Debug 

#ifndef NRF_LOG_DEFAULT_LEVEL
#define NRF_LOG_DEFAULT_LEVEL 3   //默认为Info
#endif

#ifndef NRF_LOG_LEVEL
    #define NRF_LOG_LEVEL NRF_LOG_DEFAULT_LEVEL
#endif

#ifndef NRF_LOG_INITIAL_LEVEL
    #define NRF_LOG_INITIAL_LEVEL NRF_LOG_LEVEL
#endif

#ifndef NRF_LOG_INFO_COLOR
    #define NRF_LOG_INFO_COLOR NRF_LOG_COLOR_DEFAULT
#endif

#ifndef NRF_LOG_DEBUG_COLOR
    #define NRF_LOG_DEBUG_COLOR NRF_LOG_COLOR_DEFAULT
#endif

#define COMPILED_LOG_LEVEL NRF_LOG_LEVEL   //实际用到的值

// <0=> Off 
// <1=> Error 
// <2=> Warning 
// <3=> Info 
// <4=> Debug 

#ifndef NRF_LOG_DEFAULT_LEVEL
#define NRF_LOG_DEFAULT_LEVEL 3
#endif

#define NRF_LOG_FILTER              NRF_LOG_SEVERITY_DEBUG

#ifndef NRF_LOG_USES_COLORS
#define NRF_LOG_USES_COLORS       0
#endif  


#ifndef NRF_LOG_USES_TIMESTAMP
#define NRF_LOG_USES_TIMESTAMP    0
#endif

#define NRF_SECTION_DEF(section_name, data_type)                \
extern data_type * CONCAT_2(__start_, section_name);        \
extern void      * CONCAT_2(__stop_,  section_name)

1.6、适配不同编译链

#if defined(__CC_ARM)   //ARM编译链
#define NRF_SECTION_ITEM_REGISTER(section_name, section_var) \
    section_var __attribute__ ((section(STRINGIFY(section_name)))) __attribute__((used))


#elif defined(__GNUC__)
#define NRF_SECTION_ITEM_REGISTER(section_name, section_var) \
    section_var __attribute__ ((section("." STRINGIFY(section_name)))) __attribute__((used))
//实际为GUNC编译链
#elif defined(__ICCARM__)
#define NRF_SECTION_ITEM_REGISTER(section_name, section_var) \
    __root section_var @ STRINGIFY(section_name)
#endif

1.7、项目信息

<ProgramSection alignment="4" keep="Yes" load="Yes" name=".log_const_data" inputsections="*(SORT(.log_const_data*))" address_symbol="__start_log_const_data" end_symbol="__stop_log_const_data" />

1.8、具体函数

#define LOG_INTERNAL_1(type, str, arg0) \
    /*lint -save -e571*/nrf_log_frontend_std_1(type, str, (uint32_t)(arg0))/*lint -restore*/
void nrf_log_frontend_std_1(uint32_t            severity_mid,
                            char const * const p_str,
                            uint32_t           val0)
{
    uint32_t args[] = {val0};
    std_n(severity_mid, p_str, args,  ARRAY_SIZE(args));
}

2、 日志调用

2.1 日志初始化

2.1.1 相关宏

#define NRF_LOG_INIT(...) NRF_LOG_INTERNAL_INIT(__VA_ARGS__)
#define NRF_LOG_INTERNAL_INIT(...)               \
        nrf_log_init(GET_VA_ARG_1(__VA_ARGS__),  \
                     GET_VA_ARG_1(GET_ARGS_AFTER_1(__VA_ARGS__, LOG_TIMESTAMP_DEFAULT_FREQUENCY)))

//用来确保参数必为一个参数
#define GET_VA_ARG_1(...) GET_VA_ARG_1_(__VA_ARGS__, ) // Make sure that also for 1 argument it works
#define GET_VA_ARG_1_(a1, ...) a1

//使用第二个参数
#define GET_ARGS_AFTER_1(...) GET_ARGS_AFTER_1_(__VA_ARGS__, ) // Make sure that also for 1 argument it works
#define GET_ARGS_AFTER_1_(a1, ...) __VA_ARGS__

//确认时钟频率,用做时间戳打印
#define LOG_TIMESTAMP_DEFAULT_FREQUENCY ((NRF_LOG_TIMESTAMP_DEFAULT_FREQUENCY == 0) ?              \
                                       (NRF_LOG_LFCLK_FREQ/(APP_TIMER_CONFIG_RTC_FREQUENCY + 1)) : \
                                        NRF_LOG_TIMESTAMP_DEFAULT_FREQUENCY)

2.1.2 宏替换分析

NRF_LOG_INIT(NULL); //主函数调用宏

——–>

nrf_log_init(NULL,GET_VA_ARG_1(GET_ARGS_AFTER_1(NULL, LOG_TIMESTAMP_DEFAULT_FREQUENCY))) //宏用来确定至少只有一个参数

——–>

nrf_log_init(NULL,LOG_TIMESTAMP_DEFAULT_FREQUENCY) //第一个传参为NULL,代表打印不带时间戳

2.2 日志结构体定义

2.2.1 模块中的日志定义

使用NRF打印的函数将定义有下列宏

//文件开始包含
#define NRF_LOG_MODULE_NAME moliam  
NRF_LOG_MODULE_REGISTER();

2.2.2 宏替换分析

NRF_LOG_MODULE_REGISTER();

——–> NRF_LOG_INTERNAL_ITEM_REGISTER(moliam,#moliam,NRF_LOG_INFO_COLOR,NRF_LOG_DEBUG_COLOR,NRF_LOG_INITIAL_LEVEL,COMPILED_LOG_LEVEL);

——–>

NRF_LOG_INTERNAL_ITEM_REGISTER(moliam,”moliam”,0,0,3,3) ;

——–>

NRF_LOG_INTERNAL_CONST_ITEM_REGISTER(moliam,”moliam”,0,0,3,3) ;

——–> NRF_SECTION_ITEM_REGISTER(NRF_LOG_CONST_SECTION_NAME(moliam),const nrf_log_module_const_data_t NRF_LOG_ITEM_DATA_CONST(moliam)) = {

​ .p_module_name = “moliam”,

​ .info_color_id = 0,

​ .debug_color_id = 0,

​ .compiled_lvl = 3,

​ .initial_lvl = 3,

​ };

——–> NRF_SECTION_ITEM_REGISTER(NRF_LOG_CONST_SECTION_NAME(moliam),const nrf_log_module_const_data_t m_nrf_log_moliam_logs_data_const ) = {

​ .p_module_name = “moliam”,

​ .info_color_id = 0,

​ .debug_color_id = 0,

​ .compiled_lvl = 3,

​ .initial_lvl = 3,

​ };

——–>

NRF_SECTION_ITEM_REGISTER(log_const_data_moliam , const nrf_log_module_const_data_t m_nrf_log_moliam_logs_data_const) = {

​ .p_module_name = “moliam”,

​ .info_color_id = 0,

​ .debug_color_id = 0,

​ .compiled_lvl = 3,

​ .initial_lvl = 3,

​ };

——–>

const nrf_log_module_const_data_t m_nrf_log_moliam_logs_data_const attribute ((section(“.” “log_const_data_moliam”))) attribute((used))= {

​ .p_module_name = “moliam”,

​ .info_color_id = 0,

​ .debug_color_id = 0,

​ .compiled_lvl = 3,

​ .initial_lvl = 3,

​ };

——–>

const nrf_log_module_const_data_t m_nrf_log_moliam_logs_data_const attribute ((section(“.” “log_const_data_moliam”))) attribute((used)) = {

​ .p_module_name = “moliam”,

​ .info_color_id = 0,

​ .debug_color_id = 0,

​ .compiled_lvl = 3,

​ .initial_lvl = 3,

​ };

——–>

const nrf_log_module_const_data_t m_nrf_log_moliam_logs_data_const attribute ((section(“.log_const_data_moliam”))) attribute((used)) = {

​ .p_module_name = “moliam”,

​ .info_color_id = 0,

​ .debug_color_id = 0,

​ .compiled_lvl = 3,

​ .initial_lvl = 3,

​ }; //“a” “b” 会被编译器认为”ab” 多用来换行,以及字符串拼接

//整体替换

——–>

const nrf_log_module_const_data_t m_nrf_log_moliam_logs_data_const attribute ((section(“.log_const_data_moliam”))) attribute((used)) = {

​ .p_module_name = “moliam”,

​ .info_color_id = NRF_LOG_INFO_COLOR,

​ .debug_color_id = NRF_LOG_DEBUG_COLOR,

​ .compiled_lvl = COMPILED_LOG_LEVEL,

​ .initial_lvl = NRF_LOG_INITIAL_LEVEL,

​ } ;

——–>

const nrf_log_module_const_data_t m_nrf_log_moliam_logs_data_const attribute ((section(“.log_const_data_moliam”))) attribute((used)) = {

​ .p_module_name = “moliam”,

​ .info_color_id = NRF_LOG_INFO_COLOR,

​ .debug_color_id = NRF_LOG_DEBUG_COLOR,

​ .compiled_lvl = (COMPILED_LOG_LEVEL),

​ .initial_lvl = (NRF_LOG_INITIAL_LEVEL),

​ } ;

2.2.3 Tips

——–> 该宏定义了一个常量,且该常量的ram被固定分配在被命名为<.log_const_data_moliam>的地址空间(实际地址由编译器分配) 所有的该类型变量全分配在了地址相近的ram中,这也是后面可通过 LOG_SEVERITY_MOD_ID(level_id) 能够找到模块数据地址的真正原因,而不需要外部 extern 去声明**attribute**((used))说明该变量需要保存,不提示变量不使用的警告信息,即使模块中没有使用

2.3 调用日志

以NRF_LOG_INFO为例

2.3.1 宏替换分析

NRF_LOG_INFO(“moliam is %s”, “handsome”);

——–>

NRF_LOG_INTERNAL_INFO(“moliam is %s”, “handsome”)

——–>

NRF_LOG_INTERNAL_MODULE(3,3,”moliam is %s”, “handsome”) //NRF_LOG_SEVERITY_INFO 日志等级为3

——–>

if(NRF_LOG_ENABLED && (NRF_LOG_LEVEL >= 3) && (3 <= 3) ){

​ if(4 >= 3){

LOG_INTERNAL(LOG_SEVERITY_MOD_ID(3), “moliam is %s”, “handsome”);

​ }

​ } //这一步是等级过滤,低优先级的会被过滤掉不进行打印

——–>

LOG_INTERNAL(LOG_SEVERITY_MOD_ID(3), “moliam is %s”, “handsome”); //过滤后

——–>

LOG_INTERNAL(((3) | NRF_LOG_MODULE_ID << NRF_LOG_MODULE_ID_POS) , “moliam is %s”, “handsome”);

——–>

LOG_INTERNAL((3 | NRF_LOG_MODULE_ID << 16) , “moliam is %s”, “handsome”); // << 的优先级比 | 大一些

——–>

LOG_INTERNAL((3 | NRF_LOG_MODULE_ID_GET_CONST ( & NRF_LOG_ITEM_DATA_CONST(NRF_LOG_MODULE_NAME)) << 16) , “moliam is %s”, “handsome”);

——–>

LOG_INTERNAL((3 | NRF_LOG_MODULE_ID_GET_CONST (&NRF_LOG_ITEM_DATA_CONST(moliam)) << 16) , “moliam is %s”, “handsome”);

——–>

LOG_INTERNAL((3 | NRF_LOG_MODULE_ID_GET_CONST (&m_nrf_log_moliam_logs_data_const) << 16) , “moliam is %s”, “handsome”);

//这个变量在上边已被定义

——–>

LOG_INTERNAL((3 | (((uint32_t)(&m_nrf_log_moliam_logs_data_const) - \

(uint32_t)NRF_SECTION_START_ADDR(log_const_data)) / sizeof(nrf_log_module_const_data_t)) << 16) , “moliam is %s”, “handsome”);

——–>

LOG_INTERNAL((3 | (((uint32_t)(&m_nrf_log_moliam_logs_data_const) - \

​ (uint32_t)&__start_log_const_data) / sizeof(nrf_log_module_const_data_t)) << 16), “moliam is %s”, “handsome”);

——–>

LOG_INTERNAL((3 | (((uint32_t)(&m_nrf_log_moliam_logs_data_const) - (uint32_t)&__start_log_const_data) / sizeof(nrf_log_module_const_data_t)) << 16), “moliam is %s”, “handsome”);

——–>

LOG_INTERNAL((3 | (((uint32_t)(&m_nrf_log_moliam_logs_data_const) - (uint32_t)&__start_log_const_data) / 8) << 16), “moliam is %s”, “handsome”);

// __start_log_const_data 在前面已说过,是GUNC编译链实现的(ses开发环境用的是开源编译器GCC,linux编译器也使用此编译链)

// ARM编译链(keil 开发环境)和__ICCARM__编译链(IAR开发环境)实现不一样

//该宏是为了确定具体模块定义的地址,然后进行取值,了解了功能,再将LOG_SEVERITY_MOD_ID(3) 换回来

——–>

LOG_INTERNAL(LOG_SEVERITY_MOD_ID(3), “moliam is %s”, “handsome”); //过滤后

——–>

LOG_INTERNAL_X(NUM_VA_ARGS_LESS_1( “moliam is %s”, “handsome”), LOG_SEVERITY_MOD_ID(3), “moliam is %s”, “handsome”);

//NUM_VA_ARGS_LESS_1 这个宏brief上写的是获取可变参数的数量 我看不懂,但我大受震撼 宏名就是参数值少1 ,是把第一个参数当成格式化输出

//NUM_VA_ARGS_LESS_1(“%p”,”aaa”) = 1 ; NUM_VA_ARGS_LESS_1(“%p”) = 0 //将第一个参数作为字符串,后面的才算数

//其实这个地方已经和标准库的格式控制符有些不一致了,比如说 %.*s 则需要两个参数,但是会被此宏给分解掉 虽说arg依然是2 这也是%.*s 会被NRF_LOG 只打印 s 这个字符的真正原因

——–>

LOG_INTERNAL_X(1,LOG_SEVERITY_MOD_ID(3), “moliam is %s”, “handsome”); //可变参数量为1(handsome)

//通过可变参数目的方式,再将宏中的X具体化 此例具体化为1

——–>

LOG_INTERNAL_1(LOG_SEVERITY_MOD_ID(3), “moliam is %s”, “handsome”); //最大定义到LOG_INTERNAL_6 这也就是NRF最大只能由6个可变参的原因

——–> nrf_log_frontend_std_1(LOG_SEVERITY_MOD_ID(3),”moliam is %s”, (uint32_t)”handsome”); //在此处变成了函数

——–>

uint32_t args[] = {val0};

std_n(LOG_SEVERITY_MOD_ID(3),”moliam is %s”,args,1);

//只有一个参数 LOG_SEVERITY_MOD_ID(3) 用来索引moliam的module_id 且

//std_n 在nrf_log_frontend.c 中有定义

——–>

此时并没有将数据转换为 moliam is handsome 只是将first这个地址放入了全局变量中 如果没有autoflush 此宏就执行完毕了,日志信息都在 m_log_data 中,而wr_idx ++ , m_buffer_mask 则是通过掩码防止溢出 NRF_LOG_BUFSIZE 的手段

// 可以仔细分析一下这种防溢出,这个方式在uart_fifo中也有使用

// 掩码为NRF_LOG_BUFSIZE - 1 每次 fifo[(wr_idx ++) & mask] 可保证超出fifo自动从0开始 但是wr_idx 却是递增!就可以直接用 wr_idx - rd_idx 获取长度,也可以判断出是否超出 NRF_LOG_BUFSIZE,此方式也有一个弊端,长度必须是2的指数

2.4 日志输出分析

在调用NRF_LOG_INFO()之后,并没有进行输出,而是通过NRF_LOG_FLUSH()进行输出。

这也是如果使用官方移植的FreeRTOS如果空闲钩子为1时,为什么功耗维持在7mA左右的原因。

就是因为官方在FreeRTOS的空闲任务一直执行此函数,然后导致的芯片没有休眠。

解决这个问题的办法就是,自己找个地方调用该函数,而将钩子设置为1

NRF_LOG_FLUSH();

——–>

在函数 nrf_log_frontend_dequeue 中使用 nrf_memobj_write函数 将数据放入结构体 具体元素nrf_log_internal.h 313 - 356 行有定义 severity 元素为携带的等级 3

——–>

通过 m_log_data.p_backend_head->p_api->put() 这个函数指针进行发送 而此函数指针对于RTT输出和串口输出有以下定义

RTT的输出函数

const nrf_log_backend_api_t nrf_log_backend_rtt_api = {
                         .put       = nrf_log_backend_rtt_put,
                         .flush     = nrf_log_backend_rtt_flush,
                         .panic_set = nrf_log_backend_rtt_panic_set,
                  };

串口的输出函数

const nrf_log_backend_api_t nrf_log_backend_uart_api = {
                            .put       = nrf_log_backend_uart_put,
                            .flush     = nrf_log_backend_uart_flush,
                            .panic_set = nrf_log_backend_uart_panic_set,
                                                        };

最终都会调用 nrf_log_backend_serial_put() 函数,

—->

颜色在该函数中定义 params.use_colors = NRF_LOG_USES_COLORS; 并作为传参传入 nrf_log_std_entry_process

—->

再调用 nrf_log_std_entry_process 该函数中 p_ctx 即为打印的真正字符串

nrf_log_std_entry_process 函数 执行功能如下:

{

//该函数是整理需要放入到用户字符串前面的数据 (有红色的drop在此处定义 终于知道如果打印过多的话,为什么会有红色drop)

// 输出:颜色控制 + 时间戳 + 模块信息头

// 1、颜色

// nrf_fprintf(p_ctx, “%s”, m_colors[nrf_log_color_id_get( p_params->module_id, p_params->severity)]);

// static const char * m_colors 这个颜色中根据Module_id来获取

// 而颜色字符串为 ANSI 控制码 https://blog.csdn.net/weixin_44110772/article/details/105860997 // 函数中判断了 NRF_LOG_USES_COLORS 如果不为 0(sdk_config.h 中有定义),则使用m_colors[NRF_LOG_USES_COLORS] 进行颜色打印

// 如若为0 则使用 NRF_LOG_MODULE_REGISTER()中的颜色参数

// 2、时间戳

// 获取uint32_t 的时间戳 是NRF_LOG_INIT()函数中的第一个参数 NRF_LOG_USES_TIMESTAMP 为1时需要传入

// NRF_LOG_STR_FORMATTER_TIMESTAMP_FORMAT_ENABLED 为1时 为格式化的时间戳 // nrf_fprintf(p_ctx, “[%02d:%02d:%02d.%03d,%03d] “, hours, mins, seconds, ms, us);

// 3、模块信息头

// 填充完毕颜色则 fmt(“<%s> %s: “,severity_names,module_name) 变成了打印头 moliam :prefix_process(p_params, p_ctx);

//格式化字符串,此时才将handsome真正的放入到了 “moliam is %s” 此时是Nordic实现的可变参

//在此之前都是用的nrf_log_header_t 这个结构体进行存储日志相关数据 nrf_log_internal.h 313 - 356 行所显示的进行存储,一行为4字节

nrf_fprintf(p_ctx)

//该函数是整理需要放入到用户字符串后面的数据

// 其中有ANSI 控制码 还有换行使用的\r\n

postfix_process(p_params, p_ctx, false);

}

——>

字符串组成 为”\x1B[0m[12:00:43.002,005] moliam: moliam is handsome”

显示为 默认颜色 终端显示为 [12:00:43.002,005] moliam: moliam is handsome