程序日志模块的两种模式

2023-05-16

 程序员都知道程序的运行日志在不少时候都非常有用,利于排查、理清逻辑。

一般而言,日志都按天生成,并且具备自动清理多少天以前的旧日志,避免无限增长占用磁盘。

下图展示了2种日志模式。

模式一

1)在某固定根目录(C:/log/)下,每天生成1个日志文件(单个日志文件达到上限大小(比如50M)后,还能自动切换至新文件)。

比较适用于项目中单进程需要日志输出的情形。

模式二

2)在某固定根目录(C:/log/)下,每天生成1个子目录(2022-07-01),在该子目录下,有1个或多个应用对应的日志文件,比如app.log        otherapp.log等等。  比较适用于项目中有多进程需要日志输出的情形。

 

 模式一代码:

SysLog.h


#ifndef _CLASS_SYS_LOG_H_
#define _CLASS_SYS_LOG_H_

#include <stdio.h>
#include <string>
#include "common_platform.h"


struct tagDate
{
	int year;
	int month;
	int day;
};

//enum ELOG_LEVEL
//{
//    LOG_NRM = 1,//正常
//    LOG_WRN = 2,//警告
//    LOG_ERR = 3,//错误
//};
 
class CSysLog
{
public:

    /*若是windows系统,参照CSysLog aLogger("D:\\", "MyApp",true),将会在D:\MyAppLog目录下产生日志文件;
     * 若是Linux/Unix,系统参照CSysLog aLogger("/home/sunyy/", "MyApp",true),将会在/home/sunyy/MyAppLog目录下产生日志文件;
     * Note:注意Windowsx下路径间隔是'\\',lINUX/UNIX下路径间隔是'/';
     * 参数isUseWriteMutexMode为写日志时是否开启互斥锁保护,用于多线程,默认为true。
    */
    CSysLog(std::string rootPath,std::string appName, bool isUseWriteMutexMode=true);
public:
    ~CSysLog(void);
    int      WriteLogFile(const char *fmt, ...);
    int      WriteLogFile(const std::string& content);
private:
    void     SetWriteUseMutexMode(bool flag);
 
private:
    char        log_dir_path[256];
    FILE *      fp;                 //文件描述符
    int         day;
    int         changefile;       //是否切换文件,为1时切换
    char        m_szFileFlag[20]; //日志文件名的一个固定标识
    int         sameFileFlag;     //一天可能会有多个日志文件(量大时切分为多个文件存储),对应的日志文件名中会有0,1,2...的标识
	std::string m_rootPath;

    int         nameDelSize;//日志文件名长度。用于删除N天前的文件,可跟随名字格式来变动
    bool        nameDelSizeFlag;//初始为true,使用一次之后变为false
    bool        m_isUseMutexMode;//是否启用互斥写日志
 
#ifdef OS_WINDOWS
    CRITICAL_SECTION m_mutexLock;
#else
    pthread_mutex_t  m_mutexLock;
#endif

private:
    void        ClearLog(uint32_t nDays); // 删除N天前的日志;
public://现在其余地方也想使用该以下成员函数,将其改为公共、静态。
	//以下是日期比较,根据是文件名上的日期和当前系统日期
    static int  IsLeapYear(int year);//闰年
    static int  GetLastDay(tagDate date);
    static int  IsDateValid(tagDate date);
    static void AddDay(tagDate *date);
    static int  Compare(tagDate date1, tagDate date2);
    static long DateDiff(tagDate date1, tagDate date2);//返回日期比较的结果
};

#endif //_CLASS_LOG_MY

SysLog.cpp

#include "SysLog.h"

#define _ONE_LOG_FILE_MAX_SIZE  (52428800)//10485760 10M, 52428800 50*1024*1024 50M
#define _DELETE_PRE_DATE_LOG    (7) //N天前的记录

#define MaxJsonBuffer 1024

//不同系统的路径分割符
#ifdef OS_WINDOWS
    #define PATH_SEPARATOR  "\\"
#else
    #define PATH_SEPARATOR  "/"
#endif


CSysLog::CSysLog(std::string rootPath, std::string appName, bool isUseWriteMutexMode)
{

    m_rootPath = rootPath;
	memset(m_szFileFlag, 0, sizeof(m_szFileFlag));
    if (appName.empty())
	{
       return;
	}
    memcpy(m_szFileFlag, appName.c_str(), appName.length());
    fp = NULL;
    day = -1;
	changefile = 0;
	sameFileFlag = 0;
	nameDelSize = 0;
	nameDelSizeFlag = true;
    std::string strPath = rootPath + appName + std::string("Log") + PATH_SEPARATOR;

    memset(log_dir_path,0,sizeof(log_dir_path));
    strcpy(log_dir_path, strPath.c_str());//c_str必带 /0 strcpy_s为win独有的函数

    // 创建目录这里也得注意区分系统
#ifdef OS_WINDOWS
    if (_access(log_dir_path, 0) == -1)
       _mkdir(log_dir_path);//如果不存在,就创建子目录
#else
    if (access(log_dir_path, 0) == -1)
        mkdir(log_dir_path, 0xFFFFFFFF/*S_IROTH|S_IWOTH|S_IXOTH*/);//如果不存在,就创建子目录
#endif

	//ClearLog(31);//删除15天前的日志(包括当天),移到write进行检测,自动检测名字长度,并删除

    //初始化互斥变量
#ifdef OS_WINDOWS //Windows系统
    InitializeCriticalSection(&m_mutexLock);
#else   //类Unix系统
    int res = pthread_mutex_init(&m_mutexLock,NULL);
    if(res != 0)
    {
        exit(-1);
    }
#endif
    //写日志的互斥模式
	SetWriteUseMutexMode(isUseWriteMutexMode);
}
 
CSysLog::~CSysLog(void)
{
    if (fp != NULL)
    {
        fclose(fp);
        fp = NULL;
    }

#ifdef OS_WINDOWS
    DeleteCriticalSection(&m_mutexLock);
#else
    pthread_mutex_destroy(&m_mutexLock);
#endif
} 
 

 int  CSysLog::WriteLogFile(const std::string& content)
 {
   const char* fmt = content.data();
   return WriteLogFile(fmt);
 }

