在嵌入式設備應用場景中,系統日志時??梢员O控設備軟件的運行狀態,及時記錄問題點以及關鍵信息,方便開發人員后期定位以及解決問題。本文將講述一種簡易的系統日志記錄方法,用于保存設備的系統日志,視具體嵌入式設備情況而定,可存儲在MCU內部Flash、外部Flash、EEPROM等,本文采用外部Flash作為示例展開介紹。
思路分析 對于系統日志可以當成文件系統,可以劃分為三個重要部分:目錄區、參數區、日志區。目錄區:根據日期進行歸類,記錄當天的日志的存儲地址、日志索引、日志大小,通過目錄可以獲取整個日志文件的概況;參數區:存儲記錄日志寫位置、目錄項個數、寫狀態等參數;日志區:這是我們主要的存儲區,記錄系統的日志,支持環寫。這三個區域都需要占用部分內存,可以自行分配大小。
實現的效果如下圖所示,設置通過指令可查詢到整個日志目錄區的概況。
查詢系統日志目錄:AT+CATALOG? LOG_ID: 存儲日志按日期分類,該ID用于查詢對應日期日志,從1開始計數; LOG_DATE: 系統日志存儲日期; LOG_ADDR: 系統日志存儲外部FLASH地址; LOG_OFFSET: 系統日志存儲偏移量(各日期日志大小,單位:字節)。
查詢指定日期系統日志:AT+CATALOG=
LOG_ID:在查詢系統日志目錄時獲取,當LOG_ID為0時,為查詢整個系統日志。
另外提供移除系統日志(清除日志目錄)指令:AT+RMLOG,后面將講述具體實現。
FLASH內存劃分 FLASH內存需要看具體設備進行合理劃分,目錄區、參數區與日志區實現環形存儲,延長擦寫壽命。
#defineFLASH_SECTOR_SIZE((uint32_t)0x001000) #defineFLASH_BLOCK_32K_SIZE((uint32_t)0x008000) #defineFLASH_BLOCK_64K_SIZE((uint32_t)0x010000) #defineSECTOR_MASK(FLASH_SECTOR_SIZE-1)/*扇區掩碼------*/ #defineSECTOR_BASE(addr)(addr&(~SECTOR_MASK))/*扇區的基地址--*/ #defineSECTOR_OFFSET(addr)(addr&SECTOR_MASK)/*扇區內的偏移--*/ #defineBLOCK_32K_BASE(addr)(addr&(~(FLASH_BLOCK_32K_SIZE))) #defineBLOCK_64K_BASE(addr)(addr&(~(FLASH_BLOCK_64K_SIZE))) typedefenum{ FLASH_BLOCK_4K=0,/**
Flash底層實現擦除、讀寫操作接口,由讀者自行實現。
flash_table_t*get_flash_table(flash_zone_ezone) { inti=0; for(i=0;istart_address||address>flash_table_tmp->end_address) return-1; returnbsp_spi_flash_erase(address,block_type); } intflash_write(flash_zone_ezone,uint32_taddress,constuint8_t*data,uint32_tlength) { flash_table_t*flash_table_tmp=get_flash_table(zone); if(flash_table_tmp==NULL) return-1; if((addressstart_address)||((address+length)>flash_table_tmp->end_address)) return-1; returnbsp_spi_flash_buffer_write(address,(uint8_t*)data,length); } intflash_read(flash_zone_ezone,uint32_taddress,uint8_t*buffer,uint32_tlength) { flash_table_t*flash_table_tmp=get_flash_table(zone); if(flash_table_tmp==NULL) return-1; if((addressstart_address)||((address+length)>flash_table_tmp->end_address)) return-1; bsp_spi_flash_buffer_read(buffer,address,length); return0; }
參數與結構體定義 日志數據存儲時間戳,便于問題定位,需要實現RTC接口調用。
typedefstruct{ uint16_tYear;/*年份:YYYY*/ uint8_tMonth;/*月份:MM*/ uint8_tDay;/*日:DD*/ uint8_tHour;/*小時:HH*/ uint8_tMinute;/*分鐘:MM*/ uint8_tSecond;/*秒:SS*/ }time_t; intbsp_rtc_get_time(time_t*date);
參數區應當保證數據的正確性,應加入參數校驗存儲,定義校驗結構體。
#defineSYSTEM_LOG_MAGIC_PARAM0x87654321/*日志參數標識符*/ typedefstruct{ uint32_tmagic;/*參數標識符*/ uint16_tcrc;/*校驗值*/ uint16_tlen;/*參數長度*/ }single_sav_t;
參數區需記錄當前日志記錄的寫位置,以及目錄項個數,還有日志區和目錄區環寫狀態,并且存儲最新時間等等。
/*日志區參數*/ typedefstruct{ uint32_twrite_pos;/*寫位置*/ uint32_tcatalog_num;/*目錄項個數*/ uint8_tlog_cyclic_status;/*系統日志環形寫狀態*/ uint8_tcatalog_cyclic_status;/*日志目錄環形寫狀態*/ time_tlog_latest_time;/*存儲最新時間*/ }system_log_t; /*目錄區參數*/ typedefstruct{ uint32_tlog_id;/*日志索引*/ uint32_tlog_addr;/*日志地址*/ uint32_tlog_offset;/*日志偏移大小,單位:字節*/ time_tlog_time;/*日志存儲時間*/ }system_catalog_t; /*系統日志參數*/ typedefstruct{ single_sav_tcrc_val; system_log_tsystem_log; system_catalog_tsystem_catalog; }sys_log_param_t; typedefstruct{ uint8_tsystem_log_print_enable;/*系統日志打印使能*/ uint16_tsystem_log_print_id;/*打印指定id系統日志*/ uint32_tsystem_log_param_addr;/*當前日志寫地址*/ }sys_ram_t; sys_ram_tSysRam; sys_log_param_tSysLogParam; sys_ram_t*gp_sys_ram=&SysRam; sys_log_param_t*gp_sys_log=&SysLogParam;
實現接口說明 CRC校驗接口,可以自定義實現。
/*16位CRC校驗高位表*/ staticconstuint8_tauchCRCHi[]={ 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40,0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41, 0x00,0xc1,0x81,0x40,0x01,0xc0,0x80,0x41,0x01,0xc0,0x80,0x41,0x00,0xc1,0x81,0x40 }; /*16位CRC校驗低位表*/ staticconstuint8_tauchCRCLo[]={ 0x00,0xc0,0xc1,0x01,0xc3,0x03,0x02,0xc2,0xc6,0x06,0x07,0xc7,0x05,0xc5,0xc4,0x04, 0xcc,0x0c,0x0d,0xcd,0x0f,0xcf,0xce,0x0e,0x0a,0xca,0xcb,0x0b,0xc9,0x09,0x08,0xc8, 0xd8,0x18,0x19,0xd9,0x1b,0xdb,0xda,0x1a,0x1e,0xde,0xdf,0x1f,0xdd,0x1d,0x1c,0xdc, 0x14,0xd4,0xd5,0x15,0xd7,0x17,0x16,0xd6,0xd2,0x12,0x13,0xd3,0x11,0xd1,0xd0,0x10, 0xf0,0x30,0x31,0xf1,0x33,0xf3,0xf2,0x32,0x36,0xf6,0xf7,0x37,0xf5,0x35,0x34,0xf4, 0x3c,0xfc,0xfd,0x3d,0xff,0x3f,0x3e,0xfe,0xfa,0x3a,0x3b,0xfb,0x39,0xf9,0xf8,0x38, 0x28,0xe8,0xe9,0x29,0xeb,0x2b,0x2a,0xea,0xee,0x2e,0x2f,0xef,0x2d,0xed,0xec,0x2c, 0xe4,0x24,0x25,0xe5,0x27,0xe7,0xe6,0x26,0x22,0xe2,0xe3,0x23,0xe1,0x21,0x20,0xe0, 0xa0,0x60,0x61,0xa1,0x63,0xa3,0xa2,0x62,0x66,0xa6,0xa7,0x67,0xa5,0x65,0x64,0xa4, 0x6c,0xac,0xad,0x6d,0xaf,0x6f,0x6e,0xae,0xaa,0x6a,0x6b,0xab,0x69,0xa9,0xa8,0x68, 0x78,0xb8,0xb9,0x79,0xbb,0x7b,0x7a,0xba,0xbe,0x7e,0x7f,0xbf,0x7d,0xbd,0xbc,0x7c, 0xb4,0x74,0x75,0xb5,0x77,0xb7,0xb6,0x76,0x72,0xb2,0xb3,0x73,0xb1,0x71,0x70,0xb0, 0x50,0x90,0x91,0x51,0x93,0x53,0x52,0x92,0x96,0x56,0x57,0x97,0x55,0x95,0x94,0x54, 0x9c,0x5c,0x5d,0x9d,0x5f,0x9f,0x9e,0x5e,0x5a,0x9a,0x9b,0x5b,0x99,0x59,0x58,0x98, 0x88,0x48,0x49,0x89,0x4b,0x8b,0x8a,0x4a,0x4e,0x8e,0x8f,0x4f,0x8d,0x4d,0x4c,0x8c, 0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,0x43,0x83,0x41,0x81,0x80,0x40 }; /*實現crc功能函數*/ staticuint16_tCRC16(uint8_t*puchMsg,uint16_tusDataLen) { uint8_tuchCRCHi=0xff; uint8_tuchCRCLo=0xff; uint16_tuIndex; while(usDataLen--){ uIndex=uchCRCHi^*(puchMsg++); uchCRCHi=uchCRCLo^auchCRCHi[uIndex]; uchCRCLo=auchCRCLo[uIndex]; } returnuchCRCHi<<8|uchCRCLo; }
保存系統日志參數,每實現寫日志操作后都需要保存當前的參數值,防止意外丟失。
voidsave_system_log_param(void) { uint32_ti=0; uint32_taddr=0; uint32_tremainbyte=0; uint32_tstart_addr; intlen=sizeof(sys_log_param_t); uint8_t*pdata=(uint8_t*)&SysLogParam; flash_table_t*flash_tmp=get_flash_table(FLASH_SYSLOG_PARA_ZONE); /*校驗參數*/ gp_sys_log->crc_val.magic=SYSTEM_LOG_MAGIC_PARAM; gp_sys_log->crc_val.len=sizeof(sys_log_param_t)-sizeof(single_sav_t); gp_sys_log->crc_val.crc=CRC16(&pdata[sizeof(single_sav_t)],gp_sys_log->crc_val.len); start_addr=gp_sys_ram->system_log_param_addr; /*剩余內存不夠寫,則重新從起始地址開始寫,實現環形存儲功能*/ if((start_addr+len)>flash_tmp->end_address){ start_addr=flash_tmp->start_address; } gp_sys_ram->system_log_param_addr=start_addr+len; /*首地址存儲,擦除整個系統日志參數存儲區,如果劃分的內存較大,可能出現第一次擦寫等待時間較長, 但實際應用嵌入式設備應該不會占用太多的內存存儲系統日志,只當為輔助使用,有額外應用可自行實現*/ if(flash_tmp->start_address==start_addr){ /*for(i=flash_tmp->start_address;iend_address;i+=FLASH_SECTOR_SIZE) flash_erase(FLASH_SYSLOG_PARA_ZONE,SECTOR_BASE(i),FLASH_BLOCK_4K); */ addr=flash_tmp->start_address; do{ if((addr+FLASH_BLOCK_64K_SIZE)<=?flash_tmp->end_address){ flash_erase(FLASH_SYSLOG_PARA_ZONE,BLOCK_64K_BASE(i),FLASH_BLOCK_64K); addr+=FLASH_BLOCK_64K_SIZE; }elseif((addr+FLASH_BLOCK_32K_SIZE)<=?flash_tmp->end_address){ flash_erase(FLASH_SYSLOG_PARA_ZONE,BLOCK_32K_BASE(i),FLASH_BLOCK_32K); addr+=FLASH_BLOCK_32K_SIZE; }elseif((addr+FLASH_SECTOR_SIZE)<=?flash_tmp->end_address){ flash_erase(FLASH_SYSLOG_PARA_ZONE,SECTOR_BASE(i),FLASH_BLOCK_4K); addr+=FLASH_SECTOR_SIZE; }else{ break; } }while(addrend_address); } remainbyte=FLASH_SECTOR_SIZE-(start_addr%FLASH_SECTOR_SIZE); if(remainbyte>len){ remainbyte=len; } while(1){ flash_write(FLASH_SYSLOG_PARA_ZONE,start_addr,pdata,remainbyte); if(remainbyte==len){ break; }else{ pdata+=remainbyte; start_addr+=remainbyte; len-=remainbyte; remainbyte=(len>FLASH_SECTOR_SIZE)?FLASH_SECTOR_SIZE:len; } } }
導入系統日志默認參數接口,初始化默認參數或者移除日志。
voidload_system_log_default_param(void) { /*系統日志默認參數*/ /*目錄環寫狀態標志*/ gp_sys_log->system_log.catalog_cyclic_status=0x00; /*目錄項個數*/ gp_sys_log->system_log.catalog_num=0; /*日志環寫標志,1:環寫狀態*/ gp_sys_log->system_log.log_cyclic_status=0; /*設置默認值,實際會重新從RTC獲取最新時間*/ gp_sys_log->system_log.log_latest_time.Year=2019; gp_sys_log->system_log.log_latest_time.Month=5; gp_sys_log->system_log.log_latest_time.Day=8; gp_sys_log->system_log.log_latest_time.Hour=13; gp_sys_log->system_log.log_latest_time.Minute=14; gp_sys_log->system_log.log_latest_time.Second=10; /*日志寫位置從0開始*/ gp_sys_log->system_log.write_pos=0; gp_sys_log->system_catalog.log_addr=0; gp_sys_log->system_catalog.log_id=0; gp_sys_log->system_catalog.log_offset=0; gp_sys_log->system_catalog.log_time.Year=2019; gp_sys_log->system_catalog.log_time.Month=5; gp_sys_log->system_catalog.log_time.Day=8; gp_sys_log->system_catalog.log_time.Hour=12; gp_sys_log->system_catalog.log_time.Minute=12; gp_sys_log->system_catalog.log_time.Second=14; gp_sys_log->crc_val.magic=SYSTEM_LOG_MAGIC_PARAM; /*導入默認參數后進行保存*/ save_system_log_param(); }
設備開機或者復位都會進行導入系統日志參數操作,恢復日志讀寫參數,參數區為頻繁讀寫操作區域,每一次寫操作都會進行一次偏移,有效的導入參數方法是從參數區結束地址到起始地址進行掃描,掃描不到合法的參數則會導入默認日志參數。
/*參數初始化,在終端啟動時調用*/ intload_system_log_param(void) { uint32_ti=0; single_sav_tpsav; uint32_tend_addr; uint32_tinteral=sizeof(sys_log_param_t); intdata_len=sizeof(sys_log_param_t)-sizeof(single_sav_t); uint8_t*pram=(uint8_t*)&SysLogParam; flash_table_t*flash_tmp=get_flash_table(FLASH_SYSLOG_PARA_ZONE); end_addr=flash_tmp->end_address-(flash_tmp->end_address-flash_tmp->start_address)%interal; for(i=end_addr-interal;i>flash_tmp->start_address;i-=interal){ flash_read(FLASH_SYSLOG_PARA_ZONE,i,(uint8_t*)&psav,sizeof(single_sav_t)); if((psav.magic==SYSTEM_LOG_MAGIC_PARAM)&&(psav.len==data_len)){ flash_read(FLASH_SYSLOG_PARA_ZONE,i+sizeof(single_sav_t),&pram[sizeof(single_sav_t)],data_len); if(psav.crc!=CRC16(&pram[sizeof(single_sav_t)],data_len)) continue; gp_sys_ram->system_log_param_addr=i; log_info("LoadSystemLogParamAddr[0x%08x]!",gp_sys_ram->system_log_param_addr); return0; } } /*掃描不到合法的參數,導入默認系統日志參數*/ load_system_log_default_param(); /*獲取日志寫地址*/ gp_sys_ram->system_log_param_addr=flash_tmp->start_address; log_info("LoadSystemLogParamAddr(Default)[0x%08x]!",gp_sys_ram->system_log_param_addr); return1; }
讀寫系統日志目錄接口,讀寫指定日志索引目錄信息。實際實現會定義最新的目錄信息存儲在日志參數區,當日期發生改變,則表示當前目錄信息已經完結,將最新的目錄信息錄入日志目錄區保存,最多每天寫入一次目錄區。
/*讀取日志目錄區指定日志索引目錄信息*/ intsystem_catalog_read(system_catalog_t*catalog,uint32_tid) { uint32_taddr; intrlen=sizeof(system_catalog_t); uint8_t*pbuf=(uint8_t*)catalog; flash_table_t*flash_tmp=get_flash_table(FLASH_CATALOG_ZONE); if(0==id) return-1; addr=flash_tmp->start_address+(rlen*(id-1)); if(addr>flash_tmp->end_address) return-1; returnflash_read(FLASH_CATALOG_ZONE,addr,pbuf,rlen); } /*寫日志目錄區目錄信息*/ intsystem_catalog_write(system_catalog_t*catalog,uint32_tid) { uint32_tstart_offset; uint32_tstart_addr; uint32_tstart_base; uint32_tremainbyte; intwlen=sizeof(system_catalog_t); uint8_t*pdata=(uint8_t*)catalog; flash_table_t*flash_tmp=get_flash_table(FLASH_CATALOG_ZONE); if(0==id)return-1; start_addr=flash_tmp->start_address+wlen*(id-1); if((start_addr+wlen)>flash_tmp->end_address){ start_addr=flash_tmp->start_address; } /*本扇區剩余空間大小*/ remainbyte=FLASH_SECTOR_SIZE-(start_addr%FLASH_SECTOR_SIZE); /*寫入數據長度小于本扇區剩余長度,直接寫入*/ if(remainbyte>wlen){ remainbyte=wlen; } /*寫目錄次數不會太頻繁,視具體情況改寫操作實現*/ while(1){ start_base=SECTOR_BASE(start_addr); start_offset=SECTOR_OFFSET(start_addr); flash_read(FLASH_CATALOG_ZONE,start_base,sector_buf,FLASH_SECTOR_SIZE); flash_erase(FLASH_CATALOG_ZONE,start_base,FLASH_BLOCK_4K); memcpy((char*)§or_buf[start_offset],pdata,remainbyte); flash_write(FLASH_CATALOG_ZONE,start_base,sector_buf,FLASH_SECTOR_SIZE); if(remainbyte==wlen){ break; }else{ pdata+=remainbyte; start_addr+=remainbyte; wlen-=remainbyte; remainbyte=(wlen>FLASH_SECTOR_SIZE)?FLASH_SECTOR_SIZE:wlen; } } return0; }
打印系統日志目錄區信息,可實現通過指令查詢到目錄區信息。
intsystem_catalog_all_print(void) { inti=0; system_catalog_tcatalog; printf("SystemLogCommandInformation: "); printf("QuerySpecifiesLog:AT+CATALOG="); printf("QueryAllLog:AT+CATALOG=<0> "); printf("QueryAllSystemCatalog: "); printf("LOG_IDLOG_DATELOG_ADDRLOG_OFFSET "); for(i=0;isystem_log.catalog_num;i++){ /*當前最新目錄信息*/ if(i==(gp_sys_log->system_catalog.log_id-1)){ catalog=gp_sys_log->system_catalog;/*獲取當前最新目錄信息*/ }else{ system_catalog_read(&catalog,i+1); } printf("%d%04d-%02d-%02d0x%08X%d ", catalog.log_id,catalog.log_time.Year,catalog.log_time.Month,catalog.log_time.Day, catalog.log_addr,catalog.log_offset); memset((char*)&catalog,0,sizeof(system_catalog_t)); } return0; }
讀取指定日志目錄索引信息接口,可指定日志索引或者讀取全部日志數據。
intsystem_log_task(intargc) { intrlen=0; uint32_toffset,start_addr,end_addr; system_catalog_tcatalog; flash_table_t*flash_tmp=get_flash_table(FLASH_SYSLOG_ZONE); if(0==gp_sys_ram->system_log_print_enable) return1; gp_sys_ram->system_log_print_enable=0x00; if(gp_sys_ram->system_log_print_id==ALL_LOG_PRINT){ /*log回環寫標志,打印整個LOG存儲區*/ if(0x01==gp_sys_log->system_log.log_cyclic_status){ start_addr=flash_tmp->start_address; end_addr=flash_tmp->end_address; offset=end_addr-start_addr; }else{ start_addr=flash_tmp->start_address; end_addr=start_addr+gp_sys_log->system_log.write_pos; offset=gp_sys_log->system_log.write_pos; } }else{/*讀取指定ID日志*/ if(gp_sys_ram->system_log_print_id==gp_sys_log->system_catalog.log_id){ catalog=gp_sys_log->system_catalog; }else{ system_catalog_read(&catalog,gp_sys_ram->system_log_print_id); } start_addr=catalog.log_addr; offset=catalog.log_offset; } if(0==offset) return1; while(1){ rlen=(offset>512)?512:offset; system_log_read(sector_buf,start_addr,rlen); HAL_Delay(80); /*目錄信息通過調式串口打印*/ bsp_debug_send(sector_buf,rlen); start_addr+=rlen; offset-=rlen; if(0==offset) break; } return0; }
存儲系統日志接口,實現更新存儲日期,當寫位置為扇區地址,則擦除一個扇區作為存儲日志,這樣避免每寫一次就擦除一次。
intsystem_log_write(uint8_t*wbuf,intwlen) { uint32_tstart_addr; uint8_t*pdata=wbuf; uint32_tremainbyte; intsystem_catalog_max_id; flash_table_t*flash_tmp=get_flash_table(FLASH_SYSLOG_ZONE); /*計算目錄區的最大存儲目錄項個數*/ system_catalog_max_id=((flash_tmp->end_address-flash_tmp->start_address)/sizeof(system_catalog_t)); start_addr=flash_tmp->start_address+gp_sys_log->system_log.write_pos; /*存儲數據地址大于規劃內存地址范圍處理*/ if((start_addr+wlen)>flash_tmp->end_address){ start_addr=flash_tmp->start_address; /*寫位置偏移量重置*/ gp_sys_log->system_log.write_pos=0; /*LOG回環存儲標志置位*/ gp_sys_log->system_log.log_cyclic_status=0x01; } /*寫位置偏移*/ gp_sys_log->system_log.write_pos+=wlen; if((gp_sys_log->system_log.log_latest_time.Year!=gp_sys_log->system_catalog.log_time.Year)|| (gp_sys_log->system_log.log_latest_time.Month!=gp_sys_log->system_catalog.log_time.Month)|| (gp_sys_log->system_log.log_latest_time.Day!=gp_sys_log->system_catalog.log_time.Day)){ /*日期改變,記錄目錄信息,當log_id為0,則不寫入*/ system_catalog_write(&gp_sys_log->system_catalog,gp_sys_log->system_catalog.log_id); /*記錄存儲日期*/ gp_sys_log->system_catalog.log_time=gp_sys_log->system_log.log_latest_time; if((gp_sys_log->system_catalog.log_id+1)>=system_catalog_max_id){ gp_sys_log->system_log.catalog_num=system_catalog_max_id;/*目錄循環寫,目錄數應為最大*/ gp_sys_log->system_log.catalog_cyclic_status=1;/*目錄回環寫標志*/ }else{ if(0==gp_sys_log->system_log.catalog_cyclic_status){ /*獲取目錄數*/ gp_sys_log->system_log.catalog_num=gp_sys_log->system_catalog.log_id+1; } } /*存儲最新目錄項信息*/ gp_sys_log->system_catalog.log_id=(gp_sys_log->system_catalog.log_id+1)%system_catalog_max_id; gp_sys_log->system_catalog.log_addr=start_addr; gp_sys_log->system_catalog.log_offset=wlen; }else{ gp_sys_log->system_catalog.log_offset+=wlen; } /*寫位置為存儲起始地址并且不為扇區首地址*/ if((flash_tmp->start_address==start_addr)&&(SECTOR_OFFSET(flash_tmp->start_address))){ flash_read(FLASH_SYSLOG_ZONE,SECTOR_BASE(start_addr),sector_buf,FLASH_SECTOR_SIZE); flash_erase(FLASH_SYSLOG_ZONE,SECTOR_BASE(start_addr),FLASH_BLOCK_4K); /*將扇區頭部至起始地址區間的數據回寫*/ flash_write(FLASH_SYSLOG_ZONE,SECTOR_BASE(start_addr),§or_buf[0],SECTOR_OFFSET(start_addr)); } /*寫位置為扇區首地址,則擦除一個扇區的存儲區*/ if(0==SECTOR_OFFSET(start_addr)){ flash_erase(FLASH_SYSLOG_ZONE,SECTOR_BASE(start_addr),FLASH_BLOCK_4K); } /*本扇區剩余空間大小*/ remainbyte=FLASH_SECTOR_SIZE-(start_addr%FLASH_SECTOR_SIZE); /*寫入數據長度小于本扇區剩余長度,直接寫入*/ if(remainbyte>wlen){ remainbyte=wlen; } while(1){ flash_write(FLASH_SYSLOG_ZONE,start_addr,pdata,remainbyte); if(remainbyte==wlen){ break; }else{ pdata+=remainbyte; start_addr+=remainbyte; wlen-=remainbyte; remainbyte=(wlen>FLASH_SECTOR_SIZE)?FLASH_SECTOR_SIZE:wlen; /*扇區首地址則擦除整個扇區,該扇區數據不保存*/ if(0==SECTOR_OFFSET(start_addr)){ flash_erase(FLASH_SYSLOG_ZONE,SECTOR_BASE(start_addr),FLASH_BLOCK_4K); } } } /*環形存儲參數*/ save_system_log_param(); return0; }系統調試對接 為了更好記錄系統日志,將應用調試等級結合一塊,實現記錄錯誤調試信息以及需要保存的關鍵信息。定義的調試等級有:關閉調試等級、錯誤調試等級、警告調試等級、關鍵調試等級、debug調試等級,而LOG_RECORD_LEVEL將主動保存日志并輸出信息,LOG_ERROR_LEVEL會存儲對應的日志信息,但需要根據應用調試等級輸出信息。設置與讀取應用調試等級由讀者自行定義。#defineLOG_CLOSE_LEVEL0x00/*關閉調試信息*/ #defineLOG_ERROR_LEVEL0x01/*錯誤調試信息*/ #defineLOG_WARN_LEVEL0x02/*警告調試信息*/ #defineLOG_INFO_LEVEL0x03/*關鍵調試信息*/ #defineLOG_DEBUG_LEVEL0x04/*debug調試信息*/ #defineLOG_RECORD_LEVEL0x10/*保存日志并輸出信息*/ #defineLOG_PRINT_LEVEL0xff #defineSET_LOG_LEVEL(LEVEL)(gp_sys_param->system_print_level=LEVEL) #defineGET_LOG_LEVEL()(gp_sys_param->system_print_level) #definelog_debug(fmt,args...)log_format(LOG_DEBUG_LEVEL,fmt,##args) #definelog_info(fmt,args...)log_format(LOG_INFO_LEVEL,fmt,##args) #definelog_warn(fmt,args...)log_format(LOG_WARN_LEVEL,fmt,##args) #definelog_error(fmt,args...)log_format(LOG_ERROR_LEVEL,fmt,##args) #definelog_record(fmt,args...)log_format(LOG_RECORD_LEVEL,fmt,##args) #defineprintf(fmt,args...)log_format(LOG_PRINT_LEVEL,fmt,##args) typedefstruct{ intlevel; char*fmt_str; }system_print_fmt_t; system_print_fmt_tsystem_print_fmt_list[]={ {.level=LOG_ERROR_LEVEL,.fmt_str=":"}, {.level=LOG_WARN_LEVEL,.fmt_str=" :"}, {.level=LOG_INFO_LEVEL,.fmt_str=" :"}, {.level=LOG_DEBUG_LEVEL,.fmt_str=" :"}, {.level=LOG_RECORD_LEVEL,.fmt_str=" :"}, }; intlog_format(uint8_tlevel,constchar*fmt,...) { #defineTIME_PREFIX_SIZE(21) #definePRINT_MAX_SIZE(1024+TIME_PREFIX_SIZE) va_listargs; intnum=0,i=0,fmt_index=0; intfmt_str_len=0,ret=-1; intfile_str_len=0,line_str_len=0; charline_buf[20]={0}; staticcharbuf[PRINT_MAX_SIZE]; staticQueueHandle_tsem=NULL; time_ttime={0}; /*針對os系統*/ if(NULL==sem){ sem=xSemaphoreCreateCounting(1,1);/*alwaysthinkofsuccess*/ } xSemaphoreTake(sem,portMAX_DELAY); ret=-1; fmt_str_len=0; if(level!=LOG_PRINT_LEVEL){ if((GET_LOG_LEVEL()SYSTEM_PRINT_FMT_LIST_MAX){ gotoexit_end; } fmt_str_len=strlen(system_print_fmt_list[fmt_index].fmt_str); strncpy((char*)&buf[TIME_PREFIX_SIZE],system_print_fmt_list[fmt_index].fmt_str,fmt_str_len); } va_start(args,fmt); num=vsnprintf((char*)&buf[fmt_str_len+TIME_PREFIX_SIZE],PRINT_MAX_SIZE-fmt_str_len-TIME_PREFIX_SIZE-2,fmt,args); va_end(args); if(num<=?0)?{ ????????goto?exit_end; ????} ????if?(level?!=?LOG_PRINT_LEVEL)?{ ????????num?+=?fmt_str_len; ????????buf[num?+?TIME_PREFIX_SIZE]?=?' '; ????????buf[num?+?TIME_PREFIX_SIZE?+?1]?=?' '; ????????num?+=?2; ????} ????if?((GET_LOG_LEVEL()?system_log.log_latest_time=time; system_log_write((uint8_t*)buf,num+TIME_PREFIX_SIZE); } exit_end: xSemaphoreGive(sem); returnret; }
結語 本文提供的一種簡易嵌入式設備系統日志記錄方法,代碼量不多,實現簡單,針對不同的設備需要合理規劃內存使用,根據軟件運行狀態,合適加入調試信息并保存對應的日志信息,方便開發人員了解系統或軟件運行狀況,協助開發分析數據資源從而更好完善系統,提高定位以及解決問題的效果。
審核編輯:湯梓紅
-
嵌入式
+關注
關注
5141文章
19526瀏覽量
314888 -
FlaSh
+關注
關注
10文章
1663瀏覽量
150976 -
存儲
+關注
關注
13文章
4502瀏覽量
87062 -
嵌入式設備
+關注
關注
0文章
115瀏覽量
17344 -
日志
+關注
關注
0文章
142瀏覽量
10820
原文標題:嵌入式設備系統日志記錄方法
文章出處:【微信號:混說Linux,微信公眾號:混說Linux】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
嵌入式文件系統μC/FS的日志使用
嵌入式系統日志相關資料分享
如何去實現嵌入式linux設備中應用運行日志呢
嵌入式設備的網絡化方法研究
嵌入式系統死鎖檢測方法

嵌入式系統日志

嵌入式linux設備中應用運行日志的實現

評論