找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1072

积分

0

好友

153

主题
发表于 15 小时前 | 查看: 2| 回复: 0

在嵌入式项目开发中,日志功能是调试和问题追踪的利器。然而,有些问题具有偶发性,可能在开发者实时查看时并未发生。如果上位机或终端的缓冲区容量有限,关键的异常日志信息便可能丢失。因此,将日志持久化保存到文件系统中至关重要。RT-Thread软件包仓库中的ULOG_FILE包虽能实现此功能,但其更新停滞,且会将所有日志混存于单一文件,无法按标签进行区分存储。幸运的是,当前RT-Thread的ulog核心组件已内置了文件后端功能,通过合理配置即可满足分类存储的需求。

使用场景与目标

在实际的嵌入式应用(如AGV小车)中,某些状态信息(如运动数据、电池电量)需要持续记录。这些信息若不加处理地打印到控制台,会严重干扰开发者查看关键信息或输入MSH命令。因此,我们期望的日志系统应具备以下能力:

  1. 将不同类别的日志(如系统日志、运动日志)分别保存到独立的文件中。
  2. 能够按需控制控制台是否显示特定类别的日志信息。

实现步骤详解

0. 前提配置
  1. 在RT-Thread Settings中启用ULOG组件,并选择异步日志模式
  2. 确保系统已挂载可读写的文件系统(如LittleFS、SPIFFS),用于存储日志文件。
  3. 在ULOG组件配置中,勾选启用“文件日志后端”功能。

ULOG文件后端配置

  1. 务必提前测试文件系统的基本操作(创建、读写、删除)是否正常,这是所有文件日志管理功能的基础。
1. 定义日志文件配置表

首先,创建一个配置表,用于定义不同日志后端的存储路径、文件名、文件大小、数量及缓冲区大小。

  • BUFF_SIZE:写入缓冲区大小。设置过大会导致数据积压延迟写入;设置过小则会频繁触发写入操作,可能引发ULOG异步线程堆栈溢出。需要根据实际业务日志频率和系统资源权衡。
  • ROOT_PATH:日志文件存储的根目录。示例中使用/flash/log是考虑到ROMFS通常挂载为只读根目录,而/flash下的文件系统(如FATFS、LittleFS)才具备读写权限。
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) // 单个日志文件最大512KB
#define BUFF_SIZE 512 // 写入缓冲区大小

// 日志文件配置表
static struct _log_file table[] = {
    {"sys",    ROOT_PATH, 10, FILE_SIZE, BUFF_SIZE}, // 系统日志,最多10个文件
    {"motion", ROOT_PATH, 5,  FILE_SIZE, BUFF_SIZE}, // 运动日志,最多5个文件
};
// 为配置表条目定义索引
#define sys_id    0
#define motion_id 1
2. 初始化多个文件后端

为每种日志类别初始化独立的文件后端。注意:调用ulog_file_backend_init初始化后,必须调用ulog_file_backend_enable使其生效。

// 系统日志后端
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_backend_set_filter(&file_be->parent, filter); // 绑定过滤规则
    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_backend_set_filter(&file_be->parent, filter);
    ulog_file_backend_enable(file_be);
}
3. 功能测试

完成初始化后,可以先进行基础测试,确认日志文件被正确创建。

  • 文件将以后缀名.log保存在指定目录。

生成的日志文件

  • 使用cat命令可以查看文件内容,此时两个文件内容可能相同。

查看日志内容

4. 实现日志分类过滤

