基于Qt 的自定义log处理机制

qDebug重定向到 file 和 console

Qt 通过qInstallMessageHandler 函数可以自定义qDebug等输出重定向,综合网上各路大神的讲解,整理实现如下:

  • 以日期时间命名log文件,例如:20220207_141216.log
  • log输出文件夹只保留10份历史log
  • Release 版本默认不包含函数名和行数等信息,需要在 .pro 文件中加入DEFINES += QT_MESSAGELOGCONTEXT

其实现也很简单,直接上代码

LogHandler.h

#ifndef LOGHANDLER_H
#define LOGHANDLER_H

#include <iostream>
#include <QDebug>
#include <QDateTime>
#include <QMutexLocker>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTimer>
#include <QTextStream>
#include <QTextCodec>

const int g_logLimitSize = 5;

struct LogHandlerPrivate {
    
    
    LogHandlerPrivate();
    ~LogHandlerPrivate();

    static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);

    QDir   logDir;              // 日志文件夹
    QTimer flushLogFileTimer;   // 刷新输出到日志文件的定时器

    static QFile *logFile;      // 日志文件
    static QTextStream *logOut; // 输出日志的 QTextStream,使用静态对象就是为了减少函数调用的开销
    static QMutex logMutex;     // 同步使用的 mutex
};

class LogHandler {
    
    
public:
    void installMessageHandler();   // 给Qt安装消息处理函数
    void uninstallMessageHandler(); // 取消安装消息处理函数并释放资源

    static LogHandler& Get() {
    
    
        static LogHandler m_logHandler;
        return m_logHandler;
    }

private:
    LogHandler();
    LogHandlerPrivate *d;
};

#endif // LOGHANDLER_H

LogHandler.cpp

#include "LogHandler.h"

// 初始化 static 变量
QMutex LogHandlerPrivate::logMutex;
QFile* LogHandlerPrivate::logFile = nullptr;
QTextStream* LogHandlerPrivate::logOut = nullptr;

LogHandlerPrivate::LogHandlerPrivate() {
    
    
    logDir.setPath("log");
    if (!logDir.exists())
        logDir.mkpath(".");

    //保留10份历史log
    logDir.setFilter(QDir::AllEntries | QDir::NoDotAndDotDot);      // 过滤掉 ". or .." 文件
    QFileInfoList fileList = logDir.entryInfoList();
    int currentCount = logDir.count();
    foreach (QFileInfo f, fileList ) {
    
    
        std::cout << f.baseName().toStdString() <<std::endl;
        if (currentCount <= 10)
            break;
        logDir.remove(f.absoluteFilePath());
        currentCount--;
    }

    // 新建本次启动log文件
    QString logPath = logDir.absoluteFilePath(QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss.log"));
    if (logFile == nullptr) {
    
    
        logFile = new QFile(logPath);
        logOut  = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Append)) ?  new QTextStream(logFile) : nullptr;
        if (logOut != nullptr)
            logOut->setCodec("UTF-8");
    }

    // 定时刷新日志输出缓存到文件,单位ms
    flushLogFileTimer.setInterval(10);
    flushLogFileTimer.start();
    QObject::connect(&flushLogFileTimer, &QTimer::timeout, [] {
    
    
        QMutexLocker locker(&LogHandlerPrivate::logMutex);
        if (nullptr != logOut)
            logOut->flush();
    });
}

LogHandlerPrivate::~LogHandlerPrivate() {
    
    
    if (nullptr != logFile) {
    
    
        logFile->flush();
        logFile->close();
        delete logOut;
        delete logFile;

        logOut  = nullptr;
        logFile = nullptr;
    }
}

void LogHandlerPrivate::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
    
    
    QMutexLocker locker(&LogHandlerPrivate::logMutex);
    QString level;

    switch (type) {
    
    
    case QtDebugMsg:
        level = "debug";
        break;
    case QtInfoMsg:
        level = "info ";
        break;
    case QtWarningMsg:
        level = "warn ";
        break;
    case QtCriticalMsg:
        level = "err  ";
        break;
    case QtFatalMsg:
        level = "fatal";
        break;
    default:
        break;
    }

    // 输出到标准输出: Windows 下 std::cout 使用 GB2312,而 msg 使用 UTF-8,但是程序的 Local 也还是使用 UTF-8
