相对于第三方的日志库,在 Qt 中使用 QDebug 打印更便捷,有时候也需要对 QDebug 输出进行重定向,如写入文件等。
在 Qt4 中使用 qInstallMsgHandler 函数设置重定向的函数指针:
typedef void (*QtMsgHandler)(QtMsgType, const char *);
Q_CORE_EXPORT QT_DEPRECATED QtMsgHandler qInstallMsgHandler(QtMsgHandler);
在 Qt5 中应该使用 qInstallMessageHandler 来注册函数指针:
typedef void (*QtMessageHandler)(QtMsgType, const QMessageLogContext &, const QString &);
Q_CORE_EXPORT QtMessageHandler qInstallMessageHandler(QtMessageHandler);
返回的函数指针我们可以保存起来,需要输出到控制台时进行调用。
最简单的操作
一个最简单的示例如下,重定向到文件:
#include <QApplication>
#include <QMutex>
#include <QMutexLocker>
#include <QFile>
#include <QTextStream>
#include <QDebug>
//重定向qdebug输出到文件
void myMessageHandle(QtMsgType , const QMessageLogContext& , const QString& msg)
{
static QMutex mut; //多线程打印时需要加锁
QMutexLocker locker(&mut);
QFile file("log.txt");
if(file.open(QIODevice::WriteOnly|QIODevice::Append))
{
QTextStream stream(&file);
stream<<msg<<endl;
file.close();
}
}
int main()
{
//设置重定向操作的函数
qInstallMessageHandler(myMessageHandle);
qDebug()<<"Test debug111";
qDebug()<<"Test debug222";
return 0;
}
实现一个简单的日志管理类
需求很简单,同时输出到界面中的编辑框、文件、控制台。
代码链接:https://github.com/gongjianbo/SimpleQtLogger
运行效果:
输出到控制台,我是保存了调用 qInstallMessageHandler 时返回的函数指针,然后调用进行默认的输出。
输出到文件,因为函数调用发生在 qDebug() 调用的线程,所以需要加锁。
输出到界面,我使用了信号槽的方式,将文本发送给 connect 的槽。
当然,还可以添加一些细节,如日志等级的控制等。
下面是部分源码:
#include <QApplication>
#include "LogManager.h"
#include "mainwindow.h"
int main(int argc, char *argv[])
{
LogManager::getInstance()->initManager();//初始化
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
#ifndef LOGMANAGER_H
#define LOGMANAGER_H
#include <QObject>
#include <QFile>
#include <QMutex>
#include <QElapsedTimer>
#include <QDebug>
/**
* @brief 简易的日志管理类,作为单例
* @details
* 初始化时调用initManager重定向
* @note
* 改为手动调用initManager是为了便于流程控制
* 此外,也可以手动调用freeManager
*/
class LogManager : public QObject
{
Q_OBJECT
LogManager();
public:
~LogManager();
//获取单例实例
static LogManager *getInstance();
//重定向qdebug输出
void outputLog(QtMsgType type, const QMessageLogContext& context, const QString& msg);
//初始化,如重定向等
void initManager(const QString &path=QString());
//释放
void freeManager();
signals:
//可以关联信号接收日志信息,如显示到ui中
//注意,如果槽函数为lambda或者其他没有接收者的情况,需要保证槽函数中的变量有效性
//因为static变量的生命周期更长,可能槽函数所在模块已经释放资源,最好connect加上接收者
void newLog(int msgType, const QString &log);
private:
//保留默认handle,用于输出到控制台
QtMessageHandler defaultOutput = nullptr;
//输出到文件
QFile file;
//输出路径
QString filePath;
//多线程操作时需要加锁
QMutex logMutex;
//计算操作间隔,分时生成文件
QElapsedTimer elapsedTimer;
};
#endif // LOGMANAGER_H
#include "LogManager.h"
#include <QApplication>
#include <QDir>
#include <QThread>
#include <QTextStream>
#include <QDateTime>
//重定向qdebug输出
void outputLogMessage(QtMsgType type, const QMessageLogContext& context, const QString& msg)
{
//转发给单例的成员函数
LogManager::getInstance()->outputLog(type,context,msg);
}
LogManager::LogManager()
{
//initManager();
}
LogManager::~LogManager()
{
freeManager();
}
LogManager *LogManager::getInstance()
{
//单例,c++ 11
static LogManager instance;
return &instance;
}
void LogManager::outputLog(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
//如果要写文件需要加锁,因为函数调用在debug调用线程
QMutexLocker locker(&logMutex);
QString out_text;
QTextStream stream(&out_text);
//区分日志类型给文本加头
switch (type) {
case QtDebugMsg: stream<<"[Debug]"; break;
case QtInfoMsg: stream<<"[Info]"; break;
case QtWarningMsg: stream<<"[Warning]"; break;
case QtCriticalMsg: stream<<"[Critical]"; break;
case QtFatalMsg: stream<<"[Fatal]"; break;
default: stream<<"[Unknown]"; break;
}
//加个线程id用于测试
stream<<QDateTime::currentDateTime().toString("[yyyy-MM-dd hh:mm:ss]")
<<msg
<<QThread::currentThreadId();
//写入文件
if(file.isOpen()){
//elapsed距离start的毫秒数
//这里设置1分钟用来测试
if(elapsedTimer.elapsed()>1000*60){
file.close();
//重新计时
elapsedTimer.restart();
}
}
if(!file.isOpen()){
//新的文件
file.setFileName(filePath+QString("/log_%1.txt")
.arg(QDateTime::currentDateTime().toString("yyyyMMdd_hhmm")));
//Append追加模式,避免同一文件被清除
if(!file.open(QIODevice::WriteOnly|QIODevice::Append)){
emit newLog(QtWarningMsg,"Open log file error:"+file.errorString()+file.fileName());
}
}
if(file.isOpen()){
//写入文件
stream.setDevice(&file);
stream<<out_text<<endl;
}
//发送信号给需要的对象,如ui上显示日志
emit newLog(type, msg);
//默认的输出,控制台
//区分日志类型给文本加颜色
//常见格式为:\e[显示方式;前景色;背景色m输出字符串\e[0m
//其中\e=\033
QString cmd_text;
stream.setString(&cmd_text);
switch (type) {
//debug绿色
case QtDebugMsg: stream<<"\033[32m"; break;
//info蓝色
case QtInfoMsg: stream<<"\033[34m"; break;
//warning黄色
case QtWarningMsg: stream<<"\033[33m"; break;
//critical黑底白字
case QtCriticalMsg: stream<<"\033[0;37;40m"; break;
//fatal黑底红字
//qFatal表示致命错误,默认处理会报异常的
case QtFatalMsg: stream<<"\033[0;31;40m"; break;
//defualt默认颜色
default: stream<<"\033[0m"; break;
}
stream<<out_text<<"\033[0m";
defaultOutput(type,context,cmd_text);
}
void LogManager::initManager(const QString &path)
{
//保存路径
filePath=path;
if(filePath.isEmpty())
{
//用到了QApplication::applicationDirPath(),需要先实例化一个app
int argc=0;
QApplication app(argc,nullptr);
filePath=app.applicationDirPath()+"/log";
}
QDir dir(filePath);
if(!dir.exists())
{
//虽然QFile能够创建不存在的文件,但是它就是不会自动创建不存在的目录
dir.mkpath(filePath);
}
elapsedTimer.start();
//重定向qdebug到自定义函数
defaultOutput=qInstallMessageHandler(outputLogMessage);
}
void LogManager::freeManager()
{
file.close();
qInstallMessageHandler(nullptr);
}
日志重定向到窗口(不要看)
封装类
1、logbrowser.h
#ifndef LOG_BROWSER_H
#define LOG_BROWSER_H
#include <QWidget>
#include <QVBoxLayout>
#include <QTextBrowser>
#include <QPushButton>
#include <QCoreApplication>
#include <QDebug>
#include <QMessageBox>
#include <QCloseEvent>
#include <QKeyEvent>
class LogBrowser : public QWidget
{
Q_OBJECT
bool is_finished;
QTextBrowser *browser;
QPushButton * start_button ;
QPushButton * clear_button;
public:
explicit LogBrowser(QWidget *parent = nullptr);
void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg);
void closeEvent(QCloseEvent *event);
void keyPressEvent(QKeyEvent *event);
public:
void start();
signals:
};
#endif // LOG_BROWSER_H
2、logbrowser.cpp
#include "logbrowser.h"
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(testLogger)
Q_LOGGING_CATEGORY(testLogger, "ssh.client", QtDebugMsg)
LogBrowser::LogBrowser(QWidget *parent) : QWidget(parent)
{
this->resize(400, 300);
is_finished = false;
browser = new QTextBrowser();
start_button = new QPushButton();
clear_button = new QPushButton();
start_button->setText("start");
clear_button->setText("clear");
QHBoxLayout *button_layout = new QHBoxLayout();
button_layout->addStretch();
button_layout->addWidget(start_button);
button_layout->addWidget(clear_button);
button_layout->setSpacing(10);
button_layout->setContentsMargins(0, 0, 10, 10);
QVBoxLayout *main_layout = new QVBoxLayout();
main_layout->addWidget(browser);
main_layout->addLayout(button_layout);
main_layout->setSpacing(10);
main_layout->setContentsMargins(0, 0, 0, 0);
this->setLayout(main_layout);
connect(start_button, &QPushButton::clicked, this, &LogBrowser::start);
connect(clear_button, &QPushButton::clicked, browser, &QTextBrowser::clear);
}
void LogBrowser::outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
QString message;
switch(type)
{
case QtDebugMsg:
message = QString("Debug:");
break;
case QtWarningMsg:
message = QString("Warning:");
break;
case QtCriticalMsg:
message = QString("Critical:");
break;
case QtFatalMsg:
message = QString("Fatal:");
}
browser->append(message.append(msg));
}
void LogBrowser::start()
{
if(!is_finished)
{
for(int i=0; i<1000000; i++)
{
QCoreApplication::processEvents();
qCDebug(testLogger) << ": test ";
qDebug()<<QString("This is a Qt log browser").append(QString::number(i, 10));
}
is_finished = true;
}
}
void LogBrowser::closeEvent(QCloseEvent *event)
{
QMessageBox::StandardButton answer = QMessageBox::question(
this,
tr("Close Log Browser?"),
tr("Do you really want to close the log browser?"),
QMessageBox::Yes | QMessageBox::No
);
if (answer == QMessageBox::Yes)
event->accept();
else
event->ignore();
}
void LogBrowser::keyPressEvent(QKeyEvent *event)
{
event->ignore();
}
使用
1、main.cpp
#include "mainwindow.h"
#include <QPointer>
#include <QApplication>
LogBrowser *mylog;
void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
if(mylog)
mylog->outputMessage(type, context, msg);
}
//void test(){
// for(int i=0; i<20; i++)
// {
// QCoreApplication::processEvents();
// qDebug()<<QString("This is a Qt log browser tetststststts").append(QString::number(i, 10));
// }
//}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
mylog = new LogBrowser();
mylog->show();
qInstallMessageHandler(outputMessage);
// test();
return a.exec();
}
2、使用2:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <logbrowser.h>
LogBrowser *mylog;
void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
if(mylog)
mylog->outputMessage(type, context, msg);
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
mylog = new LogBrowser(this);
mylog->show();
qInstallMessageHandler(outputMessage);
//--------------------------------
for(int i=0; i<20; i++)
{
QCoreApplication::processEvents();
qDebug()<<QString("This is a Qt log browser tetststststts").append(QString::number(i, 10));
}
}