mcu_hi3321_watch/tjd/log/log_api.c
2025-05-26 20:15:20 +08:00

670 lines
23 KiB
C

/*----------------------------------------------------------------------------
* Copyright (c) Fenda Technologies Co., Ltd. 2020. All rights reserved.
*
* Description: log_api.c
*
* Author: saimen
*
* Create: 2022-7-16
*--------------------------------------------------------------------------*/
//lib
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
//os
//sdk
#include "apollo4b.h"
//#include "am_mcu_apollo.h"
//drv
#include "sys_typedef.h"
#include "sys_config.h"
//user
#include "crc32.h"
#include "log_api.h"
#include "log_port.h"
#define ENABLE_STATIC_PRINT false
extern uint32_t am_util_stdio_printf(const char *pcFmt, ...);
#define static_print_remind(...) am_util_stdio_printf(__VA_ARGS__)
#if ENABLE_STATIC_PRINT
#define static_print_info(...) am_util_stdio_printf(__VA_ARGS__)
#else
#define static_print_info(...)
#endif
#define ENABLE_PRINT_FILE_TXT OFF
/**
TODO:Update
增加缓冲机制,该缓冲空间同时用于行为日志,错误日志及hardfault日志记录.
1.用于行为日志及错误日志是为了优化写入性能;
2.用于hardfault日志是避免在hardfault中断中动态申请空间引发其他错误(不使用栈空间是避免过多侵入式修改cmbacktrace库);
3.行为日志及错误日志同时存在两块cache,一块等待读出保存时,另一块进行写入.
4.用户调用接口时,只将数据打包放入缓冲,缓冲满时发消息给任务处理保存到文件.
5.空间分配如下图示意所示.
|----------------------|
| Log cache Block 1 |
|----------------------|
| Log cache Block 2 |
|----------------------|
*/
//可自定义
#define HEAD_ACTION 0xFA //行为日志的标记头
#define HEAD_ERROR 0xFB //错误日志的标记头
#define HEAD_FAULT 0xFB //硬件错误日志的标记头
#define LOG_BUF_TOTAL_SIZE 2048 //日志缓冲总大小
//
#define LOG_INFO_VERSION 1 //日志系统版本
#define LOG_DATA_STRUCT_LEN 12 //日志数据结构体大小.
#define LOG_BUF_BLOCK_NUMBER 2 //行为日志及错误日志需要双缓冲,把缓冲分成两个块
#define LOG_BUF_BLOCK_INVALID 0xFF //标记该缓冲块是无效的
#define LOG_BUF_BLOCK_SIZE ((LOG_BUF_TOTAL_SIZE)/ LOG_BUF_BLOCK_NUMBER) //日志单个缓冲块大小
//日志描述单条可写入最大尺寸
#define LOG_DATA_DES_MAX_LEN (LOG_BUF_BLOCK_SIZE - LOG_DATA_STRUCT_LEN)
#define LOG_FILE_PATH_MAX 64 //日志数据文件绝对路径的最大长度
//日志种类枚举
typedef enum {
ACTION_EVT_TYPE, //行为日志
ERROR_EVT_TYPE, //错误日志
FAULT_EVT_TYPE, //Fault日志
} DATA_LOG_TYPE;
#pragma pack(1) //编译时按1个字节对齐
typedef union {
uint32_t val;
struct _field {
uint32_t info3:8; //小类
uint32_t info2:8; //中类
uint32_t info1:8; //大类
uint32_t evtId:8; //事件ID
} detail;
} LogInfoField;
typedef struct {
uint8_t head; //日志头
uint8_t rev; //保留
uint16_t len; //日志内容长度
uint32_t timestamp; //日志内容:时间戳
LogInfoField field; //日志内容:详情
//char dsc[0]; //日志内容:描述(可选)
} log_data_t;
#pragma pack() //编译时恢复默认字节对齐
//日志缓冲结构体定义
typedef struct {
uint32_t init_flag:8; //标记模块是否初始化。=0 无效数据;=1 正常数据;=2 恢复数据。
uint32_t version:8; //结构体版本
uint32_t curUseBlockId:8; //正在使用的缓冲块ID
uint32_t needSaveBlockId:8; //需要保存的缓冲块ID
uint32_t blockWrtMaxSize; //缓冲块可写入的最大数量
uint32_t blockStartTime[LOG_BUF_BLOCK_NUMBER]; //缓冲块所存数据的起始时间戳
uint8_t *pBufBlock[LOG_BUF_BLOCK_NUMBER]; //指向缓冲块的指针数组
uint32_t blockBufIdx[LOG_BUF_BLOCK_NUMBER]; //缓冲块指针偏移
uint32_t crc32;
uint32_t log_buf[LOG_BUF_TOTAL_SIZE/4]; //所有日志的缓冲
} log_info_t;
static log_info_t g_log_info; //存储行为日志缓冲块相关信息
static uint32_t g_log_count = 0; //日志流水号
static char g_full_path[LOG_FILE_PATH_MAX] = {0};
//debug start------------------------------------------------------
static void DEBUGHex(const uint8_t *dat, uint16_t len)
{
uint16_t i;
uint8_t column = 0;
uint8_t length = 0;
char buf[50];
for(i = 0; i < len; i++) {
length += snprintf(&buf[length], 50 - length, "%02X ", dat[i]);
column++;
if(column >= 16) {
column = 0;
length = 0;
static_print_info("%s\r\n", buf);
} else if((i+1) >= len){
static_print_info("%s\r\n", buf);
}
}
}
/**
* @brief 打印结构化的日志数据.
* @param path:const char *|日志路径.
* @param startAddrOft:UINT32|起始地址偏移.
* @param logNumlimit:UINT16|限制读取的结构块个数,0表示不限制.
* @retval void|无.
* @note 只能打印ACTION_EVT_TYPE及ERROR_EVT_TYPE两类,fault日志不符合结构化数据.
*/
void log_print_bin_file(char *path, uint32_t startAddrOft, uint16_t logNumlimit)
{
#if 1
int err = 0;
uint32_t offset = startAddrOft;
log_data_t logDat;
char dsc[LOG_DATA_DES_MAX_LEN + 1]={0};
uint16_t cnt = 0;
uint16_t i = 0;
do
{
err = log_port_read_file(path, offset, (uint8_t*)&logDat, sizeof(log_data_t));
if(err == 0)
{
static_print_info("%s find EOF before read head info\r\n", path);
break;
}
else if(err == sizeof(log_data_t))
{
offset += err;
if(logDat.head == HEAD_ACTION || logDat.head == HEAD_ERROR)
{
if(logDat.len > LOG_DATA_STRUCT_LEN)
{
err = log_port_read_file(path, offset, (uint8_t*)dsc, (logDat.len - LOG_DATA_STRUCT_LEN));
if(err == 0)
{
static_print_info("%s find EOF before read description info\r\n", path);
break;
}
else if(err == (logDat.len - LOG_DATA_STRUCT_LEN))
{
dsc[err] = '\0';
offset += err;
}
else
{
static_print_remind("read description failed, invalid length [%d|%d]\r\n",(logDat.len - LOG_DATA_STRUCT_LEN), err);
break;
}
static_print_info("offset:0x%x len:%d TM:%d Code:%08X Dsc:%s\r\n", offset, logDat.len, logDat.timestamp, logDat.field.val, dsc);
}
else
{
static_print_info("offset:0x%x len:%d TM:%d Code:%08X Dsc:NULL\r\n", offset, logDat.len, logDat.timestamp, logDat.field.val);
}
}
else
{
static_print_info("error:offset:0x%x logDat.head=0x%x\r\n", offset, logDat.head);
break;
}
cnt++;
if(logNumlimit != 0 && cnt >= logNumlimit)
{
static_print_info("!!! read log number reach limit %d, exit read file\r\n", logNumlimit);
break;
}
}
else
{
static_print_remind("read head info failed, file:[%s] err:%d\r\n", path, err);
break;
}
} while(1);
#endif
}
//debug end------------------------------------------------------
/**
* @brief 将日志数据写入到缓冲.
* @param type:DATA_LOG_TYPE|日志种类.
* @param path:const char *|文件的绝对路径.
* @param dat:const uint8_t *|数据指针.
* @param len:UINT16|数据的长度.
* @param timestamp:UINT32|该条日志对应的时间.
* @retval int|返回写入的数据长度,负数表示错误码.
*/
static int log_write_to_buffer(DATA_LOG_TYPE type, uint32_t timestamp, uint8_t evtId, uint8_t info1, uint8_t info2, uint8_t info3, const char *dsc)
//static int log_write_to_buffer(DATA_LOG_TYPE type, const uint8_t *dat, uint16_t len, uint32_t timestamp)
{
bool switch_block = false;
uint8_t *p_cur_block;
log_info_t *pLogBuf = NULL;
log_data_t *p_log_data;
uint16_t len = 0;
uint16_t des_len = 0;
pLogBuf = &g_log_info;
if(dsc)
{
des_len = strlen(dsc);
des_len = (des_len+3)&0xfffc;//四字节对齐
}
if(des_len >=LOG_DATA_DES_MAX_LEN) {
static_print_info("log dsc info is too large, auto truncation length:(%d -> %d)\r\n", des_len, LOG_DATA_DES_MAX_LEN);
des_len = LOG_DATA_DES_MAX_LEN;
}
len = LOG_DATA_STRUCT_LEN + des_len;
if(pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] + len > pLogBuf->blockWrtMaxSize)
{
pLogBuf->needSaveBlockId = pLogBuf->curUseBlockId;
pLogBuf->curUseBlockId = ((pLogBuf->curUseBlockId + 1) % LOG_BUF_BLOCK_NUMBER);
pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] = 0;
pLogBuf->blockStartTime[pLogBuf->curUseBlockId] = timestamp;
switch_block = true;
static_print_info("og cur use buf block is full, change block, block id info[using:%d|toSave:%d]\r\n", pLogBuf->curUseBlockId, pLogBuf->needSaveBlockId);
}
else
{
if(pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] == 0)
{
static_print_info("type #%d# log cur block idx is 0, fill new buf start timestamp: %d\r\n", type, timestamp);
pLogBuf->blockStartTime[pLogBuf->curUseBlockId] = timestamp;
}
}
static_print_info("g_log_count:%d curUseBlockId:%d blockBufIdx:%d needSaveBlockId:%d\r\n", g_log_count,pLogBuf->curUseBlockId,pLogBuf->blockBufIdx[pLogBuf->curUseBlockId],pLogBuf->needSaveBlockId);
p_cur_block = pLogBuf->pBufBlock[pLogBuf->curUseBlockId];
p_log_data = (log_data_t*)&p_cur_block[pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]];
if(type == ACTION_EVT_TYPE)
{
p_log_data->head = HEAD_ACTION;
}
else if(type == ERROR_EVT_TYPE)
{
p_log_data->head = HEAD_ERROR;
}
else if(type == FAULT_EVT_TYPE)
{
p_log_data->head = HEAD_FAULT;
}
else
{
return 0;
}
p_log_data->rev = g_log_count++;
p_log_data->field.detail.evtId = evtId;
p_log_data->field.detail.info1 = info1;
p_log_data->field.detail.info2 = info2;
p_log_data->field.detail.info3 = info3;
p_log_data->len = len-4;
p_log_data->timestamp = timestamp;
pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] += LOG_DATA_STRUCT_LEN;
if(des_len)
{
memcpy(&p_cur_block[pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]], dsc, des_len);
pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] += des_len;
}
//if(switch_block==true)
if(pLogBuf->needSaveBlockId != LOG_BUF_BLOCK_INVALID)//防止因为高优化级中断错过打点
{
#if 0
//TODO:
//!!!此处暂时直接同步保存缓冲数据到文件,因为多个锁的存在,可能存在死锁问题,后续测试若存在,需改成发送消息给任务,在任务中调用保存接口,同时避免长时间占用锁.
static_print_info("!!! ready write buf[len:%d] to file\r\n", pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId]);
saveLen = SaveDataLogBufToFile(type);
static_print_info("!!! write ret: %d\r\n", saveLen);
#else
//TODO:
//!!!因需要支持硬中断打点,故不再适合使用同步写入方式,采用信号量实现异步写入
if (__get_IPSR() == 0) {
// not interrupt
log_port_notify(LOG_EVENT_CACHE_FULL);
}
#endif
}
return 0;
}
/**
* @brief 将日志缓冲有效数据写入到文件.一般关机时使用
* @retval int|返回写入的数据长度,负数表示错误码.
*/
static int log_write_valid_cache_to_file(void)
{
uint16_t ret=0;
uint16_t len;
uint32_t timestamp = 0;
uint8_t *buf;
log_info_t *pLogBuf = NULL;
pLogBuf = &g_log_info;
static_print_info("log using block=%d data len=%d,save block=%d\r\n", pLogBuf->curUseBlockId, pLogBuf->blockBufIdx[pLogBuf->curUseBlockId],pLogBuf->needSaveBlockId);
if(pLogBuf->needSaveBlockId != LOG_BUF_BLOCK_INVALID)
{
timestamp = pLogBuf->blockStartTime[pLogBuf->needSaveBlockId];
len = pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId];
buf = pLogBuf->pBufBlock[pLogBuf->needSaveBlockId];
log_port_write_file(g_full_path, buf, len);
ret += len;
// memcpy(buf, pLogBuf->pBufBlock[pLogBuf->needSaveBlockId], len);
static_print_info("log unsaved block %d data len %d\r\n", pLogBuf->needSaveBlockId, len);
}
if(pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] > 0)
{
timestamp = pLogBuf->blockStartTime[pLogBuf->curUseBlockId];
len = pLogBuf->blockBufIdx[pLogBuf->curUseBlockId];
buf = pLogBuf->pBufBlock[pLogBuf->curUseBlockId];
log_port_write_file(g_full_path, buf, len);
ret += len;
static_print_info("log using block %d data len %d\r\n", pLogBuf->curUseBlockId, pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]);
}
pLogBuf->blockStartTime[0] = 0;
pLogBuf->blockStartTime[1] = 0;
pLogBuf->blockBufIdx[0] = 0;
pLogBuf->blockBufIdx[1] = 0;
pLogBuf->curUseBlockId = 0;
pLogBuf->needSaveBlockId = LOG_BUF_BLOCK_INVALID;
return ret;
}
/**
* @brief 获取选定日志所有未保存缓冲数据.
* @param type:DATA_LOG_TYPE|日志种类.
* @param buf:UINT8 *|用来保存缓冲数据的缓存指针.
* @param timestamp:UINT32 *|unix时间戳.
* @retval UINT32|返回数据长度,负数表示错误码.
*/
static int log_get_not_saved_cache(uint8_t *buf, uint32_t *timestamp)
{
uint32_t len = 0;
log_info_t *pLogBuf = NULL;
pLogBuf = &g_log_info;
if (__get_IPSR() == 0) {
// not interrupt
log_port_lock();
}
if(pLogBuf->needSaveBlockId != LOG_BUF_BLOCK_INVALID) {
*timestamp = pLogBuf->blockStartTime[pLogBuf->needSaveBlockId];
len = pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId];
memcpy(buf, pLogBuf->pBufBlock[pLogBuf->needSaveBlockId], len);
static_print_info("get log unsaved block %d data len %d\r\n", pLogBuf->needSaveBlockId, len);
} else {
*timestamp = pLogBuf->blockStartTime[pLogBuf->curUseBlockId];
}
if(pLogBuf->blockBufIdx[pLogBuf->curUseBlockId] > 0) {
memcpy(buf + len, pLogBuf->pBufBlock[pLogBuf->curUseBlockId], pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]);
len += pLogBuf->blockBufIdx[pLogBuf->curUseBlockId];
static_print_info("get log using block %d data len %d\r\n", pLogBuf->curUseBlockId, pLogBuf->blockBufIdx[pLogBuf->curUseBlockId]);
}
if (__get_IPSR() == 0) {
// not interrupt
log_port_unlock();
}
return (len == 0) ? 0 : len;
}
static int log_api_restore(void)
{
log_info_t *p_log_info = &g_log_info;
static_print_info("log_api_restore\n");
p_log_info->init_flag = true;
p_log_info->version = LOG_INFO_VERSION;
p_log_info->curUseBlockId = 0;
p_log_info->needSaveBlockId = LOG_BUF_BLOCK_INVALID;
p_log_info->blockWrtMaxSize = LOG_BUF_BLOCK_SIZE;
p_log_info->pBufBlock[0] = (uint8_t *)&g_log_info.log_buf[0];
p_log_info->pBufBlock[1] = (uint8_t *)&g_log_info.log_buf[LOG_BUF_BLOCK_SIZE];
p_log_info->blockStartTime[0] = 0;
p_log_info->blockStartTime[1] = 0;
p_log_info->blockBufIdx[0] = 0;
p_log_info->blockBufIdx[1] = 0;
}
//log api start-------------------------------------------------
/**
* @brief 创建日志模块嵌套互斥锁
* @retval int| 返回错误码,0=成功.
*/
int log_api_create_lock(void)
{
return log_port_create_lock();
}
/**
* @brief 初始化日志数据.
* @retval int|返回结果,0=成功,负数表示错误码.
*/
int log_api_init(void)
{
#if 1
int err = 0;
log_info_t *p_log_info = NULL;
uint32_t ui32_crc;
p_log_info = &g_log_info;
//获取日志路径
log_port_get_full_path(NULL, g_full_path);
static_print_remind("[%s],sizeof(log_info_t) = %d\r\n",g_full_path,sizeof(log_info_t));
if(sizeof(log_data_t) != LOG_DATA_STRUCT_LEN || sizeof(log_data_t) > LOG_BUF_TOTAL_SIZE)
{
err = true;
static_print_remind("error:sizeof(log_data_t)");
}
if(LOG_BUF_BLOCK_NUMBER != 2)
{
err = true;
static_print_remind("error:LOG_BUF_BLOCK_NUMBER");
}
if(err)
{
p_log_info->init_flag = false;
return RET_ERROR;
}
ui32_crc = crc32_ex(0,(uint8_t*)&g_log_info, (sizeof(g_log_info) - 4- LOG_BUF_TOTAL_SIZE));
if(ui32_crc != g_log_info.crc32 || g_log_info.version != LOG_INFO_VERSION)
{
log_api_restore();
}
//保存缓冲区数据
log_write_valid_cache_to_file();
//打几条log
// log_print_bin_file(g_full_path,0,10);
return RET_SUCCESS;
#endif
}
/**
* @brief 将选定的缓冲数据保存到对应的日志文件.
* @retval int|返回写入的数据长度,负数表示错误码.
*/
int log_api_save_at_cache_full(void)
{
int err = 0;
char file_path[LOG_FILE_PATH_MAX];
log_info_t *pLogBuf = NULL;
pLogBuf = &g_log_info;
static_print_info("log_api_save_at_cache_full()\r\n");
if(pLogBuf->needSaveBlockId == LOG_BUF_BLOCK_INVALID || pLogBuf->needSaveBlockId >= LOG_BUF_BLOCK_NUMBER ||\
pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId] > pLogBuf->blockWrtMaxSize) {
static_print_remind("save log to file failed, verify not passed\r\n");
return -1;
}
err = log_port_write_file(g_full_path, pLogBuf->pBufBlock[pLogBuf->needSaveBlockId], \
pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId]);
pLogBuf->blockBufIdx[pLogBuf->needSaveBlockId] = 0;
pLogBuf->needSaveBlockId = LOG_BUF_BLOCK_INVALID;
return err;
}
/**
* @brief 在正常关机前保存所有未保存的缓冲数据.
* @retval UINT32|返回数据长度,负数表示错误码.
*/
int log_api_save_at_power_off(void)
{
uint16_t len;
uint32_t timestamp = 0;
uint8_t *buf;
log_info_t *pLogBuf = NULL;
static_print_info("log_api_save_at_power_off()\r\n");
pLogBuf = &g_log_info;
if (__get_IPSR() == 0) {
// not interrupt
log_port_lock();
}
len = log_write_valid_cache_to_file();
if (__get_IPSR() == 0) {
// not interrupt
log_port_unlock();
}
return len;
}
/**
* @brief 记录单条行为日志.
* @param timestamp:UINT32|unix时间戳.
* @param evtId:UINT8|日志事件ID.
* @param info1:UINT8|日志事件大类.
* @param info2:UINT8|日志事件中类.
* @param info3:UINT8|日志事件小类.
* @param dsc:const char *|日志事件描述.
* @retval UINT16|返回记录的数据长度,负数表示错误码.
*/
int log_api_record_action(uint32_t timestamp, uint8_t evtId, uint8_t info1, uint8_t info2, uint8_t info3, const char *dsc)
{
int err = 0;
/*
if (__get_IPSR() != 0) {
static_print_info("!!! not support record action log in isr[%d:%d:%d:%d]\r\n", evtId, info1, info2, info3);
return -1;
} */
if(g_log_info.init_flag == false)
{
static_print_info("!!! record action log failed, not initialization\r\n");
return -1;
}
log_port_lock();
log_write_to_buffer(ACTION_EVT_TYPE, timestamp, evtId, info1, info2, info3, dsc);
log_port_unlock();
static_print_info("action log:[0x%08X]\r\n", (evtId<<24)+(info1<<16)+(info2<<8)+info3);
return err;
}
/**
* @brief 记录单条错误日志.
* @param timestamp:UINT32|unix时间戳.
* @param evtId:UINT8|日志事件ID.
* @param info1:UINT8|日志事件大类.
* @param info2:UINT8|日志事件中类.
* @param info3:UINT8|日志事件小类.
* @param dsc:const char *|日志事件描述.
* @retval UINT16|返回记录的数据长度,负数表示错误码.
*/
int log_api_record_error(uint32_t timestamp, uint8_t evtId, uint8_t info1, uint8_t info2, uint8_t info3, const char *dsc)
{
int err = 0;
/*
if (__get_IPSR() != 0) {
static_print_info("!!! not support record error log in isr\r\n");
return -1;
} */
if(g_log_info.init_flag == false) {
static_print_info("!!! record error log failed, not initialization\r\n");
return -1;
}
log_port_lock();
log_write_to_buffer(ERROR_EVT_TYPE, timestamp, evtId, info1, info2, info3, dsc);
log_port_unlock();
static_print_info("error log:[0x%08X]\r\n", (evtId<<24)+(info1<<16)+(info2<<8)+info3);
return err;
}
/**
* @brief 记录单条行为日志.
* @param timestamp:UINT32|unix时间戳.
* @param evtId:UINT8|日志事件ID.
* @param info1:UINT8|日志事件大类.
* @param info2:UINT8|日志事件中类.
* @param info3:UINT8|日志事件小类.
* @param dsc:const char *|日志事件描述.
* @retval UINT16|返回记录的数据长度,负数表示错误码.
*/
int log_api_record_hardfault(uint32_t timestamp, uint8_t evtId, uint8_t info1, uint8_t info2, uint8_t info3, const char *dsc)
{
int err = 0;
/*
if (__get_IPSR() != 0) {
static_print_info("!!! not support record action log in isr[%d:%d:%d:%d]\r\n", evtId, info1, info2, info3);
return -1;
} */
if(g_log_info.init_flag == false) {
static_print_info("!!! record action log failed, not initialization\r\n");
return -1;
}
log_port_lock();
log_write_to_buffer(FAULT_EVT_TYPE, timestamp, evtId, info1, info2, info3, dsc);
log_port_unlock();
static_print_info("action log:[0x%08X]\r\n", (evtId<<24)+(info1<<16)+(info2<<8)+info3);
return err;
}
//外部存储使用
void* log_api_get_info(uint32_t *len, char **path)
{
g_log_info.crc32 = crc32_ex(0,(uint8_t*)&g_log_info, (sizeof(g_log_info) - 4- LOG_BUF_TOTAL_SIZE));
*len = sizeof(g_log_info);
*path = (char*)g_full_path;
return &g_log_info;
}
void log_api_set_save_flag(uint8_t value)
{
if(value == false)
{
log_api_restore();
}
}
uint8_t log_api_get_save_flag(void)
{
if(g_log_info.needSaveBlockId == LOG_BUF_BLOCK_INVALID && g_log_info.blockBufIdx[g_log_info.curUseBlockId]==0)
{
return false;
}
else
{
return true;
}
}
//log api end-------------------------------------------------