嵌入式设备系统日志记录及调试技巧:提升开发效率的实用指南

2025-01-01 0 619

嵌入式设备的状态监控与分析怎样才能做到精确?系统日志扮演着至关重要的角色。但在嵌入式设备的开发过程中,与Web后台丰富的框架支持相比,日志的实现方式较为简陋,这让开发者们感到颇为困扰。

系统日志的价值所在

2020-05-31 00:00:36.063 DEBUG [cloud-data-communication-service,a4d26cd44853a39b,a4d26cd44853a39b,false] 5888 --- [http-nio-15050-exec-5] c.s.s.c.c.a.interceptor.UserInterceptor  : //TODO 校验token:null
2020-05-31 00:00:36.064 DEBUG [cloud-data-communication-service,a4d26cd44853a39b,a4d26cd44853a39b,false] 5888 --- [http-nio-15050-exec-5] c.s.s.c.c.core.util.UserContextHolder    : 设置上下文信息:{}
2020-05-31 00:00:36.192 DEBUG [cloud-data-communication-service,a4d26cd44853a39b,a4d26cd44853a39b,false] 5888 --- [http-nio-15050-exec-5] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found

系统日志对于设备运行至关重要。无论是大型服务器还是普通嵌入式设备,都可能遭遇故障或问题。例如,2019年某大型网络公司的服务器故障,就是通过系统日志找到的,原因竟是某个程序模块的代码漏洞。对于智能家居这类嵌入式设备,系统日志能记录软件运行的每一刻,便于开发者随时了解设备状态。同时,它能迅速捕捉问题发生的瞬间和关键信息,就像为设备健康状况做了详尽的记录。

系统日志让开发者能迅速锁定设备故障原因并加以解决。这在项目开发中极为关键,有助于提高开发速度和确保设备稳定,否则开发者需投入大量时间精力逐一排查。

web与嵌入式在日志框架上的区别

在web后台开发领域,开发者可以选用众多成熟且便捷的日志框架。这些框架显著提高了开发速度和日志记录的便捷性。开发者只需依照框架的指南进行配置,调用相应的方法,就能轻松实现系统日志的全面功能。在此过程中,开发者无需关心日志输出的底层架构等复杂细节。


/********** 禁用半主机模式 **********/
#pragma import(__use_no_semihosting)
 
struct __FILE
{
	int a;
};
 
FILE __stdout;
 
void _sys_exit(int x)
{	
}
/*****************************************************
*function:	写字符文件函数
*param1:	输出的字符
*param2:	文件指针
*return:	输出字符的ASCII码
******************************************************/
int fputc(int ch, FILE *f)
{
	while(USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);		//等待上次发送结束
	USART_SendData(USART1, (unsigned char)ch);				//发送数据到串口
	return ch;
}

嵌入式开发与此不同。嵌入式设备在硬件和软件方面有特定的需求与限制。因此,在嵌入式开发中不能直接采用web的日志框架。这类设备通常资源有限,功能较为单一,比如工业控制领域的嵌入式设备。在这种背景下,开发者必须自行构建日志系统,从基础的日志输出设计到应对各种复杂情况,比如不同存储介质下的日志存储问题,都需要开发者亲自深入研究和解决。

嵌入式常见的日志输出方式

 
#ifdef ENBALE_DEBUG
	#define DBG_ERROR(fmt,args...) printf("[%s,%s,%d] ERROR:"#fmt"rn", __FILE__,__FUNCTION__,__LINE__,##args)
	#define DBG_WARN(fmt,args...)  printf("[%s,%s,%d] WARN:"#fmt"rn", __FILE__,__FUNCTION__,__LINE__,##args)
	#define DBG_DEBUG(fmt,args...) printf("[%s,%s,%d] DEBUG:"#fmt"rn", __FILE__,__FUNCTION__,__LINE__,##args)
#elif  ENBALE_WARN
	#define DBG_ERROR(fmt,args...) printf("[%s,%s,%d] ERROR:"#fmt"rn", __FILE__,__FUNCTION__,__LINE__,##args)
	#define DBG_WARN(fmt,args...)  printf("[%s,%s,%d] WARN:"#fmt"rn", __FILE__,__FUNCTION__,__LINE__,##args)
	#define DBG_DEBUG(fmt,args...)  
#elif  ENBALE_ERROR
	#define DBG_ERROR(fmt,args...) printf("[%s,%s,%d] ERROR:"#fmt"rn", __FILE__,__FUNCTION__,__LINE__,##args)
	#define DBG_WARN(fmt,args...)  
	#define DBG_DEBUG(fmt,args...) 
#else
	#define DBG_ERROR(fmt,args...)  
	#define DBG_WARN(fmt,args...)   
	#define DBG_DEBUG(fmt,args...)   
#endif

