一个简单又高效的日志系统

 摘要:本文给出一个性能高,使用简单的日志解决方案。本模块实现日志信息的批量写入文件,定时自动flush到文件中,写入文件的日志级别可动态调整,单个日志文件大小可配置,循环对日志文件写入,这样不会造成机器空间被日志文件耗尽。

关键字:日志 性能 日志级别

一、程序日志是商品程序中必不可少的部分。在正式商用的程序中一般对于日志都会有一些类似的要求:

  1. 性能要求
  2. 运行时日志级别可调整
  3. 日志文件空间使用安全性问题

下面逐一针对上面的问题一起分析程序实现。

二、性能问题。

  客户对程序的要求当然是越高越好。如果对于日志打印采用普通的方法,来一条日志就写一条日志到文件中,这样性能是很低的。因为程序不断的与磁盘进行交付,对系统的冲击很大,有可能会影响到正常的磁盘IO请求。
对于这个问题,一般的,都是采用批量写入的方法来解决。每写一条日志,并不是把日志立即写入文件中,而是先写到一个缓冲区中。当这个缓冲区达到一定的量时,再一次批量写入到文件中。见如下代码实现:

    if (!strLog.IsEmpty())    {        m_strWriteStrInfo += GetCurTimeStr();        // 增加日志级别信息        if (enLevel == ENUM_LOG_LEVEL_ERROR)        {            m_strWriteStrInfo += _T("Error! ");        }        m_strWriteStrInfo += strLog;        m_strWriteStrInfo += _T("\r\n");    }    if ( bForce        || m_strWriteStrInfo.GetLength() > MAX_STR_LOG_INFO_LEN        || m_iWriteBinLogLen > MAX_BIN_LOG_INFO_LEN/10)    {        // write info,达到一定量时才提交到文件中        WriteLogToFile();    }

  但这样会带来一个问题,如果日志量比较少,很可能要很久才能达到批量提交的量,这样就会造成程序写了日志,但是日志写入器还是把消息写在缓冲区里,文件中没有及时体现出来。我们可以采用定时又定时的办法来输出日志。程序对缓冲区内的日志消息定时强制刷新到文件中去。为了体现程序的使用简单性,把这个功能放在日志模块中实现了,从而调用日志的程序就不用考虑定时来刷新文件了。见如下程序实现:

CSuperLog::CSuperLog(void){    // 初始化临界区变量    InitializeCriticalSection(&m_csWriteLog); // 启动信息  m_strWriteStrInfo = WELCOME_LOG_INFO;    // Create the Logger thread.    m_hThread = (HANDLE)_beginthreadex( NULL, 0, &LogProcStart, NULL, 0, &m_uiThreadID );}unsigned __stdcall CSuperLog::LogProcStart( void* pArguments ){    int nCount = 1;     do     {        Sleep(300);        if (++nCount % 10 == 0 )        {            WriteLog(strTemp, ENUM_LOG_LEVEL_ERROR, true); // 每隔三秒写一次日志        }    } while (m_bRun);}

采有一个全局日志类变量,在构造函数中启动线程,线程每隔三秒去刷新一次文件。

二、日志级别可动态调整

  程序的日志一般会进行日志分类,比如说日志级别一般会有调试日志,运行日志,错误日志等分类。在程序发布后运行时一般都会设置在运行日志级别,这时程序中的调试日志就不会被打印出来。如果程序运行中需要定位分析问题时,又需要把日志级别调低,把一些调试信息打印出来。见如下程序实现:

int CSuperLog::WriteLog(CString &strLog,enLogInfoLevel enLevel/* = ENUM_LOG_LEVEL_RUN*/, bool bForce /*= false*/){    if (enLevel < m_iLogLevel)    {        return -1;    }    。。。}

  对于调整日志级别,我没有把实现放在调用者去设置。而是把这个日志级别信息存放在共享内存中,如果要调整日志级别,则需要一个小工具去改那一个共享内存。实际上在整个设计中我一直想把日志系统设计得更独立一点,尽量不和外部调用程序有更多牵连。

        //创建共享文件。        m_hMapLogFile = CreateFileMapping(INVALID_HANDLE_VALUE,NULL,PAGE_READWRITE,0,1024, _T("SuperLogShareMem"));        if (m_hMapLogFile != NULL)        {            //拷贝数据到共享文件里。            m_psMapAddr = (LPTSTR)MapViewOfFile(m_hMapLogFile,FILE_MAP_ALL_ACCESS, 0,0,0);            if (m_psMapAddr != NULL)          {                _tcscpy_s(m_psMapAddr, 1024, g_pszLogLevel[m_iLogLevel]);                                  FlushViewOfFile(m_psMapAddr, _tcslen(g_pszLogLevel[m_iLogLevel]));                WriteLog(_T("设置默认日志级别到共享内存中成功。"), ENUM_LOG_LEVEL_RUN);        }       }

在线程中定时去检查这个日志级别有否有变化,有变化则立即调整当前的级别设置。

三、日志文件空间使用安全性问题

  对于长期运行的商品程序来说,一定会要考虑到文件系统安全性的问题。如果程序不停的打印垃圾信息,用不了多太,日志文件可能会变得很大。如果把用户空间占满了,那有可能会引起更严重的问题。所以一定要限制日志文件的大小。程序中考虑到日志文件更换,采用了三个文件轮换写,写满一个时,更换一个文件再写,不用考虑到日志文件会耗尽磁盘。

CSuperLog::enLogStatus CSuperLog::OpenLogFile(void){    EnterCriticalSection(&m_csWriteLog);      for (int iRunCount = 0; iRunCount < MAX_LOG_FILE_COUNT; iRunCount++)    {        if (m_pFile == NULL)        {            m_pFile = new CStdioFile;            if (m_pFile == NULL)            {                LeaveCriticalSection(&m_csWriteLog);                return m_enStatus = ENUM_LOG_INVALID;            }              BOOL bRet = m_pFile->Open(                g_pszLogFileName[(m_iCurLogFileSeq++)%MAX_LOG_FILE_COUNT],                CFile::modeWrite | CFile::modeCreate | CFile::typeBinary | CFile::shareDenyNone | CFile::modeNoTruncate);            if (bRet)            {                WriteUnicodeHeadToFile(m_pFile);            }            else            {        delete m_pFile;                m_pFile = NULL;                LeaveCriticalSection(&m_csWriteLog);                return m_enStatus = ENUM_LOG_INVALID;            }        }        if (m_pFile->GetLength() > MAX_LOG_FILE_LEN)        {            m_pFile->Close();            BOOL bRet = FALSE;            // 上一个文件是最大的那个文件或是写过一遍了的。            if (m_iCurLogFileSeq >= MAX_LOG_FILE_COUNT)             {                // 所有文件都是写满了,则强制从第一个文件开始写,同时先清空文件                bRet = m_pFile->Open(                    g_pszLogFileName[(m_iCurLogFileSeq++)%MAX_LOG_FILE_COUNT],                    CFile::modeWrite | CFile::modeCreate | CFile::typeBinary | CFile::shareDenyNone);             }            else            {                // 打开第二个文件,再检查是否过了最大值                bRet = m_pFile->Open(                    g_pszLogFileName[(m_iCurLogFileSeq++)%MAX_LOG_FILE_COUNT],                    CFile::modeWrite | CFile::modeCreate | CFile::typeBinary | CFile::shareDenyNone | CFile::modeNoTruncate);            }            if (bRet)            {                WriteUnicodeHeadToFile(m_pFile);            }            else            {                delete m_pFile;                m_pFile = NULL;                LeaveCriticalSection(&m_csWriteLog);                return m_enStatus = ENUM_LOG_INVALID;            }        }        else        {            break;        }    }    m_pFile->SeekToEnd();    LeaveCriticalSection(&m_csWriteLog);    return m_enStatus = ENUM_LOG_RUN;}

四、其它部分

  程序中使用了CStdioFile来处理文件写入,在实现中如果使用text模式打开文件写入,会发现无法写入中文字符的问题。查找了一些资料,发现是字符编码的问题。有一种解决方法是用二进制方式打开,在文件的开头处写入unicode头部标识。

int CSuperLog::WriteUnicodeHeadToFile(CFile * pFile){    if (pFile == NULL)    {        return -1;    }    try    {   if (pFile->GetLength() == 0)        {            m_pFile->Write("\377\376", 2); // 就是FF FE             if (m_enStatus == ENUM_LOG_RUN)            {                m_pFile->WriteString(WELCOME_LOG_INFO);            }            m_pFile->Flush();        }    }    catch (...)    {        return -1;    }    return 0;}

为了保证调用者尽可能的简单,程序把类接口都实现为静态方法,调用都可以直接使用。

#define   WRITE_LOG           CSuperLog::WriteLog#define   LOG_LEVEL_DEBUG     CSuperLog::ENUM_LOG_LEVEL_DEBUG#define   LOG_LEVEL_RUN       CSuperLog::ENUM_LOG_LEVEL_RUN#define   LOG_LEVEL_ERROR     CSuperLog::ENUM_LOG_LEVEL_ERROR

调用者使用如下:

// 包含头文件#include "common/SuperLog.h"WRITE_LOG(_T("短信发送失败,重试一次。"), LOG_LEVEL_ERROR);

  日志线程是在全局变量的析构函数中通知退出的。这时有可能还要会打印日志。为了保证性能,在取得当前时间的字符串时使用了两个静态局部变量

CString& CSuperLog::GetCurTimeStr(){    static CTime g_tmCurTime;    g_tmCurTime = CTime::GetCurrentTime();// time(NULL);    CString g_strTime;    g_strTime = g_tmCurTime.Format(_T("%Y-%m-%d %H:%M:%S "));    return g_strTime;}

  在使用中发现,每次退出时,如果还有日志打印,程序总会异常。后来分析发现,静态全局变量每次都会先于全局变量析构,导致strTime析构后无效访问。只好把这个变量变成了全局变量规避。

CString& CSuperLog::GetCurTimeStr(){    g_tmCurTime = CTime::GetCurrentTime();// time(NULL);    g_strTime = g_tmCurTime.Format(_T("%Y-%m-%d %H:%M:%S "));    return g_strTime;}

四、结束语

  程序实现仓促,基本的功能都调试完毕,但目前还有带参数的写日志接口没有写,二进制内容日志信息的接口也没有实现。后续作者会及时完成。有兴趣的同不学可以发邮件联系。Email:y63508@vip.qq.com

转载于:https://www.cnblogs.com/cccc123/archive/2010/04/17/1714146.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/475998.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

R语言chorolayer_R语言空间可视化:绘制英国脱欧投票地图

添加法国&#xff0c;它位于右下方&#xff0c;因此我们应该看到一点…plot(FR,addTRUE)然后&#xff0c;我们可以检索英国退欧公投数据referendumddply(referendum,.(Region,HASC_code),summarise,Remainsum(Remain),Leavesum(Leave))我们可以发现&#xff0c;脱欧赢得了51.89…

概率潜在语义分析(Probabilistic Latent Semantic Analysis,PLSA)

文章目录1. 概率潜在语义分析模型1.1 基本想法1.2 生成模型1.3 共现模型1.4 模型性质2. 概率潜在语义分析的算法概率潜在语义分析&#xff08;probabilistic latent semantic analysis&#xff0c;PLSA&#xff09;&#xff0c;也称概率潜在语义索引&#xff08;probabilistic …

网站变成灰色调

为方便站点哀悼&#xff0c;特提供css滤镜代码&#xff0c;以表哀悼。以下为全站CSS代码。html { filter:progid:DXImageTransform.Microsoft.BasicImage(grayscale1); } 使用方法&#xff1a;这段代码可以变网页为黑白&#xff0c;将代码加到CSS最顶端就可以实现素装。建议全国…

马尔可夫链蒙特卡罗法(Markov Chain Monte Carlo,MCMC)

文章目录1. 蒙特卡罗法2. 马尔可夫链3. 马尔可夫链蒙特卡罗法4. Metropolis-Hastings 算法5. 吉布斯抽样蒙特卡罗法&#xff08;Monte Carlo method&#xff09;&#xff0c;也称为统计模拟方法&#xff08;statistical simulation method&#xff09;&#xff0c;是通过从概率…

NHibernate 异常及解决办法(长期添加中)

Mapping 错误&#xff1a; 1&#xff09; Could not determine type for:Namespance.Class,AssemblyName, for columns: NHibernate.Mapping.Column(ColumnName) 通常是Mapping中的 type attribute设定错误&#xff0c;在Assembly找不到。如 <property name"PropertyNa…

mysql scope runtime_maven scope provided和runtime的例子

maven常用的scope有compile,provided,runtime,test。complie是默认值&#xff0c;表示在build,test,runtime阶段的classpath下都有依赖关系。test表示只在test阶段有依赖关系&#xff0c;例如junitprovided表示在build,test阶段都有依赖&#xff0c;在runtime时并不输出依赖关系…

[网站seo优化] 史上最全增加外链的方法!

目前在国内网站在百度的权重尤为重要 百度的权重主要取决于 1&#xff0c;收录量2&#xff0c;外链数与质量3&#xff0c;建站时间 可见外链的重要性现在就分享一篇关于外链的文章&#xff0c;希望对大家有用。 一、网站内容1. 写一篇权威的文章(毫无疑问是获得链接的最好方法…

python自动化安装软件_python自动化安装源码软件包

#!/usr/bin/env python# -*- coding:utf:8 -*-#create by 、矿泉水 2015/7/30import sys,commandsif len(sys.argv) 2:SOFTWARE sys.argv[1]commands.getstatusoutput(‘tar zxvf %s &> install.log 2>&1‘%SOFTWARE)SOFTWARE SOFTWARE.split(‘.‘)SOFTWARE.…

蒙特卡罗法近似求解圆周率π

文章目录1. 原理2. 模拟代码1. 原理 给出 x∈[0,1),y∈[0,1)x \in [0,1),y\in[0,1)x∈[0,1),y∈[0,1) 的均匀分布随机点&#xff0c;模拟 ttt 次&#xff0c;落在以 (0,0)(0,0)(0,0) 为圆心&#xff0c;半径 r1r1r1 的圆以内的次数为 ccc当模拟次数足够大时&#xff0c;可以看成…

算法导论2.3-7

Q: 请给出一个运行时间为θ(nlgn)的算法&#xff0c;使之能在一个由n个整数构成的集合S和另一个整数X时&#xff0c;判断出S中是否存在有两个其和等于X的元素。A: 先对S[1 TO N]进行合并排序--------------------------------θ(nlgn) FOR a <- [1 TO N-1]-----------------…

LeetCode 1318. 或运算的最小翻转次数(位运算)

1. 题目 给你三个正整数 a、b 和 c。 你可以对 a 和 b 的二进制表示进行位翻转操作&#xff0c;返回能够使按位或运算 a OR b c 成立的最小翻转次数。 「位翻转操作」是指将一个数的二进制表示任何单个位上的 1 变成 0 或者 0 变成 1 。 示例 1&#xff1a; 输入&#x…

redis和mysql数据不一致_高并发下为什么 redis 和数据库不一致?怎么解决?

现在的web架构一般都用redis作为缓存层来减轻数据库的压力&#xff0c;数据在此架构下的读取问题&#xff0c;一般都是先判断redis缓存是否有数据&#xff0c;如果有&#xff0c;直接返回&#xff0c;否则读取数据库的数据&#xff0c;写入redis&#xff0c;返回数据&#xff0…

LeetCode 91. 解码方法(动态规划)

1. 题目 一条包含字母 A-Z 的消息通过以下方式进行了编码&#xff1a; A -> 1 B -> 2 ... Z -> 26给定一个只包含数字的非空字符串&#xff0c;请计算解码方法的总数。 示例 1: 输入: "12" 输出: 2 解释: 它可以解码为 "AB"&#xff08;1 2&am…

vim粘贴板和系统粘贴板的共享(linux)

不的不说,当你习惯了vim给你的编程带来乐趣后&#xff0c;你将会越来越喜欢它&#xff01; 在以前刚开始用vim的时候&#xff0c;总觉的在vim里面&#xff0c;鼠标没有起到像其他编辑器那样的功能&#xff0c;不能通过鼠标控制vim下光标移动&#xff0c;而当时又不熟悉vim的移动…

java的vector_java中的Vector类

public class VectorVector 类实现了可动态扩充的对象数组。类似数组&#xff0c;它包含的元素可通过数组下标来访问。但是&#xff0c;在 Vector 创建之后。Vector 可根据增加和删除元素的需要来扩大或缩小。每个向量可通过维护 capacity 和 capacityIncrement 来优化存储空间…

LeetCode 1238. 循环码排列(格雷编码+旋转数组)

1. 题目 给你两个整数 n 和 start。你的任务是返回任意 (0,1,2,,...,2^n-1) 的排列 p&#xff0c;并且满足&#xff1a; p[0] start p[i] 和 p[i1] 的二进制表示形式只有一位不同 p[0] 和 p[2^n -1] 的二进制表示形式也只有一位不同示例 1&#xff1a; 输入&#xff1a;n 2…

java 0 1背包_浅谈java实现背包算法(0-1背包问题)

0-1背包的问题背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品&#xff0c;每种物品都有自己的重量和价格&#xff0c;在限定的总重量内&#xff0c;我们如何选择&#xff0c;才能使得物品的总价格最高。问题的名称来源于如何选择最合适的…

关于Python的应用发布技术

收集如何 将Py应用打包发布的各种技巧: 1.1. 工具 {{{k <yanbo.yuangmail.com> reply-to python-cngooglegroups.com, to python-cngooglegroups.com, date Tue, Apr 1, 2008 at 2:58 PM subject [CPyUG:45605]}}}[http://groups.google.com/group/python-cn/t/24…

LeetCode 第 25 场双周赛(718/1832,前39.2%)

文章目录1. 比赛结果2. 题目1. LeetCode 5384. 拥有最多糖果的孩子 easy2. LeetCode 5385. 改变一个整数能得到的最大差值 medium3. LeetCode 5386. 检查一个字符串是否可以打破另一个字符串 medium4. LeetCode 5387. 每个人戴不同帽子的方案数 hard1. 比赛结果 做出来了 1、2…

target java_java元注解 @Target注解用法

Target&#xff1a;Target说明了Annotation所修饰的对象范围&#xff1a;Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了tar…