上一步实现了日志的多文件存储,但内容并未区分。接下来通过自定义过滤函数实现按标签分类。

  1. 编写过滤函数
    过滤函数决定了哪些日志可以进入该后端。例如,系统日志文件需要排除所有带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) {
        // 排除标签为“MOVE”的日志,不存入系统日志文件
        if (rt_strncmp(tag, MOTION_TAG, sizeof(MOTION_TAG)) == 0)
            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) {
        // 只允许标签为“MOVE”的日志存入运动日志文件
        if (rt_strncmp(tag, MOTION_TAG, sizeof(MOTION_TAG)) == 0)
            return RT_TRUE;
        else
            return RT_FALSE;
    }
  2. 在应用代码中使用特定标签输出

    // 定义运动日志输出的宏
    #define LOG_MV(...) ulog_i(MOTION_TAG, __VA_ARGS__)
    
    void motion_log_thread_entry(void *parameter) {
        float set_angle = 0, get_angle = 0, value = 0;
        int set_speed = 0, get_speed = 0;
    
        while (1) {
            set_angle += 0.1;
            get_angle -= 0.1;
            set_speed += 1;
            get_speed -= 1;
            value += 3.14;
    
            // 使用MOTION_TAG标签输出运动日志
            LOG_MV("%f %f %d %d %f",
                   set_angle, get_angle,
                   set_speed, get_speed,
                   value);
            rt_thread_mdelay(500);
        }
    }
  3. 验证结果
    现在,两个日志文件将保存不同的内容,且文件大小会根据各自接收的日志量增长。
    分类保存的日志文件

    • sys.log 包含除MOVE外的所有日志。
      系统日志内容
    • motion.log 仅包含MOVE标签的日志。
      运动日志内容
5. 动态控制文件后端

考虑到Flash等存储介质的写寿命,或需要临时删除日志文件时(文件打开状态下无法删除),需要能够动态启用/禁用文件后端。

  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: ulog_be_ctrl [sys/motion] [enable/disable]\n");
            return;
        }
    
        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 enabled\n", operator);
            }
        } else if (!rt_strcmp(operator, table[motion_id].name)) {
            // ... 类似地处理 motion 后端 ...
        } 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);
  2. 卸载后端以删除文件

    static void log_file_backend_deinit(uint8_t argc, char **argv) {
        const char *operator = argv[1];
        if (argc < 2) {
            rt_kprintf("Usage: ulog_be_deinit [sys/motion]\n");
            return;
        }
        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 deinited\n", operator);
        } else if (!rt_strcmp(operator, "sys")) {
            // ... 类似地处理 sys 后端 ...
        }
    }
    MSH_CMD_EXPORT_ALIAS(log_file_backend_deinit, ulog_be_deinit, Deinit ulog file backend);
6. 控制台输出过滤

为避免运动日志等高频信息刷屏,影响控制台使用,需要对其在控制台的显示进行过滤。

  1. 修改控制台后端(console_be.c
    在控制台后端初始化代码中,为其设置一个弱定义的过滤函数,并允许运行时设置。

    // 在 console_be.c 中增加
    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;
    }
  2. 实现控制台过滤函数
    在应用代码中实现该函数,例如过滤掉运动日志。

    // 在应用程序中实现强符号函数,覆盖弱定义
    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) {
        // 过滤掉标签为“MOVE”的日志,不显示在控制台
        if (rt_strncmp(tag, MOTION_TAG, sizeof(MOTION_TAG)) == 0)
            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: console_filter [enable/disable]\n");
            return;
        }
        const char *operator = argv[1];
        if (!rt_strcmp(operator, "enable")) {
            // 启用过滤(即不显示MOVE日志)
            ulog_backend_set_filter(&console, ulog_console_backend_filter);
            rt_kprintf("Console filter enabled.\n");
        } else if (!rt_strcmp(operator, "disable")) {
            // 禁用过滤(显示所有日志)
            ulog_backend_set_filter(&console, RT_NULL);
            rt_kprintf("Console filter disabled.\n");
        }
    }
    MSH_CMD_EXPORT_ALIAS(ulog_console_backend_filter_set, console_filter, console filter control);
    #endif

总结与扩展

本文详细介绍了在RT-Thread中利用ULOG组件原生文件后端,实现多日志文件分类存储、动态控制以及控制台显示过滤的完整方案。该方案解决了嵌入式开发中日志管理的关键痛点,提升了网络与系统调试效率。此实现源于社区实践,开发者可根据自身项目需求,调整过滤逻辑和存储策略,构建更健壮的日志系统。




上一篇:开源工具助力邮件地址有效性批量检测与数据清洗
下一篇:12月15日安全动态:Windows LNK漏洞、VSCode恶意扩展与固件逆向分析
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 16:02 , Processed in 0.124043 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表