window参考一
一、服务器端日志与客户端日志的区别
在正式讲解之前,我们先来看一个日志类的实现方法,这个日志类也是代表着大多数客户端日志的主流写法:
/**
*@desc: 程序运行log类,log.h
*@author: zhangyl
*@date: 2017.01.17
**/
#ifndef LOG_H
#define LOG_H
#ifdef ZYL_LOG
#define LogInfo(…) Log::GetInstance().AddLog(“INFO”, FILE, LINE, FUNCSIG, VA_ARGS)
#define LogWarning(…) Log::GetInstance().AddLog(“WARNING”, FILE, LINE, FUNCSIG, VA_ARGS)
#define LogError(…) Log::GetInstance().AddLog(“ERROR”, FILE, LINE, FUNCSIG, VA_ARGS)
#else
#define LogInfo(…) (void(0))
#define LogError(…) (void(0))
#endif
class Log
{
public:
static Log& GetInstance();
bool AddLog(const char* pszLevel, const char* pszFile, int lineNo, const char* pszFuncSig, char* pszFmt, ...);
private:
Log();
~Log();
Log(const Log&);
Log& operator=(const Log&);
private:
FILE* m_file;
};
#endif //!LOG_H
/**
*@desc: 程序运行log类,log.cpp
*@author: zhangyl
*@date: 2017.01.17
**/
#include <time.h>
#include <stdio.h>
#include <stdarg.h>
#include “Log.h”
Log& Log::GetInstance()
{
static Log log;
return log;
}
bool Log::AddLog(const char* pszLevel, const char* pszFile, int lineNo, const char* pszFuncSig, char* pszFmt, …)
{
if (m_file == NULL)
return false;
char tmp[8192*10] = { 0 };
va_list va; //定义一个va_list型的变量,这个变量是指向参数的指针.
va_start(va, pszFmt); //用va_start宏初始化变量,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数
_vsnprintf(tmp, ARRAYSIZE(tmp), pszFmt, va);//注意,不要漏掉前面的_
va_end(va);
time_t now = time(NULL);
struct tm* tmstr = localtime(&now);
char content[8192 * 10 + 256] = {0};
sprintf_s(content, ARRAYSIZE(content), "[%04d-%02d-%02d %02d:%02d:%02d][%s][0x%04x][%s:%d %s]%s\r\n",
tmstr->tm_year + 1900,
tmstr->tm_mon + 1,
tmstr->tm_mday,
tmstr->tm_hour,
tmstr->tm_min,
tmstr->tm_sec,
pszLevel,
GetCurrentThreadId(),
pszFile,
lineNo,
pszFuncSig,
tmp);
if (fwrite(content, strlen(content), 1, m_file) != 1)
return false;
fflush(m_file);
return true;
}
Log::Log()
{
time_t now = time(NULL);
struct tm* tmstr = localtime(&now);
char filename[256];
sprintf_s(filename, ARRAYSIZE(filename), “%04d%02d%02d%02d%02d%02d.runlog”,
tmstr->tm_year + 1900,
tmstr->tm_mon + 1,
tmstr->tm_mday,
tmstr->tm_hour,
tmstr->tm_min,
tmstr->tm_sec);
m_file = fopen(filename, "at+");
}
Log::~Log()
{
if (m_file != NULL)
fclose(m_file);
}
这个Log类的定义和实现代码节选自我的一款12306刷票软件,如果需要使用这个类的话包含Log.h头文件,然后使用宏:LogInfo/LogWarning/LogError这三个宏就可以了。示例如下:
string strResponse;
string strCookie = "Cookie: ";
strCookie += m_strCookies;
if (!HttpRequest(osURL.str().c_str(), strResponse, true, strCookie.c_str(), NULL, false, 10))
{
LogError(“QueryTickets2 failed”);
return false;
}
这个日志类,每次输出一行,一行中输出时间、日志级别、线程id、文件名、行号、函数签名和自定义的错误信息,演示如下:
[2017-02-16 17:30:08][INFO][0x0e7c][f:\mycode\hack12306\12306demo\client12306.cpp:1401 bool __thiscall Client12306::HttpRequest(const char *,class std::basic_string<char,struct std::char_traits,class std::allocator > &,bool,const char *,const char *,bool,int)]http response: {“validateMessagesShowId”:“_validatorMessage”,“status”:true,“httpstatus”:200,“data”:{“loginAddress”:“10.1.232.219”,“otherMsg”:“”,“loginCheck”:“Y”},“messages”:[],“validateMessages”:{}}
[2017-02-16 17:30:08][INFO][0x0e7c][f:\mycode\hack12306\12306demo\client12306.cpp:1379 bool __thiscall Client12306::HttpRequest(const char *,class std::basic_string<char,struct std::char_traits,class std::allocator > &,bool,const char *,const char *,bool,int)]http post: url=https://kyfw.12306.cn:443/otn/login/userLogin, headers=Cookie: JSESSIONID=0A01D965C45FE88A1FB289F288BD96C255E3547783; BIGipServerotn=1708720394.50210.0000; , postdata=_json_att=
[2017-02-16 17:30:08][INFO][0x0e7c][f:\mycode\hack12306\12306demo\client12306.cpp:1401 bool __thiscall Client12306::HttpRequest(const char *,class std::basic_string<char,struct std::char_traits,class std::allocator > &,bool,const char *,const char *,bool,int)]http response:
[2017-02-16 17:30:08][INFO][0x0e7c][f:\mycode\hack12306\12306demo\client12306.cpp:1379 bool __thiscall Client12306::HttpRequest(const char *,class std::basic_string<char,struct std::char_traits,class std::allocator > &,bool,const char *,const char *,bool,int)]http post: url=https://kyfw.12306.cn:443/otn/index/initMy12306, headers=Cookie: JSESSIONID=0A01D965C45FE88A1FB289F288BD96C255E3547783; BIGipServerotn=1708720394.50210.0000; , postdata=
上文中也说了,以上示例是我曾经写的一款客户端程序的日志,注意“客户端”这个重要的关键字。因为上述日志的实现虽然通用,但其局限性也只能用于客户端这样对性能和效率要求不高的程序(这里的性能和效率是相对于高并发高性能的服务器程序来说的,也就是说上述日志实现可用于大多数客户端程序,但不能用于高性能高并发的服务器程序)。那么上述程序存在什么问题?问题是效率低!
不知道读者有没有注意上,上述日志类实现,是在调用者线程中直接进行IO操作,相比较于高速的CPU,IO磁盘操作是很慢的,直接在某些工作线程(包括UI线程)写文件,程序执行速度太慢,尤其是当日志数据比较多的时候。
这也就是服务器端日志和客户端日志的区别之一,客户端程序日志一般可以在直接在所在的工作线程写日志,因为这点性能和时间损失对大多数客户端程序来说,是可以忽略的,但对于要求高并发(例如并发量达百万级乃至千万级的系统)的服务器程序来说,单位时间内耗在磁盘写操作上的时间就相当可观了。我目前的做法是参考陈硕的muduo库的做法,使用一个队列,需要写日志时,将日志加入队列中,另外一个专门的日志线程来写日志,我给出下我的具体实现代码,如果需要查看muduo库的做法,请参考陈硕的书《Linux多线程服务端编程:使用muduo C++网络库》关于日志章节。注意:以下是纯C++11代码:
/**
- 日志类头文件, Logger.h
- zhangyl 2017.02.28
**/
#ifndef LOGGER_H
#define LOGGER_H
#include
#include
#include
#include
#include <condition_variable>
#include
//struct FILE;
#define LogInfo(…) Logger::GetInstance().AddToQueue(“INFO”, FILE, LINE, PRETTY_FUNCTION, VA_ARGS)
#define LogWarning(…) Logger::GetInstance().AddToQueue(“WARNING”, FILE, LINE, PRETTY_FUNCTION, VA_ARGS)
#define LogError(…) Logger::GetInstance().AddToQueue(“ERROR”, FILE, LINE, PRETTY_FUNCTION, VA_ARGS)
class Logger
{
public:
static Logger& GetInstance();
void SetFileName(const char* filename);
bool Start();
void Stop();
void AddToQueue(const char* pszLevel, const char* pszFile, int lineNo, const char* pszFuncSig, char* pszFmt, ...);
private:
Logger() = default;
Logger(const Logger& rhs) = delete;
Logger& operator =(Logger& rhs) = delete;
void threadfunc();
private:
std::string filename_;
FILE* fp_{};
std::shared_ptrstd::thread spthread_;
std::mutex mutex_;
std::condition_variable cv_; //有新的日志到来的标识
bool exit_{false};
std::liststd::string queue_;
};
#endif //!LOGGER_H
/**
- 日志类实现文件, Logger.cpp
- zhangyl 2017.02.28
**/
#include “Logger.h”
#include <time.h>
#include <stdio.h>
#include
#include <stdarg.h>
Logger& Logger::GetInstance()
{
static Logger logger;
return logger;
}
void Logger::SetFileName(const char* filename)
{
filename_ = filename;
}
bool Logger::Start()
{
if (filename_.empty())
{
time_t now = time(NULL);
struct tm* t = localtime(&now);
char timestr[64] = { 0 };
sprintf(timestr, “%04d%02d%02d%02d%02d%02d.imserver.log”, t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec);
filename_ = timestr;
}
fp_ = fopen(filename_.c_str(), "wt+");
if (fp_ == NULL)
return false;
spthread_.reset(new std::thread(std::bind(&Logger::threadfunc, this)));
return true;
}
void Logger::Stop()
{
exit_ = true;
cv_.notify_one();
//等待时间线程结束
spthread_->join();
}
void Logger::AddToQueue(const char* pszLevel, const char* pszFile, int lineNo, const char* pszFuncSig, char* pszFmt, …)
{
char msg[256] = { 0 };
va_list vArgList;
va_start(vArgList, pszFmt);
vsnprintf(msg, 256, pszFmt, vArgList);
va_end(vArgList);
time_t now = time(NULL);
struct tm* tmstr = localtime(&now);
char content[512] = { 0 };
sprintf(content, "[%04d-%02d-%02d %02d:%02d:%02d][%s][0x%04x][%s:%d %s]%s\n",
tmstr->tm_year + 1900,
tmstr->tm_mon + 1,
tmstr->tm_mday,
tmstr->tm_hour,
tmstr->tm_min,
tmstr->tm_sec,
pszLevel,
std::this_thread::get_id(),
pszFile,
lineNo,
pszFuncSig,
msg);
{
std::lock_guard<std::mutex> guard(mutex_);
queue_.emplace_back(content);
}
cv_.notify_one();
}
void Logger::threadfunc()
{
if (fp_ == NULL)
return;
while (!exit_)
{
//写日志
std::unique_lock<std::mutex> guard(mutex_);
while (queue_.empty())
{
if (exit_)
return;
cv_.wait(guard);
}
//写日志
const std::string& str = queue_.front();
fwrite((void*)str.c_str(), str.length(), 1, fp_);
fflush(fp_);
queue_.pop_front();
}
}
以上代码只是个简化版的实现,使用std::list来作为队列,使用条件变量来作为新日志到来的触发条件。当然,由于使用了两个固定长度的数组,大小是256和512,如果日志数据太长,会导致数组溢出,这个可以根据实际需求增大缓冲区或者改用动态长度的string类型。使用这两个文件只要包含Logger.h,然后使用如下一行代码启动日志线程就可以了:
Logger::GetInstance().Start();
生成日志,使用头文件里面定义的三个宏LogInfo、LogWarning、LogError,当然你也可以扩展自己的日志级别。
二、日志里面应该写些什么?
我开始在试着去写日志的时候,也走了不少弯路,无论是客户端还是服务器端,日志写的内容倒是不少,但都是些废话,虽然也报出故障,但对解决实际问题时毫无作用。尤其是在服务器上生产环境以后,出现很多问题,问题也暴露出来了,但是由于日志含有的当时现场的环境信息太少,只能看到错误,却没法追踪问题,更别说解决问题了。我们来看两个具体的例子:
CIULog::Log(LOG_WARNING, FUNCSIG, _T(“Be cautious! Unhandled net data! req_ans_command=%d.”), header.cmd);
这条日志记录,只打印出一条警告信息和命令号(cmd),对具体产生这个警告的输入参数和当时的环境也没进行任何记录,即使产生问题,事后也无法追踪。再看一条:
if (!HttpRequest(osURL.str().c_str(), strResponse, true, strCookie.c_str(), NULL, false, 10))
{
LogError(“QueryTickets1 failed”);
return false;
}
这条日志,因为http请求报了个简单的错误,至于产生错误的参数和原因一概没有交待,这种日志如果在生产环境上出现如何去排查呢?出错原因可能是设置的参数非法,这是外部原因,可以解决的,甚至是交互双方的一端传过来的,需要对方去纠正;也可能是当时的网络故障,这个也可以解决,也不算是程序的bug,不需要解决;也可能是的bug引起的,这个需要程序作者去解决。另外,如果是服务器程序,甚至应该在错误中交待下产生日志的用户id、操作类型等信息,这样事后才能便于定位位置,进行重现等。
总结起来,日志记录应该尽量详细,能反映出当时出错的现场情节、产生的环境等。比如一个注册请求失败,至少要描述出当时注册的用户名、密码、用户状态(比如是否已经注册)、请求的注册地址等等。因为日志报错不一定是程序bug,可能是用户非法请求。日志详细了,请不用担心服务器的磁盘空间,因为相比较定位错误,这点磁盘空间还是值得的,实在不行可以定期清理日志嘛。
另外一点是,可以将错误日志、运行状态日志等分开,甚至可以将程序记录日志与业务本身日志分开,这样排查故障时优先查看是否有错误日志文件产生,再去错误日志里面去找,而不用在一堆日志中筛选错误日志。我的很多项目在生产环境也是这么做的。
以上是关于日志的一些个人心得吧,如有一些说的不对的地方,欢迎指正。
windo参考二
笔者在写作本章节的时候,并不敢把此章节的标题叫做《高性能日志系统的设计》,之所以不敢加上“高性能”三个字的原因是,第一,我的对于日志系统设计知识和经验都来自于学习和工作经验,我并不是原创者,只是知识的搬运工;第二,目前有许多优秀的、被广泛使用的开源的日志系统,他们给了我很多启发,不敢在此班门弄斧。不管怎样,笔者还是想写一些自己关于对日志系统的理解和经验,让我们开始吧。
为什么需要日志
实际的软件项目产出都有一个流程,即先开发、测试,再发布生产,由于人的因素,既然是软件产品就不可能百分百没有bug或者逻辑错误,对于已经发布了生产的项目,一旦某个时刻产生非预期的结果,我们就需要去定位和排查问题。但是一般正式的生产环境的服务器或者产品是不允许开发人员通过附加调试器去排查问题的,主要有如下可能原因:
在一些大的互联网公司或者部门分工明确的公司,开发部门、测试部分和产品运维部门是分工明确的,软件产品一旦发布到生产环境以后,将全部交由运维部门人员去管理和维护,而原来开发此产品的开发人员不再拥有相关的操作程序的权限。
对于已经上了生产环境的服务,其数据是公司的核心产值所在,一般不允许或者不敢被开发人员随意调试或者操作,以免对公司造成损失。
发布到生产环境的服务,一般为了让程序执行效率更高、文件体积更小,都是去掉调试符号后的版本,不方便也不利于调试。
既然我们无法通过调试器去调试,这个时候我们为了跟踪和回忆当时的程序行为进而定位问题,我们就需要日志系统。
退一步说,即使在开发或者测试环境,我们可以把程序附加到调试器上去调试,但是对于一些特定的程序行为,我们无法通过设置断点,让程序在某个时刻暂停下来进行调试。例如,对于某些网络通信功能,如果暂停时间过长(相对于操作系统的操作来说),通信的对端可能由于彼端没有在规定时间内响应而断开连接,导致程序逻辑无法进入我们想要的执行流中去;再例如,对于一些高频操作(如心跳包、定时器、界面绘制下的某些高频重复行为),可能在少量次数下无法触发我们想要的行为,而通过断点的暂停方式,我们不得不重复操作几十次、上百次甚至更多,这样排查问题效率是非常低下的。对于这类操作,我们可以通过打印日志,将当时的程序行为上下文现场记录下来,然后从日志系统中找到某次不正常的行为的上下文信息。这也是日志的另外一个作用。
本文将从技术和业务上两个方面来介绍日志系统相关的设计与开发,所谓技术上,就是如何从程序开发的角度设计一款功能强大、性能优越、使用方便的日志系统;而业务上,是指我们在使用日志系统时,应该去记录哪些行为和数据,既简洁、不啰嗦,又方便需要时准确快速地定位问题。
日志系统的技术上的实现
日志的最初的原型即将程序运行的状态打印出来,对于C/C++这门语言来说,即可以利用printf、std::cout等控制台输出函数,将日志信息输出到控制台,这类简单的情形我们不在此过多赘述。
对于,实际的商业项目,为了方便排查问题,我们一般不将日志写到控制台,而是输出到文件或者数据库系统。不管哪一种,其思路基本上一致,我们这里以写文件为例来详细介绍。
同步写日志
所谓同步写日志,指的是在输出日志的地方,将日志即时写入到文件中去。根据笔者的经验,这种设计广泛地用于相当数量的客户端软件。笔者曾从事过数年的客户端开发(包括pc、安卓版本),设计过一些功能复杂的金融客户端产品,在这些系统中采用的就是这种同步写日志的方式。之所以使用这种方式其主要原因就是设计简单,而又不会影响用户使用体验。说到这里读者可能有这样一个疑问:一般的客户端软件,一般存在界面,而界面部分所属的逻辑就是程序的主线程,如果采取这种同步写日志的方式,当写日志时,写文件是磁盘IO操作,相比较程序其他部分是CPU操作,前者要慢很多,这样势必造成CPU等待,进而导致主线程“卡”在写文件处,进而造成界面卡顿,从而导致用户使用软件的体验不好。读者的这种顾虑确实是存在的。但是,很多时候我们不用担心这种问题,主要有两个原因:
对于客户端程序,即使在主线程(UI线程)中同步写文件,其单次或者几次磁盘操作累加时间,与人(用户)的可感知时间相比,也是非常小的,也就是说用户根本感觉不到这种同步写文件造成的延迟。当然,这里也给您一个提醒就是,如果在UI线程里面写日志,尤其是在一些高频操作中(如Windows的界面绘制消息WM_PAINT处理逻辑中),一定要控制写日志的长度和次数,否则就会因频繁写文件和一次写入数据过大而对界面造成卡顿。
客户端程序除了UI线程,还有其他与界面无关的工作线程,在这些线程中直接写文件,一般不会对用户的体验产生什么影响。
说了这么多,我们给出一个具体的例子。
#include “log.h”
LOG* LOG::Log = NULL;
string LOG::logBuffer = “”;
HANDLE LOG::mFileHandle = INVALID_HANDLE_VALUE;
mutex LOG::log_mutex;
CRITICAL_SECTION LOG::criticalSection;
int LOG::writtenSize = 0;
LOG::LOG()
{
// 初始化
init(LOG_LEVEL_NONE, LOG_TARGET_FILE);
}
void LOG::init(LOGLEVEL loglevel, LOGTARGET logtarget)
{
setLogLevel(loglevel);
setLogTarget(logtarget);
InitializeCriticalSection(&criticalSection);
createFile();
}
void LOG::uninit()
{
if (INVALID_HANDLE_VALUE != mFileHandle)
{
CloseHandle(mFileHandle);
}
DeleteCriticalSection(&criticalSection);
}
LOG* LOG::getInstance()
{
if (NULL == Log)
{
log_mutex.lock();
if (NULL == Log)
{
Log = new LOG();
}
log_mutex.unlock();
}
return Log;
}
LOGLEVEL LOG::getLogLevel()
{
return this->logLevel;
}
void LOG::setLogLevel(LOGLEVEL iLogLevel)
{
this->logLevel = iLogLevel;
}
LOGTARGET LOG::getLogTarget()
{
return this->logTarget;
}
void LOG::setLogTarget(LOGTARGET iLogTarget)
{
this->logTarget = iLogTarget;
}
int LOG::createFile()
{
TCHAR fileDirectory[MAX_PATH];
GetCurrentDirectory(MAX_PATH, fileDirectory);
// 创建log文件的路径
TCHAR logFileDirectory[256];
_stprintf_s(logFileDirectory, _T("%s\\Log\\"), fileDirectory);// 使用_stprintf_s需要包含头文件<TCHAR.H>
// 文件夹不存在则创建文件夹
if (_taccess(logFileDirectory, 0) == -1)
{
_tmkdir(logFileDirectory);
}
WCHAR moduleFileName[MAX_PATH];
GetModuleFileName(NULL, moduleFileName, MAX_PATH);
PWCHAR p = wcsrchr(moduleFileName, _T('\\'));
p++;
// 去掉后缀名
for (int i = _tcslen(p); i > 0; i--)
{
if (p[i] == _T('.'))
{
p[i] = _T('\0');
break;
}
}
WCHAR pszLogFileName[MAX_PATH];
_stprintf_s(pszLogFileName, _T("%s%s.log"), logFileDirectory, p);
mFileHandle = CreateFile(
pszLogFileName,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE == mFileHandle)
{
return -1;
}
return 0;
}
static int printfToBuffer(char* buffer, int size, char* format, …){
va_list ap;
va_start(ap, format);
int ret = vsnprintf(buffer, 100, format, ap);
va_end(ap);
return ret;
}
static int getSystemTime(char* timeBuffer)
{
if (!timeBuffer){
return -1;
}
SYSTEMTIME localTime;
// Retrieves the current system date and time. The system time is expressed in Coordinated Universal Time (UTC).
// UTC 协调世界时,又称世界统一时间、世界标准时间、国际协调时间,协调世界时是以原子时秒长为基础,在时刻上尽量接近于世界时的一种时间计量系统;
// 中国大陆、中国香港、中国澳门、中国台湾、蒙古国、新加坡、马来西亚、菲律宾、西澳大利亚州的时间与UTC的时差均为+8,也就是UTC+8
//GetSystemTime(&systemTime);
// Retrieves the current local date and time.
GetLocalTime(&localTime);
char* format = "[%04d-%02d-%02d %02d:%02d:%02d.%03d]";
int ret = printfToBuffer(timeBuffer, 100, format,
localTime.wYear,
localTime.wMonth,
localTime.wDay,
localTime.wHour,
localTime.wMinute,
localTime.wSecond,
localTime.wMilliseconds);
return ret;
}
int LOG::writeLog(
LOGLEVEL loglevel, // Log级别
unsigned char* fileName, // 函数所在文件名
unsigned char* function, // 函数名
int lineNumber, // 行号
char* format, // 格式化
…)
{
int ret = 0;
EnterCriticalSection(&criticalSection);
// 获取日期和时间
char timeBuffer[100];
ret = getSystemTime(timeBuffer);
logBuffer += string(timeBuffer);
// LOG级别
char* logLevel;
if (loglevel == LOG_LEVEL_DEBUG){
logLevel = "DEBUG";
}
else if (loglevel == LOG_LEVEL_INFO){
logLevel = "INFO";
}
else if (loglevel == LOG_LEVEL_WARNING){
logLevel = "WARNING";
}
else if (loglevel == LOG_LEVEL_ERROR){
logLevel = "ERROR";
}
// [进程号][线程号][Log级别][文件名][函数名:行号]
char locInfo[100];
char* format2 = "[PID:%4d][TID:%4d][%s][%-s][%s:%4d]";
ret = printfToBuffer(locInfo, 100, format2,
GetCurrentProcessId(),
GetCurrentThreadId(),
logLevel,
fileName,
function,
lineNumber);
logBuffer += string(locInfo);
// 日志正文
char logInfo2[256];
va_list ap;
va_start(ap, format);
ret = vsnprintf(logInfo2, 256, format, ap);
va_end(ap);
logBuffer += string(logInfo2);
logBuffer += string("\n");
writtenSize += logBuffer.length();
outputToTarget();
LeaveCriticalSection(&criticalSection);
return 0;
}
void LOG::outputToTarget()
{
if (LOG::getInstance()->getLogTarget() & LOG_TARGET_FILE)
{
SetFilePointer(mFileHandle, 0, NULL, FILE_END);
DWORD dwBytesWritten = 0;
WriteFile(mFileHandle, logBuffer.c_str(), logBuffer.length(), &dwBytesWritten, NULL);
FlushFileBuffers(mFileHandle);
}
if (LOG::getInstance()->getLogTarget() & LOG_TARGET_CONSOLE)
{
printf(“%s”, logBuffer.c_str());
}
// 清除buffer
logBuffer.clear();
}
#ifndef LOG_H
#define LOG_H
#include <stdio.h>
#include <Windows.h>
#include
#include <TCHAR.H>
#include
using namespace std;
enum LOGLEVEL
{
LOG_LEVEL_NONE,
LOG_LEVEL_ERROR, // error
LOG_LEVEL_WARNING, // warning
LOG_LEVEL_DEBUG, // debug
LOG_LEVEL_INFO, // info
};
enum LOGTARGET
{
LOG_TARGET_NONE = 0x00,
LOG_TARGET_CONSOLE = 0x01,
LOG_TARGET_FILE = 0x10
};
#define FILENAME(x) strrchr(x,‘\’)?strrchr(x,‘\’)+1:x
#define LOG_DEBUG(…) LOG::writeLog(LOG_LEVEL_DEBUG, (unsigned char*)(FILENAME(FILE)), (unsigned char*)(FUNCTION), (int)(LINE), VA_ARGS)
#define LOG_INFO(…) LOG::writeLog(LOG_LEVEL_INFO, (unsigned char*)(FILENAME(FILE)), (unsigned char*)(FUNCTION), (int)(LINE), VA_ARGS)
#define LOG_WARNING(…) LOG::writeLog(LOG_LEVEL_WARNING, (unsigned char*)(FILENAME(FILE)), (unsigned char*)(FUNCTION), (int)(LINE), VA_ARGS)
#define LOG_ERROR(…) LOG::writeLog(LOG_LEVEL_ERROR, (unsigned char*)(FILENAME(FILE)), (unsigned char*)(FUNCTION), (int)(LINE), VA_ARGS)
#define ENTER() LOG_INFO(“enter”)
#define EXIT() LOG_INFO(“exit”)
#define FAIL() LOG_ERROR(“fail”)
// 单个日志文件最大存储
#define MAX_SIZE 210241024 // 2M
class LOG
{
public:
// 初始化
void init(LOGLEVEL loglevel, LOGTARGET logtarget);
//
void uninit();
// file
int createFile();
static LOG* getInstance();
// Log级别
LOGLEVEL getLogLevel();
void setLogLevel(LOGLEVEL loglevel);
// Log输出位置
LOGTARGET getLogTarget();
void setLogTarget(LOGTARGET logtarget);
// 打log
static int writeLog(
LOGLEVEL loglevel, // Log级别
unsigned char* fileName, // 函数所在文件名
unsigned char* function, // 函数名
int lineNumber, // 行号
char* format, // 格式化
...); // 变量
// 输出log
static void outputToTarget();
private:
LOG();
~LOG();
static LOG* Log;
// 互斥锁
static mutex log_mutex;
// 临界区
static CRITICAL_SECTION criticalSection;
// 存储log的buffer
static string logBuffer;
// 已写的log长度
static int writtenSize;
// Log级别
LOGLEVEL logLevel;
// Log输出位置
LOGTARGET logTarget;
// Handle
static HANDLE mFileHandle;
};
#endif // LOG_H
#include “log.h”
/非线程安全 单例模式/
#include <process.h>
#include <Windows.h>
#define THREAD_NUM 5
// 全局资源变量
int g_num = 0;
unsigned int __stdcall func(void *pPM)
{
LOG_INFO(“enter”);
//int nThreadNum = *(int *)pPM;
Sleep(50);
g_num++;
LOG_INFO(“g_num = %d”, g_num);
LOG_INFO("exit");
return 0;
}
int main()
{
LOG *logger = LOG::getInstance();
HANDLE handle[THREAD_NUM];
//线程编号
int threadNum = 0;
while (threadNum < THREAD_NUM)
{
handle[threadNum] = (HANDLE)_beginthreadex(NULL, 0, func, NULL, 0, NULL);
//等子线程接收到参数时主线程可能改变了这个i的值
threadNum++;
}
//保证子线程已全部运行结束
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
//system("pause");
return 0;
}
文章评论