初级代码游戏的专栏介绍与文章目录-CSDN博客
github位置:codetoys/ctfc.git src/env/myLog.h和.cpp
这个类功能细节比较多,因为是长期使用的版本,累积了各种功能。之前介绍的两个是我的测试代码用的版本,非常简单,那两个在这里:
C++类设计:设计一个日志类(源码)_初级代码游戏的博客-CSDN博客
C++类设计:一个不同版本的日志类(完整源码)_初级代码游戏的博客-CSDN博客
其实这个版本跟上面两个很不一样,这个版本是基于unix/linux的(64位),控制台程序,支持多线程(需要链接pthread),支持日志文件切换,统计各种信息的条数,能缓存信息,可用于如果一段代码正确结束就不输出、出错才输出。
目录
一、完整源码
二、详解
2.1 关于endi、ende、endr
2.2 多线程支持
2.3 缓存支持
2.4 日志切换
一、完整源码
头文件:
//输出信息所在位置的日志宏------g_pEnv->GetLog()返回Log,可以直接定义为全局变量
#define thelog ((g_pEnv->GetLog()).LogPos(__FILE__,__LINE__,__func__))
#define theLog (g_pEnv->GetLog()) //不带文件名行号函数名
//输出调试信息的宏,G_DEBUG G_TRANCE可以直接定义为全局变量
#define DEBUG_LOG if(G_DEBUG)thelog
#define TRANCE_LOG if(G_TRANCE)thelog
//
//日至
struct LogEnd
{
enum { fENDE = 1, fENDW, fENDI, fENDF, fENDD };
char end;
LogEnd(int ,int n):end(n){}
};
ostream & operator<<(ostream & s, LogEnd const & end);//这个好像没啥用,但是我暂时没验证,不敢删
LogEnd const ende(0, LogEnd::fENDE); // error
LogEnd const endw(0, LogEnd::fENDW); // warning
LogEnd const endi(0, LogEnd::fENDI); // information
LogEnd const endf(0, LogEnd::fENDF); // fatal
LogEnd const endd(0, LogEnd::fENDD); // debug
class Log
{
private:
//线程数据
struct _ThreadSpec
{
long m_thread_id;
stringstream m_buf;
string m_strSource;
bool m_bOutputThis;//仅控制当前一行输出
string m__file;
long m__line;
string m__func;
_ThreadSpec() :m_thread_id(0), m_bOutputThis(true), m__line(0) {}
};
static void _CloseThreadSpec(void * _p)
{
_ThreadSpec * p=(_ThreadSpec *)_p;
delete p;
}
_ThreadSpec * _getThreadSpec()
{
_ThreadSpec * p = (_ThreadSpec *)pthread_getspecific(tsd_key);
if (p)return p;
else
{
p = new _ThreadSpec;
if (!p)
{
throw "new _ThreadSpec return NULL";
}
m_mutex.lock();
p->m_thread_id = m_thread_count;
++m_thread_count;
m_mutex.unlock();
if (0 == pthread_setspecific(tsd_key, p))
{
return p;
}
else
{
throw "pthread_setspecific error";
}
}
}
private:
long m_thread_count;//线程计数
pthread_key_t tsd_key;//线程存储key
CPThreadMutex m_mutex;
string m_appname;//用来构造m_filename,用于定时切换文件,若未设置则直接使用m_filename
string m_appname_old;//切换日志时用来保存原来的,以便切换换回来
string m_filename;//当前的日志文件名
ofstream m_ofs;
bool m_bOutput;//控制所有输出
bool m_bCache;//当!m_bOutput时缓存输出结果
string::size_type m_maxcache;//最大缓存的数目
string m_cache;//缓存的输出
void(*m_pUD)(const string& strLog);//用户定义的函数,输出的时候会执行一次,一般也不用
//错误和警告统计
long countw;
long counte;
long countall;
//正常记录统计
long countn;
public:
//设置用户定义的函数,输出的时候会执行一次,一般也不用
void SetUserFunction(void(*pUD)(const string& strLog)) { m_pUD = pUD; }
public:
Log()
{
m_thread_count = 0;
pthread_key_create(&tsd_key, _CloseThreadSpec);
m_mutex.init();
SetSource("应用");
m_bOutput = true;
m_bCache = false;
m_maxcache = 0;
m_pUD = NULL;
countw = 0;
counte = 0;
countn = 0;
countall = 0;
}
void GetCount(long & c_w, long & c_e, long & c_all)const { c_w = countw; c_e = counte; c_all = countall; }
string const &GetFileName()const { return m_filename; }
Log& LogPos(char const * file, long line,char const * func)
{
string tmp = file;
string::size_type pos = tmp.find_last_of("/");
if (pos != tmp.npos)_getThreadSpec()->m__file = tmp.substr(pos + 1);
else _getThreadSpec()->m__file = file;
_getThreadSpec()->m__line = line;
_getThreadSpec()->m__func = func;
return *this;
}
string const & _makesource(string const & source, string & ret);
template <typename T>
Log& operator<<(T const& t)
{
_getThreadSpec()->m_buf << (t);
return *this;
}
Log& operator<<(stringstream const& ss)
{
_getThreadSpec()->m_buf << ss.str();
return *this;
}
Log & operator<<(ostream &(*p)(ostream &))
{
_getThreadSpec()->m_buf <<(p);
return *this;
}
Log & operator<<(LogEnd const & end)
{
m_mutex.lock();
_ThreadSpec * ThreadSpec = _getThreadSpec();
char nCh = end.end;
bool isImportant = false;
string strType;
switch (nCh)
{
case LogEnd::fENDI:
strType = "[信息]";
++countn;
break;
case LogEnd::fENDW:
strType = "[警告]";
isImportant = true;
++countw;
break;
case LogEnd::fENDE:
strType = "[出错]";
isImportant = true;
++counte;
break;
case LogEnd::fENDF:
strType = "[致命]";
isImportant = true;
break;
case LogEnd::fENDD:
strType = "[调试]";
break;
}
if (isImportant)++countall;
time_t t;
tm const * t2;
char buf[2048];
time(&t);
t2 = localtime(&t);
sprintf(buf, "%02d-%02d %02d:%02d:%02d", t2->tm_mon + 1, t2->tm_mday, t2->tm_hour, t2->tm_min, t2->tm_sec);
string strTime = buf;
strTime = "[" + strTime + "]";
//若未设置则不输出文件名和行号
if (0 != ThreadSpec->m__file.size())
{
sprintf(buf, "[%-24s:%4ld(%s)][%6.2f]", ThreadSpec->m__file.c_str()
, ThreadSpec->m__line, ThreadSpec->m__func.c_str(), (clock() / (float)CLOCKS_PER_SEC));
}
else buf[0] = '\0';
string tmpSource;
string strMsg;
strMsg = strTime + _makesource(ThreadSpec->m_strSource, tmpSource) + strType + buf + ThreadSpec->m_buf.str();
if (LogEnd::fENDE == nCh)
{
if (G_ERROR_MESSAGE().str().size() > 1024 * 1024)G_ERROR_MESSAGE().str(G_ERROR_MESSAGE().str().substr(512 * 1024));
G_ERROR_MESSAGE() << strMsg << endl;
}
if (!m_bCache && m_filename.size() != 0)
{
string newfile = makelogfilename();
if (m_filename != newfile)
{
m_ofs.close();
_Open(newfile);
cout << "文件切换,原文件 " << m_filename << " 新文件 " << newfile << endl;
m_ofs << "文件切换,原文件 " << m_filename << " 新文件 " << newfile << endl;
}
m_ofs << strMsg.c_str() << endl;
m_ofs.flush();
if (m_ofs.bad())
{
m_ofs.close();
_Open(newfile);
cout << "写文件错误,关闭后重新打开文件" << endl;
m_ofs << "写文件错误,关闭后重新打开文件" << endl;
m_ofs << strMsg.c_str() << endl;
m_ofs.flush();
}
}
if (m_bOutput && ThreadSpec->m_bOutputThis)
{
cout << strMsg.c_str() << endl;
}
else
{
if (m_bCache)
{
if (m_cache.size() > m_maxcache)
{
m_cache.erase(0, m_cache.size() / 2);//超长时删去前半部分
}
m_cache += strMsg + "\n";
}
}
ThreadSpec->m_bOutputThis = true;
if (m_pUD) (*m_pUD)(strMsg); // 用户定义功能
ThreadSpec->m__file = "";
ThreadSpec->m__line = 0;
ThreadSpec->m_buf.str("");
m_mutex.unlock();
return *this;
}
Log& SetSource(const string& strSource)
{
_getThreadSpec()->m_strSource = "[" + strSource + "]";
return *this;
}
void setMaxFileSize(long)
{
cout << "theLog.setMaxFileSize(maxsize) 此功能已取消" << endl;
}
void setCache(string::size_type maxcache)
{
m_maxcache = maxcache;
m_bCache = (maxcache > 0);
}
bool getCache()const { return m_bCache; }
string & GetCachedLog(string & ret)//获得缓存的日志并清理缓存
{
ret = m_cache;
m_cache.clear();
return ret;
}
void ClearCache()//结束缓存,丢弃缓存的东西
{
m_cache.clear();
setCache(0);
}
void setOutput(bool bEnable = true) { m_bOutput = bEnable; }
bool getOutput() { return m_bOutput; }
Log & setOutputThis(bool bEnable = false) { _getThreadSpec()->m_bOutputThis = bEnable; return *this; }
#ifdef _LINUXOS
int _Open(const string& strFile, std::_Ios_Openmode nMode = ios::out | ios::app)
#else
int _Open(const string& strFile, int nMode = ios::out | ios::app)
#endif
{
m_ofs.close();
m_ofs.clear();
m_ofs.open(strFile.c_str(), nMode);
if (!m_ofs.good())
{
cout << "打开文件出错 " << strFile << " " << strerror(errno) << endl;
return -1;
}
m_filename = strFile;
return 1;
}
//以固定文件方式打开日志,不会根据日期切换
int Open(char const * filename)
{
cout << "theLog.Open(filename) 此操作将取消按日期生成日志文件的功能,若要使用按日期分文件请取消此调用" << endl;
m_appname_old = m_appname;
m_appname = "";
return _Open(filename);
}
int ReturnToOldFile()
{
return ActiveOpen(m_appname_old.c_str());
}
//根据当前年月日构造日志文件名,若未设置m_appname则为m_filename
string makelogfilename()
{
if (0 == m_appname.size())return m_filename;
time_t t;
tm const * t2;
char buf[256];
time(&t);
t2 = localtime(&t);
sprintf(buf, "%s.%04d%02d%02d.log", m_appname.c_str(), t2->tm_year + 1900, t2->tm_mon + 1, t2->tm_mday);
return buf;
}
//按日期打开日志,日期改变日志文件自动切换,格式为“appname.YYYYMMDD.log”
int ActiveOpen(char const * appname)
{
m_appname = appname;
return _Open(makelogfilename());
}
//获得当前日志位置
long tellEndP()
{
m_ofs.seekp(0, ios::end);
return m_ofs.tellp();
}
//返回当前日志记录数
long getCountN()
{
return countn;
}
long getCountW()
{
return countw;
}
long getCountE()
{
return counte;
}
};
//日志接口
#define LOG (thelog)
#define ENDI (endi)
#define ENDW (endw)
#define ENDE (ende)
源文件:
//这个好像没啥用,但是我暂时没验证,不敢删
ostream & operator<<(ostream & s, LogEnd const & end)
{
switch (end.end)
{
case LogEnd::fENDI:
s << "[信息]";
break;
case LogEnd::fENDW:
s << "[警告]";
break;
case LogEnd::fENDE:
s << "[出错]";
break;
case LogEnd::fENDF:
s << "[致命]";
break;
case LogEnd::fENDD:
s << "[调试]";
break;
}
s << endl;
return s;
}
string const & Log::_makesource(string const & source, string & ret)
{
if (m_thread_count < 2)return source;
_ThreadSpec * ThreadSpec = _getThreadSpec();
char buf[256];
if (0 == ThreadSpec->m_thread_id)
{
sprintf(buf, "[%lu]", (unsigned long)getpid());
}
else
{
sprintf(buf, "[%lu-%2ld]", (unsigned long)getpid(), ThreadSpec->m_thread_id);
}
return ret = source + buf;
}
代码里面用到了一个互斥对象,代码是这样的,简单包装而已:
//线程同步对象
class CPThreadMutex
{
private:
pthread_mutex_t m_mutex;//互斥对象
pthread_cond_t m_cond;//条件变量
public:
void init()
{
pthread_mutex_init(&m_mutex, NULL);
pthread_cond_init(&m_cond, NULL);
}
void destory()
{
pthread_cond_destroy(&m_cond);
pthread_mutex_destroy(&m_mutex);
}
int lock()const { return pthread_mutex_lock((pthread_mutex_t*)&m_mutex); }
int unlock()const { return pthread_mutex_unlock((pthread_mutex_t*)&m_mutex); }
int wait()const { return pthread_cond_wait((pthread_cond_t*)&m_cond, (pthread_mutex_t*)&m_mutex); }
int reltimedwait()const
{
timespec to;
to.tv_sec = time(NULL) + 1;
to.tv_nsec = 0;
int ret;
ret = pthread_cond_timedwait((pthread_cond_t*)&m_cond, (pthread_mutex_t*)&m_mutex, &to);
if (0 == ret)return true;
else if (ETIMEDOUT == ret)return false;
else throw ret;
return false;
}
int signal()const { return pthread_cond_signal((pthread_cond_t*)&m_cond); }
};
二、详解
2.1 关于endi、ende、endr
这一套是传统留下来的,在结束输出的时候表明这是什么性质的日志。含义很简单:
endi 信息
endd 调试
ende 出错
endr 报告——由于endr和某些版本的oracle客户端冲突,再说也没有实际使用,所以就删掉了
这个东西可以根据需要扩展。
2.2 多线程支持
由于日志对象是个全局对象,为了支持多线程使用,需要在内部实现多线程支持。具体办法是将临时缓存的数据放在线程存储里面。这里用的是POSIX线程库:
//线程数据
struct _ThreadSpec
{
long m_thread_id;
stringstream m_buf;
string m_strSource;
bool m_bOutputThis;//仅控制当前一行输出
string m__file;
long m__line;
string m__func;
_ThreadSpec() :m_thread_id(0), m_bOutputThis(true), m__line(0) {}
};
static void _CloseThreadSpec(void * _p)
{
_ThreadSpec * p=(_ThreadSpec *)_p;
delete p;
}
_ThreadSpec * _getThreadSpec()
{
_ThreadSpec * p = (_ThreadSpec *)pthread_getspecific(tsd_key);
if (p)return p;
else
{
p = new _ThreadSpec;
if (!p)
{
throw "new _ThreadSpec return NULL";
}
m_mutex.lock();
p->m_thread_id = m_thread_count;
++m_thread_count;
m_mutex.unlock();
if (0 == pthread_setspecific(tsd_key, p))
{
return p;
}
else
{
throw "pthread_setspecific error";
}
}
}
_getThreadSpec()用于获取线程数据,日志类的其他部分只用这个函数来操作数据。
为了保证输出文件不混乱,在输出的时候使用了互斥锁。
2.3 缓存支持
如果执行没有失败,就没有必要输出详细日志。程序在开始一段操作时打开缓存,执行结束检查状态,成功就清除缓存,失败才输出缓存的日志。
2.4 日志切换
日志文件不能越滚越大,一般我们按天切换文件,在每次输出的时候检查日期是否已经改变,如果改变了就关闭文件再重新打开一个。
日志太大搜索信息也很慢,一度我还用过这种策略:增加一个日志文件专门输出错误信息。
日志处理的功能全看自己的需要。文章来源:https://www.toymoban.com/news/detail-846740.html
(这里是结束)文章来源地址https://www.toymoban.com/news/detail-846740.html
到了这里,关于C++类设计:一个比较复杂的日志类 支持多线程、文件切换、信息缓存(源码)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!