嵌入式设备在日志输出方面有多种常见形式。首先便是串口输出,这种在嵌入式开发早期调试阶段极为便利。它对系统配置的要求不高,程序逻辑也相对简单。操作起来只需修改putc函数,并避免使用半主机模式。许多初学者甚至能通过网络找到相应的代码示例。

此外,文件处理方式也很关键。特别在设备初期开发调试完毕后,若要分析设备过往运行情况,排查潜在问题,文件处理方式更为适宜。文件能存储大量日志信息,便于随时查阅不同时期的设备状态。例如,在部分嵌入式医疗设备中,要检查数月甚至数年间的设备运行是否稳定,有无异常数据,文件日志管理就显得尤为重要。

串口打印的优化历程

起初的串口打印程序比较基础简略。程序直接展示大量日志数据,缺乏合理的分类和层次管理,使得输出信息显得杂乱无序。当需要寻找特定问题或查看特定类型的日志时,往往难以在这些纷繁的信息中迅速找到目标。

#include  
#include  
const unsigned short debugLevel __attribute__((section("ConfigSector"))) = 0; //指定到配置扇区
enum LogLevel
{
    ERROR_FILTER = 1,
    WARN_FILTER  = 2,
    DEBUG_FILTER = 3,
};
#define OUPUT_ERROR (debugLevel>= ERROR_FILTER)
#define OUPUT_WARN (debugLevel>= WARN_FILTER)
#define OUPUT_DEBUG (debugLevel>= DEBUG_FILTER)
//strrchr(__FILE__, '\')去除目录路径,只保留文件
#define DBG_ERROR(fmt,args...) error_core(strrchr(__FILE__, '\'),__FUNCTION__,__LINE__,fmt,##args)
#define DBG_WARN(fmt,args...)  warn_core(strrchr(__FILE__, '\'),__FUNCTION__,__LINE__,fmt,##args)
#define DBG_DEBUG(fmt,args...) debug_core(strrchr(__FILE__, '\'),__FUNCTION__,__LINE__,fmt,##args)
void change_debug_level(enum LogLevel level)
{
	unsigned short temp = level; //枚举sizeof会按子值优化
	FLASH_Write(&temp,&debugLevel ,1); //flash写入一个半字两字节至debuglevel的位置
}
void error_core(const char* filename,const char* func, int line,const char* fmt, ...)
{
	if(OUPUT_ERROR)
	{
		va_list valist;/*可变参数的宏*/
		printf("[%s,%s,%d] ERROR:",filename,func,line);
		va_start(valist,fmt);
		vprintf(fmt,valist);
		va_end(valist);
		printf("rn");
	}
}	
void debug_core(const char* filename,const char* func, int line,const char* fmt, ...)
{
	if(OUPUT_DEBUG)
	{
		va_list valist;/*可变参数的宏*/
		printf("[%s,%s,%d] DEBUG:",filename,func,line);
		va_start(valist,fmt);
		vprintf(fmt,valist);
		va_end(valist);
		printf("rn");
	}
}	
void warn_core(const char* filename,const char* func, int line,const char* fmt, ...)
{
	if(OUPUT_WARN)
	{
		va_list valist;
		printf("[%s,%s,%d] WARN:",filename,func,line);
		va_start(valist,fmt);
		vprintf(fmt,valist);
		va_end(valist);
		printf("rn");
	}
}	

后来,我们改进了日志的编码方法,采用了层级式的日志结构。这种方法借鉴了Linux内核的设计,对日志信息进行了细致的分级。不过,在编译过程中,必须正确设置编译选项。但这个方法也有不足之处,比如在调整调试级别时,必须重新编译并更新软件。在嵌入式设备的调试环境中,设备一旦出现异常,即便更新了固件,故障往往难以再次出现。例如,那些在野外工作的物联网传感器,想要再次找到问题点就特别困难。

进一步优化,我们可以在程序运行时调整日志的级别。这通过在闪存中设置调试级别,并使用封装的函数来记录日志来完成。不过,使用时还需留意一些细节,比如,当芯片采用哈弗架构时,全局常量通常由编译器默认分配到闪存。在使用片内闪存时,需确保存储调试级别的闪存区域与代码区不重叠,否则设备可能会出现死机现象。这在设计紧凑的嵌入式设备中尤其重要,一旦出现此类错误,后果可能非常严重。

日志文件存储需要注意的要点

Config_ROM2 0x08010800 FIXED 0x0000800  {  ;指定根区,即load address = execution address,
   .ANY (ConfigSector)
  }

日志文件在嵌入式设备中存放需遵循特定规范。串口主要用于即时信息展示,因此,它在处理长期故障检测和记录历史数据方面显得尤为重要。若使用fatfs管理文件日志,需关注代码逻辑的调整。比如,需将串口输出的日志转换成文件输出的格式。

