Qt 软件调试(一) Log日志调试

终于这段时间闲下来了,可以系统的编写Qt软件调试的整个系列。前面零零星星的也有部分输出,但终究没有形成体系。借此机会,做一下系统的总结。慎独、精进~

日志是有效帮助我们快速定位,找到程序异常点的实用方法。但是好的日志才能提高问题排查的效率。在代码江湖里闯荡的这些年头了,见独篇写入、日积月累下体态无限臃肿的单日志文件;见过中英文混杂,查个日志还容易语言系统紊乱;见过没有时间节点,更没有文件名、API名的,更别提行号的,如果能反向从代码中找到输出字符的蛛丝马迹,就要谢天谢地的;当然也见过规整清爽、分类清晰的日志系统。日志系统的搭建不是本系列的重点,如果大家有兴趣,我们后面可以开一个系列专门聊聊和深入研究探讨下。

一、Qt下日志模块

基于日志系统的调试,首先必须要有日志才行。开源的日志项目,如glog、log4cpp等,这里不做过多分享。我们先简单说说Qt下的日志。下面先给个自定义Log的例子:

#ifndef CLOG_H
#define CLOG_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

class CLog : public QThread
{
    Q_OBJECT
public:
    enum LogType
    {
        DEBUG,
        WARNING,
        Critical,
        Info,
        Fatal
    };
    Q_ENUM(LogType)
    Q_DECLARE_FLAGS(LogTypes, LogType)
    Q_FLAG(LogTypes)

public:
    static CLog* instance();
    ~CLog();

    bool init(const QString& strLogPath,const QString& logName = "");
    void uninit();
    bool add(const QString &strMsg, LogType eLogType);

protected:
    void run() override;
    void createNewLogFile();
private:
    QDate                       m_dateCurFile;
    QString						m_strLogPath;
    QString						m_strLogName;
    QFile						m_fileLog;
    QMutex						m_lock;
    QQueue<QString>				m_queData;
    volatile bool				m_bThreadRun;
    static CLog* instance_;
};

using LogType = typename CLog::LogType;
#define _ins_clog_  CLog::instance()

#endif
#include "CLog.h"
#include 
#include 
#include 
#include 

CLog* CLog::instance_ = nullptr;

CLog *CLog::instance()
{
    static std::once_flag s_flag;
    std::call_once(s_flag, [&]() { instance_ = new CLog;});

    return instance_;
}

CLog::~CLog()
{
    uninit();
}


bool CLog::init(const QString &strLogPath, const QString& logName)
{
    m_strLogPath = strLogPath;
    if (strLogPath.isEmpty()) {
        QString strAppDirPath = QCoreApplication::applicationDirPath();
        m_strLogPath = QString("%1/log").arg(strAppDirPath);
    }

    m_strLogName = logName;
    if (m_strLogName.isEmpty()) {
        m_strLogName = QCoreApplication::applicationName();
    }

    QDir dir(m_strLogPath);
    if (!dir.exists()) {
        if(!dir.mkpath(m_strLogPath)) {
            return false;
        }
    }


    m_dateCurFile = QDate::currentDate();

    QString strFolder = QString("%1\\%2").arg(m_strLogPath).arg(m_dateCurFile.toString("yyyy-MM-dd"));
    if (!dir.exists(strFolder)) {
        if (!dir.mkpath(strFolder)) {
            return false;
        }
    }

    QString fileName = QString("%1\\%2\\%3_%4_%5.txt")
            .arg(m_strLogPath)
            .arg(m_dateCurFile.toString("yyyy-MM-dd"))
            .arg(m_dateCurFile.toString("yyyy-MM-dd"))
            .arg(QTime::currentTime().toString("HH"))
            .arg(m_strLogName);

    m_fileLog.setFileName(fileName);

    if (!m_fileLog.open(QIODevice::Append | QIODevice::Text | QIODevice::WriteOnly))
    {
        return false;
    }

    m_bThreadRun = true;
    start();

    return true;
}

void CLog::uninit()
{
    QMutexLocker locker(&m_lock);
    m_bThreadRun = false;
    m_queData.enqueue("");
}

