项目地址:https://github.com/rdpoor/ulog
uLog 为嵌入式微控制器或任何资源有限的系统提供结构化的日志记录机制。它继承了流行的 Log4c
和 Log4j
平台背后的一些概念,但开销更低。
将ulog
中的ulog.c
和ulog.h
文件移植进入自己的工程。
#define ULOG_ENABLED
typedef enum {
ULOG_TRACE_LEVEL=100,
ULOG_DEBUG_LEVEL,
ULOG_INFO_LEVEL,
ULOG_WARNING_LEVEL,
ULOG_ERROR_LEVEL,
ULOG_CRITICAL_LEVEL,
ULOG_ALWAYS_LEVEL
} ulog_level_t;
记住一个点:自定义里面的等级A,只有我们使用的ULOG_XXX函数中XXX的等级 >= A 的时候,该函数才会被真正的执行
#include
#include
#include "ulog.h"
#include
#include
#include
// 用户自定义用于调试的函数函数
void my_console_logger(ulog_level_t severity, char *msg)
{
printf("console: %s [%s]: %s\n",
"time", // user defined function
ulog_level_name(severity),
msg);
}
// 用户自定义用于将日志存储进文件的函数
void my_file_logger(ulog_level_t severity, char *msg)
{
int fd = open("file.txt",O_RWRD|O_CRATE,0655);
printf(,"file: %s [%s]: %s\n",
"time", // user defined function
ulog_level_name(severity),
msg)
close(fd);
}
int main()
{
int arg = 42;
// ulog的初始化
ULOG_INIT();
// 订阅my_console_logger函数,给予相应的等级为 ULOG_WARNING_LEVEL
ULOG_SUBSCRIBE(my_console_logger, ULOG_WARNING_LEVEL);
// 订阅my_file_logger函数,给予相应的等级为 ULOG_DEBUG_LEVEL
ULOG_SUBSCRIBE(my_file_logger, ULOG_DEBUG_LEVEL);
// 只会执行 my_file_logger的函数,理由是 ULOG_WARNING_LEVEL > ULOG_INFO_LEVEL 的等级
// 但是 ULOG_DEBUG_LEVEL < ULOG_INFO_LEVEL 的等级
// 自定义函数相应等级只有小于等于的时候才会执行相应的操作函数
ULOG_INFO("Info, arg=%d", arg); // logs to file but not console
// ULOG_WARNING_LEVEL > ULOG_DEBUG_LEVEL
ULOG_CRITICAL("Critical, arg=%d", arg); // logs to file and console
// 改变my_console_logger函数的操作等级为 ULOG_INFO_LEVEL
ULOG_SUBSCRIBE(my_console_logger, ULOG_INFO_LEVEL);
// 改变后两个自定义函数的操作等级都小于等于 ULOG_INFO_LEVEL,所以两个函数都会执行
ULOG_INFO("Info, arg=%d", arg); // logs to file and console
// 取消 my_file_logger 函数的订阅操作
ULOG_UNSUBSCRIBE(my_file_logger);
// 其中my_file_logger取消了,又 ULOG_INFO_LEVEL >= ULOG_INFO_LEVEL, 所以打印调试信息
ULOG_INFO("Info, arg=%d", arg);
}
char* get_timestamp()
{
time_t ti_t;
struct tm s_tm;
time(&ti_t);
localtime_s(&s_tm, &ti_t);
char* time_chr = (char*)malloc(sizeof(char)*20);
memset(time_chr,0,20);
sprintf(time_chr, "%d-%d-%d %d:%d:%d",
s_tm.tm_year + 1900, s_tm.tm_mon + 1, s_tm.tm_mday,
s_tm.tm_hour, s_tm.tm_min, s_tm.tm_sec);
return time_chr;
}
void my_console_logger(ulog_level_t severity, const char* msg) {
char *get_time = get_timestamp();
printf("%s [%s]: %s\n",
get_time, // user defined function
ulog_level_name(severity),
msg);
free(get_time);
}
char *get_timestamp()
{
time_t tt;
struct tm *t;
time(&tt);
char *time_chr = (char *)malloc(sizeof(char) * 20);
memset(time_chr, 0, 20);
t = localtime(&tt);
sprintf(time_chr, "%d-%d-%d %d:%d:%d", t->tm_year+1900,t->tm_mon + 1,
t->tm_mday,(t->tm_hour + 15)%24,t->tm_min,t->tm_sec);
return time_chr;
}
void my_console_logger(ulog_level_t severity, const char *msg)
{
char *get_time = get_timestamp();
printf("%s [%s]: %s\n",
get_time, // user defined function
ulog_level_name(severity),
msg);
free(get_time);
}
void my_file_logger(ulog_level_t severity, const char *msg)
{
char *get_time = get_timestamp();
FILE* fp = fopen("my_test.txt", "a+"); // 打开文件my_test.txt 这里文件可以跟改为时间
fprintf(fp, "%s [%s]: %s\n",
get_time, // user defined function
ulog_level_name(severity),
msg);
free(get_time);
}
执行后,文件中的内容类似以下,这个格式是按照自己喜好在日志函数中自己编写。
2023-4-18 20:23:14 [INFO]: Info, arg=42
2023-4-18 20:23:14 [CRITICAL]: main.c:67 arg=42
2023-4-18 20:23:14 [INFO]: main.c:66 arg=42
#define ULOG_DEBUG(...) ulog_message(ULOG_DEBUG_LEVEL, __VA_ARGS__)
// 这里的...表示的是可变参数 __VA_ARGS__ 这个也是对应的表示可变参数 这里也可以改为
// #define ULOG_DEBUG(...) ulog_message(ULOG_DEBUG_LEVEL, const char* fmt, ...)
void ulog_message(ulog_level_t severity, const char* fmt, ...) {
// va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行
va_list ap;
int i;
// 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,也就是const char* fmt
va_start(ap, fmt);
// 依次获取ap的类型,并进行sprintf函数类型功能组合成字符串,
// 最大长度 ULOG_MAX_MESSAGE_LENGTH,超过会被截断
vsnprintf(s_message, ULOG_MAX_MESSAGE_LENGTH, fmt, ap);
// 将这个 ap 指针关掉,以免发生危险
va_end(ap);
for (i = 0; i < ULOG_MAX_SUBSCRIBERS; i++) {
// 函数是依次执行的,那个函数先注册,那个先运行
if (s_subscribers[i].fn != NULL) {
// 只有使用的输出等级大于自定义函数等级的时候,就会执行
if (severity >= s_subscribers[i].threshold) {
s_subscribers[i].fn(severity, s_message);
}
}
}
}
// 错误标志枚举
typedef enum {
ULOG_ERR_NONE = 0,
ULOG_ERR_SUBSCRIBERS_EXCEEDED,
ULOG_ERR_NOT_SUBSCRIBED,
} ulog_err_t;
// 执行函数指针
typedef void (*ulog_function_t)(ulog_level_t severity, char* msg);
// 执行结构体
typedef struct {
ulog_function_t fn;
ulog_level_t threshold;
} subscriber_t;
// 执行结构体数组
#define ULOG_MAX_SUBSCRIBERS 6
static subscriber_t s_subscribers[ULOG_MAX_SUBSCRIBERS];
// search the s_subscribers table to install or update fn
// 订阅函数,也就是将函数放入执行结构体数组中,并赋予相应的输出等级
ulog_err_t ulog_subscribe(ulog_function_t fn, ulog_level_t threshold) {
int available_slot = -1;
int i;
for (i = 0; i < ULOG_MAX_SUBSCRIBERS; i++) {
if (s_subscribers[i].fn == fn) {
// 执行结构体数组已经含有该执行函数,进行更新即可
// already subscribed: update threshold and return immediately.
s_subscribers[i].threshold = threshold;
return ULOG_ERR_NONE;
}
else if (s_subscribers[i].fn == NULL) {
// found a free slot
// 找到数组中最近的一个未使用的执行结构体,赋予标志信息
available_slot = i;
}
}
// fn is not yet a subscriber. assign if possible.
if (available_slot == -1) {
// 执行结构体数组中已经存储满
return ULOG_ERR_SUBSCRIBERS_EXCEEDED;
}
// 将执行函数信息,放入执行结构体数组
s_subscribers[available_slot].fn = fn;
s_subscribers[available_slot].threshold = threshold;
return ULOG_ERR_NONE;
}
// search the s_subscribers table to remove
// 取消订阅,就是将该函数从执行结构体数组中删除
ulog_err_t ulog_unsubscribe(ulog_function_t fn) {
int i;
for (i = 0; i < ULOG_MAX_SUBSCRIBERS; i++) {
if (s_subscribers[i].fn == fn) {
s_subscribers[i].fn = NULL; // mark as empty
return ULOG_ERR_NONE;
}
}
return ULOG_ERR_NOT_SUBSCRIBED;
}
// 获取相应的输出等级字符串
const char* ulog_level_name(ulog_level_t severity) {
switch (severity) {
case ULOG_TRACE_LEVEL: return "TRACE";
case ULOG_DEBUG_LEVEL: return "DEBUG";
case ULOG_INFO_LEVEL: return "INFO";
case ULOG_WARNING_LEVEL: return "WARNING";
case ULOG_ERROR_LEVEL: return "ERROR";
case ULOG_CRITICAL_LEVEL: return "CRITICAL";
case ULOG_ALWAYS_LEVEL: return "ALWAYS";
default: return "UNKNOWN";
}
}