终于这段时间闲下来了,可以系统的编写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、软件开发人员在编写打印输出的日志信息时,需要统一输出格式,对问题点输出可靠、可读性强的提示;否则输出过多无关紧要的信息,反而不利于问题的排查和分析。