目录
在前文“利用c语言中宏定义及宏替换特性构建自己的log输出_py_free的博客-CSDN博客”,利用宏定义及宏替换,可以实现日志信息屏幕输出,但通常我们在实际项目中:
【1】需要将日志信息记录在文件中
【2】日志记录顺序需要得到保证
【3】日志信息是不确定的,需要支持到不确定参数集输入
鉴于以上三点,来设计一个日志小系统
一、日志小系统设计知识点
在c/c++中VA_LIST是解决变参问题的一组宏,所在头文件:#include <stdarg.h>,通常用法是:
(1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
(2)然后用VA_START宏初始化刚定义的VA_LIST变量;
(3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
(4)最后用VA_END宏结束可变参数的获取。
void myprint(const char* lpszFormat, ...)
{
va_list args;
char szBuffer[2048] = {0};
va_start(args, lpszFormat);
#ifdef WIN32
vsnprintf_s(szBuffer,sizeof(szBuffer), lpszFormat, args);
#else
vsnprintf(szBuffer, sizeof(szBuffer), lpszFormat, args);
#endif
va_end(args);
//printf(szBuffer);
}
在c/c++中,单体实例实现主要就是将类的构造、拷贝构造函数等隐藏起来,使其仅在第一次实例化时可以创建实例,后续其每次实例化或使用时,仅提供或指向其第一次实例化创建的唯一实例指针。
class SingleClass
{
public:
static SingleClass* createInstance( void )
{
if (m_Instance== NULL)
{
m_Instance= new SingleClass();
return m_Instance;
}
else
return m_Instance;
}
private:
SingleClass(){};
~SingleClass(){};
private:
static SingleClass* m_Instance;
};
二、日志小系统设计
鉴于以上的要求,计划设计一个单体类CLogger,该类具备独立线程能力,采用一个队列来接收来自各个线程、各个功能模块的日志推送,CLogger实例负责将加入到队列的日志,添加一些附加信息后,逐条记录在文件中。
为了区分日志事件,通过枚举创建日志类型;针对日志运行是一个长期的过程,不可能全部存储在一个日志文件中,因此指定一个文件目录,并按天来存放.
log_test
bin #编译目标输出目录
build_win #win编译文件及中间文件目录
build_linux #Linux编译文件及中间文件目录
src #源码
log.h #单体实例,继承自定义基类MyThread线程类
log.cpp
Mutex.h
Mutex.cpp
myThread.h #linux下线程类实现
myThread.cpp
win32Thread.h #win下线程类实现
win32Thread.cpp
CMakeLists.txt #cmake工程配置文件
log.h:
#ifndef CHANNELLOG_H
#define CHANNELLOG_H
#include <stdio.h>
#include <stdarg.h>
#include <string>
#ifdef WIN32
#include "win32Thread.h"
#endif
#ifdef linux
#include "myThread.h"
#include <string.h>
#endif
#include <queue>
#include "Mutex.h"
enum eLogType
{
eHardError = 1,
eSoftError = 2,
eConfigError = 3,
eParameterError = 4,
eReadError = 5,
eWriteError = 6,
eControlMessage = 7,
eResponseMessage = 8,
eTipMessage = 9
};
struct MyLogStruct
{
MyLogStruct():type(0)
{
memset(szBuffer, 0, 1024);
};
int type;
char szBuffer[1024];
};
class CLogger : public MyThread
{
public:
void Log(const eLogType type, const char* lpszFormat, ...);
static CLogger* createInstance( void );
private:
CLogger();
~CLogger();
int Run();
bool getFirstLog(MyLogStruct &it);
void addLog(MyLogStruct it);
private:
static CLogger* m_pLogInstance;
bool running;
//for cache
std::queue<MyLogStruct> mylogs;
PYMutex m_Mutex_Log;
int i_log_over;
};
#endif
log.cpp
#include "Log.h"
#include <time.h>
#include <sys/timeb.h>
#ifdef __linux__
#ifndef sprintf_s
#define sprintf_s sprintf
#endif
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#else
#include <windows.h>
#endif
namespace GlobalVar
{
std::string logdir = "log"; //后期记录日志目录重配置文件读取
std::string logname = "pyfree"; //可设置为服务名
};
CLogger* CLogger::m_pLogInstance = NULL;
void WriteLogToFile( std::string strPath,std::string strTime,const char * strMsg)
{
FILE * fpLog = NULL;
//open
#ifdef WIN32
fopen_s(&fpLog, strPath.c_str(), "a+");
#else
fpLog = fopen(strPath.c_str(), "a+");
#endif
if (NULL != fpLog)
{
fseek(fpLog, 0, SEEK_END);
fwrite(strTime.c_str(), strTime.length(), 1, fpLog);
fwrite(strMsg, strlen(strMsg), 1, fpLog);
fwrite("\n", 1, 1, fpLog);
fclose(fpLog);
}
}
std::string createFileName(std::string strTime_)
{
//file name
std::string strPath = GlobalVar::logdir;
#ifdef WIN32
strPath += "\\";
#else
strPath += "/";
#endif
strPath += strTime_;
strPath += "_";
strPath += GlobalVar::logname;
strPath += ".log";
return strPath;
}
std::string strTime()
{
time_t tt;
struct timeb tm0;
struct tm tm1;
char buf[512];
//time(&tt);
ftime(&tm0);
tt = tm0.time;
#ifdef WIN32
localtime_s(&tm1, &tt);
#else
localtime_r(&tt, &tm1);
#endif
sprintf_s(buf, "%04d-%02d-%02d %02d:%02d:%02d.%03d "
, tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday
, tm1.tm_hour, tm1.tm_min, tm1.tm_sec, tm0.millitm);
std::string strTime_ = buf;
buf[10] = '\0';
return strTime_;
}
std::string createLogTimeAndTypeDesc(const int iMsgType, std::string strTime_)
{
std::string strTimeAndTypeDesc = strTime_;
//
switch (iMsgType)
{
case eHardError:
strTimeAndTypeDesc += "[HardErrorIMsg] ";
break;
case eSoftError:
strTimeAndTypeDesc += "[SoftErrorIMsg] ";
break;
case eConfigError:
strTimeAndTypeDesc += "[ConfErrorIMsg] ";
break;
case eParameterError:
strTimeAndTypeDesc += "[ParamErrorMsg] ";
break;
case eReadError:
strTimeAndTypeDesc += "[ReadErrorIMsg] ";
break;
case eWriteError:
strTimeAndTypeDesc += "[WriteErrorMsg] ";
break;
case eControlMessage:
strTimeAndTypeDesc += "[ControlExeMsg] ";
break;
case eResponseMessage:
strTimeAndTypeDesc += "[ResponseUpMsg] ";
break;
case eTipMessage:
strTimeAndTypeDesc += "[NoticeTipIMsg] ";
break;
default:
strTimeAndTypeDesc += "[PromptUnNoMsg] ";
break;
}
return strTimeAndTypeDesc;
}
void WriteLog( const int iMsgType, const char * strMsg)
{
try {
//system time to string
std::string strTime_ = strTime();
//file name
std::string strPath = createFileName(strTime_.substr(0,10));
// log time and type desc
std::string strTimeAndTypeDesc = createLogTimeAndTypeDesc(iMsgType, strTime_);
WriteLogToFile(strPath,strTimeAndTypeDesc,strMsg);
}
catch (...) {
printf("write log[%d]{%s}error\n", iMsgType, strMsg);
}
}
CLogger::CLogger()
: running(true)
, i_log_over(0)
{
char buf[256] = {0};
sprintf_s(buf,"mkdir %s",GlobalVar::logdir.c_str());
system(buf);
this->start();
};
CLogger::~CLogger()
{
running = false;
};
CLogger* CLogger::createInstance( void )
{
if (m_pLogInstance == NULL)
{
m_pLogInstance = new CLogger();
return m_pLogInstance;
}
else
return m_pLogInstance;
};
int CLogger::Run()
{
MyLogStruct log_;
while (running) {
if (getFirstLog(log_))
{
WriteLog(log_.type, log_.szBuffer);
#ifndef WIN32
printf("Log::[%d]-->%s\n", getpid(), log_.szBuffer);
#else
printf("Log::-->%s\n", log_.szBuffer);
#endif
}
#ifdef WIN32
Sleep(10);
#else
usleep(10000);
#endif
}
return 0;
};
void CLogger::Log(const eLogType type, const char* lpszFormat, ...)
{
va_list args;
//char szBuffer[2048] = {0};
MyLogStruct log_;
log_.type = static_cast<int>(type);
va_start(args, lpszFormat);
#ifdef WIN32
vsnprintf_s(log_.szBuffer,sizeof(log_.szBuffer), lpszFormat, args);
#else
vsnprintf(log_.szBuffer, sizeof(log_.szBuffer), lpszFormat, args);
#endif
va_end(args);
addLog(log_);
}
bool CLogger::getFirstLog(MyLogStruct &it) {
bool ret = false;
m_Mutex_Log.Lock();
if (!mylogs.empty()) {
it = mylogs.front();
mylogs.pop();
ret = true;
}
m_Mutex_Log.Unlock();
return ret;
}
void CLogger::addLog(MyLogStruct it) {
m_Mutex_Log.Lock();
if (mylogs.size() > 100) {
i_log_over++;
mylogs.pop();
}
mylogs.push(it);
m_Mutex_Log.Unlock();
if (i_log_over >= 100) {//每溢出100次,报告一次
MyLogStruct log_;
log_.type = static_cast<int>(eTipMessage);
sprintf(log_.szBuffer,"the size of mylogs queue is up to limmit size[100],[%s %s %d]."
, __FILE__, __FUNCTION__, __LINE__);
m_Mutex_Log.Lock();
mylogs.push(log_);
m_Mutex_Log.Unlock();
i_log_over = 0;
}
}
Mutex.h
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#ifndef PYMUTEX_H
#define PYMUTEX_H
#ifdef WIN32
//#include <windows.h>
#else
#include <pthread.h>
#endif
typedef void *HANDLE;
class IMutex
{
public:
virtual ~IMutex() {}
virtual void Lock() const = 0;
virtual bool TryLock() const = 0;
virtual void Unlock() const = 0;
};
class PYMutex : public IMutex
{
public:
PYMutex();
~PYMutex();
virtual void Lock() const;
virtual bool TryLock() const;
virtual void Unlock() const;
private:
#ifdef _WIN32
HANDLE m_mutex;
#else
mutable pthread_mutex_t m_mutex;
#endif
};
#endif //PYMUTEX_H
Mutex.cpp
#include "Mutex.h"
#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>
PYMutex::PYMutex()
{
#ifdef _WIN32
m_mutex = ::CreateMutex(NULL, FALSE, NULL);
#else
pthread_mutex_init(&m_mutex, NULL);
#endif
}
PYMutex::~PYMutex()
{
#ifdef _WIN32
::CloseHandle(m_mutex);
#else
pthread_mutex_destroy(&m_mutex);
#endif
}
void PYMutex::Lock() const
{
#ifdef _WIN32
WaitForSingleObject(m_mutex, INFINITE);
#else
pthread_mutex_lock(&m_mutex);
#endif
}
bool PYMutex::TryLock() const
{
#ifdef _WIN32
DWORD dwWaitResult = WaitForSingleObject(m_mutex, 0);
if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_TIMEOUT) {
printf("thread WARNING: bad result from try-locking mutex\n");
}
return (dwWaitResult == WAIT_OBJECT_0) ? true : false;
#else
return (0==pthread_mutex_trylock(&m_mutex))?true:false;
#endif
};
void PYMutex::Unlock() const
{
#ifdef _WIN32
::ReleaseMutex(m_mutex);
#else
pthread_mutex_unlock(&m_mutex);
#endif
}
linux下myThread.h
/*
* add arg in linux system and compile: -lpthread
*
*/
#ifndef _MYTHREAD_H
#define _MYTHREAD_H
#include <pthread.h>
#include <unistd.h>
class MyThread
{
private:
//current thread ID
pthread_t tid;
//thread status
int threadStatus;
//get manner pointer of execution
static void* run0(void* pVoid);
//manner of execution inside
void* run1();
public:
//threadStatus-new create
static const int THREAD_STATUS_NEW = 0;
//threadStatus-running
static const int THREAD_STATUS_RUNNING = 1;
//threadStatus-end
static const int THREAD_STATUS_EXIT = -1;
// constructed function
MyThread();
~MyThread();
//the entity for thread running
virtual int Run()=0;
//start thread
bool start();
//gte thread ID
pthread_t getThreadID();
//get thread status
int getState();
//wait for thread end
void join();
//wait for thread end in limit time
void join(unsigned long millisTime);
};
#endif /* _MYTHREAD_H */
linux下myThread.cpp
#include "myThread.h"
#include <stdio.h>
void* MyThread::run0(void* pVoid)
{
MyThread* p = (MyThread*) pVoid;
p->run1();
return p;
}
void* MyThread::run1()
{
threadStatus = THREAD_STATUS_RUNNING;
tid = pthread_self();
Run();
threadStatus = THREAD_STATUS_EXIT;
tid = 0;
pthread_exit(NULL);
}
MyThread::MyThread()
{
tid = 0;
threadStatus = THREAD_STATUS_NEW;
}
MyThread::~MyThread()
{
join(10);
}
int MyThread::Run()
{
while(true){
printf("thread is running!\n");
sleep(100);
}
return 0;
}
bool MyThread::start()
{
return pthread_create(&tid, NULL, run0, this) == 0;
}
pthread_t MyThread::getThreadID()
{
return tid;
}
int MyThread::getState()
{
return threadStatus;
}
void MyThread::join()
{
if (tid > 0)
{
pthread_join(tid, NULL);
}
}
void MyThread::join(unsigned long millisTime)
{
if (tid == 0)
{
return;
}
if (millisTime == 0)
{
join();
}else
{
unsigned long k = 0;
while (threadStatus != THREAD_STATUS_EXIT && k <= millisTime)
{
usleep(100);
k++;
}
}
}
win下win32Thread.h
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#ifndef WIN32THREAD_H
#define WIN32THREAD_H
#include <process.h>
#include <iostream>
typedef void *HANDLE;
class MyThread
{
public:
MyThread();
~MyThread();
void start();
virtual int Run();
HANDLE getThread();
private:
HANDLE hThread;
static void agent(void *p);
};
#endif
win下win32Thread.cpp
#include "win32Thread.h"
#include <windows.h>
MyThread::MyThread()
{
}
MyThread::~MyThread()
{
WaitForSingleObject(hThread, INFINITE);
}
void MyThread::start()
{
hThread =(HANDLE)_beginthread(agent, 0, (void *)this);
}
int MyThread::Run()
{
printf("Base Thread\n");
return 0;
}
void MyThread::agent(void *p)
{
MyThread *agt = (MyThread *)p;
agt->Run();
}
HANDLE MyThread::getThread()
{
return hThread;
}
main.cpp,展示如何使用log类
#include <stdio.h>
#include <stdlib.h>
#ifdef WIN32
#include <windows.h>
#endif
#include "Log.h"
char LOG_FILE_NAME[128] = "pyfree_log";
std::string logdir = "./log";
namespace GlobalVar {
extern std::string logname;
};
int main(int argc, char *argv[])
{
#ifdef WIN32
std::string appname = std::string(argv[0]);
GlobalVar::logname = appname.substr(0,appname.length()-4);
#else
GlobalVar::logname = std::string(argv[0]);
#endif
int i = 0;
while(i<1)
{
CLogger::createInstance()->Log(eLogType(rand()%10),"log test for %d\n",i++);
#ifdef WIN32
Sleep(10);
#else
usleep(10000);
#endif
}
return 0;
}
三、编译及展示
CMakeLists.txt:
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (log_test)
#
if(WIN32)
message(STATUS "windows compiling...")
add_definitions(-D_PLATFORM_IS_WINDOWS_)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
set(WIN_OS true)
else(WIN32)
message(STATUS "linux compiling...")
add_definitions( -D_PLATFORM_IS_LINUX_)
add_definitions("-Wno-invalid-source-encoding")
# add_definitions("-O2")
set(UNIX_OS true)
set(_DEBUG true)
endif(WIN32)
#
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
# 指定源文件的目录,并将名称保存到变量
SET(source_h
#
${PROJECT_SOURCE_DIR}/src/Mutex.h
${PROJECT_SOURCE_DIR}/src/log.h
)
SET(source_cpp
#
${PROJECT_SOURCE_DIR}/src/Mutex.cpp
${PROJECT_SOURCE_DIR}/src/log.cpp
${PROJECT_SOURCE_DIR}/src/main.cpp
)
#头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)
if (${UNIX_OS})
SET(source_h_linux
#
${PROJECT_SOURCE_DIR}/src/myThread.h
)
SET(source_cpp_linux
${PROJECT_SOURCE_DIR}/src/myThread.cpp
)
add_definitions(
"-W"
"-fPIC"
"-Wall"
# "-Wall -g"
"-Werror"
"-Wshadow"
"-Wformat"
"-Wpointer-arith"
"-D_REENTRANT"
"-D_USE_FAST_MACRO"
"-Wno-long-long"
"-Wuninitialized"
"-D_POSIX_PTHREAD_SEMANTICS"
"-DACL_PREPARE_COMPILE"
"-Wno-unused-parameter"
"-fexceptions"
)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")
link_directories()
# 指定生成目标
add_executable(log_test ${source_h} ${source_cpp} ${source_h_linux} ${source_cpp_linux})
#link
target_link_libraries(log_test
-lpthread -pthread -lz -lrt -ldl
)
endif(${UNIX_OS})
if (${WIN_OS})
SET(source_h_win
${PROJECT_SOURCE_DIR}/src/win32Thread.h
)
SET(source_cpp_win
${PROJECT_SOURCE_DIR}/src/win32Thread.cpp
)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819")
add_definitions(
"-D_CRT_SECURE_NO_WARNINGS"
"-D_WINSOCK_DEPRECATED_NO_WARNINGS"
"-DNO_WARN_MBCS_MFC_DEPRECATION"
"-DWIN32_LEAN_AND_MEAN"
)
link_directories()
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(log_testd ${source_h} ${source_cpp} ${source_h_win} ${source_cpp_win})
else(CMAKE_BUILD_TYPE)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(log_test ${source_h} ${source_cpp} ${source_h_win} ${source_cpp_win})
endif (CMAKE_BUILD_TYPE)
endif(${WIN_OS})
Linux下
cd log_test
mkdir build_linux
cd build_linux
cmake ..
make
编译及执行过程
[py@pyfree build_linux]$ cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- linux compiling...
-- Configuring done
-- Generating done
-- Build files have been written to: /mnt/hgfs/workForMy/workspace/log_test/build_linux
[py@pyfree build_linux]$ make
Scanning dependencies of target log_test
[ 20%] Building CXX object CMakeFiles/log_test.dir/src/Mutex.cpp.o
[ 40%] Building CXX object CMakeFiles/log_test.dir/src/log.cpp.o
[ 60%] Building CXX object CMakeFiles/log_test.dir/src/main.cpp.o
[ 80%] Building CXX object CMakeFiles/log_test.dir/src/myThread.cpp.o
[100%] Linking CXX executable ../bin/log_test
[100%] Built target log_test
[py@pyfree build_linux]$
[py@pyfree bin]$ ./log_test
mkdir: 无法创建目录"log": 文件已存在
Log::[5153]-->log test for 0
[py@pyfree bin]$
win编译时采用cmake+vs编译,具体编译版本可以依据自身电脑安装版本决定
cd log_test && mkdir build_win && cd build_win
cmake -G "Visual Studio 10 2010 Win64" -DCMAKE_BUILD_TYPE=Release ..
msbuild log_test.sln /p:Configuration="Release" /p:Platform="x64"
省略编译过程,结果展示如下:
四、完整代码目录:
文案中已经提供完整代码信息,如果还是不知道如何组织项目代码,请这里下载: