学习笔记--easylogger+freertos+zynq 小demo学习

写在最前面


  • 去年在项目中有用easylogger来作为日志输出,但因为是裸机实现项目,在项目发展后期选择了更轻量的log实现方式摒弃了easylogger,如今想把做过的项目用freertos实现以下,由此便需要能够适配RTOS的日志库,从而,用回了easylogger。
  • 基于zynq7020,学习和移植easylogger,并写下笔记作为记录

参考链接


网上有很多关于freertos移植easylogger的教程,在我实现的过程中,主要通过以下链接进行学习和移植:

  1. https://blog.csdn.net/yaq_30401/article/details/124279322
  2. 代码库中的readme文档有很详细很详细的使用说明,https://gitee.com/Armink/EasyLogger/blob/master/docs/zh/api/kernel.md

一 移植过程


  1. 从gitee获取源码 : https://gitee.com/Armink/EasyLogger?_from=gitee_search 同时我们可以看到很详细的说明文档,可以进行初步学习
  2. 复制整个easylogger目录下的easylogger目录至工程中
  3. 若不将日志写入flash以及文件,可以删除plugin目录,在工程中添加include路径
  4. 根据参考链接修改elog_cfg.h中的宏定义
  5. 最重要的是,elog_port.c中互斥量的建立和使用,从而实现了freertos中日志输出的完整性,究其根本其实应该是对串口使用的互斥,因此,当使用了easylogger后,一定不要使用其他的打印方式,如xil_printf,printf等,因xilinx提供的xil_printf以及c标准库的printf均未实现串口的互斥使用机制

