`
qimo601
  • 浏览: 3449159 次
  • 性别: Icon_minigender_1
  • 来自: 苏州
社区版块
存档分类
最新评论

通用日志系统开发【转】

阅读更多

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)

{

    CTLogpLog = (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::shareDenyNoneNULL);

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

    LeaveCriticalSection(&m_csTLog);

}

 

 

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

2.4记录信息

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

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

 

BOOL CTLog::TLogLine(LPCTSTR lpTLogLineTLOGPRIORITY 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文字着色

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

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

 

void CTLogEdit::SetLogLineColor(long lPosTLOGPRIORITY tlp)

{

    SetSel(lPoslPos);

    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 lpTLogLineTLOGPRIORITY 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)

{

    CTLogEditpEdit = (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(pospItem->tlp);

           pEdit->ReplaceSel(pItem->line);

           pEdit->LimitLine();

           delete pItem;

           if (pEdit->m_bAutoScroll)

           {

              pEdit->SendMessage(WM_VSCROLLSB_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.总结

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

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

 

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

分享到:
评论

相关推荐

    真正通用的操作日志系统设计

    ### 真正通用的操作日志系统设计 #### 一、引言 操作日志系统在现代软件开发中扮演着至关重要的角色。它不仅能够帮助开发者追踪系统的操作历史,还能在出现问题时提供宝贵的信息以便进行故障排查。然而,传统的日志...

    ASP.NET通用模块及典型系统开发实例导航

    本资源包,"ASP.NET通用模块及典型系统开发实例导航",显然是针对ASP.NET开发者设计的,旨在帮助他们理解和掌握在实际项目中常用的功能模块以及如何构建典型系统。 1. ASP.NET通用模块:这些模块是可复用的组件,...

    VC++数据库通用模块及典型系统开发实例导航

    《VC++数据库通用模块及典型系统开发实例导航》是一本深入探讨如何利用VC++进行数据库应用系统开发的书籍。该书结合丰富的实例,为读者提供了详细的VC++与数据库交互的通用模块以及各种典型系统的开发指南。通过这...

    Visual c++ 数据库通用模块开发与系统移植

    在IT行业中,数据库通用模块的开发与系统移植是一项至关重要的任务,尤其是在使用Visual C++这样的高级编程语言时。Visual C++不仅提供了强大的编程环境,还支持MFC(Microsoft Foundation Classes)框架,使得...

    .NET通用开发架构

    .NET通用开发架构是一种高效、可复用且可扩展的软件开发模式,主要基于Microsoft的.NET Framework或.NET Core(现在统称为.NET)。这种架构为开发者提供了标准化的开发流程,旨在提高软件项目的质量和生产效率,减少...

    基于HBASE分布式存储的通用海量日志系统设计方法研究.pdf

    基于HBASE分布式存储的通用海量日志系统设计方法研究涉及的关键技术主要包括分布式存储系统HBASE和REDIS分布式缓存技术,以及海量日志数据的高效存储与查询处理。以下详细解析了文章中提到的关键知识点。 HBASE...

    Apache通用日志包.pdf

    良好的日志系统不仅能帮助开发者追踪应用程序的运行状态,还能够辅助后期的问题排查与性能优化等工作。Apache通用日志包作为一款成熟的日志管理工具,在诸多场景下都有着广泛的应用。本文将围绕《Apache通用日志包....

    springmvcController层通用日志配置

    Spring MVC Controller 层通用日志配置 Spring MVC 框架中,Controller 层是处理用户请求的核心组件之一。为了提高系统的可维护性和可追溯性,记录日志变得非常重要。Spring MVC 提供了多种方式来记录日志,其中一...

    通用 OA系统源代码

    本资源包含的"通用OA系统源代码"是一套完整的软件开发项目,可以帮助开发者理解OA系统的架构设计和实现细节,为自定义开发或二次开发提供基础。 1. **系统架构**: 通用OA系统通常采用B/S(Browser/Server)架构,...

    通用权限管理系统

    在实际应用中,权限管理系统可能会集成其他功能,如角色管理(创建、修改、删除角色)、权限分配界面(直观展示权限树结构,方便管理员分配权限)、审计日志(记录用户操作,便于追溯和分析权限使用情况)等。...

    .NET通用权限系统快速开发框架

    .NET通用权限系统快速开发框架是一种高效且灵活的开发工具,专为构建OA(办公自动化)、ERP(企业资源规划)和CRM(客户关系管理)等应用软件系统而设计。该框架的核心在于权限管理,旨在帮助开发者快速搭建具备完整...

    基于C#的Winform通用开发框架设计源码

    该系统是一个简单实用的二次开发框架,内置了完整的权限架构,包括菜单、角色、用户、字典、日志和代码生成等系统常规模块。该框架旨在帮助一般管理系统避免重复造轮子,开发者只需关注新增功能的form界面和业务逻辑...

    基于通用遥感数据处理系统的日志子系统的设计与实现.pdf

    日志系统在系统开发阶段充当调试工具,记录调试信息;在运行阶段则用于记录系统状态,跟踪代码执行路径,为后续分析提供依据。此外,错误预处理功能提高了系统的容错能力,防止系统因错误陷入不稳定状态。对于GHIPS...

    基于TypeScript开发,适用于Nodejs/Browser的通用日志输出库

    【标题】中的“基于TypeScript开发,适用于Nodejs/Browser的通用日志输出库”意味着这个日志库是使用TypeScript编程语言构建的,旨在提供跨平台兼容性,既能在Node.js环境中运行,也能在浏览器环境下工作。...

    PHP实例开发源码—乐卡同城一卡通通用积分系统 php版UTF8.zip

    【标题】"PHP实例开发源码—乐卡同城一卡通通用积分系统 php版UTF8.zip"揭示了这个压缩包是一个基于PHP语言实现的同城一卡通通用积分系统的源代码。这个系统设计的目标是为本地社区提供一种积分管理工具,使得用户在...

    一个通用的二进制日志数据分析工具系统源码.zip

    一个通用的二进制日志数据分析工具系统源码。它能做什么? 分析任意格式的二进制数据,还能同时查看协议文档 逐字节、逐位分析 手动、自动分析 对分析结果建透视图,发现规律,学习协议 怎么做到的 工具以插件化...

    SAP ABAP 通用接口日志&amp;restful 动态调用FM

    在SAP ABAP环境中,通用接口日志和RESTful动态调用FM是两个重要的概念,它们在企业级应用开发中发挥着关键作用。本文将详细阐述这两个知识点,并结合RESTful服务,探讨它们如何协同工作。 首先,让我们了解SAP ABAP...

    公司后台通用管理系统源码模板下载

    【标题】"公司后台通用管理系统源码模板下载"所涉及的知识点主要集中在企业级后端管理系统的设计、开发和实现上。此类系统通常用于处理公司的日常运营数据,包括但不限于员工管理、项目管理、财务管理、客户关系管理...

    基于SpringBoot的通用管理系统源码+数据库+项目文档,前后端分离的通用管理系统模版,可用于开发毕业设计

    系统采用了基于角色的访问控制,角色和菜单关联,一个角色可以配置多个菜单权限;然后再将用户和角色关联,一位用户可以赋予多个角色。这样用户就可以根据角色拿到该有的菜单权限,更方便管理者进行权限管控。 本...

    毕业设计,基于SpringBoot+Vue+MySql开发的前后端分离的通用管理系统开发模板,内含Java完整源代码,数据库脚本

    毕业设计,基于SpringBoot+Vue+MySql开发的前后端分离的通用管理系统开发模板,内含Java完整源代码,数据库脚本 本软件是基于 Vue 和 SpringBoot 的前后端分离开发模板,包含了登陆注册、用户管理、部门管理、文件...

Global site tag (gtag.js) - Google Analytics