int CSysLog::WriteLogFile(const char *fmt, ...)
{
    if (strlen(fmt) > MaxJsonBuffer)
	{
		return 0;
	}
	unsigned long res = 0;

	//原本为char* pLoginfo = new char[MaxJsonBuffer];改为局部变量,省的频繁申请与释放
	char pLoginfo[MaxJsonBuffer];
	char name[4096] = {0};

	va_list ap;
    memset(pLoginfo,0x00,MaxJsonBuffer);//清空

    int year = 0, month = 0,cur_day = 0;
#ifdef OS_WINDOWS //Windows系统
    SYSTEMTIME   sys;
    GetLocalTime( &sys );
    //日志时间
    sprintf(pLoginfo,"[%4d-%02d-%02d %02d:%02d:%02d.%03d] ",sys.wYear, sys.wMonth, sys.wDay, sys.wHour, sys.wMinute, sys.wSecond,sys.wMilliseconds);
    year = sys.wYear;
    month = sys.wMonth;
    cur_day = sys.wDay;
#else   //类Unix系统
    struct timeval tv;
    struct timezone tz;
    struct tm *t;
    gettimeofday(&tv, &tz);
    t = localtime(&tv.tv_sec);
    //日志时间
    sprintf(pLoginfo,"[%4d-%02d-%02d %02d:%02d:%02d.%03d] ",
            1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, (int)t->tm_sec, (int)(tv.tv_usec/1000));

    year = 1900 + t->tm_year;
    month = 1 + t->tm_mon;
    cur_day = t->tm_mday;
#endif

	va_start(ap,fmt);
    vsprintf(pLoginfo+strlen(pLoginfo),fmt,ap);
	va_end(ap);

	//打开文件开始写
	if (m_isUseMutexMode)
    {
#ifdef OS_WINDOWS
        EnterCriticalSection(&m_mutexLock);
#else
        pthread_mutex_lock(&m_mutexLock);
#endif
	}

	do
	{
		//取得文件名
		memset(name, 0x00, sizeof(name));
        sprintf(name, "%s%04d-%02d-%02d_%s_%02d.log", log_dir_path, year, month, cur_day, m_szFileFlag, sameFileFlag);

		//记录自定义名字长度
		if (true== nameDelSizeFlag)
		{
			std::string nameDelTemp = name;
            std::size_t ndelPos = nameDelTemp.find_last_of(PATH_SEPARATOR);
			nameDelSize = nameDelTemp.length() - (ndelPos + 1);
			ClearLog(_DELETE_PRE_DATE_LOG);//删除15天前的日志(包括当天) 首次开启时
			nameDelSizeFlag = false;
		}

        if (day != cur_day)//非今天时,切换
		{
            //日期切换时将name重新赋值,因为sameFileFlag也要更新,不能是昨天的,要从0开始计算
            sameFileFlag = 0;//置0
            memset(name, 0x00, sizeof(name));
            sprintf(name, "%s%04d-%02d-%02d_%s_%02d.log", log_dir_path, year, month, cur_day, m_szFileFlag, sameFileFlag);

            //日期更换时,需要清除数据
			ClearLog(_DELETE_PRE_DATE_LOG);

            day = cur_day;
			changefile = 1;
			if (fp != NULL)//如果fp存在时,关闭并清空
			{
				fclose(fp);
				fp = NULL;
			}
		}

		//将空与变更目录,放在同一段里面
		if (fp == NULL || changefile == 1)
		{
			fp = fopen(name, "a+");//打开或新建一个文本文件,可以读,但只允许在文件末尾追写
			if (fp == NULL)
			{
				if (m_isUseMutexMode)
				{
#ifdef OS_WINDOWS
                    LeaveCriticalSection(&m_mutexLock);
#else
                    pthread_mutex_unlock(&m_mutexLock);
#endif
				}

				return 0;
			}

			//查看名字是否有重复,重复侧加1
			fflush(fp);		//刷新
			fseek(fp,0L,SEEK_END);//将指针置后
            uint32_t readFileSize = ftell(fp);
            if (readFileSize > _ONE_LOG_FILE_MAX_SIZE)
			{
				//超过约定数值,执行循环
				sameFileFlag += 1;
				changefile = 1;
			}
            else//在合理范围内
			{
				changefile = 0;
			}
		}

	} while (1== changefile);

	//无论如何还是能写入的
	fwrite(pLoginfo,strlen(pLoginfo),1,fp);//向fp写入pLoginfo中前strlen(pLoginfo)的内容,成功返回1,否则为其他
    fwrite("\n", 1, 1, fp);//增加结束符号
	fflush(fp);		//刷新
	res = ftell(fp);//用于得到文件当前位置相对于文件首的偏移字节数
	if (res > _ONE_LOG_FILE_MAX_SIZE)
	{
		//即超过多大的数量后,就要进行切换
		fclose(fp);
		fp = NULL;
		changefile = 1;
	}

	if (m_isUseMutexMode)
	{
#ifdef OS_WINDOWS //Windows系统
        LeaveCriticalSection(&m_mutexLock);
#else   //类Unix系统
        pthread_mutex_unlock(&m_mutexLock);
#endif
	}
 
	return 1;
}
 

void   CSysLog::SetWriteUseMutexMode(bool flag)
{
	m_isUseMutexMode = flag;
}
 

