日志系统的实现
引言
日志系统是通过文件来记录项目的 调试信息,运行状态,访问记录,产生的警告和错误的一个系统,是项目中非常重要的一部分. 程序员可以通过日志文件观测项目的运行信息,方便及时对项目进行调整.
最简单的日志类 demo
日志类一般使用单例模式实现:
Log.h:
class Log
{
private:
Log() {};
~Log();
public:
bool init(const char* file_name);
void write_log(const char* str);
static Log* getinstance();
private:
FILE* file;
};
Log.cpp:
Log::~Log()
{
if (file != NULL)
{
fflush(file);
fclose(file);
}
}
Log* Log::getinstance()
{
static Log instance;
return &instance;
}
bool Log::init(const char * file_name)
{
file = fopen(file_name,"a");
if (file == NULL)
{
return false;
}
return true;
}
void Log::write_log(const char* str)
{
if (file == NULL)
return;
fputs(str, file);
}
main.cpp:
#include"Log.h"
int main()
{
Log::getinstance()->init("log.txt");
Log::getinstance()->write_log("Hello World");
}
这个日志类实现了最简单的写日志的功能,但是实际应用时,需要在日志系统上开发出许多额外的功能来满足工作需要,有些时候还要进行日志的分类操作,因为你不能将所有的日志信息都塞到一个日志文件中,这样会大大降低可读性,接下来讲一下在这个最简单的日志类的基础上,怎么添加一些新功能.
按天日志分类和超行日志分类
先说两个比较简单的
按天分类和超行分类
按天分类:每一个日志按照天来分类(日志前加上当前的日期作为日志的前缀) 并且写日志前检查日志的创建时间,如果日志创建时间不是今天,那么就额外新创建一个日志,更新创建时间和行数,然后向新日志中写日志信息
超行分类:写日志前检查本次程序写入日志的行数,如果当前本次程序写入日志的行数已经到达上限,那么额外创建新的日志,更新创建时间,然后向新日志中写日志信息
为了实现这两个小功能,我们需要先向日志类中添加以下成员:
- 程序本次启动,写入日志文件的最大行数
- 程序本次启动,已经写入日志的行数
- 日志的创建时间
- 日志的路径名+文件名(创建新日志的时候,命名要跟之前的命名标准一样,最好是标准日志名+后缀的形式,这样便于标识)
更新后的日志类:
Log.h:
#pragma once
#include<stdio.h>
#include<string.h>
#include<string>
#include<time.h>
using namespace std;
class Log
{
private:
Log() ;
~Log();
public:
//初始化文件路径,文件最大行数
bool init(const char* file_name,int split_lines= 5000000);
void write_log(const char* str);
static Log* getinstance();
private:
FILE* file;
char dir_name[128];//路径名
char log_name[128];//日志名
int m_split_lines; //日志文件最大行数(之前的日志行数不计,只记录本次程序启动写入的行数)
long long m_count; //已经写入日志的行数
int m_today; //日志的创建时间
};
Log.cpp
#define _CRT_SECURE_NO_WARNINGS
#include"Log.h"
Log::Log()
{
m_count = 0;
}
Log::~Log()
{
if (file != NULL)
{
fflush(file);
fclose(file);
}
}
Log* Log::getinstance()
{
static Log instance;
return &instance;
}
bool Log::init(const char * file_name,int split_lines)
{
m_split_lines = split_lines; //设置最大行数
time_t t = time(NULL);
struct tm* sys_tm = localtime(&t);
struct tm my_tm = *sys_tm; //获取当前的时间
const char* p = strrchr(file_name, '/');//这里需要注意下,windows和linux的路径上的 斜杠符浩方向是不同的,windows是\,linux是 / ,而且因为转义符号的原因,必须是 \\
char log_full_name[256] = { 0 };
if (p == NULL) //判断是否输入了完整的路径+文件名,如果只输入了文件名
{
strcpy(log_name, file_name);
snprintf(log_full_name, 255, "%d_%02d_%02d_%s", my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, file_name);
}
else //如果输入了完整的路径名+文件名
{
strcpy(log_name, p + 1);
strncpy(dir_name, file_name, p - file_name + 1);
//规范化命名
snprintf(log_full_name,255, "%s%d_%02d_%02d_%s", dir_name, my_tm.tm_year + 1900, my_tm.tm_mon + 1, my_tm.tm_mday, log_name);
}
m_today = my_tm.tm_mday; //更新时间
file = fopen(log_full_name,"a"); //打开文件,打开方式:追加
if (file == NULL)
{
return false;
}
return true;
}
void Log::write_log(const char* str)
{
if (file == NULL)
return;
time_t t = time(NULL);
struct tm* sys_tm = localtime(&t);
struct tm my_tm = *sys_tm; //获取当前的时间,用来后续跟日志的创建时间作对比
m_count++; //日志行数+1
if (m_today != my_tm.tm_mday || m_count % m_split_lines == 0) //如果创建时间!=当前时间或者本次写入行数达到上限
{
char ne