一個跨平台的日志系統
用法如下
test_log.cpp
#include <iostream> #include "log.h" simpleLog::Log *g_p_default_Log = NULL; int main() { static simpleLog::Log mylog("./log/my.log"); g_p_default_Log = &mylog; mylog.start(); debug_log("this is a(n) debug log"); info_log("this is a(n) info log"); warn_log("this is a(n) warn log"); err_log("this is a(n) error log"); fatal_log("this is a(n) fatal log"); //bt_log("this is a(n) backtrace log"); return 0; }
文件結構如下
log
src\
--ThreadGroup.h
--log.h
--simpleLog.cpp
--simpleLog.h
--test_log.cpp
以下是實現部分
線程部分的封裝 ThreadGroup.h
#ifndef __THREAD_GROUP_H #define __THREAD_GROUP_H #ifdef _MSC_VER #pragma once #include <process.h> #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include <windows.h> #else #include <pthread.h> #include <unistd.h> #endif #include <algorithm> #include <functional> #include <cstddef> #include <ctime> #include <vector> #include <iterator> //#include "mynoncopyable.h" #ifndef func_type #ifdef _MSC_VER #define func_type unsigned int __stdcall #else #define func_type void* #endif #endif /* * by Summer * 2017-03-12 * header-file only */ class ThreadMutex { private: // noncopyable ThreadMutex( const ThreadMutex& ); ThreadMutex& operator=( const ThreadMutex& ); public: ThreadMutex() { #ifdef _MSC_VER m_threadParameter = CreateMutex(NULL, FALSE, NULL); InitializeCriticalSection(&m_criticalSection); #else pthread_mutex_init(&m_mutex,NULL); #endif } ~ThreadMutex() { #ifdef _MSC_VER CloseHandle(m_threadParameter); DeleteCriticalSection(&m_criticalSection); #else pthread_mutex_destroy( &m_mutex ); #endif } void lock() { #ifdef _MSC_VER WaitForSingleObject(m_threadParameter, INFINITE); #else pthread_mutex_lock(&m_mutex); #endif } void unlock() { #ifdef _MSC_VER ReleaseMutex(m_threadParameter); #else pthread_mutex_unlock(&m_mutex); #endif } void enterCriticalSection() { #ifdef _MSC_VER EnterCriticalSection(&m_criticalSection); #else lock(); #endif } void leaveCriticalSection() { #ifdef _MSC_VER LeaveCriticalSection(&m_criticalSection); #else unlock(); #endif } private: #ifdef _MSC_VER HANDLE m_threadParameter; CRITICAL_SECTION m_criticalSection; #else pthread_mutex_t m_mutex; #endif }; template <typename MutexType> class ScopedLock { private: // noncopyable ScopedLock( const ScopedLock& ); ScopedLock& operator=( const ScopedLock& ); public: ScopedLock(MutexType & threadlock) : m_mutex(&threadlock), isLocked(false) { lock(); } ~ScopedLock() { if (ownsLock()) { m_mutex->unlock(); } } void lock() { if (m_mutex == 0) { return; } if (ownsLock()) { return; } m_mutex->lock(); isLocked = true; } void unlock() { if (m_mutex == 0) { return; } if (!ownsLock()) { return; } m_mutex->unlock(); isLocked = false; } protected: bool ownsLock() const { return isLocked; } private: MutexType *m_mutex; bool isLocked; }; inline #ifdef _MSC_VER void SleepSeconds(DWORD counts) #else void SleepSeconds( unsigned int counts) #endif { #ifdef _MSC_VER Sleep(counts*1000); #else sleep(counts); #endif } inline void SleepMicroseconds(time_t counts) { #ifdef _MSC_VER HANDLE timer; LARGE_INTEGER ft; ft.QuadPart = -(10*counts); // Convert to 100 nanosecond interval, negative value indicates relative time timer = CreateWaitableTimer(NULL, TRUE, NULL); SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE); CloseHandle(timer); #else usleep(counts); #endif } class ThreadSleep { public: static void seconds(unsigned int counts) { SleepSeconds(counts); } static void microseconds(time_t counts) { SleepMicroseconds(counts); } private: ThreadSleep( const ThreadSleep& ); ThreadSleep& operator=( const ThreadSleep& ); }; class ThreadGroup { private: // noncopyable ThreadGroup( const ThreadGroup& ); ThreadGroup& operator=( const ThreadGroup& ); public: #ifdef _MSC_VER typedef HANDLE thread_type; #else typedef pthread_t thread_type; #endif typedef size_t size_type; ThreadGroup() : joinedSize(0) {} template<typename F, typename A> bool createThread(F threadfunc, const A &arg) //bool createThread(func_type threadfunc(void *), void * arg) { ScopedLock<ThreadMutex> lock(m_mutex); // auto lock and unlock thread_type handle; #ifdef _MSC_VER if((handle = (HANDLE)_beginthreadex(NULL, 0, threadfunc, (void*)&arg, 0, NULL)) == NULL) { return false; } #else if(pthread_create(&handle, NULL, threadfunc, (void*)&arg) != 0) { return false; } #endif m_threadHandle.push_back(handle); return true; } void join() { ScopedLock<ThreadMutex> lock(m_mutex); if(joinedSize == m_threadHandle.size()) return; #ifdef _MSC_VER WaitForMultipleObjects(m_threadHandle.size(), &m_threadHandle.front()+joinedSize, TRUE, INFINITE); #else container_type::iterator start = m_threadHandle.begin(); std::advance (start, joinedSize); std::for_each(start, m_threadHandle.end(), std::bind2nd(std::ptr_fun(pthread_join), (void*)0)); #endif joinedSize = m_threadHandle.size(); } size_type size() const { ScopedLock<ThreadMutex> lock(m_mutex); return m_threadHandle.size(); } ~ThreadGroup() { #ifdef _MSC_VER std::for_each(m_threadHandle.begin(), m_threadHandle.end(), CloseHandle); #else #endif } private: typedef std::vector<thread_type> container_type; container_type m_threadHandle; mutable ThreadMutex m_mutex; std::vector<thread_type>::size_type joinedSize; }; #endif //__THREAD_GROUP_H
log.h
#ifndef LOG_H #define LOG_H #include "simpleLog.h" extern simpleLog::Log *g_p_default_Log; #define debug_log(format,...) g_p_default_Log->print_log(simpleLog::LOG_LELEVE_DEBUG,"%s:%d|%s <DEBUG> "format,__FILE__,__LINE__,__FUNCTION__,##__VA_ARGS__) #define info_log(format,...) g_p_default_Log->print_log(simpleLog::LOG_LELEVE_INFO,"<INFO> "format,##__VA_ARGS__) #define warn_log(format,...) g_p_default_Log->print_log(simpleLog::LOG_LELEVE_WARNING,"%s:%d|%s <WARN> "format,__FILE__,__LINE__,__FUNCTION__,##__VA_ARGS__) #define err_log(format,...) g_p_default_Log->print_log(simpleLog::LOG_LELEVE_ERROR,"%s:%d|%s <ERR> "format,__FILE__,__LINE__,__FUNCTION__,##__VA_ARGS__) #define fatal_log(format,...) g_p_default_Log->print_log(simpleLog::LOG_LELEVE_FATAL,"%s:%d|%s <FATAL> "format,__FILE__,__LINE__,__FUNCTION__,##__VA_ARGS__) #define bt_log(format,...) g_p_default_Log->backtrace_log(simpleLog::LOG_LELEVE_INFO,"%s:%d|%s <BT> "format,__FILE__,__LINE__,__FUNCTION__,##__VA_ARGS__) #endif
simpleLog.h
#ifndef __SIMPLE_LOG__H #define __SIMPLE_LOG__H #include <string> #include <vector> #include <stdio.h> #include "ThreadGroup.h" namespace simpleLog { enum { LOG_LELEVE_FATAL = 0, LOG_LELEVE_ERROR = 1, LOG_LELEVE_WARNING = 2, LOG_LELEVE_INFO = 3, LOG_LELEVE_DEBUG = 4, TOTLE_LOG_LELEVE_SIZE = 5 }; enum ConstructorMode { SET_NAME = 0, READ_CONFIG = 1 // Not implemented }; class simpleLogTool { public: std::string getNewFileName(const std::string & oldname); bool TimeIs(int hour); bool isExist(const std::string &file); bool isFileEmpty(const std::string &file); void newfile(const std::string &name); }; class Log { public: Log(const std::string & name, ConstructorMode mode = SET_NAME); ~Log(); void start(); public: void setRoll(int roll);//是否按日期滾動文件名,1滾動,0不滾動 int getRoll() const { return m_roll; } void print_log(int log_level, const char *fmt, ...); std::string getLogFileName() const; private: Log(const Log &); void logImpl(FILE * stream, const char *fmt, va_list argList); int checkLogLevel(int log_level); static const char *log_info[TOTLE_LOG_LELEVE_SIZE + 1]; struct LogArgs { std::string name; ConstructorMode mode; }; std::string m_logfilename; ThreadGroup m_LogThread; simpleLogTool m_tool; LogArgs m_LogArgs; int m_roll; }; }; #endif
simpleLog.cpp
#ifdef _MSC_VER #pragma warning(disable:4996) #include <windows.h> #else #include <execinfo.h> #endif #include "simpleLog.h" #include <string> #include <vector> #include <fstream> #include <time.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <stdarg.h> #include <ctype.h> #include <climits> #include <assert.h> using namespace simpleLog; #ifdef _MSC_VER #ifndef snprintf #define snprintf(_DstBuf, _SizeInBytes, format,...) _snprintf_s(_DstBuf, _SizeInBytes, _TRUNCATE, format,##__VA_ARGS__) #endif #endif namespace simpleLog { namespace detail { ThreadMutex s_writeLogLock; ThreadMutex &g_writeLogLock() { return s_writeLogLock; } std::string GetSystemDayTime2() { char timebuf[64] = {0}; time_t timep; struct tm *t; time(&timep); t = localtime(&timep); snprintf(timebuf, sizeof(timebuf), "%4d-%02d-%02d %d:%02d:%02d", t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec); return std::string(timebuf); } std::string GetSystemDay() { char Time[30] = {0}; time_t timep; struct tm *p; time(&timep); p = localtime(&timep); snprintf(Time, sizeof(Time), "%4d-%02d-%02d", 1900 + p->tm_year, 1 + p->tm_mon, p->tm_mday); std::string SystemTime(Time); return SystemTime; } func_type dobackup(void * p) { simpleLogTool tool; Log *pLog = (Log*)p; std::string oldfile(pLog->getLogFileName()); int roll = pLog->getRoll(); while(true) { if(roll && tool.TimeIs(23)) //23::00::00~23::59::59 { try { if(tool.isExist(oldfile) && !tool.isFileEmpty(oldfile)) { ScopedLock<ThreadMutex> lock(g_writeLogLock()); rename(oldfile.c_str(), tool.getNewFileName(oldfile).c_str()); tool.newfile(oldfile); } } catch(std::exception &ex) { printf("[simpleLog::Log] [dobackup] %s\n", ex.what()); } SleepSeconds(3600*2); } SleepSeconds(60*5); } } }; // namespace detail using namespace detail; std::string simpleLogTool::getNewFileName(const std::string & oldname) { std::string tryname (oldname); std::string dateSuffix = "." + GetSystemDay(); tryname += dateSuffix; std::string newname(tryname); char numSuffix[64 + 2] = {0}; for(int i = 1; isExist(newname) && i < INT_MAX; i++) { newname = tryname; memset(numSuffix, 0, sizeof(numSuffix)); snprintf(numSuffix, sizeof(numSuffix), ".%d", i); newname += numSuffix; } return newname; } bool simpleLogTool::TimeIs(int hour) { time_t timep; struct tm *t; time(&timep); t = localtime(&timep); return (/*t->tm_min == 0 &&*/ t->tm_hour == hour); } bool simpleLogTool::isExist(const std::string &file) { std::ifstream ifs(file.c_str()); return ifs.good(); } bool simpleLogTool::isFileEmpty(const std::string &file) { std::ifstream ifs(file.c_str()); return ifs.peek() == std::ifstream::traits_type::eof(); } void simpleLogTool::newfile(const std::string &name) { std::ofstream ofs(name.c_str()); if (!ofs) { printf("[simpleLog::Log] Cannot open the output file: %s", name.c_str()); } ofs.close(); } }; Log::Log(const std::string & name, ConstructorMode mode) { m_LogArgs.mode = mode; m_LogArgs.name = name; m_roll = 1; } Log::~Log() { if(getRoll()) { rename(m_logfilename.c_str(), m_tool.getNewFileName(m_logfilename).c_str()); m_tool.newfile(m_logfilename); } } void Log::start() { if(m_LogArgs.name.empty()) { printf("[simpleLog::Log] log filename is empty\n"); assert(false); } switch(m_LogArgs.mode) { case SET_NAME: m_logfilename = m_LogArgs.name; break; case READ_CONFIG: // TODO break; default: assert(false); break; } m_LogThread.createThread(dobackup, *this); } const char * Log::log_info[TOTLE_LOG_LELEVE_SIZE + 1] = {"FATAL", "ERROR", "WARNING", "INFO", "DEBUG", ""}; void Log::setRoll(int roll) { m_roll = roll; } std::string Log::getLogFileName() const { return m_logfilename; } int Log::checkLogLevel(int log_level) { // TODO; return 0; } void Log::logImpl(FILE * stream, const char *fmt, va_list argList) { int ret=fprintf(stream,"%s ", GetSystemDayTime2().c_str()); if(ret<0) { return; } ret=vfprintf (stream, fmt, argList); if(ret<0) { return ; } ret=fprintf(stream,"\n"); if(ret<0) { return; } } void Log::print_log(int log_level, const char *fmt, ...) { if(checkLogLevel(log_level) != 0) { return; } ScopedLock<ThreadMutex> lock(g_writeLogLock()); FILE *fp = fopen(m_logfilename.c_str(), "a"); if(fp == NULL) return; va_list ap; va_start (ap, fmt); logImpl(fp, fmt, ap); va_end (ap); fclose(fp); }
編譯命令
g++ -g -c *.cpp g++ -g -o simpleLog *.o -O2 -lpthread