基于Qt實現(xiàn)日志打印系統(tǒng)
Qt 打印日志系統(tǒng),實現(xiàn)打印日志保存,窗口顯示日志,網(wǎng)絡(luò)傳輸日志
一套成熟的系統(tǒng)往往都有相應(yīng)的日志系統(tǒng),以便調(diào)試查看
Qt的打印信息默認(rèn)處理程序?qū)⑾⒋蛴〉絏11下的標(biāo)準(zhǔn)輸出或Windows下的調(diào)試器,其實我們可以自己處理相關(guān)打印信息,可以選擇保存下來、或者界面顯示,網(wǎng)絡(luò)傳輸?shù)鹊龋琿InstallMessageHandler(QtMessageHandler handler)可以幫助我們快速實現(xiàn)我們的日志系統(tǒng)
實現(xiàn)方法也簡單,下面是幫助文檔里面為我們提供的日志
#include <qapplication.h> #include <stdio.h> #include <stdlib.h> void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { QByteArray localMsg = msg.toLocal8Bit(); switch (type) { case QtDebugMsg: fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); break; case QtInfoMsg: fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); break; case QtWarningMsg: fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); break; case QtCriticalMsg: fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); break; case QtFatalMsg: fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); abort(); } } int main(int argc, char **argv) { qInstallMessageHandler(myMessageOutput); QApplication app(argc, argv); ... return app.exec(); }
下面稍微改造,符合我們實際使用的三種日志形式,有使用到c++11 lambda表達(dá)式方式安裝,看不慣可以按例子編寫,都一樣的
enum HandlerFlag { eSave = 0x0001, eNet = 0x0002, eNewLog = 0x0004, }; Q_DECLARE_FLAGS(HandlerFlags, HandlerFlag)
列出主要實現(xiàn)代碼
Q_DECLARE_OPERATORS_FOR_FLAGS(QLog::HandlerFlags) void QLog::handler(HandlerFlags flags) { { if(d->m_flags == flags){ return; } QMutexLocker locker(&(d->m_handlermutex)); qInstallMessageHandler(nullptr); d->m_flags = flags; if(!d->m_flags.testFlag(eNet)){ if(d->mp_toNet){ d->mp_toNet->deleteLater(); d->mp_toNet = nullptr; } if(d->mp_netThread){ d->mp_netThread->quit(); d->mp_netThread->wait(); d->mp_netThread->deleteLater(); d->mp_netThread = nullptr; } } if(!d->m_flags.testFlag(eSave)){ if (d->m_file.isOpen()) { d->m_file.close(); } d->m_fileName.clear(); } if(!d->m_flags){ return; } } qInstallMessageHandler( [](QtMsgType type, const QMessageLogContext &context, const QString &msg) { Q_UNUSED(context) QLogPrivate* d = QLog::instance()->d; QMutexLocker locker(&(d->m_handlermutex)); if(!d->m_flags){ return; } QString content; switch (type) { case QtDebugMsg: content = QString("%1").arg(msg); break; case QtWarningMsg: content = QString("%1").arg(msg); break; case QtCriticalMsg: content = QString("%1").arg(msg); break; case QtFatalMsg: content = QString("%1").arg(msg); break; case QtInfoMsg: content = QString("%1").arg(msg); break; } if(d->m_flags.testFlag(eSave)){ QString fileName = QString("%1/%2_log_%3.txt").arg(d->m_path).arg(d->m_appname).arg(QDate::currentDate().toString("yyyy-MM-dd")); if (d->m_fileName != fileName) { d->m_fileName = fileName; if (d->m_file.isOpen()) { d->m_file.close(); } QLog::instance()->autoDeleteLog(); d->m_file.setFileName(fileName); d->m_file.open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text); } QTextStream logStream(&(d->m_file)); logStream << content << "\n"; if(d->m_file.size() > d->m_maxLogsize){ QString fileName = QString("%1/%2_log_%3.txt").arg(d->m_path).arg(d->m_appname).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd-hhmmss")); d->m_file.rename(fileName); d->m_file.close(); d->m_fileName.clear(); } } if(d->m_flags.testFlag(eNet)){ if(nullptr == d->mp_toNet){ QLog::instance()->createLogNet(); } QLog::instance()->toNet(content); } if(d->m_flags.testFlag(eNewLog)){ QLog::instance()->newLog(content); } }); }
簡要說明下其中的部分代碼
日志安裝,使用到c++11 lambda表達(dá)式方式安裝,看不慣可以按例子編寫,都一樣的
qInstallMessageHandler( [](QtMsgType type, const QMessageLogContext &context, const QString &msg) { });
文件保存:
1.每次又新消息都會進入我們大安裝函數(shù)中來,我們可以選擇是否保存到文件當(dāng)中去
2.在每次保存之前先判斷下當(dāng)前的文件名是否為當(dāng)前日期,不是的話關(guān)閉舊的文件句柄,在按當(dāng)前日期打開新的文件
3.如果文件大小大于最大值,比如1000M的大小(file.size() > d->m_maxLogsize),則重名當(dāng)前文件,再打開一個新文件,防止日志文件太大
QString fileName = QString("%1/%2_log_%3.txt").arg(d->m_path).arg(d->m_appname).arg(QDate::currentDate().toString("yyyy-MM-dd")); if (d->m_fileName != fileName) { d->m_fileName = fileName; if (d->m_file.isOpen()) { d->m_file.close(); } QLog::instance()->autoDeleteLog(); d->m_file.setFileName(fileName); d->m_file.open(QIODevice::WriteOnly | QIODevice::Append | QFile::Text); } QTextStream logStream(&(d->m_file)); logStream << content << "\n"; if(d->m_file.size() > d->m_maxLogsize){ QString fileName = QString("%1/%2_log_%3.txt").arg(d->m_path).arg(d->m_appname).arg(QDateTime::currentDateTime().toString("yyyy-MM-dd-hhmmss")); d->m_file.rename(fileName); d->m_file.close(); d->m_fileName.clear(); }
過期刪除,比如大于d->m_saveday (30天)的就給他刪掉
void QLog::autoDeleteLog() { qint64 var = d->m_saveday * (-1); QDateTime deleteday = QDateTime::currentDateTime().addDays(var); QDir dir(d->m_path); QStringList filters; filters<<"*.txt"; dir.setNameFilters(filters); QFileInfoList filelst = dir.entryInfoList(); if( filelst.count()< d->m_saveday){ return; } foreach (QFileInfo mItem, filelst) { if(mItem.fileName().contains(d->m_appname) && mItem.created() <= deleteday ){ QFile::remove(mItem.absoluteFilePath()); } } }
網(wǎng)絡(luò)傳輸,首先定義日志服務(wù)器
class QLogToNet:public QObject { Q_OBJECT private: QLogToNet(quint16 port,QObject* parent = nullptr); ~QLogToNet(); public slots: void slot_toNet(QString log); private: class QLogToNetPrivate; QLogToNetPrivate* const d; friend class QLog; };
class QLogToNet::QLogToNetPrivate { public: QMutex m_mutex; QTcpServer* mp_server; QList<QTcpSocket*> m_lstTcpSocket; }; QLogToNet::QLogToNet(quint16 port, QObject *parent) :QObject(parent),d(new QLogToNetPrivate()) { d->mp_server = new QTcpServer(this); connect(d->mp_server,&QTcpServer::newConnection,this,[=](){ QMutexLocker locker(&d->m_mutex); while (d->mp_server->hasPendingConnections()) { QTcpSocket* pTcpSocket = d->mp_server->nextPendingConnection(); if(pTcpSocket){ connect(pTcpSocket,&QTcpSocket::disconnected,this,[=]() { QMutexLocker locker(&d->m_mutex); QTcpSocket* pTcpSocket = qobject_cast<QTcpSocket*>(sender()); if(pTcpSocket){ d->m_lstTcpSocket.removeAll(pTcpSocket); pTcpSocket->deleteLater(); } }); d->m_lstTcpSocket<<pTcpSocket; } } }); d->mp_server->listen(QHostAddress::AnyIPv4,port); } QLogToNet::~QLogToNet() { if(d->mp_server->isListening()){ d->mp_server->close(); } delete d; } void QLogToNet::slot_toNet(QString log) { QMutexLocker locker(&d->m_mutex); foreach (QTcpSocket* pTcpSocket ,d->m_lstTcpSocket) { pTcpSocket->write(log.toUtf8()); pTcpSocket->flush(); } }
然后使用該日志服務(wù)器,為了性能,順便移動到線程中去了
void QLog::createLogNet() { if(nullptr == d->mp_toNet){ d->mp_toNet = new QLogToNet(d->m_netPort,nullptr); connect(this,&QLog::sig_toNet,d->mp_toNet,&QLogToNet::slot_toNet); d->mp_netThread = new QThread(this); d->mp_toNet->moveToThread(d->mp_netThread); d->mp_netThread->start(); } }
則在有新消息時,我們就可以發(fā)射一個信號出去了
void QLog::toNet(QString log) { emit sig_toNet(log); }
日志服務(wù)器怎么用呢,你可以隨便找個TCP工具連上你的日志服務(wù)器的IP和端口,這樣你就可以遠(yuǎn)程調(diào)試查看了,如果系統(tǒng)在外地或者比較惡劣的環(huán)境,那調(diào)試就真是美滋滋方便多了,而且還可以多人查看呢,是不是很美好,愿意的話還可以稍微改下服務(wù)端接收一些調(diào)試命令,比如打印一些內(nèi)存信息返回回來,方便調(diào)試。
窗口顯示,我們定義一個顯示控件QTextEdit,和newlog相連,每次有新消息直接append就可以了
connect(QLog::instance(),&QLog::sig_newLog,ui->textEdit,&QTextEdit::append);
在QTextEdit上模糊收索消息,主要用到QSyntaxHighlighter
#ifndef MARKDOWNHIGHLIGHTER_H #define MARKDOWNHIGHLIGHTER_H #include <QSyntaxHighlighter> #include <QTextEdit> #include <QVector> class MarkdownHighlighter : public QSyntaxHighlighter { Q_OBJECT public: MarkdownHighlighter(QTextEdit *parent = 0); void highlightBlock(const QString &text); void SetColorText(const QString &str, const QColor &color); void clearRules(); int markCount() const; void setMarkCount(int markCount); private: struct HighlightingRule { QRegExp pattern; QTextCharFormat format; }; QVector<HighlightingRule> m_highlightingRules; }; #endif // MARKDOWNHIGHLIGHTER_H
實現(xiàn)
#include "markdownhighlighter.h" MarkdownHighlighter::MarkdownHighlighter(QTextEdit *parent) :QSyntaxHighlighter(parent) { this->setDocument(parent->document()); } void MarkdownHighlighter::highlightBlock(const QString &text) { foreach (HighlightingRule rule, m_highlightingRules) { QRegExp expression(rule.pattern); int index = text.indexOf(expression); while (index >= 0) { int length = expression.matchedLength(); setFormat(index, length, rule.format); index = text.indexOf(expression, index + length); } } } void MarkdownHighlighter::SetColorText(const QString &str, const QColor &color) { HighlightingRule rule; rule.pattern = QRegExp(str); QTextCharFormat format; format.setForeground(color); rule.format = format; m_highlightingRules.append(rule); } void MarkdownHighlighter::clearRules() { m_highlightingRules.clear(); }
怎么用這個搜索類呢,我們可以在每次文字改變時,更新下搜索
void LogWidget::on_le_find_textChanged(const QString &) { QString str = ui->le_find->text(); if(str.isEmpty()){ mp_findMark->clearRules(); mp_findMark->rehighlight(); return; } mp_findMark->clearRules(); mp_findMark->SetColorText(str,Qt::red); mp_findMark->rehighlight(); }
羅里吧嗦了一堆,主要時利用qInstallMessageHandler(QtMessageHandler handler)來處理qt的打印信息,下面分享下一些比較常用的打印格式
#define LOG_DEBUG qDebug() << qPrintable(QString("[%1 %2 %3 %4]:").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")).arg(__FILE__).arg(__FUNCTION__).arg(__LINE__)) #define LOG_INFO qInfo() << qPrintable(QString("[%1]:").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"))) #define LOG_WARN qWarning() << qPrintable(QString("[%1 %2 %3 %4]:").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")).arg(__FILE__).arg(__FUNCTION__).arg(__LINE__)) #define LOG_CRIT qCritical() << qPrintable(QString("[%1 %2 %3 %4]:").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss")).arg(__FILE__).arg(__FUNCTION__).arg(__LINE__))
以上就是基于Qt實現(xiàn)日志打印系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于Qt打印日志的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解如何配置CLion作為Qt5開發(fā)環(huán)境的方法
這篇文章主要介紹了詳解如何配置CLion作為Qt5開發(fā)環(huán)境的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04c++ Protobuf解決數(shù)據(jù)傳輸瓶頸面試精講
這篇文章主要介紹了c++ Protobuf解決數(shù)據(jù)傳輸瓶頸利器面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10Cocos2d-x中使用CCScrollView來實現(xiàn)關(guān)卡選擇實例
這篇文章主要介紹了Cocos2d-x中使用CCScrollView來實現(xiàn)關(guān)卡的選擇實例,本文在代碼中用大量注釋講解了CCScrollView的使用,需要的朋友可以參考下2014-09-09C++算法之在無序數(shù)組中選擇第k小個數(shù)的實現(xiàn)方法
這篇文章主要介紹了C++算法之在無序數(shù)組中選擇第k小個數(shù)的實現(xiàn)方法,涉及C++數(shù)組的遍歷、判斷、運算等相關(guān)操作技巧,需要的朋友可以參考下2017-03-03C++簡單實現(xiàn)RPC網(wǎng)絡(luò)通訊的示例詳解
RPC是遠(yuǎn)程調(diào)用系統(tǒng)簡稱,它允許程序調(diào)用運行在另一臺計算機上的過程,就像調(diào)用本地的過程一樣。本文將用C++簡單實現(xiàn)RPC網(wǎng)絡(luò)通訊,感興趣的可以了解一下2023-04-04