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