github位置:codetoys/ctfc.git src/env/myLog.h和.cpp
这个类功能细节比较多,因为是长期使用的版本,累积了各种功能。之前介绍的两个是我的测试代码用的版本,非常简单,那两个在这里:
C++类设计:设计一个日志类(源码)_初级代码游戏的博客-CSDN博客
C++类设计:一个不同版本的日志类(完整源码)_初级代码游戏的博客-CSDN博客
其实这个版本跟上面两个很不一样,这个版本是基于unix/linux的(64位),控制台程序,支持多线程(需要链接pthread),支持日志文件切换,统计各种信息的条数,能缓存信息,可用于如果一段代码正确结束就不输出、出错才输出。
目录
头文件:
- //输出信息所在位置的日志宏------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); }
- };
这一套是传统留下来的,在结束输出的时候表明这是什么性质的日志。含义很简单:
endi 信息
endd 调试
ende 出错
endr 报告——由于endr和某些版本的oracle客户端冲突,再说也没有实际使用,所以就删掉了
这个东西可以根据需要扩展。
由于日志对象是个全局对象,为了支持多线程使用,需要在内部实现多线程支持。具体办法是将临时缓存的数据放在线程存储里面。这里用的是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()用于获取线程数据,日志类的其他部分只用这个函数来操作数据。
为了保证输出文件不混乱,在输出的时候使用了互斥锁。
如果执行没有失败,就没有必要输出详细日志。程序在开始一段操作时打开缓存,执行结束检查状态,成功就清除缓存,失败才输出缓存的日志。
日志文件不能越滚越大,一般我们按天切换文件,在每次输出的时候检查日期是否已经改变,如果改变了就关闭文件再重新打开一个。
日志太大搜索信息也很慢,一度我还用过这种策略:增加一个日志文件专门输出错误信息。
日志处理的功能全看自己的需要。
(这里是结束)