// 功能:删除nDays天及之前的日志文件
// @nDays: 0-不删除日志,3-删除3天及之前的日志(保留今天、昨天、前天的日志) ...
void CSysLog::ClearLog(uint32_t nDays) // 删除N天前的日志
{
    std::string sLogFolder = log_dir_path;
#ifdef OS_WINDOWS
    WIN32_FIND_DATAA FindFileData;
    char szFileName[512] = { 0 };
    strcpy(szFileName, log_dir_path);
    strcat(szFileName, "*.log");

    HANDLE hFind = ::FindFirstFileA(szFileName, &FindFileData);
    if (INVALID_HANDLE_VALUE == hFind)
        return;
    while (TRUE)
    {
        if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // 遇到文件夹
        {}
        else // 遇到文件
        {
            try
            {
                std::string sFileName = FindFileData.cFileName;
                if (nameDelSize == sFileName.length())
                {
                    std::string sFileFullPath = sLogFolder /*+ PATH_SEPARATOR*/ + sFileName;// 文件全路径
                    //int  nFileName = atoi(sFileName.c_str());   // 取得文件的日期,如:20101030
                    SYSTEMTIME systi;
                    GetLocalTime(&systi);
                    tagDate dtBegin;
                    tagDate dtEnd;
                    //只是部分提取
                    dtBegin.year = atoi(sFileName.substr(0, 4).c_str());
                    dtBegin.month = atoi(sFileName.substr(5, 2).c_str());
                    dtBegin.day = atoi(sFileName.substr(8, 2).c_str());
                    dtEnd.year = systi.wYear;
                    dtEnd.month = systi.wMonth;
                    dtEnd.day = systi.wDay;

                    if ((uint32_t)DateDiff(dtBegin, dtEnd) >= nDays)
                    {
                        ::DeleteFileA(sFileFullPath.c_str());
                    }
                }
            }
            catch (std::exception)
            {

            }
        }
        if (!FindNextFileA(hFind, &FindFileData))
            break;
    }
    FindClose(hFind);
#else
    //UNIX系统下遍历日志文件
    DIR *pDir = NULL;
    struct dirent *pFile = NULL;
    pDir = opendir(sLogFolder.c_str());
    if (pDir == NULL)
        return;

    while ((pFile = readdir(pDir)) != NULL)
    {
        if (pFile->d_type & DT_DIR) //忽略目录
        {
            continue;
        }
        else
        {
            try
            {
                std::string sFileName = pFile->d_name;
                std::string sFileFullPath = sLogFolder /*+ PATH_SEPARATOR*/ + sFileName;// 文件全路径

                if (nameDelSize == (int)sFileName.length())
                {
                    if (nameDelSize > 4)
                    {
                        std::string substr = sFileName.substr( nameDelSize - 4 ,4);
                        if(substr == std::string(".log"))
                        {
                            struct timeval tv;
                            struct timezone tz;
                            struct tm *t;
                            gettimeofday(&tv, &tz);
                            t = localtime(&tv.tv_sec);

                            tagDate dtBegin;
                            tagDate dtEnd;
                            //只是部分提取
                            dtBegin.year = atoi(sFileName.substr(0, 4).c_str());
                            dtBegin.month = atoi(sFileName.substr(5, 2).c_str());
                            dtBegin.day = atoi(sFileName.substr(8, 2).c_str());

                            dtEnd.year = 1900 + t->tm_year;
                            dtEnd.month = 1 + t->tm_mon;
                            dtEnd.day = t->tm_mday;

                            if (DateDiff(dtBegin, dtEnd) >= (long)nDays)
                            {
                                remove(sFileFullPath.c_str());
                            }
                        }
                    }
                }
            }
            catch (std::exception)
            {

            }

        }
    }

    closedir(pDir);
#endif

}
 
//是否闰年  
int CSysLog::IsLeapYear(int year)
{
   bool tmp = (year % 4 == 0)  && (year % 100 != 0);
    return  tmp  ||  (year % 400 == 0) ;
}
 
//得到date.month的最大天数  
int CSysLog::GetLastDay(tagDate date)
{
	int num;
	switch (date.month)
	{
	case 1:
	case 3:
	case 5:
	case 7:
	case 8:
	case 10:
	case 12:
		num = 31;
		break;
	case 2:
		num = 28 + IsLeapYear(date.year);
		break;
	default:
		num = 30;
	}
	return num;
}
 
//日期是否合法  
int CSysLog::IsDateValid(tagDate date)
{
	if (date.year < 0 || date.month < 1 || date.month> 12)
		return 0;
 
	if (date.day <1 || date.day> GetLastDay(date))
		return 0;
 
	return 1;
}
 
//date+1  
void CSysLog::AddDay(tagDate *date)
{
	date->day++;
	if (date->day > GetLastDay(*date))
	{
		date->day = 1;
		date->month++;
		if (date->month > 12)
		{
			date->month = 1;
			date->year++;
		}
	}
}
 
//date1比date2小返回值为1,否则为0  
int CSysLog::Compare(tagDate date1, tagDate date2)
{
	if (date1.year < date2.year)
		return 1;
	if (date1.year <= date2.year && date1.month < date2.month)
		return 1;
	if (date1.year <= date2.year && date1.month <= date2.month && date1.day < date2.day)
		return 1;
 
	return 0;
}
//计算两个日期的间隔天数  
long CSysLog::DateDiff(tagDate date1, tagDate date2)
{
	long delta = 0;
	tagDate date3;
	//若date1 > date2,交换date1,date2  
	if (!Compare(date1, date2))
	{
		date3 = date2;
		date2 = date1;
		date1 = date3;
	}
 
	//date1比date2少时,date1日期加1  
	while (Compare(date1, date2))
	{
		AddDay(&date1);
		delta++;
	}
	return delta;
}

 common_platform.h



#ifndef COMMON_PLATFORM_H
#define COMMON_PLATFORM_H

//根据操作系统定义对应的宏
#if defined (_WIN32)|| defined(_WIN64)|| defined(__WIN32__) || defined(__WINDOWS__)  //Windows平台
    #define OS_WINDOWS
#elif defined(unix) || defined(__unix__) || defined(__unix)                          //Linux/Unix平台
    #define OS_UNIX
#else                                                                                //暂不支持其他平台
    #error "Unsupported_operating_system_platform"
#endif

//若是windows系统
#ifdef OS_WINDOWS
    #define WIN32_LEAN_AND_MEAN
    #include <Windows.h>
    #include <mmsystem.h>
    #include <time.h>
    #include <direct.h>
    #include <corecrt_io.h>
    #pragma comment( lib, "shell32.lib")
#endif

//若是unix系列系统
#ifdef OS_UNIX
    #include <unistd.h>
    #include <pthread.h>
    #include <stdarg.h>
    #include <sys/time.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <dirent.h>
    #include <sys/types.h>
    #include <string.h>// include such as 'memset' 'memcpy' 'strcpy' ..etc
#endif

#endif // COMMON_PLATFORM_H

模式二代码:

Tool.h

#pragma once
#include <iostream>
#include <vector>



void listSubdir(const char * dir, std::vector<std::string>& subdirs);


void ToUnixPath(std::string& s);


bool createMultiDir(const std::string& strPath);

Tool.cpp

#include "Tool.h"

