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,
*
* 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
#include
/**
* 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
log_e("我是 wifi.proto 日志");
//WiFi 数据打包处理(位于 /wifi/package.c 源代码文件)
#define LOG_TAG "wifi.package"
#include
log_w("我是 wifi.package 日志");
//CAN 命令解析(位于 /can/disp.c 源代码文件)
#define LOG_TAG "can.disp"
#include
log_w("我是 can.disp 日志");
技巧二 :为了实现按照模块、子模块作用域来限制日志输出级别的功能,可以按照下面的方式,在模块的头文件中定义以下宏定义:
/**
* Log default configuration for EasyLogger.
* NOTE: Must defined before including the
*/
#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
#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