在编程开发中,日志功能至关重要,对于在开发期间或者是程序上线后,都有助于排查问题;
对于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环境不受影响,可正常使用!