在編程開發中,日志功能至關重要,對于在開發期間或者是程序上線后,都有助于排查問題;
對于C/C++和QT方向,日志庫有log4cpp、plog、log4qt等,本篇文章將使用qt自帶的日志方式去實現。
定義日志函數:
void Log(QtMsgType type, const QMessageLogContext &context, const QString &msg);
函數名可隨意,但參數必須固定3個,如上面代碼;
QtMsgType是一個枚舉,記錄了多種打印類型;
enum QtMsgType { QtDebugMsg, QtWarningMsg, QtCriticalMsg, QtFatalMsg, QtInfoMsg, QtSystemMsg = QtCriticalMsg };
QMessageLogContext是日志上下文,可獲得qDebug()打印時所在的函數名和行號等;
class QMessageLogContext
{Q_DISABLE_COPY(QMessageLogContext)
public:Q_DECL_CONSTEXPR QMessageLogContext(): version(2), line(0), file(nullptr), function(nullptr), category(nullptr) {}Q_DECL_CONSTEXPR QMessageLogContext(const char *fileName, int lineNumber, const char *functionName, const char *categoryName): version(2), line(lineNumber), file(fileName), function(functionName), category(categoryName) {}void copy(const QMessageLogContext &logContext);int version;int line;const char *file;const char *function;const char *category;private:friend class QMessageLogger;friend class QDebug;
};
QString則是qDebug()打印輸出的內容。
如下定義一個日志函數:
void Log(QtMsgType type, const QMessageLogContext &context, const QString &msg) {//加鎖,防止多線程中qdebug太頻繁導致崩潰static QMutex mutex;QMutexLocker locker(&mutex);QString strContent;// 根據日志類型添加不同前綴 switch (type) {case QtDebugMsg:strContent = QString("[Debug] %1").arg(msg);break;case QtWarningMsg:strContent = QString("[Warning] %1").arg(msg);break;case QtCriticalMsg:strContent = QString("[Critical] %1").arg(msg);break;case QtFatalMsg:strContent = QString("[Fatal] %1").arg(msg);break;}// 構建完整日志信息 QString strMessage = QString("[%1] [%2:%3] %4").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz")).arg(context.function).arg(context.line).arg(strContent);// 寫入文件/* 在這里處理將 strMessage 內容寫入文件中 */
}
然后調用qInstallMessageHandler函數安裝日志鉤子:
qInstallMessageHandler(Log);
之后就可以將qDebug()、qWorning()等打印內容輸出到文件中。
如果是卸載的話,直接參數傳0即可:qInstallMessageHandler(0);
qDebug() << "調試信息";
qInfo() << "信息";
qWarning() << "警告信息";
qCritical() << "關鍵錯誤、嚴重錯誤";
qFatal:致命錯誤;
下面提供一個實現好的日志類,提供參考:
loghelper.h
#ifndef LOGHELPER_H
#define LOGHELPER_H#include <QObject>class QFile;
class QMutex;#ifdef quc
#if (QT_VERSION < QT_VERSION_CHECK(5,7,0))
#include <QtDesigner/QDesignerExportWidget>
#else
#include <QtUiPlugin/QDesignerExportWidget>
#endifclass QDESIGNER_WIDGET_EXPORT SaveLog : public QObject
#else
class LogHelper : public QObject
#endif
{Q_OBJECT
public:static LogHelper *Instance();explicit LogHelper(QObject *parent = 0);~LogHelper();private:static QScopedPointer<LogHelper> self;static QMutex mutexInstance;//文件對象QFile *file;//日志文件路徑QString path;//日志文件名稱QString name;// 當前日志文件對應的日期(yyyy-MM-dd)QString currentDate;//日志文件完整名稱QString fileName;public slots://啟動日志服務void start();//暫停日志服務void stop();//保存日志void save(const QString &content);//設置日志文件存放路徑void setPath(const QString &path);QString getPath() const;//設置日志文件名稱void setName(const QString &name);QString getName() const;
};#endif // LOGHELPER_H
loghelper.cpp
#include "loghelper.h"
#include <QFile>
#include <QDir>
#include <QDateTime>
#include <QApplication>
#include <QTimer>
#include <QStringList>
#include <QTextStream>
#include <QMutex>
#include <QDebug>// 初始化靜態成員
QMutex LogHelper::mutexInstance;
QScopedPointer<LogHelper> LogHelper::self;//日志重定向
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
void Log(QtMsgType type, const char *msg)
#else
void Log(QtMsgType type, const QMessageLogContext &context, const QString &msg)
#endif
{//加鎖,防止多線程中qdebug太頻繁導致崩潰static QMutex mutex;QMutexLocker locker(&mutex);QString strContent;// 根據日志類型添加不同前綴switch (type) {case QtDebugMsg:strContent = QString("[Debug] %1").arg(msg);break;case QtWarningMsg:strContent = QString("[Warning] %1").arg(msg);break;case QtCriticalMsg:strContent = QString("[Critical] %1").arg(msg);break;case QtFatalMsg:strContent = QString("[Fatal] %1").arg(msg);break;case QtInfoMsg:strContent = QString("[Info] %1").arg(msg);break;}// 構建完整日志信息QString strMessage = QString("[%1] [%2:%3] %4").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss.zzz")).arg(context.function).arg(context.line).arg(strContent);// 寫入文件LogHelper::Instance()->save(strMessage);
}LogHelper *LogHelper::Instance()
{if (self.isNull()) {QMutexLocker locker(&mutexInstance);if (self.isNull()) {self.reset(new LogHelper);}}return self.data();
}LogHelper::LogHelper(QObject *parent) : QObject(parent)
{file = new QFile(this);//默認取應用程序根目錄setPath(qApp->applicationDirPath() + "/logs");//默認取應用程序可執行文件名稱QFileInfo appInfo(QApplication::applicationFilePath());setName(appInfo.baseName());fileName = "";// 獲取當前日期 (格式:yyyy-MM-dd)currentDate = QDate::currentDate().toString("yyyy-MM-dd");// 構建新文件名fileName = QString("%1/%2_log_%3.txt").arg(path, name, currentDate);QFileInfo info(fileName);if (!info.exists()) {currentDate = "";}
}LogHelper::~LogHelper()
{file->close();
}//安裝日志鉤子,輸出調試信息到文件,便于調試
void LogHelper::start()
{
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))qInstallMsgHandler(Log);
#elseqInstallMessageHandler(Log);
#endif
}//卸載日志鉤子
void LogHelper::stop()
{
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))qInstallMsgHandler(0);
#elseqInstallMessageHandler(0);
#endif
}void LogHelper::save(const QString &content)
{// 獲取當前日期 (格式:yyyy-MM-dd)QString today = QDate::currentDate().toString("yyyy-MM-dd");// 檢查日期是否變化if (currentDate != today) {currentDate = today;// 關閉已打開的文件if (file->isOpen()) {file->close();}// 構建新文件名QString newFileName = QString("%1/%2_log_%3.txt").arg(path, name, currentDate);// 確保目錄存在QDir dir;if (!dir.exists(path)) {dir.mkpath(path);}// 打開新日志文件file->setFileName(newFileName);if (!file->open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text)) {// 直接輸出到stderr,避免遞歸調用日志系統fprintf(stderr, "無法打開日志文件: %s\n", fileName.toUtf8().constData());return;}fileName = newFileName;}// 確保文件已打開if (!file->isOpen()) {// 打開新日志文件file->setFileName(fileName);if (!file->open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text)) {// 直接輸出到stderr,避免遞歸調用日志系統fprintf(stderr, "無法打開日志文件: %s\n", fileName.toUtf8().constData());return;}}// 寫入日志內容 (添加換行符)QTextStream logStream(file);logStream << content << "\n";logStream.flush(); // 刷新緩沖區確保及時寫入
}void LogHelper::setPath(const QString &path)
{QDir dirPath(path);if(!dirPath.exists()){dirPath.mkdir(path);}this->path = path;// 重置當前日期,強制下次寫入時重新打開文件currentDate = "";
}void LogHelper::setName(const QString &name)
{this->name = name;// 重置當前日期,強制下次寫入時重新打開文件currentDate = "";
}QString LogHelper::getPath() const
{return path;
}QString LogHelper::getName() const
{return name;
}
使用:
#include "loghelper.h"LogHelper::Instance()->start(); //啟動日志鉤子qDebug() << "qDebug 測試日志打印、、、";
qWarning() << "qWarning 測試日志打印、、、";
qCritical() << "qCritical 測試日志打印、、、";
qInfo() << "qInfo 測試日志打印、、、";
注意,如果是在統信UOS系統ARM架構運行,因為統信系統的原因,默認情況下,只會打印qWarning、qCritical、qFatal三個級別的,qDebug和qInfo將不會處理;
如果希望qDebug和qInfo也能打印到文件,需要設置環境變量;
QT環境:
需要在main函數的最前方設置:
qputenv("QT_LOGGING_RULES", "*.debug=false;default.debug=true");
#include "mainwidget.h"
#include <QApplication>int main(int argc, char *argv[])
{// 強制啟用默認的debug日志打印輸出;否則在UOS系統里qDebug()無法將內容輸出到日志文件;Window環境不影響
#ifdef Q_OS_UNIXqputenv("QT_LOGGING_RULES", "*.debug=false;default.debug=true");
#endifQApplication a(argc, argv);MainWidget w;//w.show();w.showFullScreen();return a.exec();
}
windows環境不受影響,可正常使用!