#include <iostream>
#include <stdio.h>
#include <string>
#include <regex>
#include <direct.h>
#include <corecrt_io.h>

void listSubdir(const char * dir, std::vector<std::string>& subdirs)
{
	subdirs.clear();

	std::string dirStr = dir;
	if (dirStr.length() <= 1)
		return;

	if (dirStr.at(dirStr.length() - 1) != '/')
	{
		dirStr.append("/");
	}

	dirStr += "*.*";

	intptr_t handle;
	_finddata_t findData;

	handle = _findfirst(dirStr.data(), &findData);
	if (handle == -1)        // 检查是否成功
		return;

	do
	{
		// 是否是子目录并且不为"."或".."
		if (findData.attrib & _A_SUBDIR)
		{
			if (strcmp(findData.name, ".") == 0
				|| strcmp(findData.name, "..") == 0)
			{
				continue;
			}
			subdirs.push_back(findData.name);
		}
		else
		{
			//cout <<"file:"<< findData.name << endl;
		}

	} while (_findnext(handle, &findData) == 0);    // 查找目录中的下一个文件
													//cout << "Done!\n";
	_findclose(handle);
}



void ToUnixPath(std::string& s)
{
	std::string::size_type pos = 0;

	while ((pos = s.find('\\', pos)) != std::string::npos)
	{
		s.replace(pos, 1, "/");
		pos = pos + 2;
	}
}

bool createMultiDir(const std::string& strPath)
{
	std::string path = strPath;
	ToUnixPath(path);

	if (path[path.length() - 1] != '/')
	{
		path += '/';
	}

	std::string::size_type pos = 0;
	std::string::size_type prevpos = 0;
	std::string tempStr(path);
	std::string strFolderPath;
	std::string stdFolderName;

	while ((pos = tempStr.find('/', pos)) != std::string::npos)
	{
		strFolderPath = tempStr.substr(0, pos);
		stdFolderName = tempStr.substr(prevpos, pos - prevpos);
		if (_access(strFolderPath.data(), 0) == 0)//目录存在
		{
			pos = pos + 1;
			prevpos = pos;
			continue;
		}
		_mkdir(strFolderPath.data());
		pos = pos + 1;
		prevpos = pos;
	}

	if (_access(path.data(), 0) == 0)
	{
		return true;
	}

	return false;
}

SysLog.h

/*M///

//M*/
#ifndef _CLASS_SYS_LOG_H_
#define _CLASS_SYS_LOG_H_

#include <stdio.h>
#include <string>
#include "common_platform.h"


struct tagDate
{
	int year;
	int month;
	int day;
};


class CSysLog
{
public:
	CSysLog(bool isUseWriteMutexMode = true);

public:
    ~CSysLog(void);
    int      WriteLogFile(const char *fmt, ...);
    int      WriteLogFile(const std::string& content);

	//设置日志文件的基路径目录,路径请以'/'结尾. 若该目录不存在则创建.
	void    SetRootPath(std::string rootPath);
	void    SetAppName(std::string appName);
	//设置是否删除过期的日志文件. maxDays=0:无保质期,不删除;  maxDays>0:保质期天数.
	void    SetLogOverdueDays(int maxDays);

private:
    void     SetWriteUseMutexMode(bool flag);
 
private:
    char        m_log_dir_path[256];
    FILE *      m_fp;                 //文件描述符
    int         m_day;
    int         changefile;       //是否切换文件,为1时切换
    std::string m_appName; //日志文件名的一个固定标识
    int         sameFileFlag;     //一天可能会有多个日志文件(量大时切分为多个文件存储),对应的日志文件名中会有0,1,2...的标识
	std::string m_rootPath;
    bool        m_isUseMutexMode;//是否启用互斥写日志
	int         m_ExpiredDays;//日志的过期天数
 
#ifdef OS_WINDOWS
    CRITICAL_SECTION m_mutexLock;
#else
    pthread_mutex_t  m_mutexLock;
#endif

private:
	//找parentDirPath(以'/'结尾)下的所有子目录(不递归),根据判断子目录的命名(2022-11-07)来判断其是否过期,
	//若过期,则删除该子目录下的m_appName.log文件.
    void        ClearLog(std::string parentDirPath, uint32_t nDays, tagDate curYYMMDD); // 删除N天前的日志;

public://现在其余地方也想使用该以下成员函数,将其改为公共、静态。
	//以下是日期比较,根据是文件名上的日期和当前系统日期
    /*static*/ int  IsLeapYear(int year);//闰年
    /*static*/ int  GetLastDay(tagDate date);
    /*static*/ int  IsDateValid(tagDate date);
    /*static*/ void AddDay(tagDate *date);
    /*static*/ int  Compare(tagDate date1, tagDate date2);
    /*static*/ long DateDiff(tagDate date1, tagDate date2);//返回日期比较的结果
};

#endif //_CLASS_LOG_MY

SysLog.cpp

/*M///

//M*/
#include <iostream>
#include <direct.h>
#include <io.h>
#include <regex>
#include <iostream>
using namespace std;

#include "SysLog.h"

#include "Tool.h"

#define _ONE_LOG_FILE_MAX_SIZE  (52428800)//10485760 10M, 52428800 50*1024*1024 50M
#define _DELETE_PRE_DATE_LOG    (180) //N天前的记录

#define MaxJsonBuffer 1024

//不同系统的路径分割符
#ifdef OS_WINDOWS
const char PATH_SEPARATOR = '/';
#else
const char PATH_SEPARATOR = '/';
#endif

//日志文件的后缀
#define LOG_FILE_SUFF	".log"


CSysLog::CSysLog(bool isUseWriteMutexMode)
{
	m_fp = NULL;
	m_day = -1;
	m_appName = "default_app_name";
	changefile = 0;
	sameFileFlag = 0;
	m_ExpiredDays = _DELETE_PRE_DATE_LOG;

	//写日志的互斥模式
	SetWriteUseMutexMode(isUseWriteMutexMode);
	if (isUseWriteMutexMode)
	{
		//初始化互斥变量
#ifdef OS_WINDOWS //Windows系统
		InitializeCriticalSection(&m_mutexLock);
#else   //类Unix系统
		int res = pthread_mutex_init(&m_mutexLock, NULL);
		if (res != 0)
		{
			exit(-1);
		}
#endif
	}
}


