1.前言

中大型软件项目中,日志系统是不可或缺的组成部分。尤其随着软件规模越来越大,内部结构越来越复杂,日志调试成为一种重要的调试方法。本文介绍了一个通用,高效,简洁的日志系统的设计思路。

文章分两部分,一部分是负责日志记录的CTLog类,另一部分是负责日志显示CTLogEdit类。

2.记录类CTLog

谈到通用日志系统,首先要考虑到三个问题:第一个是日志本身面向的对象要广泛,也就是日志要给谁看,都要记录什么样的信息。第二就是要考虑如何减少记录操作,将日志记录对整个系统的占用降到最低。最后,还要考虑如何管理日志,减少因为记录信息过多而造成对硬盘空间的浪费。

2.1日志分级

面对第一个问题,最好的解决方案就是提供日志分级系统。针对不同人的需求记录不同等级的信息。在这里我提供的建议是四级系统,我把它们写入一个enum中,如下所示:

enum  TLOGPRIORITY

{

TLP_DEBUG = 0,

TLP_DETAIL,

TLP_NORMAL,

TLP_ERROR

};

其中,TLP_ERROR是最高等级,代表记录软件的各种异常信息,这也是最应该被反映在你的日志当中的。

TLP_NORMAL记录的是软件中的关键信息,类似于记录开始,初始化,连接握手开始等等。这些信息也是应该被你的软件用户所知情的。

TLP_DETAIL则记录了软件后台各种更加详细的信息,比如何时开启工作线程,何时销毁数据对象,通信握手具体进行到哪一步之类。此类信息,可以帮助你很好验证软件的功能是否实现,提供debug依据。

TLP_DEBUG是最低的等级,我个人不建议没事使用这一等级。它提供了在某些特殊情况下的动态调试,可以作为安插在代码中的记录标签,用于那些静态跟踪无能为力的场合。这是调试的终极手段。

2.2记录线程

提高日志效率的本质就是尽量减少对硬盘的操作次数。为了达到这个目的,我们可以使用开辟缓冲区的方法,并且用另外一个线程定时从缓冲中将信息写入文件中。在CTLog中,负责提取数据的是线程函数TLogThread,代码如下:

UINT CTLog::TLogThread(LPVOID wParam)

{

CTLog* pLog = (CTLog*)wParam;

while(pLog->m_bRun)

{

Sleep(3000);

EnterCriticalSection(&pLog->m_csTLog);

if (pLog->m_strTLogBuffer.GetLength())

{

pLog->m_pFile->WriteString(pLog->m_strTLogBuffer);

pLog->m_pFile->Flush();

pLog->m_strTLogBuffer.Empty();

}

LeaveCriticalSection(&pLog->m_csTLog);

if (pLog->m_pFile->m_hFile != INVALID_HANDLE_VALUE)

{

if (pLog->m_pFile->GetLength() >= pLog->m_ullMaxFileSize)

{

pLog->OpenNewFile();

}

}

}

EnterCriticalSection(&pLog->m_csTLog);

if (pLog->m_pFile->m_hFile != INVALID_HANDLE_VALUE)

{

pLog->m_pFile->WriteString(pLog->m_strTLogBuffer + _T("/r/n"));

pLog->m_pFile->Flush();

}

LeaveCriticalSection(&pLog->m_csTLog);

return 0;

}

该线程每三秒钟会唤醒一次,从CTLog中的缓冲区m_strTLogBuffer中提取字符流写入硬盘,然后将缓冲清空。需要注意的是,所有这些操作都应该在临界段中完成。

2.3日志管理

前面提到的最后一个问题就是如果日志数量太多了该怎么办?很多信息都是没用的了。答案很简单,把它删掉!为了方便删除,我们可以限制每个日志文件的大小,超过一定大小就重新开启一个新文件记录,并为旧日志打上时间戳。这样,你就可以根据文件建立时间很方便的删掉不需要的记录了。

前文中日志线程TLogThread会在每次唤醒时检查文件大小,如果超过限制,就会调用OpenNewFile重新开启新记录,该函数代码如下:

void CTLog::OpenNewFile()

