基于Qt實(shí)現(xiàn)日志打印系統(tǒng)
Qt 打印日志系統(tǒng),實(shí)現(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)試器,其實(shí)我們可以自己處理相關(guān)打印信息,可以選擇保存下來(lái)、或者界面顯示,網(wǎng)絡(luò)傳輸?shù)鹊龋琿InstallMessageHandler(QtMessageHandler handler)可以幫助我們快速實(shí)現(xiàn)我們的日志系統(tǒng)
實(shí)現(xiàn)方法也簡(jiǎ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();
}
下面稍微改造,符合我們實(shí)際使用的三種日志形式,有使用到c++11 lambda表達(dá)式方式安裝,看不慣可以按例子編寫(xiě),都一樣的
enum HandlerFlag
{
eSave = 0x0001,
eNet = 0x0002,
eNewLog = 0x0004,
};
Q_DECLARE_FLAGS(HandlerFlags, HandlerFlag)
列出主要實(shí)現(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);
}
});
}
簡(jiǎn)要說(shuō)明下其中的部分代碼
日志安裝,使用到c++11 lambda表達(dá)式方式安裝,看不慣可以按例子編寫(xiě),都一樣的
qInstallMessageHandler(
[](QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
});
文件保存:
1.每次又新消息都會(huì)進(jìn)入我們大安裝函數(shù)中來(lái),我們可以選擇是否保存到文件當(dāng)中去
2.在每次保存之前先判斷下當(dāng)前的文件名是否為當(dāng)前日期,不是的話關(guān)閉舊的文件句柄,在按當(dāng)前日期打開(kāi)新的文件
3.如果文件大小大于最大值,比如1000M的大小(file.size() > d->m_maxLogsize),則重名當(dāng)前文件,再打開(kāi)一個(gè)新文件,防止日志文件太大
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();
}
過(guò)期刪除,比如大于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ù)器,為了性能,順便移動(dòng)到線程中去了
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();
}
}
則在有新消息時(shí),我們就可以發(fā)射一個(gè)信號(hào)出去了
void QLog::toNet(QString log)
{
emit sig_toNet(log);
}
日志服務(wù)器怎么用呢,你可以隨便找個(gè)TCP工具連上你的日志服務(wù)器的IP和端口,這樣你就可以遠(yuǎn)程調(diào)試查看了,如果系統(tǒng)在外地或者比較惡劣的環(huán)境,那調(diào)試就真是美滋滋方便多了,而且還可以多人查看呢,是不是很美好,愿意的話還可以稍微改下服務(wù)端接收一些調(diào)試命令,比如打印一些內(nèi)存信息返回回來(lái),方便調(diào)試。
窗口顯示,我們定義一個(gè)顯示控件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實(shí)現(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();
}
怎么用這個(gè)搜索類呢,我們可以在每次文字改變時(shí),更新下搜索
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();
}
羅里吧嗦了一堆,主要時(shí)利用qInstallMessageHandler(QtMessageHandler handler)來(lái)處理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實(shí)現(xiàn)日志打印系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于Qt打印日志的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
總結(jié)了24個(gè)C++的大坑,你能躲過(guò)幾個(gè)
這篇文章主要介紹了總結(jié)了24個(gè)C++的大坑,你能躲過(guò)幾個(gè),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2021-05-05
詳解如何配置CLion作為Qt5開(kāi)發(fā)環(huán)境的方法
這篇文章主要介紹了詳解如何配置CLion作為Qt5開(kāi)發(fā)環(huán)境的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
c++ Protobuf解決數(shù)據(jù)傳輸瓶頸面試精講
這篇文章主要介紹了c++ Protobuf解決數(shù)據(jù)傳輸瓶頸利器面試精講,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
c++只保留float型的小數(shù)點(diǎn)后兩位問(wèn)題
這篇文章主要介紹了c++只保留float型的小數(shù)點(diǎn)后兩位問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11
Cocos2d-x中使用CCScrollView來(lái)實(shí)現(xiàn)關(guān)卡選擇實(shí)例
這篇文章主要介紹了Cocos2d-x中使用CCScrollView來(lái)實(shí)現(xiàn)關(guān)卡的選擇實(shí)例,本文在代碼中用大量注釋講解了CCScrollView的使用,需要的朋友可以參考下2014-09-09
C++算法之在無(wú)序數(shù)組中選擇第k小個(gè)數(shù)的實(shí)現(xiàn)方法
這篇文章主要介紹了C++算法之在無(wú)序數(shù)組中選擇第k小個(gè)數(shù)的實(shí)現(xiàn)方法,涉及C++數(shù)組的遍歷、判斷、運(yùn)算等相關(guān)操作技巧,需要的朋友可以參考下2017-03-03
C++簡(jiǎn)單實(shí)現(xiàn)RPC網(wǎng)絡(luò)通訊的示例詳解
RPC是遠(yuǎn)程調(diào)用系統(tǒng)簡(jiǎn)稱,它允許程序調(diào)用運(yùn)行在另一臺(tái)計(jì)算機(jī)上的過(guò)程,就像調(diào)用本地的過(guò)程一樣。本文將用C++簡(jiǎn)單實(shí)現(xiàn)RPC網(wǎng)絡(luò)通訊,感興趣的可以了解一下2023-04-04

