在应用系统开发中,我们经常需要在程序中输出一些系统日志,调试日志,以及警告和错误等信息。以下是我总结与归纳的一个日志系统,能够方便的在你的应用开发中重复使用。
功能与特性
-
支持多线程、自定义日志文件大小、最大保存天数
-
四级日志分类:LOG,SYS,WAR,ERR
-
支持输出到控制台,文本文件,以及VC调试窗口
-
日志的输出格式:YYYY-MM-DDHH:MM:SS:MS [thread_id] [level] content
注:
1.使用了c++ 11里的smartpointer,thread
2.若要使用宽字符,则在本文件中定义 #define USE_WCHAR即可,反之使用单字符集
3.若要移植到Linux平台,则需要修改文件删除操作等方面的函数
在这个日志系统中用到一些c++11里面的东西。比如shared_ptr,thread等。我们在这个日志系统里面可以自定义生成的日志文件名前辍,后辍,最大保存天数,在内存中最大缓存的日志条数,即log_watermark_high = 1时,即产生一条日志立即flush到磁盘中,但这样做的效率是比较低,所以建立将log_watermark_high设置为100以上,即100条100条的写入磁盘中。
还有要注意的地方是,生成的日志文件保存在与调用日志系统的exe同层目录下的log文件夹中,所以要手动创建这个log目录。
相关文件源码:
LLog.h
/*-----------------------------------------------------------------------
Author: Litianming(ltm5180@126.com)
Date: 2014-01-21
Description: 日志相关类头文件
1.支持多线程、自定义日志文件大小、最大保存天数
2.四级日志分类:LOG,SYS,WAR,ERR
3.支持输出到控制台,文本文件,以及VC调试窗口
日志的输出格式:YYYY-MM-DD HH:MM:SS:MS [thread_id] [level] content
注:
1.使用了c++ 11 里的smart pointer,thread
2.若要使用宽字符,则在本文件中定义 #define USE_WCHAR即可,反之使用单
字符集
3.若要移植到Linux平台,则需要修改文件删除操作等方面的函数
示例
// 初始化日志模块
LTM::log_param param;
memset(¶m,0,sizeof(LTM::log_param));
param.log_file_size = 1024 * 1024 * 2;
param.log_max_store_days = 5;
param.log_watermark_high = 1;
param.bOpenDebugout_ = true;
strcpy_s(param.log_filename_prefix,"service");
strcpy_s(param.log_file_ext,"log");
LTM::LLogger::GetInstance()->init(¶m);
...
// 任意处调用
LTM::LLogger::GetInstance()->Log(...);
...
// 程序结束处释放
LTM::LLogger::ReleaseInstance()
-----------------------------------------------------------------------*/
#pragma once
#ifndef _H_LLOG_
#define _H_LLOG_
#include <string>
#include <vector>
#include <memory>
#include <thread>
#include "../Base/base.h"
#include "../Base/lmutex.h"
#include "../Thread/LThreadPool.h"
namespace LTM {
void DbgPrint(LCHAR * formatstr,...);
// 日志级别
enum log_type{
LEVEL_LOG = 0, // 普通级
LEVEL_ERROR, // 错误级
LEVEL_WARNING, // 警告级
LEVEL_SYSTEM // 系统级
};
struct log_param {
LCHAR log_filename_prefix[255]; // 日志文件名前辍,如 xxxx_2014-01-24.log
long log_file_size; // 单个日志文件最大大小,单位byte
long log_max_store_days; // 日志最大存储天数,即保留最近的最大天数
long log_watermark_high; // 日志队列的高水位标,达到该水位标则存储到存储体中
LCHAR log_file_ext[16]; // 日志扩展名
bool bOpenConsole_; // 是否打开控制台日志输出,缺省否
bool bOpenDebugout_; // 是否打开DEBUG调试输出(调试时用),缺省否
bool bAllDestStore_; // 是否打开所有可打开的日志存储目标,缺省否
};
/************************************************************************/
/*每条日志对应的一个该对象,基本的日志存储单元 */
/************************************************************************/
class LLog
{
#define LOG_CONTEXT_LENGTH 512
#define LOG_DATE_LENGTH 32
public:
LLog(int level_);
~LLog(void);
// 设置日志详细内容
void setContext(const LCHAR* context_)
{
bool bOverFlow = false;
if (context_ == NULL)
return;
LCHAR* pContext = m_szContext;
#ifdef USE_WCHAR
if (m_nLevel == LEVEL_LOG)
wsprintfW(m_szContext,_T("%s %s"),_T("[LOG]"),context_);
else if (m_nLevel == LEVEL_ERROR)
wsprintfW(m_szContext,_T("%s %s"),_T("[ERR]"),context_);
else if (m_nLevel == LEVEL_SYSTEM)
wsprintfW(m_szContext,_T("%s %s"),_T("[SYS]"),context_);
else
wsprintfW(m_szContext,_T("%s %s"),_T("[WAR]"),context_);
#else
if (m_nLevel == LEVEL_LOG)
wsprintfA(m_szContext,"%s %s","[LOG]",context_);
else if (m_nLevel == LEVEL_ERROR)
wsprintfA(m_szContext,"%s %s","[ERR]",context_);
else if (m_nLevel == LEVEL_SYSTEM)
wsprintfA(m_szContext,"%s %s","[SYS]",context_);
else
wsprintfA(m_szContext,"%s %s","[WAR]",context_);
#endif
}
// 获取日志内容
LCHAR* getContext() { return m_szContext;}
// 将日志格式化成string类型
std::string toString();
private:
DECL_PROP(unsigned int,nID); // 日志ID
DECL_PROP(unsigned int,nLevel); // 日志级别
DECL_PROP(unsigned int,nThreadID); // 产生该条Log的线程ID
LCHAR m_szContext[LOG_CONTEXT_LENGTH]; // 单条日志内容
LCHAR m_szDateTime[LOG_DATE_LENGTH]; // 单条日志产生的时间
};
/************************************************************************/
/*对所有的日志对象进行管理 */
/************************************************************************/
class LLogger
{
public:
~LLogger() { clear();}
void init(log_param* param_ = NULL);
void deleteLogs();
int scanfLogs();
void clear();
void flush();
void log(LCHAR* strContext_,int nType = LEVEL_LOG);
void print(LCHAR * formatstr,...);
static unsigned __stdcall assistant(LPVOID lParam);
static LLogger* GetInstance()
{
if (pInstance == NULL)
pInstance = new LLogger();
return pInstance;
}
static void ReleaseInstance()
{
if (pInstance)
{
SetEvent(pInstance->m_hRunEvent);
delete pInstance;
pInstance = NULL;
}
OutputDebugStringA("[LTools]==> LLogger is quit!\n");
}
private:
LLogger()
/*:m_Assistant(assistant,(LPVOID)this)*/{}
private:
static LLogger* pInstance; // 单实例指针
LMutex m_mutex; // 保护锁
log_param m_param; // 相关日志参数
std::vector<std::shared_ptr<LLog>> m_vcLogList; // 日志对象指针列表
std::vector<std::string> m_vcLogFileList; // 日志文件列表
static unsigned long m_ulIncrease; // 日志对象ID生成器
std::shared_ptr<LThread>m_Assistant; // 辅助事件处理线程
HANDLE m_hRunEvent; // 是否运行辅助事件处理线程
};
} // end of namespace LTM
#endif
LLog.cpp
#include "stdafx.h"
#include "LLog.h"
#include "process.h"
#include <io.h>
#include <iostream>
namespace LTM {
/*-----------------------------------------------------------------------
* 功能: 得到指定文件的大小
*
* 参数: [szPath] 文件路径
*
* 返回:
*
* 备注:
-----------------------------------------------------------------------*/
LONGLONG GetFileSize(const std::string szPath)
{
WIN32_FIND_DATAA fileInfo;
HANDLE hFind;
LONGLONG llFileSize;
LARGE_INTEGER TotalFileSize;
hFind = FindFirstFileA(szPath.c_str() ,&fileInfo);
if (hFind != INVALID_HANDLE_VALUE)
{
TotalFileSize.LowPart = fileInfo.nFileSizeLow;
TotalFileSize.HighPart = fileInfo.nFileSizeHigh;
llFileSize = TotalFileSize.QuadPart;
}
else
llFileSize = 0 ;
FindClose(hFind);
return llFileSize;
}
/*-----------------------------------------------------------------------
* 功能: 判断指定路径的文件是否存在
*
* 参数: [strPath] 文件路径
*
* 返回: 存在返回TRUE,反之返回FALSE
*
* 备注:
-----------------------------------------------------------------------*/
bool CheckFileExist(const std::string strPath)
{
return (_access(strPath.c_str(),0) == 0);
}
/*-----------------------------------------------------------------------
* 功能: 得到当前系统时间
*
* 参数:
*
* 返回:
*
* 备注: 北京时区
-----------------------------------------------------------------------*/
std::string GetLocalTimeString()
{
char szTime[255] = {0};
SYSTEMTIME st;
GetLocalTime(&st);
wsprintfA(szTime,"%04d-%02d-%02d %02d:%02d:%02d",\
st.wYear,st.wMonth,st.wDay,\
st.wHour,st.wMinute,\
st.wSecond);
return szTime;
}
/*-----------------------------------------------------------------------
* 功能: 得到安装路径
*
* 参数:
*
* 返回: 安装路径字符串
*
* 备注:
-----------------------------------------------------------------------*/
std::string GetSetupPath()
{
char szFilePath[512] = {0};
::GetModuleFileNameA(NULL, szFilePath, 512);
std::string strFile = szFilePath;
/*int nCount = strFile.Find(_T('.'));
strFile = strFile.Left(nCount);*/
// 从后向前扫描,当发现\停止
int i = 0;
for(i = strFile.length() - 1; i >=0; i--)
{
if (strFile.at(i) == _T('.'))
break;
}
strFile = strFile.substr(0,i);
i = 0;
for(i = strFile.length() - 1; i >= 0 ; i--)
{
if (strFile.at(i) == _T('\\'))
break;
}
strFile = strFile.substr(0,i);
return strFile;
}
//////////////////////////////////////////////////////////////////////////
//unsigned long LLog::ulIncrease = 0;
//boost::mutex LLog::m_mutex;
LLog::LLog(int level_)
{
memset(m_szContext,0,LOG_CONTEXT_LENGTH);
memset(m_szDateTime,0,LOG_DATE_LENGTH);
SYSTEMTIME st;
GetLocalTime(&st);
#ifdef USE_WCHAR
GetLocalTime(&st);
wsprintfW(m_szDateTime,_T("%04d-%02d-%02d %02d:%02d:%02d:%04d"),\
st.wYear,st.wMonth,st.wDay,\
st.wHour,st.wMinute,\
st.wSecond,\
st.wMilliseconds);
#else
wsprintfA(m_szDateTime,("%04d-%02d-%02d %02d:%02d:%02d:%04d"),\
st.wYear,st.wMonth,st.wDay,\
st.wHour,st.wMinute,\
st.wSecond,\
st.wMilliseconds);
#endif
m_nThreadID = GetCurrentThreadId();
m_nLevel = level_;
m_nID = 0;
}
LLog::~LLog(void)
{
/*char buf[255] = {0};
wsprintfA(buf,"~LLog : %x,ID : %d\n",this,m_nID);
OutputDebugStringA(buf);*/
}
std::string LLog::toString()
{
char buf[512] = {0};
wsprintfA(buf,"%s [%05d] %s",m_szDateTime,m_nThreadID,m_szContext);
std::string str = buf;
return str;
}
//////////////////////////////////////////////////////////////////////////
LLogger* LLogger::pInstance = NULL;
unsigned long LLogger::m_ulIncrease = 0;
/*-----------------------------------------------------------------------
* 功能: 初始化日志系统
*
* 参数: [param_] 相关日志参数
*
* 返回:
*
* 备注:
-----------------------------------------------------------------------*/
void LLogger::init( log_param* param_ /*= NULL*/ )
{
if (param_ != NULL)
{
m_param.log_file_size = param_->log_file_size;
m_param.log_max_store_days = param_->log_max_store_days;
m_param.log_watermark_high = param_->log_watermark_high;
m_param.bAllDestStore_ = param_->bAllDestStore_;
m_param.bOpenConsole_ = param_->bOpenConsole_;
m_param.bOpenDebugout_ = param_->bOpenDebugout_;
#ifdef USE_WCHAR
_wcsncpy_l(m_param.log_filename_prefix,param_->log_filename_prefix);
_wcsncpy_l(m_param.log_file_ext,param_->log_file_ext);
#else
strcpy_s(m_param.log_filename_prefix,param_->log_filename_prefix);
strcpy_s(m_param.log_file_ext,param_->log_file_ext);
#endif
}
// 避免其自适应大小而影响效率
m_vcLogList.reserve(m_param.log_watermark_high + 10);
//m_Assistant = (HANDLE)_beginthreadex(NULL,0,assistant,PVOID(this),0,NULL);
m_Assistant = LThreadPool::GetInstance()->GetFreeThread();
if(m_Assistant)
{
m_Assistant->m_pFun = assistant;
m_Assistant->m_pParam = (LPVOID)this;
m_Assistant->Start();
}
OutputDebugStringA("[LTools]==> LLogger is initialization success!\n");
/*try
{
m_Assistant.detach();
}
catch (std::exception* e)
{
OutputDebugStringA(e->what());
}
*/
}
/*-----------------------------------------------------------------------
* 功能: 删除过期的日志文件
*
* 参数:
*
* 返回:
*
* 备注:
-----------------------------------------------------------------------*/
void LLogger::deleteLogs()
{
// 处理保存的最大天数
int nLogMaxCount = m_param.log_max_store_days;
if (nLogMaxCount < 0 || nLogMaxCount > 31)
return;
int nCurCount = scanfLogs();
std::string strPath = GetSetupPath();
strPath += "\\log\\";
// 以今天为最后一天向前推算到最大保存天数的那天
for(int i = 0; i < nCurCount - nLogMaxCount; i++)
{
std::string strLogFilepath = strPath + m_vcLogFileList.at(i);
DeleteFileA(strLogFilepath.c_str());
}
// 重新扫描日志文件
m_vcLogFileList.clear();
scanfLogs();
}
/*-----------------------------------------------------------------------
* 功能: 扫描日志文件
*
* 参数:
*
* 返回:
*
* 备注:
-----------------------------------------------------------------------*/
int LLogger::scanfLogs()
{
int nCount = 0;
std::string strPath = GetSetupPath();
strPath += "\\log\\*.*";
WIN32_FIND_DATAA finddata;
ZeroMemory(&finddata, sizeof(WIN32_FIND_DATAA));
HANDLE hFind = FindFirstFileA(strPath.c_str() ,&finddata);
if (hFind == INVALID_HANDLE_VALUE)
{
::FindClose(hFind);
return 0;
}
while(true)
{
if (!FindNextFileA(hFind,&finddata))
break;
std::string strFilename = finddata.cFileName;
if (strFilename.find(m_param.log_file_ext) != -1 && strFilename.find(m_param.log_filename_prefix) != -1)
{
nCount++;
m_vcLogFileList.push_back(strFilename);
}
}
::FindClose(hFind);
return nCount;
}
/*-----------------------------------------------------------------------
* 功能: 清空列表,释放队列中的CLog对象在堆中的内存
*
* 参数:
*
* 返回:
*
* 备注:
-----------------------------------------------------------------------*/
void LLogger::clear()
{
// 释放堆上的数据
m_mutex.lock();
m_vcLogList.clear();
m_mutex.unlock();
}
/*-----------------------------------------------------------------------
* 功能: 将内存中的日志全部写入文件中
*
* 参数:
*
* 返回:
*
* 备注:
-----------------------------------------------------------------------*/
void LLogger::flush()
{
int nCount = m_vcLogList.size();
if (nCount < m_param.log_watermark_high)
return ;
std::string strFile = GetSetupPath();
strFile += "\\log\\";
strFile += m_param.log_filename_prefix;
strFile += "-";
std::string strDate = GetLocalTimeString();
strDate = strDate.substr(0,10);
strFile += strDate;
strFile += ".log";
FILE *fp = NULL;
// 检查当天的日志文件是否存在,不存在创建,存在则清空后再追加
if (CheckFileExist(strFile))
{
// 如果当天日志文件大小超过限定的大小,则清空
if (GetFileSize(strFile) >= m_param.log_file_size)
fopen_s(&fp,strFile.c_str(),"w+");
else
fopen_s(&fp,strFile.c_str(),"a");
}
else
fopen_s(&fp,strFile.c_str(),"w+");
if (fp == NULL)
return ;
// 写入日志内容
LCHAR buf[1024] = {0};
for(unsigned int i = 0; i < m_vcLogList.size(); i++)
{
if (i < 0)
break;
std::shared_ptr<LLog> pLog = m_vcLogList.at(i);
if (pLog == NULL)
continue;
#ifdef USE_WCHAR
wsprintfW(buf,_T("%s [%04d] %d %s\n"),\
pLog->m_szDateTime,\
pLog->m_nThreadID,\
pLog->m_nLevel,\
pLog->m_szContext);
if (fp)
fputs((CW2A)buf,fp);
#else
wsprintfA(buf,("%s [%04d] %d %s\n"),\
pLog->m_szDateTime,\
pLog->m_nThreadID,\
pLog->m_nLevel,\
pLog->m_szContext);
if (fp)
fputs(buf,fp);
#endif
m_vcLogList.erase(m_vcLogList.begin() + i);
i = i - 1;
}
if (fp)
fclose(fp);
}
/*-----------------------------------------------------------------------
* 功能: 处理整个日志系统的一些事务,如删除过期日志文件等
*
* 参数:
*
* 返回:
*
* 备注:
-----------------------------------------------------------------------*/
unsigned __stdcall LLogger::assistant(LPVOID lParam)
{
Sleep(100);
LLogger* pObj = (LLogger*)lParam;
pObj->m_hRunEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
while(1)
{
pObj->deleteLogs();
if (WaitForSingleObject(pObj->m_hRunEvent,1) == WAIT_TIMEOUT)
Sleep(1000);
else
break;
}
return 0;
}
void LLogger::log( LCHAR* strContext_,int level_ /*= LEVEL_LOG*/ )
{
int nCurLogCount = 0;
m_mutex.lock();
nCurLogCount = m_vcLogList.size();
// 达到缓冲区高水位标,直接写入日志文件中
if (nCurLogCount >= m_param.log_watermark_high)
flush();
std::shared_ptr<LLog> log(new LLog(level_));
log->SetnID(LLogger::m_ulIncrease++);
log->setContext(strContext_);
m_vcLogList.push_back(log);
std::string strDebugOutput = log->toString();
strDebugOutput += "\n";
// 输出到调试窗口
if (m_param.bOpenDebugout_ || m_param.bAllDestStore_)
{
OutputDebugStringA(strDebugOutput.c_str());
}
// 输出到console窗口
if (m_param.bOpenConsole_ || m_param.bAllDestStore_)
{
std::cout << strDebugOutput;
}
m_mutex.unlock();
}
void LLogger::print(LCHAR * formatstr,...)
{
LCHAR buf[1024] = {0};
m_mutex.lock();
va_list argptr;
va_start(argptr, formatstr);
#ifdef USE_WCHAR
wvsprintfW(buf,formatstr,argptr);
#else
wvsprintfA(buf,formatstr,argptr);
#endif
m_mutex.unlock();
log(buf);
}
void DbgPrint(LCHAR * formatstr,...)
{
LCHAR buf[2048] = {0};
va_list argptr;
va_start(argptr, formatstr);
#ifdef USE_WCHAR
wvsprintfW(buf,formatstr,argptr);
OutputDebugString(buf);
#else
wvsprintfA(buf,formatstr,argptr);
OutputDebugStringA(buf);
#endif
}
} // end of LTM
日志文件效果:

33

被折叠的 条评论
为什么被折叠?



