实现自己的日志
日志简介
- 记录程序的日常运行状态,按条记录,记录内容包括:时间,模块,日志的级别(致命,出错,警告,信息,调试),输出位置(终端,指定文件,系统日志)
级别 | 详细 |
---|---|
DEBUG | 为程序的调试信息,最低级 |
INFO | 为一般要显示的信息,比如登录登出 |
ERROR | 为严重错误 主要是程序的错误 |
WARN | 为一般警告,比如session丢失 |
FATAL | 崩溃,整个程序终止运行 |
日志的格式说明
- 日志的信息格式
- 日期 + 级别 +模块+ 文件名+行号+函数+具体的信息
实现思想
打印时间
调用系统的time系列函数设计一个Getcurrenttime()
string Getcurrenttime() { time_t result; result = time(NULL); char* p=NULL; p= asctime(localtime(&result)); return string(p); }
错误思路:
设计这个函数的时候man查看了time系列函数,当时设计返回值是void的,打印时间的功能是在Getcurrenttime()内部实现的,但是这样在外面调用的时候不方便,因为我要输出很多的信息,并且是链式操作,所以我就改成返回string类型,在外面就可以输出时间信息并且可以接着输出其他的行号和文件名等等。
- 打印具体信息的内置宏
宏 作用 _FILE_
文件名 _LINE_
行号 _func_
函数名 - 日志级别
- 在不同的地方调用是不一样的,程序开始是调试,程序出错是出错。
- 输出的位置
- 默认输出终端
- 指定路径就输出到指定的路径
日志1.0版本
- 此处使用了astyle工具,将代码规范化,可以选择linux,google标准等等,是一个很方便的工具。
#include<iostream> #include<cstdlib> #include<time.h> #include<cstdio> #include<cstring> using namespace std; //得到当前时间的函数 string Getcurrenttime() { time_t result; result = time(NULL); char* p=NULL; p= asctime(localtime(&result)); return string(p); } //实现两个数相加的函数 long Add(long a,long b) { //输出日志模块 cout<<Getcurrenttime()<<"[调试]"<<"Main"<<__FILE__<<__LINE__<<__func__<<endl; return a+b; } //主函数:Main函数 int main(int argc,char** argv) { //输出日志模块 cout<<Getcurrenttime()<<"[调试]"<<"Main"<<__FILE__<<__LINE__<<__func__<<endl; if(3 != argc) { cout << "参数个数错误"<< endl; cout << "usage:" << argv[0] << "<数字1> <数字2>" << endl; return 1; } char* end = NULL; long a = strtol(argv[1],&end,10); if('\0' != *end) { cout << "第一个参数格式错误。" << endl; cout << "参数应该是整形数字" << endl; return 1; } long b = strtol(argv[2],&end,10); if('\0' != *end) { cout << "第二个参数格式错误。" << endl; cout << "参数应该是整形数字" << endl; return 1; } //调用相加函数 long c=Add(a,b); cout<<c<<endl; return 0; cout<<"end"<<endl; }
- 实现效果
[root@foundation45 include]# ./add 2 3 Wed Apr 18 18:52:26 2018 [调试]Maintinylogger1.cpp24main Wed Apr 18 18:52:26 2018 [调试]Maintinylogger1.cpp18Add 5
这个时候我们看到这个日志的输出格式需要修改,中间加一些空格输出会更加美观,同时我们看到代码重复,所以我们考虑用宏定义来使代码不会看起来那么冗长(因为宏定义还是会在预编译阶段替换的,所以其实并不会实际上减少代码)
日志2.0版本
#include<iostream> #include<cstdlib> #include<time.h> #include<cstdio> #include<cstring> using namespace std; #define PRINT_LOG(level,module,msg)\ cout<<Getcurrenttime()<<"["<<level<<"]"<<'\t'<<module<<'\t'<<__FILE__<<'\t'<<__LINE__<<'\t'<<__func__<<'\t'<<msg<<endl; string Getcurrenttime() { time_t result; result = time(NULL); char* p=NULL; p= asctime(localtime(&result)); return string(p); } long Add(long a,long b) { PRINT_LOG("调试","Add","程序开始") PRINT_LOG("调试","Add","程序结束") return a+b; } int main(int argc,char** argv) { PRINT_LOG("调试","Main","程序开始") if(3 != argc) { cout << "参数个数错误"<< endl; cout << "usage:" << argv[0] << "<数字1> <数字2>" << endl; return 1; } char* end = NULL; long a = strtol(argv[1],&end,10); if('\0' != *end) { cout << "第一个参数格式错误。" << endl; cout << "参数应该是整形数字" << endl; return 1; } long b = strtol(argv[2],&end,10); if('\0' != *end) { cout << "第二个参数格式错误。" << endl; cout << "参数应该是整形数字" << endl; return 1; } long c=Add(a,b); cout<<"a"<<"+"<<"b"<<"="<<c<<endl; PRINT_LOG("调试","Main","程序结束") return 0; }
- 实现效果
[root@foundation45 include]# ./a.out 2 3 Wed Apr 18 19:19:06 2018 [调试] Main tinylogger2.cpp 26 main 程序开始 Wed Apr 18 19:19:06 2018 [调试] Add tinylogger2.cpp 19 Add 程序开始 Wed Apr 18 19:19:06 2018 [调试] Add tinylogger2.cpp 20 Add 程序结束 a+b=5 Wed Apr 18 19:19:06 2018 [调试] Main tinylogger2.cpp 50 main 程序结束
我们实际的日志是有级别的,DEBUG,INFO,ERROR等等,不可以输出用户自定义的级别,因此我们对这个再次封装。
#define PRINT_LOG(level,module,msg)\ cout<<Getcurrenttime()<<"["<<level<<"]"<<'\t'<<module<<'\t'<<__FILE__<<'\t'<<__LINE__<<'\t'<<__func__<<'\t'<<msg<<endl; #define ERROR(module,msg) PRINT_LOG("错误",module,msg) #define DEBUG(module,msg) PRINT_LOG("调试",module,msg) #define INFO(module,msg) PRINT_LOG("信息",module,msg)
修改后的代码
#include<iostream> #include<cstdlib> #include<time.h> #include<cstdio> #include<cstring> using namespace std; #define PRINT_LOG(level,module,msg)\ cout<<Getcurrenttime()<<"["<<level<<"]"<<'\t'<<module<<'\t'<<__FILE__<<'\t'<<__LINE__<<'\t'<<__func__<<'\t'<<msg<<endl; #define ERROR(module,msg) PRINT_LOG("错误",module,msg) #define DEBUG(module,msg) PRINT_LOG("调试",module,msg) #define INFO(module,msg) PRINT_LOG("信息",module,msg) string Getcurrenttime() { time_t result; result = time(NULL); char* p=NULL; p= asctime(localtime(&result)); return string(p); } long Add(long a,long b) { DEBUG("Add","程序开始"); //PRINT_LOG("调试","Add","程序开始") DEBUG("Add","程序结束"); //PRINT_LOG("调试","Add","程序结束") return a+b; } int main(int argc,char** argv) { DEBUG("Main","程序结束"); //PRINT_LOG("调试","Main","程序开始") if(3 != argc) { cout << "参数个数错误"<< endl; cout << "usage:" << argv[0] << "<数字1> <数字2>" << endl; return 1; } char* end = NULL; long a = strtol(argv[1],&end,10); if('\0' != *end) { cout << "第一个参数格式错误。" << endl; cout << "参数应该是整形数字" << endl; return 1; } long b = strtol(argv[2],&end,10); if('\0' != *end) { cout << "第二个参数格式错误。" << endl; cout << "参数应该是整形数字" << endl; return 1; } long c=Add(a,b); cout<<"a"<<"+"<<"b"<<"="<<c<<endl; DEBUG("Main","程序开始"); //PRINT_LOG("调试","Main","程序结束") return 0; }
- 错误思路:
我们知道c++中内联函数是用来取代宏定义的,这个地方我们可以用内联函数来减少代码的冗长么?
inline void print_log(const string level,const string module,const string msg){ cout<<Getcurrenttime()<<"["<<level<<"]"<<'\t'<<module<<'\t'<<__FILE__<<'\t'< <__LINE__<<'\t'<<__func__<<'\t'<<msg<<endl; }
答案是不可以的,宏定义是在预编译阶段来进行替换的,而内联是在编译阶段来替换的,像
__FILE__
这些内置的宏打印具体的信息用内联函数的话无法打印出具体的行数,而都是内联实现实际的那一个行数。- 错误实现效果如下
[root@foundation45 include]# ./a.out 2 3 Wed Apr 18 21:43:36 2018 [调试] Main tinylogger2.cpp 9 print_log 程序开始 Wed Apr 18 21:43:36 2018 [调试] Add tinylogger2.cpp 9 print_log 程序开始 Wed Apr 18 21:43:36 2018 [调试] Add tinylogger2.cpp 9 print_log 程序结束 a+b=5 Wed Apr 18 21:43:36 2018 [调试] Main tinylogger2.cpp 9 print_log 程序结束
因为c++是面向对象编程的,因此我们考虑将日志封装起来,作为一个对象,对外提供接口,同时将这个日志打包到静态库中,这样子就可以在任何我们使用的程序中使用打印日志来给我们提供必要的信息。
日志3.0版本
- TinyLogger.h文件
#include <iostream> #include <cstdlib> #include <ctime> #include <string> #include <fstream> using namespace std; string GetCurrentTime() { time_t t = time(NULL); struct tm* temp = localtime(&t); char tstr[21]= {0}; strftime(tstr,sizeof(tstr),"%F %T ",temp); return string(tstr); } // 如何控制日志输出到终端还是文件? class TinyLoger { public: // 为什么不能使用下面的方式? //OS_是左值引用,而此时的初始化列表中的ofstream(path.c_str())是一个匿名对象,是不可以给右值引用赋初始值的。 // TinyLoger():os_(cout){} // TinyLoger(string const& path):os_(ofstream(path.c_str())){} TinyLoger(ostream& os):os_(os) { if(!os_) { cerr << "OS存在问题" << endl; } } void PrintLog(string const& level,string const& module,string const& file,int line,string const& func,string const& msg) { if(os_) { os_ << GetCurrentTime() <<"[" <<level<<"]"<<'\t'<< module<<'\t'<< file<<'\t' << ":"<< line <<'\t'<< func <<'\t'<< msg << endl; } else { cerr << "OS存在问题" << endl; } } static TinyLoger* CreateLogger(string const& path = "") { //日志只能有一个,如果已经被创建,就继续抛出自己。 if(NULL != s_logger) { return s_logger; } if(path == "") { s_logger = new TinyLoger(cout); } else { static ofstream ofs(path.c_str()); s_logger = new TinyLoger(ofs); } return s_logger; } private: ostream& os_; static TinyLoger* s_logger; }; TinyLoger* TinyLoger::s_logger = NULL; // 把全局函数写到相关类中(为什么?)面向对象编程思想 //TinyLoger* CreateLogger(string const& path = ""){ // if(path == ""){ // return new TinyLoger(cout); // }else{ // static ofstream ofs(path.c_str()); // return new TinyLoger(ofs); // } //} // 问什么要定义全局变量?整个程序/系统中只有一个日志实例。 // 为什么要使用一个CreateLogger()函数?用来决定日志输出位置? // 取消全局变量(为什么要取消全局变量?) //extern TinyLoger* g_logger; #define PRINT_LOG(level,module,msg) \ TinyLoger::CreateLogger()->PrintLog(level,module,__FILE__,__LINE__,__func__,msg); #define ERROR(module,msg) PRINT_LOG("错误",module,msg) #define DEBUG(module,msg) PRINT_LOG("调试",module,msg) #define INFO(module,msg) PRINT_LOG("信息",module,msg)
- TinyLogger.cpp文件
#include<iostream> #include<cstdlib> #include<time.h> #include<cstdio> #include<cstring> #include<fstream> #include "TinyLog.h" using namespace std; long Add(long a,long b) { DEBUG("Add","程序开始"); DEBUG("Add","程序结束"); return a+b; } int main(int argc,char** argv) { DEBUG("Main","程序结束"); if(3 != argc) { ERROR("Main","参数个数错误"); cout << "usage:" << argv[0] << "<数字1> <数字2>" << endl; return 1; } char* end = NULL; long a = strtol(argv[1],&end,10); if('\0' != *end) { cout << "第一个参数格式错误。" << endl; cout << "参数应该是整形数字" << endl; return 1; } long b = strtol(argv[2],&end,10); if('\0' != *end) { cout << "第二个参数格式错误。" << endl; cout << "参数应该是整形数字" << endl; return 1; } long c=Add(a,b); cout<<"a"<<"+"<<"b"<<"="<<c<<endl; DEBUG("Main","程序开始"); return 0; }
- 实现效果
[kiosk@foundation45 include]$ ./a.out 3 45 2018-04-19 00:54:14 [调试] Main tinylogger4.cpp :20 main 程序结束 2018-04-19 00:54:14 [调试] Add tinylogger4.cpp :11 Add 程序开始 2018-04-19 00:54:14 [调试] Add tinylogger4.cpp :13 Add 程序结束 a+b=48 2018-04-19 00:54:14 [调试] Main tinylogger4.cpp :44 main 程序开始
- 代码优化(linux下终端输出可以设置颜色,我们希望打印警告级别日志是红色,调试级别日志是绿色等等)
日志5.0版本
- 修改处
#define NONE "\033[m" #define RED "\033[0;32;31m" #define GREEN "\033[0;32;32m" #define BLUE "\033[0;32;34m" #define YELLOW "\033[0;32;31m"
os_ << GetCurrentTime() << RED"[" <<level<<"]"<<'\t'<<BLUE<< module<<NONE<<'\t'<< file<< ":"<< line <<'\t'<< func <<'\t'<< msg << endl;
- 实现效果
-
左值引用和右值引用