二 使用和测试


  1. 创建空的freertos工程,移植好easylogger后,创建两个任务进行日志打印,测试easylogger打印结果和xil_printf打印结果:

  2. 使用easylogger打印代码与结果:

    #include <stdio.h>
    #include <stdlib.h>
    
    #include "xparameters.h"
    #include "xil_printf.h"
    #include "xil_cache.h"
    #include "xtime_l.h"
    
    #include "FreeRTOS.h"
    #include "task.h"
    
    #include "elog.h"
    
    
    #define THREAD_STACKSIZE  1024
    #define DEFAULT_THREAD_PRIO 2
    
    TaskHandle_t task1_handler = NULL;
    TaskHandle_t task2_handler = NULL;
    
    /**
     * init easylogger,这个函数是我们自己添加的,便于用户直接调用,需要在elog.h中添加声明
     */
    void easylogger_init(void)
    {
        /* init Easylogger */
    	elog_init();
    
    	/* set EasyLogger log format */
        elog_set_fmt(ELOG_LVL_ASSERT, ELOG_FMT_ALL);
    	elog_set_fmt(ELOG_LVL_ERROR, ELOG_FMT_LVL | ELOG_FMT_TIME | ELOG_FMT_T_INFO);
    	elog_set_fmt(ELOG_LVL_WARN, ELOG_FMT_LVL | ELOG_FMT_T_INFO);
    	elog_set_fmt(ELOG_LVL_INFO, ELOG_FMT_LVL);
    	elog_set_fmt(ELOG_LVL_DEBUG, ELOG_FMT_ALL & ~ELOG_FMT_FUNC);
    
    	/*Eenbale color*/
    	elog_set_text_color_enabled(true);
    
    	/* start EasyLogger */
    	elog_start();
    
    }
    
    
    void test(void *parameter)
    {
    	//xil_printf("enter test\r\n");
    	int *hh = (int *)parameter;
    	while(1)
    	{
    		//xil_printf("hh is %d\r\n",*hh);
    
    		log_a("%d:  Hello EasyLogger!", *hh);
    		log_e("%d: Hello EasyLogger!", *hh);
    		log_w("%d: Hello EasyLogger!", *hh);
    		log_i("%d: Hello EasyLogger!", *hh);
    		log_d("%d: Hello EasyLogger!", *hh);
    		log_v("%d: Hello EasyLogger!", *hh);
    
    
    	/*	xil_printf("%d:  Hello EasyLogger!\r\n", *hh);
    		xil_printf("%d: Hello EasyLogger!\r\n", *hh);
    		xil_printf("%d: Hello EasyLogger!\r\n", *hh);
    		xil_printf("%d: Hello EasyLogger!\r\n", *hh);
    		xil_printf("%d: Hello EasyLogger!\r\n", *hh);
    		xil_printf("%d: Hello EasyLogger!\r\n", *hh);*/
    
    		vTaskDelay(1/portTICK_PERIOD_MS);
    	}
    
    }
    
    void main_task(void *p)
    {
    
    
    	int testNum = 1;
        xTaskCreate(test, ( const char * const)"task1", THREAD_STACKSIZE, (void *)&testNum, DEFAULT_THREAD_PRIO, &task1_handler);
    
        int testNum1 = 2;
        xTaskCreate(test, ( const char * const)"task2", THREAD_STACKSIZE, (void *)&testNum1, DEFAULT_THREAD_PRIO, &task2_handler);
    		//sys_thread_new("system_init_thread", system_init_thread, &i, THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
        vTaskDelete(NULL);
    }
    
    int main(void)
    {
    
    	char * pcName = "main_task";
    	easylogger_init();
    	TaskHandle_t xHandle = NULL;
    	xTaskCreate(main_task, ( const char * const) pcName, THREAD_STACKSIZE, NULL, DEFAULT_THREAD_PRIO, &xHandle);
    	vTaskStartScheduler();
    	while (1);
        return 0;
    }
    

    结果:如下图,任务间打印实现互斥,无乱序出现
    在这里插入图片描述

  3. 使用xil_printf打印代码与结果:

    
    #include <stdio.h>
    #include <stdlib.h>
    
    #include "xparameters.h"
    #include "xil_printf.h"
    #include "xil_cache.h"
    #include "xtime_l.h"
    
    #include "FreeRTOS.h"
    #include "task.h"
    
    #include "elog.h"
    
    
    #define THREAD_STACKSIZE  1024
    #define DEFAULT_THREAD_PRIO 2
    
    TaskHandle_t task1_handler = NULL;
    TaskHandle_t task2_handler = NULL;
    
    /**
     * init easylogger,这个函数是我们自己添加的,便于用户直接调用,需要在elog.h中添加声明
     */
    void easylogger_init(void)
    {
        /* init Easylogger */
    	elog_init();
    
    	/* set EasyLogger log format */
        elog_set_fmt(ELOG_LVL_ASSERT, ELOG_FMT_ALL);
    	elog_set_fmt(ELOG_LVL_ERROR, ELOG_FMT_LVL | ELOG_FMT_TIME | ELOG_FMT_T_INFO);
    	elog_set_fmt(ELOG_LVL_WARN, ELOG_FMT_LVL | ELOG_FMT_T_INFO);
    	elog_set_fmt(ELOG_LVL_INFO, ELOG_FMT_LVL);
    	elog_set_fmt(ELOG_LVL_DEBUG, ELOG_FMT_ALL & ~ELOG_FMT_FUNC);
    
    	/*Eenbale color*/
    	elog_set_text_color_enabled(true);
    
    	/* start EasyLogger */
    	elog_start();
    
    }
    
    
    void test(void *parameter)
    {
    	//xil_printf("enter test\r\n");
    	int *hh = (int *)parameter;
    	while(1)
    	{
    		//xil_printf("hh is %d\r\n",*hh);
    
    		/*log_a("%d:  Hello EasyLogger!", *hh);
    		log_e("%d: Hello EasyLogger!", *hh);
    		log_w("%d: Hello EasyLogger!", *hh);
    		log_i("%d: Hello EasyLogger!", *hh);
    		log_d("%d: Hello EasyLogger!", *hh);
    		log_v("%d: Hello EasyLogger!", *hh);*/
    
    
    		xil_printf("%d:  Hello EasyLogger!\r\n", *hh);
    		xil_printf("%d: Hello EasyLogger!\r\n", *hh);
    		xil_printf("%d: Hello EasyLogger!\r\n", *hh);
    		xil_printf("%d: Hello EasyLogger!\r\n", *hh);
    		xil_printf("%d: Hello EasyLogger!\r\n", *hh);
    		xil_printf("%d: Hello EasyLogger!\r\n", *hh);
    
    		vTaskDelay(1/portTICK_PERIOD_MS);
    	}
    
    }
    
    void main_task(void *p)
    {
    
    
    	int testNum = 1;
        xTaskCreate(test, ( const char * const)"task1", THREAD_STACKSIZE, (void *)&testNum, DEFAULT_THREAD_PRIO, &task1_handler);
    
        int testNum1 = 2;
        xTaskCreate(test, ( const char * const)"task2", THREAD_STACKSIZE, (void *)&testNum1, DEFAULT_THREAD_PRIO, &task2_handler);
    		//sys_thread_new("system_init_thread", system_init_thread, &i, THREAD_STACKSIZE, DEFAULT_THREAD_PRIO);
        vTaskDelete(NULL);
    }
    
    int main(void)
    {
    
    	char * pcName = "main_task";
    	easylogger_init();
    	TaskHandle_t xHandle = NULL;
    	xTaskCreate(main_task, ( const char * const) pcName, THREAD_STACKSIZE, NULL, DEFAULT_THREAD_PRIO, &xHandle);
    	vTaskStartScheduler();
    	while (1);
        return 0;
    }
    
    

    结果如下图:任务间打印存在乱序
    在这里插入图片描述