CSysLog::~CSysLog(void)
{
	if (m_fp != NULL)
	{
		fclose(m_fp);
		m_fp = NULL;
	}

#ifdef OS_WINDOWS
	DeleteCriticalSection(&m_mutexLock);
#else
	pthread_mutex_destroy(&m_mutexLock);
#endif
}


//路径请以'/'结尾.
void  CSysLog::SetRootPath(std::string rootPath)
{
	if (rootPath.at(rootPath.length() - 1) != PATH_SEPARATOR)
	{
		std::string tmp = &PATH_SEPARATOR;
		rootPath.append(tmp);
	}
	
	m_rootPath = rootPath;
	memset(m_log_dir_path, 0, sizeof(m_log_dir_path));
	strcpy(m_log_dir_path, m_rootPath.c_str());//c_str必带 /0 

#ifdef OS_WINDOWS
	if (_access(m_log_dir_path, 0) == -1)
		createMultiDir(m_rootPath);
		//_mkdir(m_log_dir_path);//如果不存在,创建目录
#else
	if (access(log_dir_path, 0) == -1)
		mkdir(log_dir_path, 0xFFFFFFFF/*S_IROTH|S_IWOTH|S_IXOTH*/);//如果不存在,就创建子目录
#endif
}

void   CSysLog::SetAppName(std::string appName)
{
	if (appName.empty() || appName.length() > sizeof(m_appName))
		return;
	
	m_appName = appName;
}

void  CSysLog::SetLogOverdueDays(int maxDays)
{
	if (maxDays >= 0)
		m_ExpiredDays = maxDays;
}
 
 int  CSysLog::WriteLogFile(const std::string& content)
 {
   const char* fmt = content.data();
   return WriteLogFile(fmt);
 }

int CSysLog::WriteLogFile(const char *fmt, ...)
{
    if (strlen(fmt) > MaxJsonBuffer)
		return 0;

	char pLoginfo[MaxJsonBuffer];
	memset(pLoginfo, 0x00, MaxJsonBuffer);//清空
	va_list ap;
    
    int year = 0, month = 0,cur_day = 0;

#ifdef OS_WINDOWS //Windows系统
    SYSTEMTIME   sys;
    GetLocalTime( &sys );
    //日志时间
    sprintf(pLoginfo,"[%4d-%02d-%02d %02d:%02d:%02d.%03d] ",sys.wYear, sys.wMonth, sys.wDay, sys.wHour, sys.wMinute, sys.wSecond,sys.wMilliseconds);
    year = sys.wYear;
    month = sys.wMonth;
    cur_day = sys.wDay;
#else   //类Unix系统
    struct timeval tv;
    struct timezone tz;
    struct tm *t;
    gettimeofday(&tv, &tz);
    t = localtime(&tv.tv_sec);
    //日志时间
    sprintf(pLoginfo,"[%4d-%02d-%02d %02d:%02d:%02d.%03d] ",
            1900 + t->tm_year, 1 + t->tm_mon, t->tm_mday, t->tm_hour, t->tm_min, (int)t->tm_sec, (int)(tv.tv_usec/1000));

    year = 1900 + t->tm_year;
    month = 1 + t->tm_mon;
    cur_day = t->tm_mday;
#endif

	va_start(ap,fmt);
    vsprintf(pLoginfo+strlen(pLoginfo),fmt,ap);
	va_end(ap);

	if (m_isUseMutexMode)
    {
#ifdef OS_WINDOWS
        EnterCriticalSection(&m_mutexLock);
#else
        pthread_mutex_lock(&m_mutexLock);
#endif
	}

	char yymmdd[32] = { 0 };
	sprintf(yymmdd, "%4d-%02d-%02d", year, month, cur_day);

	tagDate curYYMMDD;
	curYYMMDD.year = year;
	curYYMMDD.month = month;
	curYYMMDD.day = cur_day;

	//yymmddFullPath为诸如C:/Log/2022-11-04/
	std::string yymmddFullPath = m_rootPath + yymmdd + PATH_SEPARATOR;

	//检查并创建诸如C:/Log/2022-11-04/目录
#ifdef OS_WINDOWS
	if (_access(yymmddFullPath.data(), 0) == -1)
		createMultiDir(yymmddFullPath);
			//_mkdir(yymmddFullPath.data());//如果不存在,创建目录
#else
		if (access(yymmddFullPath.data(), 0) == -1)
			mkdir(yymmddFullPath.data(), 0xFFFFFFFF/*S_IROTH|S_IWOTH|S_IXOTH*/);//如果不存在,就创建子目录
#endif

#ifdef OS_WINDOWS
		if (_access(yymmddFullPath.data(), 0) == -1)
		{
			if (m_isUseMutexMode)
				LeaveCriticalSection(&m_mutexLock);
			return 0;
		}
#else
		if (access(yymmddFullPath.data(), 0) == -1)
		{
			if (m_isUseMutexMode)
				pthread_mutex_unlock(&m_mutexLock);
			return 0;
		}
			
#endif
		logFileFullPath为诸如C:/Log/2022-11-04/myname.log
		std::string logFileFullPath = yymmddFullPath  + m_appName + LOG_FILE_SUFF;
		//检查logFileFullPath是否存在
		bool isLogFileExist = true;
#ifdef OS_WINDOWS
		if (_access(logFileFullPath.data(), 0) == -1)
			isLogFileExist = false;//如果不存在,创建目录
#else
		if (access(logFileFullPath.data(), 0) == -1)
			isLogFileExist = false;
#endif
		if (isLogFileExist)//日志文件已经存在
		{
			if (m_fp == NULL)
			{
				m_fp = fopen(logFileFullPath.data(), "a+");//a+ : 可读可写, 可以不存在, 必不能修改原有内容, 只能在结尾追加写, 文件指针只对读有效 (写操作会将文件指针移动到文件尾)
				if (m_fp == NULL)
				{
					if (m_isUseMutexMode)
					{
#ifdef OS_WINDOWS
						LeaveCriticalSection(&m_mutexLock);
#else
						pthread_mutex_unlock(&m_mutexLock);
#endif
					}

					return 0;
				}
			}
		}
		else//日志文件不存在
		{
			if (m_fp != NULL)//需要关闭之前旧的文件描述符
			{
				fflush(m_fp);
				fclose(m_fp);
				m_fp = NULL;
			}

			//新建日志文件
			m_fp = fopen(logFileFullPath.data(), "a+");//a+ : 可读可写, 可以不存在, 必不能修改原有内容, 只能在结尾追加写, 文件指针只对读有效 (写操作会将文件指针移动到文件尾)
			if (m_fp == NULL)
			{
				if (m_isUseMutexMode)
				{
#ifdef OS_WINDOWS
					LeaveCriticalSection(&m_mutexLock);
#else
					pthread_mutex_unlock(&m_mutexLock);
#endif
				}
				return 0;
			}
		}

	//无论如何还是能写入的
	fwrite(pLoginfo,strlen(pLoginfo),1,m_fp);//向fp写入pLoginfo中前strlen(pLoginfo)的内容,成功返回1,否则为其他
    fwrite("\n", 1, 1, m_fp);//增加结束符号
	fflush(m_fp);		//刷新

	//写完了,释放锁
	if (m_isUseMutexMode)
	{
#ifdef OS_WINDOWS //Windows系统
        LeaveCriticalSection(&m_mutexLock);
#else   //类Unix系统
        pthread_mutex_unlock(&m_mutexLock);
#endif
	}

	//在这里进行目录文件的超时判断
	if (m_ExpiredDays <= 0)
		return 1;
	
	if (m_day == cur_day)//不满足 跨天或是程序刚启动,这时无需进行过期判断
		return 1;

	m_day = cur_day;


	//在	rootPath(诸如C:/Log/)下查找所有的类似于2022-11-04命名规则的子目录,并提取具体日期用于判断是否过期.
	ClearLog(m_rootPath, m_ExpiredDays,  curYYMMDD);
	return 1;

}
 

