日志系统在系统开发和调整过程中的重要性,大家应该都清楚,特别是项目出问题之后,却没有日志可以帮忙定位问题,就非常令人痛苦。
因为我们不可能一直通过调试器去单步调试程序,所以设备的运行日志显得尤为重要。
通常我们对日志有这些要求:
Debug
,Warning
, Info,
Error
, Fatal
);printf
一样简单易用;以上是比较基本的功能,但是在嵌入式设备中,有的时候我们希望可以保存设备的运行日志,我们需要以下的一些功能;
以上这些需求不一定会全部实现。
除了常用的log4c
,log4cpp
,下面给大家推荐3个非常不错的开源日志库,比较适合用在单片机的项目中。从开始的轻量,到后面的功能丰富,最后一个很强大,所以请耐心看到最后。
我们平时开发中,log打印必不可少,仅仅使用printf,则log信息不好定位。一些开源稳定、成熟的log模块功能往往比较强大,而我们可能又不需要那么多功能。
这里简单分享一个我自己用的一个极简的log模块:log颜色可设置、带时间戳、文件、行号、函数。
这个log模块仅包含log.h与log.c两个文件。
log.h:
#ifndef LOG_H
#define LOG_H
#ifdef __cplusplus
extern "C" {
#endif
#define LOG_BUF_SIZE 1024
typedef long long (*get_sys_time_ms_def)(void);
enum log_color
{
COLOR_NULL = 0,
RED = 1,
GREEN = 2,
YELLOW = 3,
BLUE = 4
};
void log_print(enum log_color color, const char *file, int line, const char *func, const char* fmt, ...);
void log_time_register(get_sys_time_ms_def p_get_sys_time_ms);
#define LOG_D(...) log_print(BLUE, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__)
#ifdef __cplusplus
}
#endif
#endif
log.c:
#include
#include
#include "log.h"
static long long default_get_sys_time_ms(void)
{
return (long long)0;
}
static get_sys_time_ms_def s_get_sys_time_ms = default_get_sys_time_ms;
void log_time_register(get_sys_time_ms_def p_get_sys_time_ms)
{
s_get_sys_time_ms = p_get_sys_time_ms;
}
void log_print(enum log_color color, const char *file, int line, const char *func, const char* fmt, ...)
{
va_list ap;
char buf[LOG_BUF_SIZE] = {0};
long long time = s_get_sys_time_ms();
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
switch(color)
{
case COLOR_NULL:
printf("<%lld ms>[%s:%d %s] %s ", time, file, line, func, buf);
break;
case RED:
printf("\033[31m<%lld ms>[%s:%d %s] %s\033[0m", time, file, line, func, buf);
break;
case GREEN:
printf("\033[32m<%lld ms>[%s:%d %s] %s\033[0m", time, file, line, func, buf);
break;
case YELLOW:
printf("\033[33m<%lld ms>[%s:%d %s] %s\033[0m", time, file, line, func, buf);
break;
case BLUE:
printf("\033[34m<%lld ms>[%s:%d %s] %s\033[0m", time, file, line, func, buf);
break;
default:
break;
}
}
其中,默认打印ms级的系统时间。因为不同的平台(Linux、Windows、STM32等),获取系统时间的方式都不一样。
使用时,再根据不同的平台自己定义一个获取系统时间的函数,以注册的方式进行绑定。不注册获取时间函数也不影响使用,时间戳打印为0。
log_test.c:
#include
#include
#include "log.h"
static long long linux_get_sys_time_ms(void)
{
long long time_ms = 0;
struct timeval tv;
gettimeofday(&tv, NULL);
time_ms = (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
return (long long)time_ms;
}
int main(void)
{
log_time_register(linux_get_sys_time_ms);
char ch = 'a';
char str[10] = "ZhengN";
float float_val = 10.10;
int num = 88;
double double_val = 10.123456;
LOG_D("字符为 %c \n", ch);
LOG_D("字符串为 %s \n" , str);
LOG_D("浮点数为 %f \n", float_val);
LOG_D("整数为 %d\n" , num);
LOG_D("双精度值为 %lf \n", double_val);
LOG_D("八进制值为 %o \n", num);
LOG_D("十六进制值为 %x \n", num);
return 0;
}
编译,运行:
第18行屏蔽掉:
log_test.c:
#include
#include
#include "log.h"
static long long win_get_sys_time_ms(void)
{
long long time_ms = 0;
time_ms = GetTickCount();
return time_ms;
}
void test(void)
{
LOG_D("Hello world\n");
}
int main(void)
{
log_time_register(win_get_sys_time_ms);
char ch = 'a';
char str[10] = "ZhengN";
float float_val = 10.10;
int num = 88;
double double_val = 10.123456;
LOG_D("字符为 %c \n", ch);
LOG_D("字符串为 %s \n" , str);
LOG_D("浮点数为 %f \n", float_val);
LOG_D("整数为 %d\n" , num);
LOG_D("双精度值为 %lf \n", double_val);
LOG_D("八进制值为 %o \n", num);
LOG_D("十六进制值为 %x \n", num);
test();
return 0;
}
编译,运行:
项目地址:https://github.com/rxi/log.c
基于 C99 实现的简单日志库,具体输出如下所示;
具体用法
将源码中的log.c
和log.h
集成到你的项目中即可,需要打印日志的话,调用下面的API即可,如下所示;
log_trace(const char *fmt, ...);
log_debug(const char *fmt, ...);
log_info(const char *fmt, ...);
log_warn(const char *fmt, ...);
log_error(const char *fmt, ...);
log_fatal(const char *fmt, ...);
除了这些API,还有log_set_quiet
,log_set_lock
,LOG_USE_COLOR
等等,详情请看原项目。
项目地址:https://github.com/rdpoor/ulog
uLog 为嵌入式微控制器或任何资源有限的系统提供结构化的日志记录机制。它继承了流行的 Log4c
和 Log4j
平台背后的一些概念,但开销更低。
uLog 的一些特点:
项目地址:https://github.com/armink/EasyLogger
这个项目我用了很长时间,强烈推荐,是RT-Thread大佬的作品,已经集成到RTOS的内部了,支持的功能非常丰富,基本满足各种开发的需求。
特点如下:
以上只是这个项目的其中一部分,具体可以参考项目地址。
在单片机/嵌入式上使用的C语言日志库,一般来说,要求该日志库不能依赖出C语言标准库之外的库,因此这也决定了在嵌入式领域的C语言日志库不可能有非常丰富的功能。
如果只是想有一个比printf功能更强大的打印,那么可以考虑用 rxi_log。
如果想将打印输出到串口,或者嵌入式文件系统中,那么建议适配 EasyLogger。
一般在单片机/嵌入式领域,都是裸机或者RTOS,如果是嵌入式Linux操作系统,那就可以使用一些更加强大的日志系统,比如zlog日志系统模块基础、C语言实现一个日志模块、zlog日志模块基础。