文件管理过程中,若能融入RTC模块将更佳。RTC模块能协助对文件进行切割管理,从而让日志文件在时间序列上更有序。但需留意,文件写入过程耗时较长,实际操作时宜独立启动日志进程。该进程专司调用相应程序执行日志文件的写入,其余任务进程则通过消息队列将日志信息发送至该日志进程。这在那些对日志实时性要求不高、却对系统运行效率有较高要求的嵌入式设备中尤为适用。

嵌入式日志的综合考量

开发嵌入式设备时,日志输出的形式和存储方式都需开发者全面考量。这包括设备的使用环境,如是否在野外长时间无人看管或室内短期测试,以及硬件资源状况,如内存和存储空间大小。同时,设备的稳定性需求也会影响日志功能的最终形态。因此,在开发初期就要对日志系统进行规划,确保其在整个设备生命周期中持续有效,以便设备正常运行,并在出现问题时能迅速定位和解决。关于优化嵌入式日志,您有何高见?欢迎分享您的想法,并点赞及转发本文。

#include  
#include  
const unsigned short debugLevel __attribute__((section("ConfigSector"))) = 0; //指定到配置扇区
enum LogLevel
{
    ERROR_FILTER = 1,
    WARN_FILTER  = 2,
    DEBUG_FILTER = 3,
};
u8 outputFile[30];
#define OUPUT_ERROR (debugLevel>= ERROR_FILTER)
#define OUPUT_WARN (debugLevel>= WARN_FILTER)
#define OUPUT_DEBUG (debugLevel>= DEBUG_FILTER)
void change_debug_level(enum LogLevel level)
{
	unsigned short temp = level; //枚举sizeof会按子值优化
	FLASH_Write(&temp,&debugLevel ,1); //flash写入一个半字两字节至debuglevel的位置
}
void error_core(const char* filename,const char* func, int line,const char* fmt, va_list valist)
{
	if(OUPUT_ERROR)
	{
		FIL fsrc;
		time_t time =rtc_get_time(); //按自己的系统获取时间
		sprintf(&outputFile[0], "0:/log_d-d-d.txt",
			time.Year, time.Month, time.Day);
		if(f_open( &fsrc , outputFile,FA_READ|FA_WRITE|FA_OPEN_APPEND) == FR_OK )
		{
			f_printf(&fsrc,"[d-d-d d:d:d] [%s,%s,%d] ERROR:",time.Year, time.Month, time.Day,time.Hour, time.Minute, time.Second,filename,func,line);
			f_vprintf(&fsrc,fmt,valist);//需要自己实现
			f_printf(&fsrc,"rn");
			f_close(&fsrc);
		}
	}
}	
void debug_core(const char* filename,const char* func, int line,const char* fmt, va_list valist)
{
	if(OUPUT_DEBUG)
	{
		FIL fsrc;
		time_t time =rtc_get_time(); //按自己的系统获取时间
		sprintf(&outputFile[0], "0:/log_d-d-d.txt",
			time.Year, time.Month, time.Day);
		if(f_open( &fsrc , outputFile,FA_READ|FA_WRITE|FA_OPEN_APPEND) == FR_OK )
		{
			f_printf(&fsrc,"[d-d-d d:d:d] [%s,%s,%d] DEBUG:",	time.Year, time.Month, time.Day,time.Hour, time.Minute, time.Second,filename,func,line);
			f_vprintf(&fsrc,fmt,valist);//需要自己实现
			f_printf(&fsrc,"rn");
			f_close(&fsrc);
		}
	}
}	
void warn_core(const char* filename,const char* func, int line,const char* fmt, va_list valist)
{
	if(OUPUT_WARN)
	{
		FIL fsrc;
		time_t time =rtc_get_time(); //按自己的系统获取时间
		sprintf(&outputFile[0], "0:/log_d-d-d.txt",
			time.Year, time.Month, time.Day);
		if(f_open( &fsrc , outputFile,FA_READ|FA_WRITE|FA_OPEN_APPEND) == FR_OK )
		{
			f_printf(&fsrc,"[d-d-d d:d:d] [%s,%s,%d] WARN:",	time.Year, time.Month, time.Day,time.Hour, time.Minute, time.Second,filename,func,line);
			f_vprintf(&fsrc,fmt,valist);//需要自己实现
			f_printf(&fsrc,"rn");
			f_close(&fsrc);
		}
	}
}	

申明:本文由第三方发布,内容仅代表作者观点,与本网站无关。对本文以及其中全部或者部分内容的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。本网发布或转载文章出于传递更多信息之目的,并不意味着赞同其观点或证实其描述,也不代表本网对其真实性负责。

七爪网 行业资讯 嵌入式设备系统日志记录及调试技巧:提升开发效率的实用指南 https://www.7claw.com/2806352.html

七爪网源码交易平台

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务