#if defined(Q_OS_WIN)
    QByteArray localMsg = QTextCodec::codecForName("GB2312")->fromUnicode(msg); //msg.toLocal8Bit();
#else
    QByteArray localMsg = msg.toLocal8Bit();
#endif

    std::cout << std::string(localMsg) << std::endl;

    if (nullptr == LogHandlerPrivate::logOut)
        return;

    // 输出到日志文件, 格式: 时间 [Level] 函数 行数: 消息
    (*LogHandlerPrivate::logOut) << QString("%1 [%2] %3 %4: %5\n")
                                    .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"))
                                    .arg(level, -5, ' ').arg(context.line).arg(context.function).arg(msg);
}

LogHandler::LogHandler() : d(nullptr) {
    
    }

// 给Qt安装消息处理函数
void LogHandler::installMessageHandler() {
    
    
    QMutexLocker locker(&LogHandlerPrivate::logMutex); // 类似C++11的lock_guard,析构时自动解锁

    if (nullptr == d) {
    
    
        d = new LogHandlerPrivate();
        qInstallMessageHandler(LogHandlerPrivate::messageHandler); // 给 Qt 安装自定义消息处理函数
    }
}

// 取消安装消息处理函数并释放资源
void LogHandler::uninstallMessageHandler() {
    
    
    QMutexLocker locker(&LogHandlerPrivate::logMutex);

    qInstallMessageHandler(nullptr);
    delete d;
    d = nullptr;
}

main.cpp

#include "LogHandler.h"

#include <QApplication>

int main(int argc, char *argv[]) {
    
    
    QApplication app(argc, argv);

    // [[1]] 安装消息处理函数
    LogHandler::Get().installMessageHandler();

    // [[2]] 输出测试,查看是否写入到文件
    qDebug() << "Hello";
    qInfo() << "当前时间是: " << QTime::currentTime().toString("hh:mm:ss");
    qWarning() << QString("God bless you!");
    qCritical("This is a critical message at thisisqt.com");
    //qFatal("This is a fatal message at thisisqt.com");  //该语句会使程序直接崩溃,且无log输出

    // [[3]] 取消安装自定义消息处理,然后启用
    LogHandler::Get().uninstallMessageHandler();

    qDebug() << "........\n"; // 不写入日志

    app.exit();
    return 0;
}

效果如下
在这里插入图片描述

附录

QMutexLocker

其旨在用于简化lock、unlock操作。
在这里插入图片描述
在这里插入图片描述
常规lock、unlock,应用场景示例(qt 官方示例)

int complexFunction(int flag)
{
    
    
    mutex.lock();

    int retVal = 0;

    switch (flag) {
    
    
    case 0:
    case 1:
        retVal = moreComplexFunction(flag);
        break;
    case 2:
        {
    
    
            int status = anotherFunction();
            if (status < 0) {
    
    
                mutex.unlock();
                return -2;
            }
            retVal = status + flag;
        }
        break;
    default:
        if (flag > 10) {
    
    
            mutex.unlock();
            return -1;
        }
        break;
    }

    mutex.unlock();
    return retVal;
}

简化后代码如下,QMutexLocker 是在函数的入口与出口处lock、unlock

int complexFunction(int flag)
{
    
    
    QMutexLocker locker(&mutex);

    int retVal = 0;

    switch (flag) {
    
    
    case 0:
    case 1:
        return moreComplexFunction(flag);
    case 2:
        {
    
    
            int status = anotherFunction();
            if (status < 0)
                return -2;
            retVal = status + flag;
        }
        break;
    default:
        if (flag > 10)
            return -1;
        break;
    }

    return retVal;
}

struct in C and C++

  • C++ 中struct 可以有成员函数(构造函数、析构函数、常规函数和虚函数)、静态成员(静态成员函数和静态成员变量),C 中不可以
  • struct 默认访问属性是 public,class 默认访问属性是 private
  • struct 继承 struct 或 class 默认是 public 继承,class 继承 struct 或 class 默认是 private 继承

–end

猜你喜欢

转载自blog.csdn.net/tianzong2019/article/details/122796700
今日推荐