原文:https://blog.csdn.net/giser_mxd/article/details/75557410
在软件运行中,系统一般会采用一个持久化的日志系统来记录运行情况。
本题你需要实现一个简易的 logger 日志类,这个类主要负责辅助调试,将日志信息输出到一个日志文件中。
- 1
- 2
比赛预期的输出为同一目录下的 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
- 2
- 3
程序的主要目标为:
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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
接下来我们进行日志写入的测试,代码如下:
#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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
最终在本平台(windows 10 64位+VS2010 +win32 debug模式下)运行结果如下:
程序其实还有需要改进的地方,比如说多线程环境下是否还能保证类的实例是唯一的、如何指定输出路径、以及如何能够进一步提升大量数据情况下的写入性能等等。
localtime()函数:获取当前时间和日期并转换为本地时间
1. time 函数 返回1970-1-1, 00:00:00以来经过的秒数 原型: time_t time(time_t *calptr) 结果可以通过返回值,也可以通过参数得到,见实例 头文件 <time.h> 返回值: 成功:秒数,从1970-1-1,00:00:00 可以当成整型输出或用于其它函数 失败:-1 例: time_t now; time(&now);// 等同于now = time(NULL) printf("now time is %d\n", now); 2. localtime函数 将时间数值变换成本地时间,考虑到本地时区和夏令时标志; 原型: struct tm *localtime(const time_t * calptr); 头文件 <time.h> 返回值: 成功: struct tm *结构体, 原型如下: struct tm { int tm_sec; /* 秒 – 取值区间为[0,59] */ int tm_min; /* 分 - 取值区间为[0,59] */ int tm_hour; /* 时 - 取值区间为[0,23] */ int tm_mday; /* 一个月中的日期 - 取值区间为[1,31] */ int tm_mon; /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */ int tm_year; /* 年份,其值等于实际年份减去1900 */ int tm_wday; /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一 */ int tm_yday; /* 从每年1月1日开始的天数– 取值区间[0,365],其中0代表1月1日 */ int tm_isdst; /* 夏令时标识符,夏令时tm_isdst为正;不实行夏令时tm_isdst为0 */ }; 此结构体空间由内核自动分配, 而且不要去释放它. 失败: NULL 例: time_t now ; struct tm *tm_now ; time(&now) ; tm_now = localtime(&now) ; printf("now datetime: %d-%d-%d %d:%d:%d\n", tm_now->tm_year+1900, tm_now->tm_mon+1, tm_now->tm_mday, tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec) ;void open (const char * filename, openmode mode);
这里filename 是一个字符串,代表要打开的文件名,mode 是以下标志符的一个组合:
ios::in | 为输入(读)而打开文件 |
ios::out | 为输出(写)而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 所有输出附加在文件末尾 |
ios::trunc | 如果文件已存在则先删除该文件 |
ios::binary | 二进制方式 |
这些标识符可以被组合使用,中间以”或”操作符(|)间隔。例如,如果我们想要以二进制方式打开文件"example.bin" 来写入一些数据,我们可以通过以下方式调用成员函数open()来实现:
ofstream file;file.open ("example.bin", ios::out | ios::app | ios::binary);
ofstream, ifstream 和 fstream所有这些类的成员函数open 都包含了一个默认打开文件的方式,这三个类的默认方式各不相同:
类 | 参数的默认方式 |
---|---|
ofstream | ios::out | ios::trunc |
ifstream | ios::in |
fstream | ios::in | ios::out |
只有当函数被调用时没有声明方式参数的情况下,默认值才会被采用。如果函数被调用时声明了任何参数,默认值将被完全改写,而不会与调用参数组合。