日志系统实现方法
V1.2
一、需求和概要实际项目中经常需要用到通过串口打印一些信息或保存日志信息到文件,从而知道程序的运行状态,这些日志信息有助于故障分析。此日志系统就是对此需求做的一个解决方法。
此日志系统实现了类似printf()的函数用法的两个函数xLogInit() 和xLog() ,在中断中需要打印信息的地方调用函数xLogInit(),在其他任何非中断的地方可以调用xLog()打印需要的信息,这两个函数分别将信息打印到数组和内存的链表中,然后在一个任务中将数组数据和内存的链表中的数据通过串口打印出来,此时随便将这两个数据拷贝到文件的缓冲区,最后在另一个任务将文件缓冲区的数据保存到文件。
二、日志系统函数接口和数据说明
主要有3个函数,分别如下:
void xLogInit(void); //日志初始化函数int xLogInt(const char *fmt, ...);//用法同Printf函数,用于中断中打印信息到数组 int xLog(const char *fmt, ...); //用法同Printf函数,用于各个任务中打印信息到动态内存。中断中禁止使用此函数,否则会出现错误。
复制代码日志系统等级函数主要由xLogInt()和xLog()函数通过宏定义实现,最终用户操作调用的也就是日志系统等级函数,例如:INFO_LOG(("SystemInit/r/n")); 尽量不要直接使用xLogInt()和xLog()函数,日志系统等级函数如下:
#define ERROR_LOG(x) if(eLogLevel & _ERROR){xLog x;}else #define WARNING_LOG(x) if(eLogLevel & _WARNING){xLog x;}else #define INFO_LOG(x) if(eLogLevel & _INFO){xLog x;}else #define TRACE_LOG(x) if(eLogLevel & _TRACE){xLog x;}else #define DEBUG_LOG(x) if(eLogLevel & _DEBUG){xLog x;}else #define ERROR_LOG_INT(x) if(eLogLevel & _ERROR){xLogInt x;}else #define WARNING_LOG_INT(x) if(eLogLevel & _WARNING){xLogInt x;}else #define INFO_LOG_INT(x) if(eLogLevel & _INFO){xLogInt x;}else #define TRACE_LOG_INT(x) if(eLogLevel & _TRACE){xLogInt x;}else #define DEBUG_LOG_INT(x) if(eLogLevel & _DEBUG){xLogInt x;}else
复制代码#define DE_SetLogLevelInt(x) eLogLevel = x#define DE_ToggleLogLevelInt(x) eLogLevel ^= x #define DE_SetLogOutInt(x) eOutDevice = x #define DE_ToggleLogOutInt(x) eOutDevice ^= x
复制代码enum LogLevelType{//日志等级枚举类型_ERROR= (unsigned int)0x0001, _WARNING= (unsigned int)0x0002, Set_WARNING= (unsigned int)0x0003, _INFO= (unsigned int)0x0004, Set_INFO= (unsigned int)0x0007, _TRACE= (unsigned int)0x0008, Set_TRACE= (unsigned int)0x000f, _DEBUG= (unsigned int)0x0010, Set_DEBUG= (unsigned int)0x001f };
复制代码enum OutDeviceType{//日志输出到哪个设备枚举类型_NoOut= (unsigned int)0x00, _ToSD= (unsigned int)0x01, _ToUart= (unsigned int)0x02, _ToFileAndSD= (unsigned int)0x03 };
复制代码struct BufferAccessType{//访问日志缓冲区二维数组的结构体类型unsigned char (* pucBuffer)[DE_BufferWide];//访问DE_BufferWide宽的二维数组指针 unsigned short uiWriteIndex; unsigned short uiReadIndex; };
复制代码struct LogNode{//日志节点类型,用于创建日志链表struct LogNode * pNext;//指向下一个节点 unsigned short usLength;//当前节点长度 unsigned char ucLevelFlag;//节点信息等级 char cStr[1];//指向节点信息,真正的日志信息存在这个地址开始的位置 };
复制代码static unsigned char ucIntLogBuffer[DE_BufferNum][DE_BufferWide] = {0};//用于存储中断里面打印的信息struct BufferAccessType stLogBufAccess;//用于访问日志缓冲区二维数组的结构体类型 enum LogLevelType eLogLevel = _INFO;//用于设定日志等级 enum OutDeviceType eOutDevice = _ToFileAndSD;//用于设定日志等级 struct LogNode *pstStartNode,*pstEndNode;//用于访问日志链表 osSemaphoreId FifoChainBinarySemHandle;//用于安全访问日志链表的信号量
复制代码void xLogInit(void);//日志初始化函数
复制代码void xLogInit(void){ eLogLevel = Set_INFO; eOutDevice = _ToFileAndSD; stLogBufAccess.pucBuffer = ucIntLogBuffer; stLogBufAccess.uiWriteIndex = 0; stLogBufAccess.uiReadIndex = 0; pstStartNode = 0; pstEndNode = 0;}
复制代码int xLogInt(const char *fmt, ...);//用法同Printf函数,用于中断中打印信息到数组
复制代码实现原理是:
1.定义一个二维数组
static unsigned char ucIntLogBuffer[DE_BufferNum][DE_BufferWide] = {0};//用于存储中断里面打印的信息
复制代码struct BufferAccessType stLogBufAccess;//用于访问日志缓冲区二维数组的结构体类型
复制代码stLogBufAccess.pucBuffer = ucIntLogBuffer;stLogBufAccess.uiWriteIndex = 0; stLogBufAccess.uiReadIndex = 0;
复制代码二维数组
stLogBufAccess.uiWriteIndex 用于指示将要把日志写到哪一行;
stLogBufAccess.uiReadIndex 用于指示将要从哪一行读取日志信息;
stLogBufAccess.pucBuffer 用于访问数组中的具体哪一个元素。
通过对变量stLogBufAccess的控制,将二维数组当成一个环形数组来访问,最后大概是下面这样的情况:
环形二维数组
具体实现代码如下:
int xLogInt(const char *fmt, ...){ unsigned int uiTmpWriteIndex; int iNum; va_list ap; va_start(ap,fmt); static char i = 0x21; i = (++i == 0x7d) ? 0x21 : i; uiTmpWriteIndex = stLogBufAccess.uiWriteIndex;//临界区需多次读取 if(((uiTmpWriteIndex + 1) & (DE_BufferNum - 1)) == stLogBufAccess.uiReadIndex){ va_end(ap); return -1;//如果缓存满了就返回 -1 }else{ stLogBufAccess.pucBuffer[uiTmpWriteIndex][1] = 0;//字符数清零 stLogBufAccess.uiWriteIndex = (uiTmpWriteIndex + 1) & (DE_BufferNum - 1);//更新临界区需加保护 stLogBufAccess.pucBuffer[uiTmpWriteIndex][0] = i;//将时间加到log缓冲区 iNum = vsnprintf((char *)&stLogBufAccess.pucBuffer[uiTmpWriteIndex][2],DE_BufferWide-3,fmt,ap); if((iNum >= 0) && (iNum < DE_BufferWide-3)){ ; }else if(iNum >= DE_BufferWide-3){ iNum = DE_BufferWide-3; }else{ return iNum;//vsnprintf()调用错误 } stLogBufAccess.pucBuffer[uiTmpWriteIndex][1] = iNum + 2;//字符数 va_end(ap); return 0; } }
复制代码数组的第一个元素stLogBufAccess.pucBuffer[uiTmpWriteIndex][0]保存的是时间标号;
数组的第二个元素stLogBufAccess.pucBuffer[uiTmpWriteIndex][1]保存的是实际日志信息长度加2;
从数组第三个元素stLogBufAccess.pucBuffer[uiTmpWriteIndex][2]开始真正保存有效日志信息。
int xLog(const char *fmt, ...);//用法同Printf函数,用于各个任务中打印信息到动态内存。中断中禁止使用此函数,否则会出现错误。
复制代码实现原理是:
1.定义一个日志链表类型,用于存储单条日志
struct LogNode{//日志节点类型,用于创建日志链表 struct LogNode * pNext;//指向下一个节点 unsigned short usLength;//当前节点长度 unsigned char ucLevelFlag;//节点信息等级 char cStr[1];//指向节点信息,真正的日志信息存在这个地址开始的位置 };
复制代码struct LogNode *pstStartNode,*pstEndNode;//用于访问日志链表osSemaphoreId FifoChainBinarySemHandle;//用于安全访问日志链表的信号量
复制代码pstStartNode = 0;pstEndNode = 0;
复制代码具体实现代码如下:
int xLog(const char *fmt, ...){ va_list ap; va_start(ap,fmt); char * p1 = 0; struct LogNode * p2; int i=0; unsigned short usFreeHeapSize; usFreeHeapSize = xPortGetFreeHeapSize(); if(((usLostNumber == 0) && (usFreeHeapSize>10240)) || (usLostNumber && (usFreeHeapSize>(10240+1024)))){ if(usLostNumber){ INFO_LOG(("%d",usLostNumber)); usLostNumber = 0; } p1 = (char *)pvPortMalloc(128); if(p1 == 0){ va_end(ap); //printf("pvPortMalloc\r\n"); return -2; } i = vsnprintf(p1,127,fmt,ap); va_end(ap); if((i >= 0) && (i < 127)){ ; }else if(i >= 127){ i = 127; }else{ return i;//vsnprintf()调用错误 } if(i>=0){ p2 = (struct LogNode*)pvPortMalloc(i+sizeof(struct LogNode)-1); if(p2 == 0){ vPortFree(p1); return -2; } p2->pNext = 0;//这3行是新节点内容填充,包含下一个节点地址、 p2->usLength = i;//此节点字符串长度、 strcpy(p2->cStr,p1);//此节点字符串 vPortFree(p1); //节点加入到链表 if((xSemaphoreTake(FifoChainBinarySemHandle,portMAX_DELAY)) == pdTRUE){ if(pstStartNode){//已有节点(非空链表) pstEndNode->pNext = p2;//最后一个节点指向新的节点 pstEndNode = p2;//更新最后一个节点 }else{//没有节点(空链表),加入新节点 pstEndNode = p2; pstStartNode = p2; } usListSize += p2->usLength+sizeof(struct LogNode)-1; xSemaphoreGive(FifoChainBinarySemHandle); } return 0; }else{ vPortFree(p1); return -1; } } else{ usLostNumber++; return -3; } }
复制代码