在编程开发中,日志功能至关重要,对于在开发期间或者是程序上线后,都有助于排查问题;
对于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>
#endif
class 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);
#else
qInstallMessageHandler(Log);
#endif
}
//卸载日志钩子
void LogHelper::stop()
{
#if (QT_VERSION <= QT_VERSION_CHECK(5,0,0))
qInstallMsgHandler(0);
#else
qInstallMessageHandler(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_UNIX
qputenv("QT_LOGGING_RULES", "*.debug=false;default.debug=true");
#endif
QApplication a(argc, argv);
MainWidget w;
//w.show();
w.showFullScreen();
return a.exec();
}
windows环境不受影响,可正常使用!