void  CSysLog::SetWriteUseMutexMode(bool flag)
{
	m_isUseMutexMode = flag;
}
 

//是否闰年  
int CSysLog::IsLeapYear(int year)
{
   bool tmp = (year % 4 == 0)  && (year % 100 != 0);
    return  tmp  ||  (year % 400 == 0) ;
}
 
//得到date.month的最大天数  
int CSysLog::GetLastDay(tagDate date)
{
	int num;
	switch (date.month)
	{
	case 1:
	case 3:
	case 5:
	case 7:
	case 8:
	case 10:
	case 12:
		num = 31;
		break;
	case 2:
		num = 28 + IsLeapYear(date.year);
		break;
	default:
		num = 30;
	}
	return num;
}
 
//日期是否合法  
int CSysLog::IsDateValid(tagDate date)
{
	if (date.year < 0 || date.month < 1 || date.month> 12)
		return 0;
 
	if (date.day <1 || date.day> GetLastDay(date))
		return 0;
 
	return 1;
}
 
//date+1  
void CSysLog::AddDay(tagDate *date)
{
	date->day++;
	if (date->day > GetLastDay(*date))
	{
		date->day = 1;
		date->month++;
		if (date->month > 12)
		{
			date->month = 1;
			date->year++;
		}
	}
}
 
//date1比date2小返回值为1,否则为0  
int CSysLog::Compare(tagDate date1, tagDate date2)
{
	if (date1.year < date2.year)
		return 1;
	if (date1.year <= date2.year && date1.month < date2.month)
		return 1;
	if (date1.year <= date2.year && date1.month <= date2.month && date1.day < date2.day)
		return 1;
 
	return 0;
}

//计算两个日期的间隔天数  
long CSysLog::DateDiff(tagDate date1, tagDate date2)
{
	long delta = 0;
	tagDate date3;
	//若date1 > date2,交换date1,date2  
	if (!Compare(date1, date2))
	{
		date3 = date2;
		date2 = date1;
		date1 = date3;
	}
 
	//date1比date2少时,date1日期加1  
	while (Compare(date1, date2))
	{
		AddDay(&date1);
		delta++;
	}
	return delta;
}

void   CSysLog::ClearLog(std::string parentDirPath, uint32_t nDays, tagDate curYYMMDD)
{
#ifdef OS_WINDOWS
	if (_access(parentDirPath.data(), 0) == -1)
		return;
#else
	if (access(parentDirPath.data(), 0) == -1)
		return;
#endif

	if (parentDirPath.length() <= 1)
		return;
	
	std::vector<std::string>		subdirs;
	//查找某目录下的子目录,不递归
	listSubdir(parentDirPath.data(), subdirs);

	//正则表达式筛选诸如'2022-11-07'规则命名的子目录
	std::regex  pattern("[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}");
	for each (auto var in subdirs)
	{
		smatch results;
		if (std::regex_match(var, results, pattern))
		{
			smatch::iterator it = results.begin();
			int i = 0;
			for (; it != results.end(); ++it, ++i)
			{
				std::string childDir = *it;
				tagDate		dtBegin;
				dtBegin.year = atoi(childDir.substr(0, 4).data());
				dtBegin.month = atoi(childDir.substr(5, 2).data());
				dtBegin.day = atoi(childDir.substr(8, 2).data());

				long diff = DateDiff(dtBegin, curYYMMDD);

				if (diff <= nDays)//没有过期
					continue;

				//删除过期的日志文件
				std::string tmp = &PATH_SEPARATOR;
				if (parentDirPath.at(parentDirPath.length() - 1) != PATH_SEPARATOR)
					parentDirPath.append(tmp);
				
				std::string logToDel = parentDirPath + var + tmp + m_appName + LOG_FILE_SUFF;
				remove(logToDel.c_str());
			}
		}
	}
}

 common_platform.h与模式一 一致

模式二调用范例:

#include "stdafx.h"
#include <iostream>
#include <regex>
#include "SysLog.h"
#include "Tool.h"

using namespace std;

