QT 日志 - qInstallMessageHandler将qDebug()打印内容输出到文件

在编程开发中,日志功能至关重要,对于在开发期间或者是程序上线后,都有助于排查问题;

对于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环境不受影响,可正常使用!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cpp_learners

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值