其实网上可以找到很多日志库,不过大多太大、考虑得太细致,所以索性自己写个最适合自己用的。基本的需求是当程序在7*24运行时,每天能自动生成当日日志文件;能写入日志信息和操作代码;能按层级显示操作的从属等级。
类定义logger.h如下:
// logger.h
#ifndef __LOGGER_H
#define __LOGGER_H
#include <string>
/**
* Logger 通用日志类。
* 日志文本格式为:yyyy-MM-dd hh:mm:ss:sss - log-message-text, message-code, extension-code.
* 根据不同的level设定,log-message-text前面会有3*N个"---"作为层级标识。
*
* 日志文件名格式为:prefix-yyyy-MM-dd.suffix。其中prefix是程序指定的文件名前缀,默认为"run";suffix是程序指定的文件扩展名。
* 默认为"log"。
* 全局Logger实例runtimeLogger的默认文件名为"run-yyyy-MM-dd.log"。
*/
class Logger
{
public:
Logger();
Logger(const char prefix[], const char suffix[]);
Logger(const std::string& prefix, const std::string& suffix);
virtual ~Logger();
public:
/**
* 写日志信息
* @param msgText 字符数组,存放了日志消息正文
* @param msgCode 整数,存放了日志消息代码,默认值为0
* @param extCode 整数,存放了日志扩展代码,默认值为0
* @param level 整数,指定日志层级,0是最高层级,默认值为0
*/
virtual void write(const char msgText[], int msgCode = 0, int extCode = 0, int level = 0);
/**
* 写日志消息
* @param msgText 字符串对象引用,存放了日志消息正文
* @param msgCode 整数,存放了日志消息代码,默认值为0
* @param extCode 整数,存放了日志扩展代码,默认值为0
* @param level 整数,指定日志层级,0是最高层级,默认值为0
*/
virtual void write(const std::string& msgText, int msgCode = 0, int extCode = 0, int level = 0);
/**
* 向日志文件中写入分隔行。分隔行由一系列相同的字符组成。
* @param sepChar 字符类型常量,指定分隔行字符
* @param length 整型常量,指定分隔行由多少个相同的字符组成
*/
virtual void writeSeperatorLine(const char sepChar, const int length);
/**
* 根据前缀和后缀生成日志文件名
* @param prefix 字符串常量对象引用,指定日志文件名前缀
* @param suffix 字符串常量对象引用,指定日志文件名后缀
* @return 形如prefix-yyyy-MM-dd.suffix的日志文件名字符串
*/
static std::string generateFilename(const std::string& prefix, const std::string& suffix);
/**
* 根据指定的日期、前缀和后缀生成日志文件名
* @param t time_t类型,指定日志文件名的日期部分
* @param prefix 字符串常量对象引用,指定日志文件名前缀
* @param suffix 字符串常量对象引用,指定日志文件名后缀
* @return 形如prefix-yyyy-MM-dd.suffix的日志文件名字符串
*/
static std::string generateFilename(const time_t t, const std::string& prefix, const std::string& suffix);
/**
* 静态方法,向指定的日志文件中写入日志信息。
* @param filename 字符串常量对象引用,指定日志文件名
* @param msgText 字符串对象引用,存放了日志消息正文
* @param msgCode 整数,存放了日志消息代码,默认值为0
* @param extCode 整数,存放了日志扩展代码,默认值为0
* @param level 整数,指定日志层级,0是最高层级,默认值为0
*/
static void write(const std::string& filename, const std::string& msgText, int msgCode = 0, int extCode = 0, int level = 0);
/**
* 静态方法,向指定的日志文件中写入日志信息。
* @param filename 字符串常量对象引用,指定日志文件名
* @param msgText 字符数组,存放了日志消息正文
* @param msgCode 整数,存放了日志消息代码,默认值为0
* @param extCode 整数,存放了日志扩展代码,默认值为0
* @param level 整数,指定日志层级,0是最高层级,默认值为0
*/
static void write(const std::string& filename, const char msgText[], int msgCode = 0, int extCode = 0, int level = 0);
/**
* 静态方法,向指定的日志文件中写入分隔行
* @param filename 字符串常量对象引用,指定日志文件名
* @param sepChar 字符类型常量,指定分隔行字符
* @param length 整型常量,指定分隔行由多少个相同的字符组成
*/
static void writeSeperatorLine(const std::string& filename, const char sepChar, const int length);
/**
* 模板函数,合成可供写入日志的文本
* @tparam T 指定待写入数据类型。该类型必须实现 << 操作符
* @param oper 操作说明字符串
* @param result 操作结果数据
* @return 合成的文本,形如:OPERATOR: xxxxxx. RESULT: yyyyyy
*/
template<typename T> static std::string getLogText(const std::string& oper, const T& result)
{
std::stringstream msgTxt;
msgTxt << "OPERATOR: " << oper << ", RESULT: " << result;
return msgTxt.str();
}
/**
* 获取日志文件名前缀
* @return 日志文件名前缀字符串
*/
const std::string& getPrefix() const;
/**
* 设置日志文件名前缀
* @param prefix 指定日志文件名的新前缀
*/
void setPrefix(const std::string &prefix);
/**
* 获取日志文件名扩展名
* @return 日志文件名扩展名字符串
*/
const std::string& getSuffix() const;
/**
* 设置日志文件名扩展名
* @param suffix 指定日志文件名的新的扩展名
*/
void setSuffix(const std::string &suffix);
protected:
/**
* 将指定的时间转换成日期字符串
* @param t time_t类型常量,指定待转换的时间值
* @param sc 字符类型常量,指定年月日之间的分隔字符,默认为'-'
* @return 返回形如yyyy-MM-dd的日期字符串
*/
static std::string getDateString(const time_t t, const char sc = '-');
/**
* 将指定的时间转换为时刻字符串
* @param t time_t类型常量,指定待转换的时间值
* @param sc 字符类型常量,指定时分秒之间的分隔字符,默认为':'
* @return 返回形如hh:mm:ss的时间字符串
*/
static std::string getTimeString(const time_t t, const char sc = ':');
/**
* 将指定的时间值转换为日期和时间字符串
* @param t time_t类型常量,指定待转换的时间值
* @param dsc 字符常量,指定年月日之间的分隔符,默认为'-'
* @param tsc 字符常量,指定时分秒之间的分隔符,默认为':'
* @return 返回形如yyyy-MM-dd hh:mm:ss的日期及时间字符串
*/
static std::string getDatetimeString(const time_t t, const char dsc = '-', const char tsc = ':');
/**
* 将指定的时间值转换为日期和时间(含毫秒)字符串
* @param t time_t类型常量,指定待转换的时间值
* @param dsc 字符常量,指定年月日之间的分隔符,默认为'-'
* @param tsc 字符常量,指定时分秒之间的分隔符,默认为':'
* @param msc 字符常量,指定秒与毫秒之间的分隔符,默认为'.'
* @return 返回形如yyyy-MM-dd hh:mm:ss.sss的日期及时间(含毫秒)字符串
*/
static std::string getDatetimeString(const timeb& t, const char dsc = '-', const char tsc = ':', const char msc = '.');
private:
std::string prefix;
std::string suffix;
};
extern Logger runtimeLogger;
#endif // __LOGGER_H
类实现logger.cpp代码如下:
// logger.cpp
#include <sys\timeb.h>
#include <fstream>
#include <sstream>
#include <iomanip>
#include "logger.h"
using namespace std;
Logger runtimeLogger("run", "log");
Logger::Logger()
{
this->prefix = "run";
this->suffix = "log";
}
Logger::Logger(const char prefix[], const char suffix[])
{
this->prefix.append(prefix);
this->suffix.append(suffix);
}
Logger::Logger(const std::string &prefix, const std::string &suffix)
{
this->prefix.assign(prefix);
this->suffix.assign(suffix);
}
Logger::~Logger()
{
}
void Logger::write(const char msgText[], int msgCode, int extCode, int level)
{
struct timeb tb;
ftime(&tb);
string fullLogFilename = generateFilename(tb.time, prefix, suffix);
ofstream ofs(fullLogFilename, ios::app);
ofs << getDatetimeString(tb, '-', ':', '.') << " - ";
for(int count = 0; count < level; count ++)
{
ofs << "---";
}
ofs << msgText << ", CODE: " << msgCode << ", " << extCode << "." << endl;
ofs.close();
}
void Logger::write(const string& msgText, int msgCode, int extCode, int level)
{
write(msgText.c_str(), msgCode, extCode, level);
}
std::string Logger::generateFilename(const std::string &prefix, const std::string &suffix)
{
string fullLogFilename = generateFilename(time(nullptr), prefix, suffix);
return fullLogFilename;
}
std::string Logger::generateFilename(const time_t t, const std::string &prefix, const std::string &suffix)
{
string filename = prefix + "-" + getDateString(t, '-') + "." + suffix;
return filename;
}
void Logger::write(const std::string &filename, const std::string &msgText, int msgCode, int exCode, int level)
{
write(filename, msgText.c_str(), msgCode, exCode, level);
}
void Logger::write(const std::string &filename, const char msgText[], int msgCode, int extCode, int level)
{
ofstream ofs(filename, ios::app);
timeb tb;
ftime(&tb);
ofs << getDatetimeString(tb, '-', ':', '.') << " - ";
for(int count = 0; count < level; count ++)
{
ofs << "---";
}
ofs << msgText << ", CODE: " << msgCode << ", " << extCode << "." << endl;
ofs.close();
}
void Logger::writeSeperatorLine(const char sepChar, const int length)
{
string filename = generateFilename(prefix, suffix);
writeSeperatorLine(filename, sepChar, length);
}
void Logger::writeSeperatorLine(const std::string &filename, const char sepChar, const int length)
{
FILE *ofp = fopen(filename.c_str(), "a");
for(int count = 0; count < length; count ++)
{
fprintf(ofp, "%c", sepChar);
}
fprintf(ofp, "\n");
fclose(ofp);
}
std::string Logger::getDateString(const time_t t, const char sc)
{
struct tm *tp = localtime(&t);
ostringstream oss;
oss << setw(4) << setfill('0') << (tp->tm_year + 1900) << sc << setw(2) << setfill('0') << (tp->tm_mon + 1) << sc << setw(2) << setfill('0') << tp->tm_mday;
return oss.str();
}
std::string Logger::getTimeString(const time_t t, const char sc)
{
struct tm *tp = localtime(&t);
ostringstream oss;
oss << setw(2) << setfill('0') << tp->tm_hour << sc << setw(2) << setfill('0') << tp->tm_min << sc << setw(2) << setfill('0') << tp->tm_sec;
return oss.str();
}
std::string Logger::getDatetimeString(const timeb& t, const char dsc, const char tsc, const char msc)
{
ostringstream oss;
oss << getDatetimeString(t.time, dsc, tsc) << msc << setw(3) << setfill('0') << t.millitm;
return oss.str();
}
std::string Logger::getDatetimeString(const time_t t, const char dsc, const char tsc)
{
ostringstream oss;
oss << getDateString(t, dsc) << " " << getTimeString(t, tsc);
return oss.str();
}
const std::string& Logger::getPrefix() const
{
return prefix;
}
void Logger::setPrefix(const std::string &prefix)
{
this->prefix = prefix;
}
const std::string& Logger::getSuffix() const
{
return suffix;
}
void Logger::setSuffix(const std::string &suffix)
{
this->suffix = suffix;
}
代码里有足够的注释,所以不解释了。
测试代码如下:
void testLogger()
{
Logger logger("tc", "log");
logger.write("write a message to the log file with default code, ext code and level.");
logger.write("write a message to the log file with specified code and ext code.", 1, 2);
logger.write("write a message to the log file with specified code, ext code and level.", 1, 2, 1);
logger.write(Logger::getLogText<string>("calling getLogText", "OK"), 1);
logger.write(Logger::getLogText<double>("calling getLogText", 0.142857), 2, 0, 1);
logger.write(Logger::getLogText<int>("calling getLogText", 1024), 3, 1, 2);
}
运行上述代码,将创建日志文件tc-2021-05-03.log,内容如下所示:
2021-05-03 09:18:33.244 - write a message to the log file with default code, ext code and level., CODE: 0, 0.
2021-05-03 09:18:33.244 - write a message to the log file with specified code and ext code., CODE: 1, 2.
2021-05-03 09:18:33.245 - ---write a message to the log file with specified code, ext code and level., CODE: 1, 2.
2021-05-03 09:18:33.245 - OPERATOR: calling getLogText, RESULT: OK, CODE: 1, 0.
2021-05-03 09:18:33.245 - ---OPERATOR: calling getLogText, RESULT: 0.142857, CODE: 2, 0.
2021-05-03 09:18:33.245 - ------OPERATOR: calling getLogText, RESULT: 1024, CODE: 3, 1.
以上日志文本中,每一行是一条日志,开头是动作发生的日期、时间(精确到毫秒),日志时间和日志正文之间用短横线分隔。日志正文前面没有三道短横线“---”的,表明这是顶级操作;前面有3*N个"---"的,表示这是上一条日志中所描述的动作的操作结果。这可以通过设置write(...)函数中的level的值来确定。