在对已发布程序查找bug时,查看日志就变得非常重要,可以快速帮助我们定位问题,解决问题。所有在程序中添加日志模块就显得非常中重要了。

在写日志模块时首先我们需要了解我们常用的qDebug,qInfo等输出打印信息的目标类QMessageLogger

QMessageLogger类生成日志消息。

QMessageLogger用于为Qt日志框架生成消息。通过qDebug()、qInfo()、qWarning()、qCritical或qFatal()函数来使用它,这些函数实际上都是宏:例如,qDebug()扩展为QMessageLogger(__FILE__, __LINE__, Q_FUNC_INFO).debug()用于调试构建,QMessageLogger(0,0,0).debug()用于发布构建。

了解后再就是需要了解QMessageLogContext类提供关于日志消息的附加信息。该类提供了关于生成qDebug()、qInfo()、qWarning()、qCritical()或qFatal()消息的源代码位置的信息。

至此就可以开始着手编写外层日志模块代码了

// 此处因为是写在main.cpp文件中的,所以用的是c风格来进行编写
void logerOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();
    // 根据设置不同的的前置打印宏判断输出不同打印信息,如果有需要可以给每类信息设置不同的文字颜色来区分
    switch (type)
    {
    case QtDebugMsg:
        fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtInfoMsg:
        fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtWarningMsg:
        fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtCriticalMsg:
        fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    case QtFatalMsg:
        fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function);
        break;
    }
    // 创建单例线程来实时获取记录程序输出的各种日志信息
    LogerThread::instance()->log(msg);
}

安装前面定义的Qt消息处理程序。返回指向上一个消息处理程序的指针。

static void prepareOther()
{
    QDateTime time = QDateTime::currentDateTime();
    int curTime = time.toTime_t();
    // 设置日志多少天删除
    int deleteTime = 7 * 24 * 60 * 60;

    // 是否输出日志
    if(Common::instance()->getIsOutPutLog() == 1)
    {
        // 删除log文件夹里面超过7天的文件
        QDir dir(QApplication::applicationDirPath()+"/log");
        if (dir.exists())
        {
            dir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);
            QFileInfoList fileList = dir.entryInfoList();
            foreach (QFileInfo file, fileList)
            {
                if(file.isFile())
                {
                    int modifyTime = file.lastModified().toTime_t();
                    if(curTime - modifyTime > deleteTime)
                    {
                        file.dir().remove(file.fileName());
                    }
                }
            }
        }

        qInstallMessageHandler(logerOutput);
    }
}

消息处理程序是一个输出调试消息、警告、关键和致命错误消息的函数。Qt库(调试模式)包含数百条警告消息,当内部错误(通常是无效的函数参数)发生时,这些警告消息将被打印出来。在发布模式下构建的Qt也包含这样的警告,除非在编译期间设置了QT_NO_WARNING_OUTPUT和/或QT_NO_DEBUG_OUTPUT。如果实现自己的消息处理程序,则可以完全控制这些消息。

默认消息处理程序将消息打印到X11下的标准输出或Windows下的调试器。如果是致命消息,则应用程序立即中止。

只能定义一个消息处理程序,因为这通常是在应用程序范围内完成的,以控制调试输出。

要恢复消息处理程序,请调用qInstallMessageHandler(0)。

然后就是调用线程与主线程并发执行

QtConcurrent::run(prepareOther);

Qt Concurrent模块扩展了Qt Core模块中的基本线程支持,并简化了可以在所有可用CPU内核上并行执行的代码开发。

——————————————————分割线——————————————————————

接下来就是实现前面提到的单例日志写入线程

#ifndef LOGERTHREAD_H
#define LOGERTHREAD_H

#include <QThread>
#include <QFile>
#include <QStringList>
#include <QMutexLocker>

class LogerThread : public QThread
{
    Q_OBJECT
private:
    explicit LogerThread(QObject *parent = nullptr);
public:
    ~LogerThread();

    void log(QString msg);                    // 日志信息设置

    void run();
    // 创建单例
    static LogerThread* instance()
    {
        if(!_pInstance)
        {
            QMutexLocker mutexLocker(&_mutex);
            if(!_pInstance)
            {
                LogerThread *pInstance = new LogerThread();
                _pInstance = pInstance;
            }
        }
        return _pInstance;
    }

private:
    static LogerThread *_pInstance;
    static QMutex _mutex;

    bool _isRun;                                     // 线程是否运行标志
    QFile *_pLogFile;                                // 日志文件
    QString _logPath;                                // 日志文件存储路径

    QStringList _msgList;                            // 日志缓冲的list

};

#endif // LOGERTHREAD_H
#include "logerthread.h"
#include <QDir>
#include <QFileInfoList>
#include <QDateTime>
#include <QApplication>
#include <QString>

LogerThread * LogerThread::_pInstance = 0;
QMutex LogerThread::_mutex;
LogerThread::LogerThread(QObject *parent) :
    QThread(parent), _isRun(true), _pLogFile(nullptr)
{
     _logPath = tr("%1/log").arg(qApp->applicationDirPath());
     this->start();
}

LogerThread::~LogerThread()
{
    _isRun = false;
    this->terminate();
    this->wait();
}

void LogerThread::run()
{
    QDir logDir(_logPath);
    fprintf(stdout, "#LogerThread# run() %s\n", _logPath.toStdString().data());
    if (!logDir.exists())
    {
        logDir.mkpath(_logPath);
    }
    // 限制日志文件最大10M
    qint64 maxLogSize = 1024*1024*10;     
    // 通过时间戳来对文件命名
    QString logPath = tr("%1/%2.log").arg(_logPath).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss_zzz"));

    _pLogFile = new QFile(logPath);
    {
        while (_isRun)
        {
            // 超过最大限制则重新创建新文件
            if (_pLogFile->isOpen() && _pLogFile->size() >= maxLogSize)
            {
                _pLogFile->close();
            }
            if (!_pLogFile->isOpen())
            {
                QString logPath = tr("%1/%2.log").arg(_logPath).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss_zzz"));
                _pLogFile->setFileName(logPath);
                // 注意写入时需要以追加形式打开文件
                if (!_pLogFile->open(QFile::WriteOnly | QFile::Append))
                    fprintf(stdout, "#LogerThread# run() open log file fail! %s %s\n", logPath.toLocal8Bit().data(), _pLogFile->errorString().toStdString().data());
                this->msleep(1000);
                continue;
            }
            QByteArray logText;
            while (_msgList.length() > 0 && _isRun)
            {
                logText = _msgList.first().toLocal8Bit();
                logText.append("\n");
                if (_pLogFile->write(logText) == -1)
                    break;
                _msgList.removeFirst();
            }
            this->msleep(500);
        }
        _pLogFile->close();
    }
}

void LogerThread::log(QString msg)
{
    QString msgStr = QString("TM:[%1],").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss:zzz"))+msg;
    _msgList.append(msgStr);
}
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