{

EnterCriticalSection(&m_csTLog);

m_pFile->Close();

CFileStatus fs;

CFile::GetStatus(m_strTLogFilePath + _T("//debug.log"), fs);

CFile::Rename(m_strTLogFilePath + _T("//debug.log"), m_strTLogFilePath + _T("//debug_") + fs.m_ctime.Format(_T("%Y%m%d_%H%M%S")) + _T(".log"));

m_pFile->Open(m_strTLogFilePath + _T("//debug.log"), CFile::modeCreate | CFile::modeReadWrite |CFile::typeBinary | CFile::shareDenyNone, NULL);

m_pFile->Write("/377/376", 2);

LeaveCriticalSection(&m_csTLog);

}

这个函数会在后台将当前的文件按照建立时间改名,并重新建立一个名为debug.log的文件,作为当前日志的写入文件。

2.4记录信息

记录信息和线程函数的操作正相反,只是向缓冲区中添加字符串。为了增加灵活性,我没有在记录函数中增加时间戳和自动换行符,这些可以根据你自己的需要安排。

需要注意的一点是,插入动作同样需要在临界段中完成。

BOOL CTLog::TLogLine(LPCTSTR lpTLogLine, TLOGPRIORITY tlp)

{

if (tlp < m_TLogPriority)

{

return FALSE;

}

if (_tcslen(lpTLogLine))

{

EnterCriticalSection(&m_csTLog);

m_strTLogBuffer += lpTLogLine;

LeaveCriticalSection(&m_csTLog);

return TRUE;

}

else

{

return FALSE;

}

}

3.日志显示类CTLogEdit

第二节讲述的记录类,往往我们的程序中还需要一个特定的窗体来同步显示日志记录,这样我们就需要一个经过重载的文本框来提供显示支持。在这里CTLogEdit类重载自CRichEditCtrl。

作为一个日志显示的控件,我们同样需要首先考虑三个问题:第一,该控件最好支持按照日志分级来给记录着色。第二,尽可能的减小显示开销,避免闪烁,而且最好能限制显示的行数。最后,可以支持自动滚屏,显示最新的数据。

3.1文字着色

关于文字着色,我们可以利用父类中CRichEditCtrl的setsel函数和SetSelectionCharFormat函数。在CHARFORMAT2中记录字符的颜色信息,通过setsel选定某一行字符,然后调用SetSelectionCharFormat来替换颜色值。

我在这里将着色部分封装成函数SetLogLineColor,代码如下:

void CTLogEdit::SetLogLineColor(long lPos, TLOGPRIORITY tlp)

{

SetSel(lPos, lPos);

CHARFORMAT2 cf;

memset(&cf, 0, sizeof(CHARFORMAT2));

cf.dwMask |= CFM_COLOR;

cf.dwEffects &= ~CFE_AUTOCOLOR;

switch (tlp)

{

case TLP_DEBUG:

{

cf.crTextColor = TLP_DEBUG_COLOR;

break;

}

case TLP_DETAIL:

{

cf.crTextColor = TLP_DETAIL_COLOR;

break;

}

case TLP_NORMAL:

{

cf.crTextColor = TLP_NORMAL_COLOR;

break;

}

case TLP_ERROR:

{

cf.crTextColor = TLP_ERROR_COLOR;

break;

}

}

SetSelectionCharFormat(cf);

}

3.2提高显示效率

日志显示跟记录一样同样存在效率问题,尽可能减少显示次数也是我们要做的工作。这里思路和CTLog是一致的,都是向缓冲区写入数据,然后依靠另外的线程将信息提取出来显示。所不同的是,这里缓冲区就不能单单记录字符,还要将着色信息一并保存。我的做法是将日志字符跟日志等级信息封装在一起,放入一个缓冲队列中。数据结构如下:

typedef struct _TLOGITEM

{

CString line;

TLOGPRIORITY tlp;

} TLOGITEM, *PTLOGITEM;

记录函数TLogLine会将信息存入一个双向队列m_TLogItemQue,该成员对象属于CPtrList类型。同样,这个操作也是在临界段中完成的。