int main()
{
	std::string dirname = "C:/Users/zhangsan/Desktop/TestLog/TestLog/ABCDE/LogDir/";

	bool isUseWriteMutexMode = true;
	CSysLog  logger(isUseWriteMutexMode);

	logger.SetAppName("myapp");// 日志文件名为myapp.log
	logger.SetRootPath(dirname);
	logger.SetLogOverdueDays(60);

	logger.WriteLogFile("你好啊   啊 啊啊啊 啊啊啊啊啊啊 啊啊啊啊啊啊啊啊啊 ");
	logger.WriteLogFile("%s is %d years old", "sam", 25);

	return 0;
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

程序日志模块的两种模式 的相关文章

  • 计算机专业术语,收藏用

    显示内存 与主板上的内存功能一样 xff0c 显存也是用于存放数据的 xff0c 只不过它存放的是显示芯片处理后的数据 显存越大 xff0c 显示卡支持的最大分辨率越大 xff0c 3D应用时的贴图精度就越高 xff0c 带3D加速功能的显
  • 7_资源文件添加

    资源文件添加 在QMainWindow中我们已经设立了一些部件 xff0c 现在我们来设置图标 通常有相对路径读取和资源文件读取两种方法 QAction actionNew 61 new QAction 基本格式 ui gt actionN
  • 智能除草机

    专利设计 xff1a 智能除草机 1 除草机背景 业界主要采用滚刀式草坪修剪车来进行运动场的除草 除草不仅浪费人力 xff0c 而且也受到天气状况的限制 传统割草机产生的环境污染也较大 随着社会经济的不断发展 xff0c 人力成本也不断攀升
  • nodejs解析http协议源码解析

    上篇文章讲到nodejs创建一个服务并且建立tcp连接的过程 接下来分析一下 xff0c 在建立tcp连接后 xff0c nodejs是如何解析http协议的 我们首先看一下nodejs在建立tcp连接时执行net js层的回调时做了什么操
  • template模板及模板类的实例化

    模板template 通常 xff0c 当我们调用一个函数时 xff0c 编译器只需要掌握函数的声明 类似的 xff0c 当我们使用一个类类型的对象时 xff0c 类定义必须是可用的 xff0c 但成员函数的定义不必已经出现 因此我们将类定
  • java学习简记录(四)

    java学习简记录 xff08 四 xff09 Java 程序是一系列对象的集合 xff0c 这些对象通过调用彼此的方法来协同工作 对象 xff1a 对象是类的一个实例 xff0c 有状态和行为 类 xff1a 类是一个模板 xff0c 它
  • VS2015+QT5.6.1环境配置后,在VS中双击无法打开*.ui文件

    在环境都搭建好以后 xff0c 在VS中新建了一个QT界面工程 双击 ui后 xff0c 期望得到Qt designer那种直接进入拖拉控件进行编辑的操作界面 but 并不能得到预期结果 解决方法如下 第1步 xff1a 在 解决方案资源管
  • 求字符串的最长回文

    主要锻炼的就是动态规划的思想 xff01 xff01 xff01 掌握这种思想 xff0c 工作中不一定用得上 xff0c 但是多一种思想就多一种可能 span class token comment dp i j 表示s的子串 xff08
  • C++查找指定目录下的特定后缀文件并按照创建时间排序

    在一个项目中 xff0c 遇到了这个需求 于是windows 43 vs平台上实现了这个功能Demo 测试完毕后移植到了具体的项目中 span class token macro property span class token dire
  • Ubuntu下Qt程序生成Core文件便于调试

    需要在运行时生成core dump文件 xff0c 以排查出错的代码行 首先在pro结尾里加入 xff1a QMAKE CC 43 61 g QMAKE CXX 43 61 g QMAKE LINK 43 61 g 在终端输入 ulimit
  • linux查看进程所有子进程和线程

    线程是现代操作系统上进行并行执行的一个流行的编程方面的抽象概念 当一个程序内有多个线程被叉分出用以执行多个流时 xff0c 这些线程就会在它们之间共享特定的资源 xff08 如 xff0c 内存地址空间 打开的文件 xff09 xff0c
  • 一款二进制文件查看器

    由于使用的是Notepad 43 43 64位版本 xff0c 在网上找了很多二进制查看插件HexEditor dll要么是32位不兼容 xff0c 要么是出现除零的错误 xff08 以前找到过一次支持Notepad 43 43 64位版本
  • YOLO目标检测

    一 背景 基于深度学习技术的视觉目标检测近年去的长足发展 但仍然有许多方面问题需要优化 二 YOLO算法的特点 YOLO作为一种性能优异的通用目标检测系统 xff0c 为了保证检测的效率 xff0c 提出one stage的思想 xff0c
  • (Qt中添加编译选项)QT在交叉编译时出现parameter passing for argument of type ‘std::_Rb_tree xxxxx changed in GCC 7.1

    QT版本都是5 1x 先是在Ubuntu机器上写的代码 xff0c GCC版本为5 4 xff0c 代码编译无 任何警告 后来移植到开发板 xff08 GCC版本为7 1 xff09 进行编译时 xff0c 提示这种警告 发生在代码中对st
  • C++按行读取文本并解析

    项目中需要按行读取文本文件 xff0c 并对每一行内容进行解析 每一行都是固定的字段数 xff0c 字段之间用空格隔开 span class token macro property span class token directive k
  • error C2447: “{”: 缺少函数标题(是否是老式的形式表?)

    error C2447 缺少函数标题 是否是老式的形式表 网上有人说 这个BUG是因为在win7上使用了 LF 的格式编码导致的 使用Notepad 43 43 修改成 BOM UTF8 和 windows 的 CR LF 格式一切正常 确
  • Visual Studio 2017 代码自动对齐

    点 编辑 高级 设置选定内容的格式 或者按Ctrl 43 K 然后再按Ctrl 43 F 就好了 你可以在常用快捷键自定义 窗口中进行查看 1 进入工具 选项 对话框 2 选择 环境 键盘 3 在 显示命令包含 下面的对话框中输入 对齐 关
  • CSDN 排版之颜色、字体、字号及背景色

    颜色 xff1a span class token operator lt span font color span class token operator 61 span blue span class token operator g
  • 使用QTCreator编程时,如何利用dmp文件定位程序奔溃

    写这篇文章之前 xff0c 看了一些其他人的博客 xff0c 但不是很详细 xff0c 缺这少那的 xff0c 好多都是复制粘贴别人的东西 自己动手弄了弄 xff0c 可以使用 xff0c 就记下来备忘与分享 前言 开发环境说明 编程IDE
  • Pytorch中transforms.RandomResizedCrop使用说明

    加载数据 训练集数据扩充 数据增强 和归一化 数据扩充 数据增强 的意义在于通过人为的随机范围裁剪 缩放 旋转等操作 增大训练集中的数据多样性 全面性 进而提高模型的泛化能力 训练集数据扩充和归一化 在验证集上仅需要归一化 data tra

随机推荐

  • C++中调用Python的办法

    1 背景 一直采用C 43 43 作为主语言开发 xff0c 最近遇到一个项目需要解析PDF文件中的文本内容 xff0c 直接采用C 43 43 来做显得不是很方便 xff0c 但用python来做就显得很简单了 难点在于如何C 43 43
  • Qt Creator远程调试嵌入式ARM开发板

    1 环境 Win10 64位系统上通过Virtual Box安装了一个Ubuntu虚拟机 ubuntu的版本 xff1a Linux kernel 4 15 0 142 generic 146 16 04 1 Ubuntu SMP Ubun
  • 套接字(描述符)读取指定的字节数

    检测fd句柄是否可读 xff0c ms毫秒超时 参数 xff1a df in 检测的句柄 ms in 超时 xff0c 毫秒 返回 xff1a 1 可读 xff0c 或者已经断开 0 超时 xff0c 仍然不可读 1 错误 int IsRe
  • 4.4线索二叉树遍历

    1 中序线索二叉树遍历 找到第一个中序遍历的结点 ThreadNode span class token operator span span class token function Firstnode span span class t
  • 自动根据本机字节序 将小端字节序的报文(字符数组)转为整数

    1 xff0c 判断本机的字节序 xff08 大端优先 小端优先 xff09 判断当前PC为大端还是小端字节序 64 返回值 xff1a 1 大端 xff1b 0 小端 int JudgeEndianOfPC int num 61 1 if
  • 智能指针的使用

    智能指针在C 43 43 11版本之后提供 xff0c 包含在头文件 lt memory gt 中 xff0c shared ptr unique ptr weak ptr 1 xff0c shared ptr的使用 shared ptr使
  • 图像检索系列——利用深度学习实现以图搜图

    转载自 xff1a 图像检索系列 利用深度学习实现以图搜图 知乎 前言 在上一篇文章 图像检索系列 利用 Python 检测图像相似度 中 xff0c 我们介绍了一个在图像检索领域非常常用的算法 感知哈希算法 这是一个很简单且快速的算法 x
  • Windows下select模型(以及EAGAIN、EWOULDBLOCK、EINTR)

    在这里记录一下 xff0c 以前都是新项目用到了就从旧项目中拷贝 自从将博客当作记事本 xff0c 发现自己多了一个好习惯 Windows下select模型 程序员攻略 CSDN博客 套接字IO超时设置和使用select实现超时管理 wj
  • RANSAC算法理解

    RANSAC是 RANdom SAmple Consensus xff08 随机抽样一致 xff09 的缩写 它可以从一组包含 局外点 的观测数据集中 xff0c 通过迭代方式估计数学模型的参数 它是一种不确定的算法 它有一定的概率得出一个
  • C++ 环形缓冲区(队列)简单实现

    1 说明 在实际工作中 xff0c 如果数据流量过大 xff0c 可以先把数据接收到数据缓冲区中 xff0c 处理之后再取出 我们定义的包协议可以采用定长包 xff0c 可以采用不定长度的包 xff0c 环形缓冲区都能处理 2 使用场景 2
  • Visual Studio Code (vscode) 配置 C / C++ 环境

    Visual Studio Code vscode 配置 C C 43 43 环境 步平凡 博客园 在电脑安装软件管控严格的情况下 xff0c 想装VS装不了 xff0c 就装轻量版的VSCode了 以上写得很好 xff0c 照做即可 本人
  • c++实现basename

    window API居然不包含Linux中很好用的basename函数 xff0c 实现了一下 xff0c 留个记录 xff0c 省得日后重复写 std string m basename std string fullPath size
  • tortoiseGit教程

    0 前言 TortoiseGit其实是一款开源的git的版本控制系统 xff0c 也叫海龟git TortoiseGit提供了人性化的图形化界面 xff0c 不用像Git一样输入许多语句 xff0c 像git init git add gi
  • 用STL库创建线程

    测试了3种方式 xff1a 1 xff1a 子线程不带返回值 2 xff1a 子线程带返回值 3 xff1a 子线程带引用类型参数 使用join方式 xff0c 让父线程等待子线程运行结束 TestTemp cpp 定义控制台应用程序的入口
  • 4.5树的存储

    双亲表示法 xff0c 孩子表示法 xff0c 孩子兄弟表示法 1 双亲表示法 查找双亲简单 空数据导致遍历更慢 xff0c 查指定节点的孩子只能遍历 span class token keyword typedef span ElemTy
  • Windows下MySQL数据库的安装、配置及C++使用案例

    1 安装及配置 Windows判断本地是否安装mysql以及mysql安装过程 企鹅要去银河思考人生 xff01 xff01 xff01 的博客 CSDN博客 windows查看是否安装mysql 注意按照文中提示 xff0c 配置好环境变
  • C++获取系统毫秒级时间(自1970年1月1日至今的毫秒数)

    跟系统时间相关的 ifdef WIN32 include lt time h gt include lt windows h gt else include lt sys time h gt endif unsigned long long
  • Window 10下SQL Server的安装配置以及C++使用案例

    1 SQL Server2008的安装与配置 参照下面这篇博客实现即可 里面提供了安装包下载方式 xff08 百度网盘有点慢 xff09 安装及配置步骤 SQLServer安装教程 xff08 史上最详细版本 xff09 杨林伟的博客 CS
  • 基于OpenCV实现的多角度、多目标模板匹配项目实战案例

    1 说明 本案例采用NCC的匹配 金字塔 为了加速 思想 基于OpenCV实现的多角度 多目标模板匹配 不支持尺度不变 若研究旋转 尺度不变性的匹配 请参考本人的OpenCV专栏内的 nbsp OpenCV实现多角度多尺度模板匹配 基于形状
  • 程序日志模块的两种模式

    程序员都知道程序的运行日志在不少时候都非常有用 xff0c 利于排查 理清逻辑 一般而言 xff0c 日志都按天生成 xff0c 并且具备自动清理多少天以前的旧日志 xff0c 避免无限增长占用磁盘 下图展示了2种日志模式 模式一 1 xf