三 easylogger 源码学习


能够移植和测试使用仅仅是第一步,还可以进一步学习源码,了解其实现原理和思路

  • 目录结构分析如下图,在easylogger的readme.md中有更详细的说明介绍:

    在这里插入图片描述

- elog_cfg.h文件:

  • 打开文件可以看到注释十分清晰,基本每个宏定义的含义都说的很清楚
  • 分为四大块:第一块为核心配置,包括输出使能,每行日志的长度等;第二块为颜色区域,给不同的日志等级分配不同的显示颜色;第三块为异步输出相关的配置,因需要pthread的支持,在freertos中需要注释掉异步输出使能?第四块为缓冲输出,即日志输出不会立刻输出,而是等待缓冲区满之后再一起输出。
  • 以上四大块的具体实现逐步再看

- elog.h文件:

  1. 宏定义:6个日志等级;过滤日志的等级;软件版本;日志格式定义(ELOG_FMT_ALL等);

  2. 数据结构:EasyLogger结构体,ElogErrCode 枚举类型;ElogFilter结构体;以及ElogTagLvlFilter结构体

    • 查看EasyLogger结构体,
    /* easy logger */
    typedef struct {
        ElogFilter filter; //日志过滤及饿哦固体
        size_t enabled_fmt_set[ELOG_LVL_TOTAL_NUM]; //日志等级个数
        bool init_ok; //初始化OK标志
        bool output_enabled;
        bool output_lock_enabled;
        bool output_is_locked_before_enable;
        bool output_is_locked_before_disable;
    
    #ifdef ELOG_COLOR_ENABLE
        bool text_color_enabled;
    #endif
    
    }EasyLogger, *EasyLogger_t;
    
    
  • 查看日志输出过滤器结构体

    /* output log's filter */
    typedef struct {
        uint8_t level;
        char tag[ELOG_FILTER_TAG_MAX_LEN + 1]; //过滤标记
        char keyword[ELOG_FILTER_KW_MAX_LEN + 1]; //过滤关键字
        ElogTagLvlFilter tag_lvl[ELOG_FILTER_TAG_LVL_MAX_NUM];
    } ElogFilter, *ElogFilter_t;
    
  • 函数声明:easylogger将所有需要的接口都集中在elog.h中进行定义,从而在使用的时候仅需要引用elog.h即可;其核心代码实现了日志结构体的初始化,开始,停止以及过滤有关的功能,

  • 重定义:通过宏定义elog_i,elog_d,elog_e等为log_i,log_d,log_e等

