本篇博客是基于实验楼12期楼赛第二题进行修改得到的。
赛题描述如下:
在软件运行中,系统一般会采用一个持久化的日志系统来记录运行情况。
本题你需要实现一个简易的 logger 日志类,这个类主要负责辅助调试,将日志信息输出到一个日志文件中。
比赛预期的输出为同一目录下的 shiyanloulogger.log日志文件,文件的内容如下:
shiyanlou logger test start[2017.07.20 16:13:02 Thursday]
info info info[2017.07.20 16:13:02 Thursday]
shiyanlou logger test end[2017.07.20 16:13:02 Thursday]
程序的主要目标为:
1. 实现 Logger 简单的单例实现(自动垃圾回收)
2. 日志文件需要使用追加的方式添加日志
C++单例模式也称为单件模式、单子模式。使用单例模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。有很多地方需要这样的功能模块,如系统的日志输出等。参考1
在具体实现的时候需要考虑两个问题:
1. 单例模式的实现问题
这样的例子有很多,参考1,单例模式的好处就是保证这个类的实例是唯一的,从而不会产生冲突。
2. 日志文件流的生命周期
在进行日志输出的时候,当日志对象创建的时候,日志文件流就应该已经创建。当日志对象结束的时候,文件流也应当关闭。这样不用每次写一行日志的时候就要重新生成打开关闭文件,效率较高。
3. 数据释放问题
可以在日志类里面创建一个私有类,在程序运行结束时,自动调用私有类的析构函数,在析构函数中进行释放当前日志对象和关闭文件等操作。这样,不需要在外部函数中进行释放,不用关心日志对象的释放问题。
最终日志对象的头文件easyLogger.h
如下:
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <memory>
#include <ctime>
#include <iostream>
#include <fstream>
using namespace std;
class easyLogger
{
public:
static easyLogger *myInst()
{
if (NULL == _instance)
{
_instance = new easyLogger();
// write to easyLogger.log
_instance->ofs.open("logger.log",ios::app);
}
return _instance;
}
void Log(const string& logInfo);
private:
easyLogger(void) {}
virtual ~easyLogger(void) {}
friend class auto_ptr<easyLogger>;
static easyLogger *_instance;
char tmp[100];
ofstream ofs;// 输出文件流
class CGarbo // 它的唯一工作就是在析构函数中删除easyLogger的实例
{
public:
~CGarbo()
{
if(easyLogger::_instance)
{
easyLogger::_instance->ofs.close();//关闭文件流
delete easyLogger::_instance;
easyLogger::_instance = NULL;
}
}
};
static CGarbo Garbo;
};
easyLogger *easyLogger::_instance = NULL;
void easyLogger::Log(const string& logInfo)
{
time_t t = time(0);
strftime(tmp, sizeof(tmp), "[%Y.%m.%d %X %A]", localtime(&t));
ofs << logInfo.c_str()<<tmp<<endl;
}
接下来我们进行日志写入的测试,代码如下:
#include "stdafx.h"
#include <iostream>
#include <ctime>
#include <cstdlib>
#include "easyLogger.h"
#define NUM 100000 // 单次写入的日志条数
using namespace std;
char logdata[NUM][100];
double random(double start,double end)
{
return start+(end-start)*rand()/(RAND_MAX+1.0);
}
// 测试日志写入的效率
int main()
{
// 以当前的时间作为种子,这样每次生成的随机数都是不一样的,否则每次随机数都是一样的
// srand(unsigned(time(0)));
clock_t t1,t2;
int sum_time =0;
// 测试10次
for (int k=0;k<10;k++)
{
// 生成随机的数据
t1 = clock();
for(int i = 0;i<NUM;++i)
{
for(int j =0;j<100-1;++j)
logdata[i][j] = rand()%74+48;
logdata[i][99] = '\0';
}
t2 = clock();
cout<<"生成日志数据"<<NUM<<"条,耗费时间:"<<(t2-t1)<<"ms"<<endl;
// 进行日志写入
t1 = clock();
for(int i = 0;i<NUM;++i)
easyLogger::myInst()->Log(logdata[i]);
t2 = clock();
cout<<"写入日志数据"<<NUM<<"条,耗费时间:"<<(t2-t1)<<"ms"<<endl;
sum_time += t2-t1;
}
cout<<"平均每次花费时间为:"<<sum_time/10<<"ms"<<endl;
system("pause");
return 0;
}
最终在本平台(windows 10 64位+VS2010 +win32 debug模式下)运行结果如下:
程序其实还有需要改进的地方,比如说多线程环境下是否还能保证类的实例是唯一的、如何指定输出路径、以及如何能够进一步提升大量数据情况下的写入性能等等。