void CTLogEdit::TLogLine(LPCTSTR lpTLogLine, TLOGPRIORITY tlp)

{

EnterCriticalSection(&m_csTLogEdit);

PTLOGITEM pItem = new TLOGITEM;

pItem->line = lpTLogLine;

pItem->tlp = tlp;

m_TLogItemQue.AddTail(pItem);

LeaveCriticalSection(&m_csTLogEdit);

}

剩下来所有的显示操作都在单独的线程TLogEditThread中完成,这个线程会每隔一秒种从缓冲队列中提取出记录信息,并将之着色显示出来。代码如下:

UINT CTLogEdit::TLogEditThread(LPVOID wParam)

{

CTLogEdit* pEdit = (CTLogEdit*)wParam;

PTLOGITEM pItem;

long pos;

while(pEdit->m_bRun)

{

Sleep(1000);

while(!pEdit->m_TLogItemQue.IsEmpty())

{

EnterCriticalSection(&pEdit->m_csTLogEdit);

pItem = (PTLOGITEM)pEdit->m_TLogItemQue.RemoveHead();

LeaveCriticalSection(&pEdit->m_csTLogEdit);

pos = pEdit->GetTextLength();

pEdit->SetLogLineColor(pos, pItem->tlp);

pEdit->ReplaceSel(pItem->line);

pEdit->LimitLine();

delete pItem;

if (pEdit->m_bAutoScroll)

{

pEdit->SendMessage(WM_VSCROLL, SB_BOTTOM);

}

}

}

return 0;

}

这里还有一个加快显示的技巧。由于每次插入的数据在最后,因此,我们只要用setsel选中当前对话框文本的最后,并且调用ReplaceSel替换成新的字符串就可以了。记住,不要使用SetWindowText之类的函数,那样只会使你的程序效率降低。

另外出于减小内存消耗的考虑,我们不可能无休止的让日志显示在文本框中,因此有必要对整个文本框的显示行数进行控制。这里我将之封装成函数LimitLine,代码如下:

void CTLogEdit::LimitLine()

{

if (GetLineCount() - 1 > m_nLineLimit)

{

SetSel(0, LineLength(0) + 1);

Clear();

}

}

这个函数会通过调用GetLineCount得到当前行数,如果超过限制,则选中最上面一行,并调用Clear清除掉。

3.3自动卷动

相比之下,实现这个功能是比较简单的。在前面的TLogEditThread中,通过向滚动条发送WM_VSCROLL消息,并将之定位SB_BOTTOM完成了这个效果。

4.总结

总体来说,一个高效日志系统需要我们提供分级,多线程写入和显示功能。将这两个类配和起来使用,能很好实现设计要求。

另外,为了方便使用,还可以在程序中建立一个全局或者静态函数,将CTLog和CTLogEdit的写入函数封装起来加以调用。你甚至还可以再定义若干个宏,可以达到更加方便的效果J。

转:http://blog.csdn.net/vector03/article/details/6419199

