项目地址
https://gitee.com/cnleika/cpp-log
一、准备工作
一个日志类可以拆分成两个步骤
- 打开文件
- 写入内容
1. 打开文件
QFile * mfile=new QFile("mylog.log");
mfile->open();
这样就打开一个mylog.log文件了。
2.写入内容
QTextStream * mtextstream =new QTextStream(mfile);
*mtextstream << "hello world";
这样就可以往mylog.log文件中写入一条hello world内容了。
至此,准备工作结束。
二、设计
按照我的设计,一个基础的日志应该包含以下功能:
- 配置文件
- 日志存储方式:日期.log
- 日志内容:时间、级别、文件、行号、内容
- 日志存储级别:error > warn > info > debug
- 日志更新方式:覆写、追加
- 多线程
- 限制日志大小
三、读写配置文件
Qt提供了一个QSettings类用于读写ini文件。
logCfg.ini
[LEVEL]
;0:ERROR
;1:ERROR+WARNING
;2:ERROR+WARNING+INFO
;3:ERROR+WARNING+INFO+DEBUG
level=2
[SIZE]
;MB
size=200
[OPTION]
;0覆盖,1追加
option=1
void LogUtils::ReadConfig()
{
QSettings *config=new QSettings("./logCfg.ini",QSettings::IniFormat);
mlevel=config->value("/LEVEL/level").toInt();
qDebug()<< "日志等级:" << QString::number(mlevel);
msize=config->value("/SIZE/size").toInt()*1000000;
qDebug()<< "日志大小:" << QString::number(msize);
moption=config->value("/OPTION/option").toInt();
qDebug()<<"日志选项:"<<(moption==0?"覆盖":(moption==1?"追加":"未知"));
}
四、日志打印
enum Level{
LOG_ERROR,
LOG_WARNING,
LOG_INFO,
LOG_DEBUG
};
const QStringList mLevel={
"ERROR",
"WARNING",
"INFO",
"DEBUG"
};
void LogUtils::log(LogUtils::Level level, QString str,QString file, uintmax_t line)
{
QString text=
"["+mLevel[level]+"]"
+"["+QTime::currentTime().toString("hh:mm:ss")+"]"
+"["+file+"]"
+"["+QString::number(line)+"]"
+":"
+str
;
if(mlevel>=level){
*mtextstream<<text<<endl;
mtextstream->flush();
}
}
到这一步日志大体框架就做好了。
现在我们遇到了第一个问题,如果程序在2023年7月31日运行,在8月1日结束,那么日志只会打印在20230731.log文件中,而没有8月1日及以后的日志。
解决方法:每次打印日志前判断一下日期有没有变动,如果有变动了,就关闭当前的日志,打开新的日志。
if(mfile->fileName()!=getMfilename())
{
close();
open();
}
第二个问题:日志打印频率过快的话,日志文件很容易过大,如何限制日志文件大小。
解决方法:每次打印日志前判断一下日志大小是否高于配置文件设定,若大于设定,则关闭该文件并改名为old,打开新的日志文件。
if(mfile->size()>=msize)
{
mfile->rename(mfile->fileName().insert(mfile->fileName().lastIndexOf("."),"_old"));
}
五、多线程
日志要在子线程中打印,这一点没什么疑问的。在主线程大量读写文件io很容易让程序卡死,及其耗费资源。
LogUtils *LogUtils::GetInstance()
{
if(mLogUtils==NULL)
{
std::thread t([=](){
SetInstance(new LogUtils);
});
t.join();
}
return mLogUtils;
}
六、全部代码
logutils.h
#ifndef LOGUTILS_H
#define LOGUTILS_H
#include <QtGlobal>
#include <QFile>
#include <QObject>
#include <QTextStream>
#include <QThread>
#define LOGCONFIG_PATH "./logCfg.ini"
#define LOGERROR(str) SimpleLog::LogUtils::GetInstance()->log(SimpleLog::LogUtils::Level::LOG_ERROR,str,__FILE__,__LINE__)
#define LOGWARNING(str) SimpleLog::LogUtils::GetInstance()->log(SimpleLog::LogUtils::Level::LOG_WARNING,str,__FILE__,__LINE__)
#define LOGINFO(str) SimpleLog::LogUtils::GetInstance()->log(SimpleLog::LogUtils::Level::LOG_INFO,str,__FILE__,__LINE__)
#define LOGDEBUG(str) SimpleLog::LogUtils::GetInstance()->log(SimpleLog::LogUtils::Level::LOG_DEBUG,str,__FILE__,__LINE__)
namespace SimpleLog {
class LogUtils : public QObject
{
Q_OBJECT
public:
explicit LogUtils(QObject *parent = nullptr);
~LogUtils();
enum Level{
LOG_ERROR,
LOG_WARNING,
LOG_INFO,
LOG_DEBUG
};
const QStringList mLevel={
"ERROR",
"WARNING",
"INFO",
"DEBUG"
};
void log(LogUtils::Level level,QString str, QString file, uintmax_t line);
void ReadConfig();
static LogUtils *GetInstance();
static void SetInstance(LogUtils *logUtils);
void getThreadId();
void open();
void close();
QString getMfilename();
void setMfilename(QString value);
private:
QString mfilename;
QFile *mfile;
QTextStream *mtextstream;
uint8_t mlevel=3;
uintmax_t msize=20000;
uint8_t moption=0;
private:
static LogUtils *mLogUtils;
QThread *th;
signals:
};
}
#endif // LOGUTILS_H
logutils.cpp
#include "logutils.h"
#include <QDate>
#include <QDebug>
#include <QSettings>
#include <thread>
using namespace SimpleLog;
LogUtils *LogUtils::mLogUtils=NULL;
void LogUtils::log(LogUtils::Level level, QString str,QString file, uintmax_t line)
{
if(mfile->size()>=msize)
{
mfile->rename(mfile->fileName().insert(mfile->fileName().lastIndexOf("."),"_old"));
}
if(mfile->fileName()!=getMfilename())
{
close();
open();
}
if(mfile->isOpen()==false)
{
open();
}
QString text=
"["+mLevel[level]+"]"
+"["+QTime::currentTime().toString("hh:mm:ss")+"]"
+"["+file+"]"
+"["+QString::number(line)+"]"
+":"
+str
;
if(mlevel>=level){
*mtextstream<<text<<endl;
mtextstream->flush();
}
}
void LogUtils::ReadConfig()
{
QSettings *config=new QSettings(LOGCONFIG_PATH,QSettings::IniFormat);
mlevel=config->value("/LEVEL/level").toInt();
qDebug()<< "日志等级:" << QString::number(mlevel);
msize=config->value("/SIZE/size").toInt()*1000000;
qDebug()<< "日志大小:" << QString::number(msize);
moption=config->value("/OPTION/option").toInt();
qDebug()<<"日志选项:"<<(moption==0?"覆盖":(moption==1?"追加":"未知"));
}
LogUtils *LogUtils::GetInstance()
{
if(mLogUtils==NULL)
{
std::thread t([=](){
SetInstance(new LogUtils);
});
t.join();
}
return mLogUtils;
}
void LogUtils::SetInstance(LogUtils *logUtils)
{
mLogUtils = logUtils;
}
void LogUtils::getThreadId()
{
qDebug()<<"now LogUtils ThreadId="<<QThread::currentThreadId();
}
void LogUtils::open()
{
mfilename=getMfilename();
mfile=new QFile(mfilename);
switch (moption) {
case 0:
{
/* 覆盖模式 */
mfile->open(QIODevice::Text | QIODevice::WriteOnly);
}
break;
case 1:
{
/* 追加模式 */
mfile->open(QIODevice::Text | QIODevice::WriteOnly | QIODevice::Append);
}
break;
default:
{
return;
}
break;
};
if( mfile->isOpen()== true)
{
mtextstream=new QTextStream(mfile);
mtextstream->setCodec("UTF-8");
*mtextstream<<mfilename<<QString("打开成功")<<endl;
mtextstream->flush();
qDebug()<<mfilename<<"打开成功";
}
else
{
qDebug()<<mfilename<<"打开失败";
return;
}
}
void LogUtils::close()
{
delete mtextstream;
mfile->close();
}
QString LogUtils::getMfilename()
{
QString filename=QDate::currentDate().toString("yyyyMMdd.log");
setMfilename(filename);
return mfilename;
}
void LogUtils::setMfilename(QString value)
{
mfilename = value;
}
LogUtils::LogUtils(QObject *parent)
{
qDebug()<<"new LogUtils";
ReadConfig();
open();
getThreadId();
}
LogUtils::~LogUtils()
{
}
main.cpp
#include <QCoreApplication>
#include <QDate>
#include <QThread>
#include <QDebug>
#include "logutils.h"
#include <thread>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug()<<"Main ThreadId:"<<QThread::currentThreadId();
while(1){
LOGERROR(QTime::currentTime().toString("hhmmss"));
LOGWARNING(QTime::currentTime().toString("hhmmss"));
LOGINFO(QTime::currentTime().toString("hhmmss"));
LOGDEBUG(QTime::currentTime().toString("hhmmss"));
// QThread::sleep(1);
}
return a.exec();
}