简介
EasyLogger 是一款超轻量级 、高性能的 C 日志库,非常适合对资源敏感的软件项目,例如:IoT 产品、可穿戴设备、智能家居等等。相比 log4c、zlog 这些知名的 C 日志库,EasyLogger 的功能更加简单,提供给用户的接口更少,但上手会很快,更多实用功能支持以插件形式进行动态扩展。
本章使用环境:
正点原子stm32407探索者开发板、工程模板:HAL库 - 实验4 串口通信实验
下载
easylogger项目托管在Github,下载地址: https://github.com/armink/EasyLogger
也可以通过git工具进行项目克隆(保证自己电脑配好git环境)
git clone https://github.com/armink/EasyLogger.git
移植
将下载的源码拖直接拖动到工程中,然后添加.C文件到keil工程中
port/elog_port.c:elog移植接口文件;
src/elog.c:elog核心功能源码;
src/elog_utils.c:elog所用到的一些c库工具函数实现;
src/elog_buf.c(可选添加):elog缓冲输出模式源码;
src/elog_async.c(可选添加):elog异步输出模式源码;
勾选支持C99模式
这里是引用
然后编译工程遇到错误
…\easylogger\src\elog_async.c(35): error: #5: cannot open source input file “pthread.h”: No such file or directory
然后我们点击错误到达错误的位置,注释掉这两个宏定义开关然后再次编译,然后我们需要配置一下easylogger需要用到的接口函数;
我们可以打开easylogger源代码下的demo文件夹里面的stm32模板工程,复制他的port文件到我们的工程中来,然后修改头文件包含即可
/* * This file is part of the EasyLogger Library. * * Copyright (c) 2015, Armink, <armink.ztl@gmail.com> * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * 'Software'), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * Function: Portable interface for non-os stm32f10x. * Created on: 2015-04-28 */ #include "elog.h" #include <stdio.h> #include <sys.h> /** * EasyLogger port initialize * * @return result */ ElogErrCode elog_port_init(void) { ElogErrCode result = ELOG_NO_ERR; return result; } /** * EasyLogger port deinit */ void elog_port_deinit(void) { } /** * output log port interface * * @param log output of log * @param size log size */ void elog_port_output(const char *log, size_t size) { /* output to terminal */ printf("%.*s", size, log); //TODO output to flash } /** * output lock */ void elog_port_output_lock(void) { __disable_irq(); } /** * output unlock */ void elog_port_output_unlock(void) { __enable_irq(); } /** * get current time interface * * @return current time */ const char *elog_port_get_time(void) { return "10:08:12"; } /** * get current process name interface * * @return current process name */ const char *elog_port_get_p_info(void) { return "pid:1008"; } /** * get current thread name interface * * @return current thread name */ const char *elog_port_get_t_info(void) { return "tid:24"; }
到这里工程移植就完成了
使用
更加详细的使用接口可用参考源码目录下的docs的参考文档,以下内容均取之与该项目文档;
初始化
初始化的 EasyLogger 的核心功能,初始化后才可以使用下面的API。
ElogErrCode elog_init(void)
启动
注意:在初始化完成后,必须调用启动方法,日志才会被输出。
void elog_start(void)
输出日志
所有日志的级别关系大小如下:
级别 标识 描述 0 [A] 断言(Assert) 1 [E] 错误(Error) 2 [W] 警告(Warn) 3 [I] 信息(Info) 4 [D] 调试(Debug) 5 [V] 详细(Verbose)
输出基本日志
所有级别的日志输出方法如下,每种级别都有两种简化方式,用户可以自行选择。
#define elog_assert(tag, ...) #define elog_a(tag, ...) //简化方式1,每次需填写 LOG_TAG #define log_a(...) //简化方式2,LOG_TAG 已经在文件顶部定义,使用前无需填写 LOG_TAG #define elog_error(tag, ...) #define elog_e(tag, ...) #define log_e(...) #define elog_warn(tag, ...) #define elog_w(tag, ...) #define log_w(...) #define elog_info(tag, ...) #define elog_i(tag, ...) #define log_i(...) #define elog_debug(tag, ...) #define elog_d(tag, ...) #define log_d(...) #define elog_verbose(tag, ...) #define elog_v(tag, ...) #define log_v(...)
参数 | 描述 |
tag | 日志标签 |
… | 不定参格式,与printf 入参一致,放入将要输出日志 |
技巧一 :对于每个源代码文件,可以在引用 elog.h
上方,根据模块的不同功能,定义不同的日志标签,如下所示,这样既可直接使用 log_x
这类无需输入标签的简化方式 API 。
//WiFi 协议处理(位于 /wifi/proto.c 源代码文件) #define LOG_TAG "wifi.proto" #include <elog.h> log_e("我是 wifi.proto 日志");
//WiFi 数据打包处理(位于 /wifi/package.c 源代码文件) #define LOG_TAG "wifi.package" #include <elog.h> log_w("我是 wifi.package 日志");
//CAN 命令解析(位于 /can/disp.c 源代码文件) #define LOG_TAG "can.disp" #include <elog.h> log_w("我是 can.disp 日志");
技巧二 :为了实现按照模块、子模块作用域来限制日志输出级别的功能,可以按照下面的方式,在模块的头文件中定义以下宏定义:
/** * Log default configuration for EasyLogger. * NOTE: Must defined before including the <elog.h> */ #if !defined(LOG_TAG) #define LOG_TAG "xx" #endif #undef LOG_LVL #if defined(XX_LOG_LVL) #define LOG_LVL XX_LOG_LVL #endif
XX 是模块名称的缩写,该段内容务必定义在 elog.h
之前,否则失效;这样做的 好处 是,如果模块内的源文件没有定义 TAG ,则会自动引用该段内容中的定义的 TAG 。同时可以在 头文件中可以配置 XX_LOG_LVL
,这样只会输出比这个优先级高或相等级别的日志。当然 XX_LOG_LVL 这个宏也可以不定义,此时会输出全部级别的日志,定义为 ASSERT 级别,就只剩断言信息了。
此时我们就能够实现 源文件->子模块->模块->EasyLogger全局 对于其中任何环节的日志配置及控制。调试时想要查看其中任何环节的日志,或者调整其中的某个环节日志级别,都会非常轻松,极大的提高了调试的灵活性及效率。
使能/失能日志输出
void elog_set_output_enabled(bool enabled)
使能/失能日志输出锁
默认为使能状态,当系统或MCU进入异常后,需要输出异常日志时,就必须失能日志输出锁,来保证异常日志能够被正常输出。
void elog_output_lock_enabled(bool enabled)
参数 | 描述 |
enabled | true: 使能,false: 失能 |
日志格式及样式
设置日志格式
每种级别可对应一种日志输出格式,日志的输出内容位置顺序固定,只可定义开启或关闭某子内容。可设置的日志子内容包括:级别、标签、时间、进程信息、线程信息、文件路径、行号、方法名。
注:默认为 RAW格式
void elog_set_fmt(uint8_t level, size_t set)
参数 | 描述 |
level | 级别 |
set | 格式集合 |
例子:
/* 断言:输出所有内容 */ elog_set_fmt(ELOG_LVL_ASSERT, ELOG_FMT_ALL); /* 错误:输出级别、标签和时间 */ elog_set_fmt(ELOG_LVL_ERROR, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME); /* 警告:输出级别、标签和时间 */ elog_set_fmt(ELOG_LVL_WARN, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME); /* 信息:输出级别、标签和时间 */ elog_set_fmt(ELOG_LVL_INFO, ELOG_FMT_LVL | ELOG_FMT_TAG | ELOG_FMT_TIME); /* 调试:输出除了方法名之外的所有内容 */ elog_set_fmt(ELOG_LVL_DEBUG, ELOG_FMT_ALL & ~ELOG_FMT_FUNC); /* 详细:输出除了方法名之外的所有内容 */ elog_set_fmt(ELOG_LVL_VERBOSE, ELOG_FMT_ALL & ~ELOG_FMT_FUNC);
使能/失能日志颜色
日志颜色功能是将各个级别日志按照颜色进行区分,默认颜色功能是关闭的。
void elog_set_text_color_enabled(bool enabled)
参数 | 描述 |
enabled | true: 使能,false: 失能 |
颜色修改
每个级别的日志均有默认颜色。如果想修改,请先查看在 elog.c
的头部定义的各种颜色及字体风格,这里以修改 VERBOSE
级别日志来举例:
首先选择前景色为白色,再选择背景色为黑色,最后字体风格为粗体
那么最终的配置如下:
#define ELOG_COLOR_VERBOSE (F_WHITE B_BLACK S_BOLD)
- 操作方法:增加并修改
ELOG_COLOR_VERBOSE
宏对应值即可,其他级别日志颜色的修改以此类推
配置文件说明
elog_cfg.h
// 开启输出使能 #define ELOG_OUTPUT_ENABLE /*设置静态输出日志级别。范围:从ELOG_LVL_ASSERT到ELOG-LVL-VERBOSE*/ #define ELOG_OUTPUT_LVL ELOG_LVL_VERBOSE // 开启断言检测 #define ELOG_ASSERT_ENABLE /*每行日志的缓冲区大小*/ #define ELOG_LINE_BUF_SIZE 1024 /*输出行号最大长度*/ #define ELOG_LINE_NUM_MAX_LEN 5 /* output filter's tag max length */ #define ELOG_FILTER_TAG_MAX_LEN 30 /*输出过滤器的标记最大长度*/ #define ELOG_FILTER_KW_MAX_LEN 16 /*输出过滤器的标记级别最大值*/ #define ELOG_FILTER_TAG_LVL_MAX_NUM 5 // 换行符定义 #define ELOG_NEWLINE_SIGN "\r\n" /*---------------------------------------------------------------------------*/ // 开启日志颜色出书 #define ELOG_COLOR_ENABLE /*如果需要,请将某些级别日志更改为非默认颜色*/ #define ELOG_COLOR_ASSERT (F_MAGENTA B_NULL S_NORMAL) #define ELOG_COLOR_ERROR (F_RED B_NULL S_NORMAL) #define ELOG_COLOR_WARN (F_YELLOW B_NULL S_NORMAL) #define ELOG_COLOR_INFO (F_CYAN B_NULL S_NORMAL) #define ELOG_COLOR_DEBUG (F_GREEN B_NULL S_NORMAL) #define ELOG_COLOR_VERBOSE (F_BLUE B_NULL S_NORMAL) /*---------------------------------------------------------------------------*/ // 开启异步输出模式 // #define ELOG_ASYNC_OUTPUT_ENABLE /*异步模式的最高输出级别,其他级别将同步输出*/ #define ELOG_ASYNC_OUTPUT_LVL ELOG_LVL_ASSERT /*异步输出模式的缓冲区大小*/ #define ELOG_ASYNC_OUTPUT_BUF_SIZE (ELOG_LINE_BUF_SIZE * 10) /*每个异步输出的日志必须以换行符结尾*/ #define ELOG_ASYNC_LINE_OUTPUT /*使用POSIX pthread实现的异步输出模式*/ #define ELOG_ASYNC_OUTPUT_USING_PTHREAD /*---------------------------------------------------------------------------*/ // 启用缓冲输出模式 // #define ELOG_BUF_OUTPUT_ENABLE /*缓冲输出模式的缓冲区大小 :如果是在逻辑程序上使用该方法这里就会出现输出10行串口才开始打印*/ #define ELOG_BUF_OUTPUT_BUF_SIZE (ELOG_LINE_BUF_SIZE * 10)
实际使用案例
#include "sys.h" #include "delay.h" #include "usart.h" #include "led.h" //开头添加 #include <stdio.h> #include "elog.h" #define LOG_TAG "main" int main(void) { u16 times=0; HAL_Init(); //初始化HAL库 Stm32_Clock_Init(336,8,2,7); //设置时钟,168Mhz delay_init(168); //初始化延时函数 uart_init(115200); //初始化USART LED_Init(); //初始化LED /* 初始化elog */ elog_init(); elog_set_text_color_enabled(true); /* 设置每个级别的日志输出格式 */ //输出所有内容 elog_set_fmt(ELOG_LVL_ASSERT, ELOG_FMT_ALL); //输出日志级别信息和日志TAG elog_set_fmt(ELOG_LVL_ERROR, ELOG_FMT_LVL | ELOG_FMT_TAG); elog_set_fmt(ELOG_LVL_WARN, ELOG_FMT_LVL | ELOG_FMT_TAG); elog_set_fmt(ELOG_LVL_INFO, ELOG_FMT_LVL | ELOG_FMT_TAG); //除了时间、进程信息、线程信息之外,其余全部输出 elog_set_fmt(ELOG_LVL_DEBUG, ELOG_FMT_ALL & ~(ELOG_FMT_TIME | ELOG_FMT_P_INFO | ELOG_FMT_T_INFO)); //输出所有内容 elog_set_fmt(ELOG_LVL_VERBOSE, ELOG_FMT_ALL); /* 启动elog */ elog_start(); while(1) { times++; if(times%30==0) { LED0=!LED0;//闪烁LED,提示系统正在运行. printf("hello\r\n"); log_a("Hello EasyLogger!"); log_e("Hello EasyLogger!"); log_w("Hello EasyLogger!"); log_i("Hello EasyLogger!"); log_d("Hello EasyLogger!"); log_v("Hello EasyLogger!"); } delay_ms(10); } }
关于该库的实现底层原理可以参考我之前写的这一篇文章C语言使用宏定义实现等级调试输出PRINT_LEVEL