程序日志模块的两种模式

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

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

下图展示了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;
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

thequitesunshine007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值