- elog.c文件:

  1. 全局变量:

    • static EasyLogger elog; //静态变量 elog,作为整个easylogger的核心变量,仅elog.c文件可见,对外采用接口形式
    • static char log_buf[ELOG_LINE_BUF_SIZE] = { 0 }; //每行可发送的数据长度缓冲区,在输出中,通过对该缓冲区进行填充,实现了日志的格式化,最终将该缓冲区内数据发出
  2. 初始化函数:ElogErrCode elog_init(void),对elog中字段进行填充,调用elog_port_init()对接口函数进行初始化;设置过滤等级和日志颜色使能

  3. 开始与结束函数:elog_start(void) < ----- > elog_stop(void) 使能和失能输出标志,即设置elog.output_enabled 标志为enable或disable;

  4. 设置输出格式:elog_set_fmt(uint8_t level, size_t set),其中level为日志的等级,set则为需要使能的格式位说明

    void elog_set_fmt(uint8_t level, size_t set) {
        ELOG_ASSERT(level <= ELOG_LVL_VERBOSE);
    
        elog.enabled_fmt_set[level] = set;
    }
    
    //格式定义如下:可以输出,日志等级,标记,当前时间,进程信息,现成信息,文件目录与文件名,函数名以及行号,每个输出格式使能占一位,使能该日志信息,则将该为置1,进行并运算即可
    typedef enum {
        ELOG_FMT_LVL    = 1 << 0, /**< level */
        ELOG_FMT_TAG    = 1 << 1, /**< tag */
        ELOG_FMT_TIME   = 1 << 2, /**< current time */
        ELOG_FMT_P_INFO = 1 << 3, /**< process info */
        ELOG_FMT_T_INFO = 1 << 4, /**< thread info */
        ELOG_FMT_DIR    = 1 << 5, /**< file directory and name */
        ELOG_FMT_FUNC   = 1 << 6, /**< function name */
        ELOG_FMT_LINE   = 1 << 7, /**< line number */
    } ElogFmtIndex;
    
    //对于全部输出的情况
    /* macro definition for all formats */
    #define ELOG_FMT_ALL    (ELOG_FMT_LVL|ELOG_FMT_TAG|ELOG_FMT_TIME|ELOG_FMT_P_INFO|ELOG_FMT_T_INFO| \
        ELOG_FMT_DIR|ELOG_FMT_FUNC|ELOG_FMT_LINE)
    
  5. 设置日志输出过滤器:void elog_set_filter(uint8_t level, const char *tag, const char *keyword),可以对某等级的tag或关键字进行过滤

  6. 日志输出函数:其参数列表为可变参数列表,包含很多信息;整个实现过程为通过elog_strcpy函数,判断各个标记位是否打开,填充log_buf发送缓冲区,最终通过elog_port_output(log_buf, log_len)函数发送

    /**
    *level:日志等级
    * tag: 日志输出标记
    * file : 若使能了输出到文件,则输出到文件的文件名
    */
    void elog_output(uint8_t level, const char *tag, const char *file, const char *func,
        const long line, const char *format, ...)
    
  7. 日志颜色输出原理

    • 实现代码如下:

      #define CSI_START                      "\033["
      #define CSI_END                        "\033[0m"
      
      #ifdef ELOG_COLOR_ENABLE
      /* color output info */
      static const char *color_output_info[] = {
              [ELOG_LVL_ASSERT]  = ELOG_COLOR_ASSERT, // ("35;"  "22m") 分别为:字体颜色,背景颜色,字体类型
              [ELOG_LVL_ERROR]   = ELOG_COLOR_ERROR,
              [ELOG_LVL_WARN]    = ELOG_COLOR_WARN,
              [ELOG_LVL_INFO]    = ELOG_COLOR_INFO,
              [ELOG_LVL_DEBUG]   = ELOG_COLOR_DEBUG,
              [ELOG_LVL_VERBOSE] = ELOG_COLOR_VERBOSE,
      };
      #endif /* ELOG_COLOR_ENABLE */
      
      
      //日志头:
      if (elog.text_color_enabled) {
              log_len += elog_strcpy(log_len, log_buf + log_len, CSI_START);
              log_len += elog_strcpy(log_len, log_buf + log_len, color_output_info[level]);
      }
      
      //日志尾:
      #ifdef ELOG_COLOR_ENABLE
          /* add CSI end sign */
          if (elog.text_color_enabled) {
              log_len += elog_strcpy(log_len, log_buf + log_len, CSI_END);
          }
      #endif
      
    • 实现原理:涉及到了ANSI转义序列(ANSI escape sequences)的知识内容,可通过参考链接学习了解:完整的Ansi 转义序列为:0x1B + “[” + <zero or more numbers, separated by “;”> + <a letter>,当如xshell或MobaXterm等工具接收到该串口信息后,会根据转义序列提供的信息显示串口数据

      • https://zhuanlan.zhihu.com/p/451658181
      • https://blog.csdn.net/q1003675852/article/details/134999871
      • https://blog.csdn.net/u013391094/article/details/127143727

四 测试说明(颜色&过滤)


  1. 整体输出情况如下,2个线程交替输出所有等级的日志信息,每个等级的日志格式与颜色均不相同

    在这里插入图片描述

  2. 设置过滤条件为,低于info,且不含有hhh关键字的日志不输出,效果如下:

    elog_set_filter(ELOG_LVL_INFO, "", "hhh");
    

在这里插入图片描述

可以设置和使用的功能还有不少,可以参考API说明多多尝试!

  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值