一个基于Qt的C++日志类

项目地址

https://gitee.com/cnleika/cpp-log

一、准备工作

一个日志类可以拆分成两个步骤

  1. 打开文件
  2. 写入内容

1. 打开文件

QFile * mfile=new QFile("mylog.log");
mfile->open();

这样就打开一个mylog.log文件了。

2.写入内容

QTextStream * mtextstream =new QTextStream(mfile);
*mtextstream << "hello world";

这样就可以往mylog.log文件中写入一条hello world内容了。

至此,准备工作结束。

二、设计

按照我的设计,一个基础的日志应该包含以下功能:

  1. 配置文件
  2. 日志存储方式:日期.log
  3. 日志内容:时间、级别、文件、行号、内容
  4. 日志存储级别:error > warn > info > debug
  5. 日志更新方式:覆写、追加
  6. 多线程
  7. 限制日志大小

三、读写配置文件

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();
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虚拟内存会梦见进程调度嘛?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值