0.前言
相对于第三方的日志库,在 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);
返回的函数指针我们可以保存起来,需要输出到控制台时进行调用。
1.最简单的操作
一个最简单的示例如下,重定向到文件:
#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;
}
运行结果:
2.实现一个简易的日志管理类
需求很简单,同时输出到界面中的编辑框、文件、控制台。
代码链接: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();
}
#pragma once
#include <QObject>
#include <QFile>
#include <QMutex>
#include <QElapsedTimer>
#include <QDebug>/*** @brief 简易的日志管理类,作为单例* @author 龚建波* @date 2020-08-13* @details* 初始化时调用 initManager 重定向* @note* 改为手动调用 initManager 是为了便于流程控制* 此外,也可以手动调用 freeManager*/
class LogManager : public QObject
{Q_OBJECTQ_DISABLE_COPY_MOVE(LogManager)LogManager();
public:~LogManager();// 获取单例实例static LogManager *getInstance();// 获取带 html 样式标签的富文本Q_INVOKABLE static QString richText(int msgType, const QString &log);// 初始化,如重定向等void initManager(const QString &path = QString());// 释放void freeManager();private:// 重定向到此接口static void outputHandler(QtMsgType type, const QMessageLogContext &context, const QString&msg);// 获取重定向的打印信息,在静态函数种回调该接口void outputLog(QtMsgType type, const QMessageLogContext &context, const QString &msg);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;
};
#include "LogManager.h"
#include <QApplication>
#include <QDir>
#include <QThread>
#include <QTextStream>
#include <QDateTime>LogManager::LogManager()
{//initManager();
}LogManager::~LogManager()
{freeManager();
}LogManager *LogManager::getInstance()
{// 单例,初次调用时实例化static LogManager instance;return &instance;
}QString LogManager::richText(int msgType, const QString &log)
{QString log_text;QTextStream stream(&log_text);switch (msgType) {case QtDebugMsg: stream << "<span style='color:green;'>"; break;case QtInfoMsg: stream << "<span style='color:blue;'>"; break;case QtWarningMsg: stream << "<span style='color:gold;'>"; break;case QtCriticalMsg: stream << "<span style='color:red;'>"; break;case QtFatalMsg: stream << "<span style='color:red;'>"; break;default: stream << "<span style='color:red;'>"; break;}stream << log << "</span>";return log_text;
}void LogManager::initManager(const QString &path)
{// 保存路径filePath = path;if (filePath.isEmpty()){// 用到了 QApplication::applicationDirPath(),需要先实例化一个appint 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(LogManager::outputHandler);
}void LogManager::freeManager()
{file.close();qInstallMessageHandler(nullptr);
}void LogManager::outputHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{// 转发给单例的成员函数LogManager::getInstance()->outputLog(type, context, msg);
}void LogManager::outputLog(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{// widget 中的 log,context.category = default// qml 中的 log,context.category = qml,此时默认的 output 会增加一个 "qml:" 前缀输出//fprintf(stderr, "print: type = %d, category = %s \n", type, context.category);// 如果要写文件需要加锁,因为函数调用在 debug 调用线程QMutexLocker locker(&logMutex);QString out_text;QTextStream stream(&out_text);// 时间stream << QDateTime::currentDateTime().toString("[yyyy-MM-dd hh:mm:ss]");// 日志类型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;}// 线程 idstream << "[" << QThread::currentThreadId() << "]";// 输出位置stream << "[" << context.file << ":" << context.line << "]";// 日志信息stream << msg;//写入文件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 << Qt::endl;}// 发送信号给需要的对象,如 ui 上显示日志emit newLog(type, out_text);// 默认的输出,控制台// 区分日志类型给文本加颜色// 常见格式为:\e[显示方式;背景颜色;前景文字颜色m << 输出字符串 << \e[0m// 其中 \e=\033// -----------------// 背景色 字体色// 40: 30: 黑// 41: 31: 红// 42: 32: 绿// 43: 33: 黄// 44: 34: 蓝// 45: 35: 紫// 46: 36: 深绿// 47: 37: 白// -----------------QString cmd_text;stream.setString(&cmd_text);switch (type) {case QtDebugMsg: // debug 绿色stream << "\033[32m"; break;case QtInfoMsg: // info 蓝色stream << "\033[34m"; break;case QtWarningMsg: // warning 黄色stream << "\033[33m"; break;case QtCriticalMsg: // critical 红字stream << "\033[31m"; break;case QtFatalMsg: // fatal 黑底红字// qFatal 表示致命错误,默认处理会报异常的stream<<"\033[0;31;40m"; break;default: // defualt 默认颜色stream << "\033[0m"; break;}stream << out_text << "\033[0m";defaultOutput(type, context, cmd_text);
}