一、前言
在项目开发中需要使用到日志功能来调试和查看问题。有些问题并不会在我们实时查看的时候发生,而是在你上个厕所的功夫可能就发生了。如果上位机的缓冲区不够大,可能错误日志都看不到。
这时候就很有必要把日志文件保存在文件系统中了。RTT软件包中ULOG_FILE这个包可以实现日志文件的保存,但是它两年没更新了,另一个是会把所有日志保存起来。并不能按不同的标签分开保存。
还好现在的ulog组件自带了文件后端,只需要我们去配置就好了。
二、使用场景
在实际应用中,有些日志信息需要不间断输出并保存。例如AGV的运动信息,电池电量等等。
这些信息如果不作处理,会一并打印在控制台上,影响查看重要信息与输入MSH命令。
三、实现功能
所以需要一个文件来实现:
1.日志分开保存在不同文件内。
2.控制台可以按需求显示与不显示某类信息。
具体操作
0.前提配置
1.ULOG组件勾选并使用异步模式。
2.使用一个文件系统来保存文件
3.ULOG组件勾选文件后端
4.先确保文件系统能够正常读写,创建,删除
1.新建一个注册表,把目录,文件名,大小,数量写入表内
BUFF_SIZE这个缓冲区代表多少大小再进行一次写入,缓冲区太大,会很久才更改一次。
BUFF_SIZE太小会写入太频繁,导致ULOG异步线程堆栈不够溢出
ROOT_PATH是保存的路径。我选择“/flash/log”是因为我使用了ROM系统保存根目录,根目录是只读的。只有/flash下的文件夹才是可读写的。
// 后端注册表
struct _log_file
{
const char *name;
const char *dir_path;
rt_size_t max_num;
rt_size_t max_size;
rt_size_t buf_size;
};
#define ROOT_PATH "/flash/log"
#define FILE_SIZE 512 * 1024
#define BUFF_SIZE 512
static struct _log_file table[] =
{
{"sys" ,ROOT_PATH,10,FILE_SIZE,BUFF_SIZE},
{"motion" ,ROOT_PATH,5,FILE_SIZE,BUFF_SIZE},
};
2.进行文件后端初始化
注意init后一定要enbale,不然没有运行
static struct ulog_backend sys_log_backend;
static struct ulog_file_be sys_log_file;
void sys_log_file_backend_init(void)
{
struct ulog_file_be *file_be = &sys_log_file;
uint8_t id = sys_id;
file_be->parent = sys_log_backend;
ulog_backend_filter_t filter = sys_log_file_backend_filter;
ulog_file_backend_init( file_be,
table[id].name,
table[id].dir_path,
table[id].max_num,
table[id].max_size,
table[id].buf_size);
ulog_file_backend_enable(file_be);
}
static struct ulog_backend motion_log_backend;
static struct ulog_file_be motion_log_file;
void motion_log_file_backend_init(void)
{
struct ulog_file_be *file_be = &motion_log_file;
uint8_t id = motion_id;
file_be->parent = motion_log_backend;
ulog_backend_filter_t filter = motion_log_file_backend_filter;
ulog_file_backend_init( file_be,
table[id].name,
table[id].dir_path,
table[id].max_num,
table[id].max_size,
table[id].buf_size);
ulog_file_backend_enable(file_be);
}
3.测试运行
这个时候就可以先测试一下,看看日志有没有都保存在两个文件内
后缀名都是.log
使用cat命令查看一下内容
分开保存不同日志
上一步只进行了同时保存在两个文件内。还需要保存不同的日志内容。
1.在初始化函数中加入滤波函数设置
void sys_log_file_backend_init(void)
{
ulog_backend_filter_t filter = sys_log_file_backend_filter;
ulog_backend_set_filter(&file_be->parent,filter);
}
2.编写滤波函数
这个函数是自己编写的,比如系统日志文件不需要标签带有”MOVE”的
#define MOTION_TAG "MOVE"
static rt_bool_t sys_log_file_backend_filter(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw,
const char *log, rt_size_t len)
{
if (rt_strncmp(tag,MOTION_TAG, sizeof(MOTION_TAG)) == 0)//排除带有"MOVE"标签输出
return RT_FALSE;
else
return RT_TRUE;
}
比如运动日志文件只需要标签带有”MOVE”的
static rt_bool_t motion_log_file_backend_filter(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw,
const char *log, rt_size_t len)
{
if (rt_strncmp(tag,MOTION_TAG, sizeof(MOTION_TAG)) == 0)//带有"MOVE"标签输出
return RT_TRUE;
else
return RT_FALSE;
}
3.在一个线程中使用带特定标签的日志输出看一下效果
#define LOG_MV(...) ulog_i(MOTION_TAG, VA_ARGS)
while(1)
{
set_angle += 0.1;
get_angle -= 0.1;
set_speed += 1;
get_speed -=1;
vaule += 3.14;
LOG_MV("%f %f %d %f %f",
set_angle,get_angle,set_speed,
get_speed,vaule);
rt_thread_mdelay(500);
}
大小不同的保存文件
一个是系统日志
一个是运动日志
内容也不同,搞定完成
5.实现文件后端输出功能的关闭与打开
像易掉电的嵌入式使用FLASH或者EEPROM,容易在进行文件操作时掉电,导致文件损坏。另一个就是文件打开状态下,是不能删除的。有的时候又需要清除无用的日志文件。这就需要实现可以对文件后端进行控制
1.控制日志是否输出到文件中
static void log_file_backend_control(uint8_t argc, char **argv)
{
const char *operator = argv[1];
const char *flag = argv[2];
if (argc < 3)
{
rt_kprintf("Usage:\n");
rt_kprintf("control ulog file backend [name] [enable/disable]\n");
return;
}
else if(!rt_strcmp(operator,table[sys_id].name))
{
if(!rt_strcmp(flag,"disable"))
{
ulog_file_backend_disable(&sys_log_file);
rt_kprintf("The file backend %s is disabled\n",operator);
}
else if(!rt_strcmp(flag,"enable"))
{
ulog_file_backend_enable(&sys_log_file);
rt_kprintf("The file backend %s is enable\n",operator);
}
else
{
rt_kprintf("Usage:\n");
rt_kprintf("control ulog file backend [name] [enable/disable]\n");
return;
}
}
else if(!rt_strcmp(operator,table[motion_id].name))
{
if(!rt_strcmp(flag,"disable"))
{
ulog_file_backend_disable(&motion_log_file);
rt_kprintf("The file backend %s is disabled\n",operator);
}
else if(!rt_strcmp(flag,"enable"))
{
ulog_file_backend_enable(&motion_log_file);
rt_kprintf("The file backend %s is enable\n",operator);
}
else
{
rt_kprintf("Usage:\n");
rt_kprintf("control ulog file backend [name] [enable/disable]\n");
return;
}
}
else
{
rt_kprintf("Failed to find the file backend:%s\n",operator);
}
}
MSH_CMD_EXPORT_ALIAS(log_file_backend_control,ulog_be_ctrl,control ulog file backend [name] [enable:disable]);
2.控制日志文件后端卸载,来删除文件
static void log_file_backend_deinit(uint8_t argc, char **argv)
{
const char *operator = argv[1];
if (argc < 2)
{
rt_kprintf("Usage:\n");
rt_kprintf("Deinit ulog file backend [name]\n");
return;
}
else
{
if(!rt_strcmp(operator,"motion"))
{
ulog_file_backend_deinit(&motion_log_file);
ulog_file_backend_disable(&motion_log_file);
rt_kprintf("The file backend %s is deinit\n",operator);
}
else if(!rt_strcmp(operator,"sys"))
{
ulog_file_backend_deinit(&sys_log_file);
ulog_file_backend_disable(&sys_log_file);
rt_kprintf("The file backend %s is deinit\n",operator);
}
else
{
rt_kprintf("Usage:\n");
rt_kprintf("Deinit ulog file backend [name]\n");
return;
}
}
}
MSH_CMD_EXPORT_ALIAS(log_file_backend_deinit,ulog_be_deinit,Deinit ulog file backend);
6.扩展功能,显示与不显示特定日志到控制台上
按照上面操作下来,基本功能都完成了。还差一个控制台可以按需求显示与不显示某类信息。
不实现这个,会导致控制台信息一直在输出,无法查看重要信息,不好输入命令
1.对console_be.c进行改造
增加ulog_console_backend_filter函数的弱定义,可以在其他地方进行实现
在控制台初始化时,挂钩ulog_console_backend_filter函数
RT_WEAK rt_bool_t ulog_console_backend_filter(struct ulog_backend *backend, rt_uint32_t level, const char *tag,
rt_bool_t is_raw,const char *log, rt_size_t len)
{
return RT_TRUE;
}
int ulog_console_backend_init(void)
{
ulog_init();
console.output = ulog_console_backend_output;
ulog_backend_register(&console, "console", RT_TRUE);
ulog_backend_set_filter(&console,ulog_console_backend_filter);
return 0;
}
INIT_PREV_EXPORT(ulog_console_backend_init);
2.实现ulog_console_backend_filter函数,过滤不需要显示信息
rt_bool_t ulog_console_backend_filter(struct ulog_backend *backend, rt_uint32_t level, const char *tag, rt_bool_t is_raw,
const char *log, rt_size_t len)
{
if (rt_strncmp(tag,MOTION_TAG, sizeof(MOTION_TAG)) == 0)//排除带有"MOVE"标签输出
return RT_FALSE;
else
return RT_TRUE;
}
3.增加MSH命令,用来控制控制台需不需要查看信息
有些时候,日志文件输出的不是实时的。打开文件查看也不方便。
能输出到控制台直接显示是最合适不过
#ifdef RT_USING_MSH
static void ulog_console_backend_filter_set(uint8_t argc, char **argv)
{
if (argc < 2)
{
rt_kprintf("Usage:\n");
rt_kprintf("console filter [option] optino:enable or disable\n");
return;
}
else
{
const char *operator = argv[1];
if (!rt_strcmp(operator, "enable"))
{
ulog_backend_set_filter(&console,ulog_console_backend_filter);
}
else if (!rt_strcmp(operator, "disable"))
{
ulog_backend_set_filter(&console,RT_NULL);
}
else
{
rt_kprintf("Usage:\n");
rt_kprintf("console filter [option] optino:enable or disable\n");
}
}
}
MSH_CMD_EXPORT_ALIAS(ulog_console_backend_filter_set,console_filter,console filter [option] optino:enable or disable);
#endif
7.结束
以上就是全部内容了。
稍微提一句,MSH命令的控制函数没有使用输入文件后端的名称去查找实现。因为ulog_backend_find()函数的返回只有ulog的后端指针,没有file_be的指针,没办法操作。觉得ulog_backend的结构体可以加一个子对象,就像ulog_file_be的结构体加入了parent一样
另外API手册多久更新一次啊。ulog_backend_set_filter就没有找到说明。
原作者:用户名由3_15位