終于這段時間閑下來了,可以系統的編寫Qt軟件調試的整個系列。前面零零星星的也有部分輸出,但終究沒有形成體系。借此機會,做一下系統的總結。慎獨、精進~
日志是有效幫助我們快速定位,找到程序異常點的實用方法。但是好的日志才能提高問題排查的效率。在代碼江湖里闖蕩的這些年頭了,見獨篇寫入、日積月累下體態無限臃腫的單日志文件;見過中英文混雜,查個日志還容易語言系統紊亂;見過沒有時間節點,更沒有文件名、API名的,更別提行號的,如果能反向從代碼中找到輸出字符的蛛絲馬跡,就要謝天謝地的;當然也見過規整清爽、分類清晰的日志系統。日志系統的搭建不是本系列的重點,如果大家有興趣,我們后面可以開一個系列專門聊聊和深入研究探討下。
一、Qt下日志模塊
基于日志系統的調試,首先必須要有日志才行。開源的日志項目,如glog、log4cpp等,這里不做過多分享。我們先簡單說說Qt下的日志。下面先給個自定義Log的例子:
#ifndef CLOG_H
#define CLOG_H#include <QString>
#include <QDate>
#include <QFile>
#include <QThread>
#include <QQueue>
#include <QMutex>
#include <QMutexLocker>
#include <QObject>
#include <QMetaEnum>
#include <QMetaType>
#include <QFlag>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 <QDir>
#include <QCoreApplication>
#include <iostream>
#include <memory>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 <QDateTime>
#include <QTime>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上場。
使用比較簡單,這里不做概述。
三、關于日志或打印信息排查問題的一些總結和思考
1、日志通常只能作為業務邏輯的輔助排查。當程序由于邏輯上執行異常時,我們可以通過判斷打印信息去推斷可能產生問題的原因。
2、通過日志排查問題對相關人員有比較高的要求,對于業務邏輯需要比較熟悉才能快速定位
3、軟件開發人員在編寫打印輸出的日志信息時,需要統一輸出格式,對問題點輸出可靠、可讀性強的提示;否則輸出過多無關緊要的信息,反而不利于問題的排查和分析。