李国帅 2018-02-06
日志记录跟随软件的发展,慢慢的也会演进成为一个日志系统,而不仅仅是一份随时可以抛弃的记录。
最近很希望一句名言,“风起于青蘋之末,浪成于微澜之间”,也许那么不经意间的改变,我们希望的事情,就会像日志系统一样,从一页纸,变成一个不可或缺的部分。
以下内容从2010~2013年间的部分日志中检出,并结合自己的经验作出的总结。
日志的作用
通常情况下,我们编写程序都会用到日志,日志有什么用呢?下面以调试日志和运行日志说明使用日志分析原因。
运行日志:
运行的时候,可以了解到资源消耗,异常操作,用户数据等依次来进行程序优化,复杂均衡,流程改造。
具体操作就是,如果出现非常重要的问题需要使用提醒(messagebox)警告(alert)甚至直接退出程序的方式告知,但依然有迹可寻。
有迹可寻指的是:使用日志,可以看到程序的运行状态,重要和异常操作,出现问题后的处理流程,程序中断或终止的原因,最后的操作,最后的运行状态是什么。
调试日志
开发的时候出现问题,能够快速定位问题出现的确切位置,方便修正和重构。
具体操作就是,记录内存及cpu状态,异常状况,崩溃记录,重要的特定跟踪信息,按重要级别和标记记录不同的信息,需要跟踪到文件名、函数名和行数。
注意点:
下面直接讲自己现在想到的一些注意点,希望对大家有用。
日志级别
日志记录一般都是分级别的,通常还会有一个配置文件专门配置级别,我们对不同的日志级别的重视程度是不同的,这一点不用多讲。
VERBOSE(0), DEBUG(1), INFO(2), WARN(3), ERROR(4), ASSERT(5), OFF(15);
有时候,我们针对不同的程序模块也会使用同步的日志配置,但通常在程序较大的时候使用。
灵活设置输出方式
通常我们可以把日志输出到日志,屏幕/控制台,第三方软件,或是远程网络,不同的方式各有优劣,根据我们的需要和情况选一种即可,没有必要同时实现。
输出格式
选择什么样的输出格式在于希望使用日志做什么?
l 如果你希望仅仅看看,那么记录一句话也行。
l 如果想跟踪,那么就需要更加详细的信息,选择添加时间,标记,类型,级别,用户,操作对象,进度,状态,消息,文件,函数,行数等等内容。
l 如果你希望进行日志的自动化统计分析,那么就需要日志具有严格的格式,甚至要求日志各个字段的长度固定,以便这些日志能够方便的导入对应的数据库。
如果针对开始达到自动统计分析的程度,那日志就真的可以成为日志系统了。
日志种类
对于一个程序,不同的人可能对不同的日志感兴趣,那么日志就可能不止一套。
对于开发者来说,更对程序细节感兴趣,比如(逻辑错误,数据错误,崩溃信息,资源占用,特定信息),就会出用于调试的调试日志。
对于测试,维护,二次开发者来说,他们更感兴趣的是稳定性,安全性,客户情况,希望能得到数据流程,用户行为,资源占用,运行状态等信息,他们需要的是运行日志。
不过记录什么东西完全要看项目和工作的实际需要,因为说到底日志的地位在小项目中是没品的,没有人在乎,只能在需要的时候才会拿出来用用。
同步问题
对于一般的程序,我们不需要考虑同步问题,但是对于类似多媒体这样的应用可就不一样了。参考下例:
同步的日志打印影响了多媒体播放速度。在进行播放之前需要添加时间段,添加的过程使用了近5秒的时间,查询结果发现,写日志出现了阻塞。
[5728] C***Ctrl::*** nRecordId=5074,tBeginTime=2012/12/24 14:7:29 tEndTime=2012/12/24 14:7:35
....持续了5秒钟,因此如果日志处理不好可能影响到应用程序的正常使用。
[5728] C***Ctrl::*** nIndex=0 nRecordId=4917 Url=rtsp://192.168.17.232:554/data1/Rec/000/000/006/238?b=0 fRate=0.00 strSeekTime=2012/12/24 12:14:37
因此,在某些应用,日志必须是异步的,独立线程的。对于记录日志的线程比较多的情况下,日志必须排队进行。
同样,程序退出前,需要记录下所有正在排队异步日志。
日志的安全性
对日志的使用应该保证,在输出到媒介之前,输出缓冲是安全的。如果你输出的内容传入日志系统然后马上销毁了,日志将无法读取到正确的日志内容,从而导致内存访问失败。
如果无法保证,那么需要在进入日志系统之后,保留一份日志内容备份,直到处理完成为止。
在采用自定义日志的时候,如果出现内存失败,也有可能是日志引起的,这一点在实际工作中时遇到的。因为打印日志出现内存错误,可能影响到所在程序的内存堆栈,从而在其他函数出现内存访问失败。
日志过多的处理
任何的日志都会影响到程序执行的效率,所以日志也并不是日志越多越详细越好,不必要的日志就不要写进去了。
在正式发布程序版本的时候,调试日志是不建议保留的。
如果使用控制台打印日志,随着程序的运行,缓存的日志可能越来越多,内存占用越来越大,因此需要对这类日志的缓存大小进行限制。因为限制又影响到了日志的跟踪,因此控制台调试并不适合需要长期调试的情况。
对于文件日志,就不存在内存占用大量增加的情况了,但是时间久了,也是需要进行清理和处理的。
可以设定定期清理那些特定类型的,确定没有问题的,没有价值的日志。
如果日志量过大,应该及时按照时间或者大小进行循环删除。
日志的字符要求
日志打印的时候,注意输入函数对字符类型的要求。
很多输出方式提供了不同的多字节和宽字节函数名,输出的时候需要进行转换。比如OutputDebugString、printf等。
static char * STRTOCHAR(StrPtrLen *theStr)
{
temp[0] = 0;
UInt32 len = theStr->Len < 2047 ? theStr->Len : 2047;
if (theStr->Len > 0 || NULL != theStr->Ptr)
{
memcpy(temp, theStr->Ptr, len);
temp[len] = 0;
}
else
strcpy(temp, "Empty Ptr or len is 0");
return temp;
}
有些输出函数对输出栈的大小有限制,比如OutputDebugString的输入缓冲不能超过2048字节。
va_start(args, format_str);
int len = _vscprintf(format_str, args) + 2;
CHAR* pInfo = new CHAR(len);
try
{
len = vsprintf_s(pInfo, len, format_str, args);//这一行提示buffer too small。估计是temp冲突
OutputDebugStringA(pInfo);
}
catch (...)
{
OutputDebugStringA("log happen a error.");
}
delete[] pInfo;
va_end(args);