bool CLog::add(const QString& strMsg, LogType eLogType)
{
    QMetaEnum m = QMetaEnum::fromType<LogTypes>();
    QString strLogInfo = QString("%1 %2 $%3:%4\n")
            .arg(QDate::currentDate().toString("yyyy-MM-dd"))
            .arg(QTime::currentTime().toString("HH:mm:ss.zzz"))
            .arg(m.valueToKey(eLogType))
            .arg(strMsg);

    QMutexLocker locker(&m_lock);
    m_queData.enqueue(strLogInfo);

    return true;
}

void CLog::createNewLogFile()
{
    QDate curDate = QDate::currentDate();
    if (curDate > m_dateCurFile)
    {
        m_dateCurFile = curDate;

        QDir dir;
        QString strFolder = QString("%1\\%2").arg(m_strLogPath).arg(m_dateCurFile.toString("yyyy-MM-dd"));
        if (!dir.exists(strFolder)) {
            if (!dir.mkpath(strFolder)) {
                return;
            }
        }

        QString fileName = QString("%1\\%2\\%3_%4_%5.txt")
                .arg(m_strLogPath)
                .arg(m_dateCurFile.toString("yyyy-MM-dd"))
                .arg(m_dateCurFile.toString("yyyy-MM-dd"))
                .arg(QTime::currentTime().toString("HH"))
                .arg(m_strLogName);

        if (m_fileLog.isOpen()) {
            m_fileLog.flush();
            m_fileLog.close();
        }

        m_fileLog.setFileName(fileName);
        m_fileLog.open(QIODevice::Append | QIODevice::Text | QIODevice::WriteOnly);
    }
}

void CLog::run()
{
    QString strData;
    while (m_bThreadRun) {
        strData.clear();

        if (m_queData.isEmpty()) {
            msleep(200);
            continue;
        }

        QMutexLocker locker(&m_lock);
        strData = m_queData.dequeue();

        createNewLogFile();

        if (!m_fileLog.isOpen()){
            continue;
        }
        m_fileLog.write(strData.toUtf8());
        m_fileLog.flush();
    }
}
#include "mainwindow.h"
#include "iapplication.h"
#include "CLog.h"
#include 
#include 


void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    LogType msgType;
    qint32 level = -1;

    switch (type)
    {
    case QtDebugMsg:
        msgType = LogType::DEBUG;
        level = 1;
        break;
    case QtWarningMsg:
        msgType = LogType::WARNING;
        level = 2;
        break;
    case QtCriticalMsg:
        msgType = LogType::Critical;
        level = 3;
        break;
    case QtFatalMsg:
        msgType = LogType::Fatal;
        level = 4;
        break;
    case QtInfoMsg:
        msgType = LogType::Info;
        level = 1;
        break;
    default:
        break;
    }

    QString addMsg = msg;
    _ins_clog_->add(addMsg, msgType);
}

void testMessageOutput()
{

    qint64 bt = QDateTime::currentMSecsSinceEpoch();
    for(int i =0; i < 10000;++i){
        _ins_clog_->add(QString("the %1 times output message.").arg(i),CLog::Info);
    }
    qint64 et = QDateTime::currentMSecsSinceEpoch();
    qDebug() << et -bt;
}


int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    /// 设置日志
    qInstallMessageHandler(outputMessage); 
    QString path;
    _ins_clog_->init(path);

    testMessageOutput();

    return a.exec();
}

二、只有打印信息,没有日志输出如何排查

经典神器 Dbgview上场。
Qt 软件调试(一) Log日志调试_第1张图片
使用比较简单,这里不做概述。

三、关于日志或打印信息排查问题的一些总结和思考

1、日志通常只能作为业务逻辑的辅助排查。当程序由于逻辑上执行异常时,我们可以通过判断打印信息去推断可能产生问题的原因。
2、通过日志排查问题对相关人员有比较高的要求,对于业务逻辑需要比较熟悉才能快速定位
3、软件开发人员在编写打印输出的日志信息时,需要统一输出格式,对问题点输出可靠、可读性强的提示;否则输出过多无关紧要的信息,反而不利于问题的排查和分析。

你可能感兴趣的:(Qt软件调试合集,Qt,日志)