通用日志系统开发【转】相关推荐

  1. 使用 ClickHouse 构建通用日志系统

    使用 ClickHouse 构建通用日志系统 序言 ClickHouse 是一款常用于大数据分析的数据库,因为其压缩存储,高性能,丰富的函数等特性,近期有很多尝试 ClickHouse 做日志系统的案 ...

  2. zlog日志系统开发中遇到的问题(2)

    巧用zlog-chk-conf工具检查.conf文件 目录 巧用zlog-chk-conf工具检查.conf文件 当.conf文件没有错误时可能存在的其他问题 当.conf文件没有错误,但仍然显示in ...

  3. 11支付功能≠支付系统:微信支付宝通用支付系统开发实战

    支付的代码只需十行,项目的代码请查看https://github.com/TLR2019/xc-edu02 1 微信支付 1.1 下单生成二维码 二维码可以利用下图的请求参数生成(下图没有截全,详系参 ...

  4. PD003-NET通用后台系统

    PD003-NET通用后台系统 开发语言.Net 成品成品 前端技术jquery 数据库sql server .net 通用后台框架 详细信息 基于EF+MVC+Bootstrap构建通用后台管理系统 ...

  5. 游戏系统开发笔记(三)——通用代码库

    墨水比较有限,工作时基本也都是着眼小处,除了工作内容涉及过的几个模块,其余的暂时并未多作关注,所以基本上还只是停留在感性认识上.不过我倒觉得这是难免,毕竟游戏产品放到整个软件行业来说也是个较复杂的东西 ...

  6. python审批系统开发_通用审批流程快速开发系统案例分享

    公司组织机构是一个树形架构,先前新加盟公司时都是总部直接添加在某个子公司下,因审计需要,要求通过下面公司申请,逐个角色处理来完成新公司的开通,开发任务最后落到我这里,时间紧,任务重,先前也没接触多少审 ...

  7. 《嵌入式系统开发之道——菜鸟成长日志与项目经理的私房菜》——02-08项目人力资源(Human Resource)管理...

    本节书摘来异步社区<嵌入式系统开发之道--菜鸟成长日志与项目经理的私房菜>一书中的第2章,第2.8节,作者:邱毅凌,更多章节内容可以访问云栖社区"异步社区"公众号查看 ...

  8. golang日志服务器_日志系统 | log/syslog (log) – Go 中文开发手册 - Break易站

    Go 中文开发手册 日志系统 | log/syslog (log) - Go 中文开发手册 import "log/syslog"概述索引示例 概述 软件包系统日志为系统日志服务提 ...

  9. 《嵌入式系统开发之道——菜鸟成长日志与项目经理的私房菜》——第1章 系统•嵌入•硬件 01-01 Welcome on board!...

    本节书摘来自异步社区<嵌入式系统开发之道--菜鸟成长日志与项目经理的私房菜>一书中的第1章,第01-01节,作者 邱毅凌,更多章节内容可以访问云栖社区"异步社区"公众号 ...

最新文章

  1. iredmail 邮件服务器搭建
  2. linux 域名 都访问了 哪些路由,Ngnix管理域名路由
  3. c# contains方法_HTML5系列之新的API(新操作方法、文件获取、地理定位、音视频、摄像头等)
  4. c3p0连接错误 An attempt by a client to checkout a Connection has timed out.
  5. Delphi中对Jpeg格式文件的处理
  6. 《21天学通Java(第7版)》——VC程序员的学习笔记1
  7. dmg文件 linux,Linux通过命令行建立dmg文件的方法(2)
  8. 对接payjs的个人支付之微信扫码支付接口
  9. java画板_java 画板画图程序
  10. 今日的金山,而非往日的金山
  11. SCL译码matlab,CRC_polar_SC_SCL 码的SC译码算法,可设置不同信噪比, 码长 改为SCL 和CA 。核心 matlab 272万源代码下载- www.pudn.com...
  12. 服务器拒绝了您发送离线文件的请求,处理qq发送离线文件被提示“服务器拒绝了您发送离线文件”的方法...
  13. 在线引流工具Tcpcopy原理、使用、采坑
  14. UTF-8和UTF-16简介
  15. 上海市政府颁布《上海市居住证申办实施细则》
  16. dp uva1626 括号序列
  17. word-200-1
  18. OpenCV每日函数 计算摄影模块(1) 图像修复算法 inpaint函数
  19. 360极速浏览器一键操作 订单速记表selenium +requests爬取 openpyxl +xlrd 操作excel
  20. Cisco 开启三层交换机ip routing

热门文章

  1. HoloLens开发手记 - 使用混合现实捕捉 Using mixed reality capture
  2. 打通版微社区(5):部署DZ3.2
  3. ASP.NET 页面缓存 @ OutputCache
  4. Take Me To Your Heart 吻别英文版
  5. java.util.concurrent.*下的常见类你了解多少?
  6. JDK8时间与java.util.Date,java.sql.Date,Timestamp等的相互转换(时间转换)
  7. 04-12 常见接口安全问题及解决方案
  8. java垃圾收集器zgc_java虚拟机ZGC垃圾收集器的实现方法
  9. 金融数据分析与挖掘实战练习2.5-2.9
  10. 程序员怒斥:阿里如此嚣张?为所欲为?谁让你动了我的浏览器