qt初入门9:qt记录日志的方式,日志库了解练习(qInstallMessageHandler,qslog, log4qt)

news2024/11/15 1:46:14

项目中用到qt,考虑有需要用到去记录日志,结合网络,整理一下,做记录。

简单了解后,qt实现日志模块思考:
1:借助qt自带的qInstallMessageHandler重定向到需要的目的地。
2:自己封装一下qt自带的消息处理器封装使其好用,以及扩展其他日志功能。
3:log4qt (可以通过配置文件进行相关输出文件的设置 也可以不通过文件,最没问题可靠的吧 )
4:QsLog 轻量级(简单了解了源码,确实轻量)
5:其他(暂时没研究到),考虑增加缓存模块SimpleQtLogger spdlog easylogging 线程异步写

用到qt写入日志的功能,结合网络,简单对其进行整理。

1:使用qt自带的,安装消息处理器 qInstallMessageHandler

使用qInstallMessageHandler 注册消息处理回调函数,可以把日志写入到文件,ui等。

1.1:官方实例

注意:测试官方例子时,遇到qt总是不输出日志,不知道什么原因。

qt帮助手册,或者参考Qt安装消息处理qInstallMessageHandler输出详细日志-阿里云开发者社区 (aliyun.com)

 #include <stdio.h>
 #include <stdlib.h>
 #include <QCoreApplication>
 //#include <QDateTime>
 //#include <QFile>

 void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
 {
     QByteArray localMsg = msg.toLocal8Bit();
     const char *file = context.file ? context.file : "";
     const char *function = context.function ? context.function : "";
     switch (type) {
     case QtDebugMsg:
         fprintf(stderr, "Debug: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
         break;
     case QtInfoMsg:
         fprintf(stderr, "Info: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
         break;
     case QtWarningMsg:
         fprintf(stderr, "Warning: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
         break;
     case QtCriticalMsg:
         fprintf(stderr, "Critical: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
         break;
     case QtFatalMsg:
         fprintf(stderr, "Fatal: %s (%s:%u, %s)\n", localMsg.constData(), file, context.line, function);
         break;
     }
 }

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 安装消息处理程序
    qInstallMessageHandler(myMessageOutput);

    // 打印信息
    qDebug()<<"mytest   123456!!!";
    qDebug("This is a debug message.");
    qWarning("This is a warning message.");
    qCritical("This is a critical message.");
    //qFatal 会终止程序  debug模式下运行会直接段错误 qt的机制
    qFatal("This is a fatal message.");
	
    qInstallMessageHandler(nullptr);
    return a.exec();
}

测试运行结果:

注意:

Debug: mytest   123456!!! (..\my_qt_test\main.cpp:44, int main(int, char**))
Debug: This is a debug message. (..\my_qt_test\main.cpp:45, int main(int, char**))
Warning: This is a warning message. (..\my_qt_test\main.cpp:46, int main(int, char**))
Critical: This is a critical message. (..\my_qt_test\main.cpp:47, int main(int, char**))
Fatal: This is a fatal message. (..\my_qt_test\main.cpp:49, int main(int, char**))

1.2:输出到文件,实际上参考进行修改。

release版本没有函数名等信息,参考 Qt开发之路60—Qt日志重定向之输出Log至文件或UI控件上_qt日志输出到文件-CSDN博客

// 自定义消息处理程序  这里就比较灵活了,按自己需要设计   写入多个目的地,写入文件,写入ui等
void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    static QMutex mutex;
   
    QString text;
    switch(type)
    {
    case QtInfoMsg:
        text =  QString("[Info\t]");
        break;
    case QtDebugMsg:
        text = QString("[Debug\t]");
        break;
    case QtWarningMsg:
        text = QString("[Warning\t]");
        break;
    case QtCriticalMsg:
        text = QString("[Critical\t]");
        break;
    case QtFatalMsg:
        text = QString("[Fatal\t]");
    }
    QDateTime current_date_time = QDateTime::currentDateTime();
    QString current_date = current_date_time.toString("yyyy-MM-dd hh:mm:ss ddd");
    QString message = text.append(current_date).append(" ").append(msg)
            .append(" file:").append(context.file)
            .append(" function:").append(context.function)
            .append(" category:").append(context.category)
            .append(" line:").append(QString::number(context.line)
            .append(" version:").append(QString::number(context.version)));
    QFile file(QString(QDate::currentDate().toString("yyyy-MM-dd") + ".txt"));
//考虑加锁逻辑   没关注和测试多线程 以及优化
mutex.lock();
    file.open(QIODevice::WriteOnly | QIODevice::Append);
    QTextStream text_stream(&file);
 
    qDebug() << message; // 实现控制台、文件多出输出
    text_stream<<message<<"\r\n";
    file.close();
// 解锁
mutex.unlock();
}

Release版本处理不打印函数名信息

#在pro文件中增加如下  需要重新构建生效
DEFINES += QT_MESSAGELOGCONTEXT
#屏蔽输出可以如下
DEFINES += QT_NO_WARNING_OUTPUT
DEFINES += QT_NO_DEBUG_OUTPUT
DEFINES += QT_NO_INFO_OUTPUT

其他:要输出到ui,同样可以在该函数中增加,注意用信号和不用信号的区别,以及界面会不会卡,未测试。

测试运行结果:

可以看到release版本和debug版本都已经正常输出日志,release版本中也屏蔽对应不打印的日志。

(注意:有qFatal 打印,会没有后续的。)

可以看到对应项目运行目录下有2024-07-19.txt 文件,如下内容
#没有在release版本pro文件下增加配置时
[Debug	]2024-07-19 18:00:12 周五 This is a debug message. file: function: category:default line:0 version:2
[Warning	]2024-07-19 18:00:12 周五 This is a warning message. file: function: category:default line:0 version:2
[Critical	]2024-07-19 18:00:12 周五 This is a critical message. file: function: category:default line:0 version:2
#pro文件下增加配置后  正常打印
[Critical	]2024-07-19 18:07:48 周五 This is a critical message. file:..\my_qt_test\main.cpp function:int main(int, char**) category:default line:66 version:2
[Fatal	]2024-07-19 18:07:48 周五 This is a fatal message. file:..\my_qt_test\main.cpp function:int main(int, char**) category:default line:68 version:2

2:借助qt自带消息处理器,自行封装自适应相关日志功能。

参考别人的内容,注意多线程安全问题,其他就是自己设计:

qt log 输出为文件,每分钟换一个log文件-CSDN博客

Qt 自定义日志类 - fengMisaka - 博客园 (cnblogs.com)

其中第二个版本实测可以用,这里拿来给整理思路做备份。

//实际还是借助qt消息注册器,注册消息处理函数   配合定时器做其他处理,加锁支持多线程
//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();

    // 打开日志文件 log.txt,如果日志文件不是当天创建的,则使用创建日期把其重命名为 yyyy-MM-dd.log,并重新创建一个 log.txt
    void openAndBackupLogFile();
    void checkLogFiles(); // 检测当前日志文件大小
    void autoDeleteLog(); // 自动删除30天前的日志

    // 消息处理函数
    static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg);

    QDir   logDir;              // 日志文件夹
    QTimer renameLogFileTimer;  // 重命名日志文件使用的定时器
    QTimer flushLogFileTimer;   // 刷新输出到日志文件的定时器
    QDate  logFileCreatedDate;  // 日志文件创建的时间

    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"); // TODO: 日志文件夹的路径,为 exe 所在目录下的 log 文件夹,可从配置文件读取
    QString logPath = logDir.absoluteFilePath("today.log"); // 获取日志的路径

    // ========获取日志文件创建的时间========
    // QFileInfo::created(): On most Unix systems, this function returns the time of the last status change.
    // 所以不能运行时使用这个函数检查创建时间,因为会在运行时变化,于是在程序启动时保存下日志文件的最后修改时间,
    logFileCreatedDate = QFileInfo(logPath).lastModified().date(); // 若日志文件不存在,返回nullptr

    // 打开日志文件,如果不是当天创建的,备份已有日志文件
    openAndBackupLogFile();

    // 十分钟检查一次日志文件创建时间
    renameLogFileTimer.setInterval(1000 *  2); // TODO: 可从配置文件读取
    renameLogFileTimer.start();
    QObject::connect(&renameLogFileTimer, &QTimer::timeout, [this] {
        QMutexLocker locker(&LogHandlerPrivate::logMutex);
        openAndBackupLogFile(); // 打开日志文件
        checkLogFiles(); // 检测当前日志文件大小
        autoDeleteLog(); // 自动删除30天前的日志
    });

    // 定时刷新日志输出到文件,尽快的能在日志文件里看到最新的日志
    flushLogFileTimer.setInterval(1000); // TODO: 可从配置文件读取
    flushLogFileTimer.start();
    QObject::connect(&flushLogFileTimer, &QTimer::timeout, [] {
        // qDebug() << QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); // 测试不停的写入内容到日志文件
        QMutexLocker locker(&LogHandlerPrivate::logMutex);
        if (nullptr != logOut) {
            logOut->flush();
        }
    });
}

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

        // 因为他们是 static 变量
        logOut  = nullptr;
        logFile = nullptr;
    }
}

// 打开日志文件 log.txt,如果不是当天创建的,则使用创建日期把其重命名为 yyyy-MM-dd.log,并重新创建一个 log.txt
void LogHandlerPrivate::openAndBackupLogFile() {
    // 总体逻辑:
    // 1. 程序启动时 logFile 为 nullptr,初始化 logFile,有可能是同一天打开已经存在的 logFile,所以使用 Append 模式
    // 2. logFileCreatedDate is nullptr, 说明日志文件在程序开始时不存在,所以记录下创建时间
    // 3. 程序运行时检查如果 logFile 的创建日期和当前日期不相等,则使用它的创建日期重命名,然后再生成一个新的 log.txt 文件
    // 4. 检查日志文件超过 LOGLIMIT_NUM 个,删除最早的
    // 备注:log.txt 始终为当天的日志文件,当第二天,会执行第3步,将使用 log.txt 的创建日期重命名它

    // 如果日志所在目录不存在,则创建
    if (!logDir.exists()) {
        logDir.mkpath("."); // 可以递归的创建文件夹
    }
    QString logPath = logDir.absoluteFilePath("today.log"); // log.txt的路径

    // [[1]] 程序每次启动时 logFile 为 nullptr
    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");

        // [[2]] 如果文件是第一次创建,则创建日期是无效的,把其设置为当前日期
        if (logFileCreatedDate.isNull()) {
            logFileCreatedDate = QDate::currentDate();
        }
    }

    // [[3]] 程序运行时如果创建日期不是当前日期,则使用创建日期重命名,并生成一个新的 log.txt
    if (logFileCreatedDate != QDate::currentDate()) {
        logFile->flush();
        logFile->close();
        delete logOut;
        delete logFile;

        QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString("yyyy-MM-dd.log"));;
        QFile::copy(logPath, newLogPath); // Bug: 按理说 rename 会更合适,但是 rename 时最后一个文件总是显示不出来,需要 killall Finder 后才出现
        QFile::remove(logPath); // 删除重新创建,改变创建时间

        // 重新创建 log.txt
        logFile = new QFile(logPath);
        logOut  = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ?  new QTextStream(logFile) : nullptr;
        logFileCreatedDate = QDate::currentDate();
        if (logOut != nullptr)
            logOut->setCodec("UTF-8");
    }
}

// 检测当前日志文件大小
void LogHandlerPrivate::checkLogFiles() {
    // 如果 protocal.log 文件大小超过5M,重新创建一个日志文件,原文件存档为yyyy-MM-dd_hhmmss.log
    if (logFile->size() > 1024*g_logLimitSize) {
        logFile->flush();
        logFile->close();
        delete logOut;
        delete logFile;

        QString logPath = logDir.absoluteFilePath("today.log"); // 日志的路径
        QString newLogPath = logDir.absoluteFilePath(logFileCreatedDate.toString("yyyy-MM-dd.log"));
        QFile::copy(logPath, newLogPath); // Bug: 按理说 rename 会更合适,但是 rename 时最后一个文件总是显示不出来,需要 killall Finder 后才出现
        QFile::remove(logPath); // 删除重新创建,改变创建时间

        logFile = new QFile(logPath);
        logOut  = (logFile->open(QIODevice::WriteOnly | QIODevice::Text | QIODevice::Truncate)) ?  new QTextStream(logFile) : NULL;
        logFileCreatedDate = QDate::currentDate();
        if (logOut != nullptr)
            logOut->setCodec("UTF-8");
    }
}

// 自动删除30天前的日志
void LogHandlerPrivate::autoDeleteLog()
{
    QDateTime now = QDateTime::currentDateTime();

    // 前30天
    QDateTime dateTime1 = now.addDays(-30);
    QDateTime dateTime2;

    QString logPath = logDir.absoluteFilePath("today.log"); // 日志的路径
    QDir dir(logPath);
    QFileInfoList fileList = dir.entryInfoList();
    foreach (QFileInfo f, fileList ) {
        // "."和".."跳过
        if (f.baseName() == "")
            continue;

        dateTime2 = QDateTime::fromString(f.baseName(), "yyyy-MM-dd");
        if (dateTime2 < dateTime1) { // 只要日志时间小于前30天的时间就删除
            dir.remove(f.absoluteFilePath());
        }
    }
}

// 消息处理函数
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 = "ERROR";
        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] (文件名:行数, 函数): 消息
    QString fileName = context.file;
    int index = fileName.lastIndexOf(QDir::separator());
    fileName = fileName.mid(index + 1);

    (*LogHandlerPrivate::logOut) << QString("%1 - [%2] (%3:%4, %5): %6\n")
                                    .arg(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")).arg(level)
                                    .arg(fileName).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函数入口
#include "LogHandler.h"

#include <QApplication>
#include <QDebug>
#include <QTime>
#include <QPushButton>

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

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

    // [[2]] 输出测试,查看是否写入到文件
    qDebug() << "Hello";
    qDebug() << "当前时间是: " << QTime::currentTime().toString("hh:mm:ss");
    qInfo() << QString("God bless you!");

    QPushButton *button = new QPushButton("退出");
    button->show();
    QObject::connect(button, &QPushButton::clicked, [&app] {
        qDebug() << "退出";
        app.quit();
    });

    // [[3]] 取消安装自定义消息处理,然后启用
    LogHandler::Get().uninstallMessageHandler();
    qDebug() << "........"; // 不写入日志
    LogHandler::Get().installMessageHandler();

    int ret = app.exec(); // 事件循环结束

    // [[4]] 程序结束时释放 LogHandler 的资源,例如刷新并关闭日志文件
    LogHandler::Get().uninstallMessageHandler();

    return ret;
}

实际测试,确实可以在运行目录下的log目录下生成对应的日志。 可以有效的和qt原生日志记录配合使用。

理解了一下,这种方式多线程无缓冲区实时进行记录,线程数多以及日志量大的情况下,是不是稍微有影响~

注意:release版本注意在pro配置文件下加 DEFINES += QT_MESSAGELOGCONTEXT

3:QsLog 轻量级日志库。

直接打开example下的log_example.pro 项目就可以运行看到效果,包含了生成动态库,链接库调用,可执行文件调用。

拷贝对应的源码到自己项目下,调用对应的qslog文件(或者借助pri文件封装)通过源码调用。

生成对应的dll,拷贝dll和对应头文件到自己项目下调用。

3.1 简单了解架构

直接获取到Qslog的源码,可以看出就是一个qt项目。

qslog运行example的pro(包括生成动态库QsLogSharedLibrary.pro,动态库调用日志库log_example_shared.pro,可执行文件调用日志库log_example_main.pro),以及单元测试pro。 可以分别学习。

3.2 首先运行生成动态库

用qt打开项目QsLogSharedLibrary.pro,不要有中文路径。(实际上log_example.pro是总入口,用这个打开也可以,实际上是按顺序执行,直接测试生成动态库,调用动态库)

分析pro文件(DESTDIR = $$PWD/build-QsLogShared),以及通过现象可以看到,在源码目录下的build-QsLogShared生成相关目标文件,也就是动态链接库QsLog2.dll,实际项目使用这个dll和对应头文件去调用。

3.3 运行这里的example。

已经生成对应的动态连接库,只需要链接以及包含头文件就可以使用。

这里提供了两种example,一种是动态库进行调用日志库,一种是可执行文件调用(直接调用日志库,调用动态库(动态库中调用了日志库))进行测试。

打开log_example.pro 可直接运行测试。

3.4 分析调用

//1:直接调用源码,类似这里,直接把qslog.pri文件以及对应的.h和.cpp文件拷贝到你需要的项目下,pro文件中调用
//2:自己用qt编译,把对应的头文件和这里生成的dll文件拷贝到项目下,pro文件中调用即可。

//动态库的调用 也就是这里的log_example_shared.pro 
//这里仅仅只是调用了日志库的接口,实际上相关日志库的初始化还是依赖于可执行main函数,没有做日志库的初始化,也是无意义的。

//可执行文件的调用 log_example_main.cpp main函数中部分代码 
	using namespace QsLogging;

   // 1. 可以看到  这里是使用了一个static指针对象 期望全局唯一
   Logger& logger = Logger::instance();
   logger.setLoggingLevel(QsLogging::TraceLevel); //设置日志级别  基本初始化
   const QString sLogPath(QDir(a.applicationDirPath()).filePath("log.txt"));

   // 2. 设置输出到目的地
	//这里要适当估算文件大小   512字节很小
   DestinationPtr fileDestination(DestinationFactory::MakeFileDestination(
     sLogPath, EnableLogRotation, MaxSizeBytes(512), MaxOldLogCount(2))); //设置输出到文件,允许分割,文件字节限制,备份两个
   DestinationPtr debugDestination(DestinationFactory::MakeDebugOutputDestination());//添加stdout为目的地  取消后看到qt控制台不输出
   DestinationPtr functorDestination(DestinationFactory::MakeFunctorDestination(&logFunction));//输出到函数 通过函数进行加工处理
	//还可以添加到控制台终端
   //下面是真正的添加
   logger.addDestination(debugDestination);
   logger.addDestination(fileDestination);
   logger.addDestination(functorDestination);

   // 3. start logging
   QLOG_INFO() << "Program started";
   QLOG_INFO() << "Built with Qt" << QT_VERSION_STR << "running on" << qVersion();

   QLOG_TRACE() << "Here's a" << QString::fromUtf8("trace") << "message";
   QLOG_DEBUG() << "Here's a" << static_cast<int>(QsLogging::DebugLevel) << "message";
   QLOG_WARN()  << "Uh-oh!";
   qDebug() << "This message won't be picked up by the logger";
   QLOG_ERROR() << "An error has occurred";
   qWarning() << "Neither will this one";
   QLOG_FATAL() << "Fatal error!";
//这里相当于关闭日志记录
   logger.setLoggingLevel(QsLogging::OffLevel);
   for (int i = 0;i < 10000000;++i) {
       QLOG_ERROR() << QString::fromUtf8("this message should not be visible");
   }
   logger.setLoggingLevel(QsLogging::TraceLevel);

已经能正常运行,但是会发现,文件中的日志仅仅是简单的日志记录,看不到行号等信息

在配置文件qslog.pri中增加DEFINES += QS_LOG_LINE_NUMBERS 可解决

在配置文件qslog.pri中增加 DEFINES += QS_LOG_SEPARATE_THREAD 可实现异步打印,专门线程打印。

# 在配置文件qslog.pri中增加
DEFINES += QS_LOG_LINE_NUMBERS     #文件中打印行号   测试发现通过lib调用还是打印行数有问题  直接调用原文件把.h和cpp拷贝到目标项目下调用可以 这里是
DEFINES += QS_LOG_SEPARATE_THREAD  #专门线程异步打印
DEFINES += QS_LOG_DISABLE          #禁止日志打印

参考:Qt轻量级日志库QsLog的使用 | 码农家园 (codenong.com)

比如下面是我的调用测试,可以正常打印行和列:

#1:项目pro文件下增加
include(qslog/QsLog.pri)

#2:创建了一个qslog文件夹  文件夹下QsLog.pri 可以取开源库下的,以及对应依赖头文件和cpp拷贝
INCLUDEPATH += $$PWD
DEFINES += QS_LOG_LINE_NUMBERS    # automatically writes the file and line for each log message
#DEFINES += QS_LOG_DISABLE         # logging code is replaced with a no-op
#DEFINES += QS_LOG_SEPARATE_THREAD # messages are queued and written from a separate thread
SOURCES += $$PWD/QsLogDest.cpp \
    $$PWD/QsLog.cpp \
    $$PWD/QsLogDestConsole.cpp \
    $$PWD/QsLogDestFile.cpp \
    $$PWD/QsLogDestFunctor.cpp

#同样的  这里可以换成对应的dll去调用也可以
HEADERS += $$PWD/QsLogDest.h \
    $$PWD/QsLog.h \
    $$PWD/QsLogDestConsole.h \
    $$PWD/QsLogLevel.h \
    $$PWD/QsLogDestFile.h \
    $$PWD/QsLogDisableForThisFile.h \
    $$PWD/QsLogDestFunctor.h
    
#3:对应main函数中调用,参考开源库下example的main进行调用即可

在这里插入图片描述

3.5:简单了解源码。

都说qsLog是个轻量级日志库,思考为何这样说,简单看了一下源码,发现还是能很快了解到其内容及架构,

1:支持写日志到文件,支持特定函数中对日志进行加工输出到终端,支持定制输出到qt应用程序输出栏,支持通过信号和槽函数的方式写日志到对应的ui界面上,list管理。

2:借助static关键字,实现类似单例的功能,只有一个日志管理类,写日志时,通过list依次遍历写入。

3:初始化以及相关定制的设计,借助工厂函数+智能指针,设置不同的入口。 不同的内部是具体的逻辑,写文件,qt输出,终端输出,调用触发槽函数。

4:写日志到文件涉及稍微复杂,需要专门定制,对文件大小等校验,底层实际还是基本的写文件。

5:支持线程异步写入,调用qt的QRunnable和QThreadPool。 每次写日志时触发调用线程池获取线程写入,但是这里指定了线程池最大一个线程,实际上每日写日志取线程池中唯一一个线程去执行(什么场景下会触发取不到执行线程等待呢?影响有多大?)。

6:支持多线程,因为write函数时,有加锁。

简单了解后,可以看出,qslog确实轻量级,能满足qt基本的一些日志输出。

4:log4qt

选择从源码入手,参考源码中自带的example入手。

查看根目录下的pro文件,可以看到根目录依次执行了src,tests,examples相关目标,直接用qt打开试试,分别执行对应的子项目试试。

测试后发现,tests目录下相关单元测试的项都能正常执行,未过多关注。

example目录下是实际代码调用演示demo,选择对应的子项目,都可以正常运行。(有两个example)

4.1: 关注自带example(basic和propertyconfigurator)

可以看出 basic 并未调用配置文件,直接使用代码配置,运行正常,能生成日志。

propertyconfigurator项目调用了配置文件设置,运行时需要交对应的配置文件log4qt.properties拷贝到运行目录下,同样运行正常,生成文件。

4.2 :简单了解逻辑

从qt自带的demo中可以了解到以下信息:

1:调用log4qt对象的实例化,可以有一下方式(参考example):

在类中通过宏LOG4QT_DECLARE_QCLASS_LOGGER 定义。

在cpp文件中通过LOG4QT_DECLARE_STATIC_LOGGER(logger, LoggerStatic) 静态设置。

直接在需要记录日志的地方,使用auto logger = Log4Qt::Logger::rootLogger();获取

2:在使用log4qt前,需要对其进行初始化。(数据采集,目标设置,格式布局)

主要通过设置对应的Appender(输出器,定义日志消息的输出方式和目标位置),来设置日志输出目标和方式,可以设置多个。

可以通过代码依次设置Appender和layout,也可以通过配置文件设置。

//简单了解log4qt下的Appender  设置输出的目标 比如:终端、文件、远程socket
Log4Qt提供了多种类型的Appender,常用的包括:

ConsoleAppender:将日志消息输出到控制台。
FileAppender:将日志消息输出到指定的文件中。
DailyFileAppender:类似于FileAppender,每天会生成新的日志。
RollingFileAppender:类似于FileAppender,但支持滚动记录日志,即当日志文件达到一定大小时自动创建新的文件并继续写入。
DailyRollingFileAppender:类似于RollingFileAppender,但按照日期进行滚动记录日志。
SyslogAppender:将日志消息通过Syslog协议发送到远程Syslog服务器。
QtDebugAppender:将日志消息转发给Qt的QDebug系统。
DatabaseAppender: 将日志写到数据库?

//layout是用来定义日志消息的格式布局   比如:xml格式、时间,日期,线程,级别)自由组合格式;
以下是一些常见的log4qt的layout选项:

SimpleLayout: 简单布局,将日志级别、时间戳和日志消息按照固定格式输出。
PatternLayout: 模式布局,通过自定义模式字符串来指定要显示的信息,并可以使用占位符来表示各种属性,如日志级别、线程ID等。
HTMLLayout: HTML布局,以HTML表格的形式展示日志事件信息,可在Web页面中直接使用。
TTCCLayout: TTCC(时间、线程、类别和上下文)布局,在模式布局基础上添加了线程名和类别名称等额外信息。
XMLLayout: XML布局,以XML格式记录日志事件信息。

更多信息还得了解源码。。。

个人觉得,使用的核心就是对Appender和Layout进行理解,按照自己想要,进行特定的配置。

4.3:简单练习使用

参考12.4-在Qt中使用Log4Qt输出Log文件,看这一篇就足够了 | 码农家园 (codenong.com)

结合百度,思考到比较好的调用方式能想到大概有三种:

1:参考链接,这个封装挺好的,但是每次每个类都需要实例化,比如借助LOG4QT_DECLARE_STATIC_LOGGER。

2:可以实现一个帮助类,比如单例的形式,全局可以调用,实现日志的注册,提供日志的接口,但这种能否正确打印函数名等信息呢?

3:结合qt自带的消息处理注册函数,把qt的输出重定向到log4qt中,如果多线程,注意内部加锁,log4qt内部记录日志是线程安全的。

初次之外,有个专门的开源库Log4Qt-examples,有一些参考demo,也能给一些参考。

4.3.1 这里参考别人的,做备份,方便回顾(只是对初始化做了封装,还是基本调用)。

注意:这里简化了入口配置,但是每个类还是得依次构造logger()对象才能去用。

借助qt源码,首先需要生成log4qt对应的dll,以及拷贝对应的头文件,以便新项目使用。

在这里插入图片描述

在外部使用的项目处,pro文件中增加 include($$PWD/log4qt_helper/log4qtlib.pri)

对应的相关文件细节:

log4qtlib.pri

#message("log4qt_lib_been_attached")
CONFIG += c++11
INCLUDEPATH += $$PWD/helper
INCLUDEPATH += $$PWD/include
DEPENDPATH += $$PWD/include

SOURCES += \
        $$PWD/helper/log4qt_init_helper_by_coding.cpp \
        $$PWD/helper/log4qt_init_helper_by_config.cpp
HEADERS += \
        $$PWD/helper/log4qt_init_helper_by_coding.h \
        $$PWD/helper/log4qt_init_helper_by_config.h

macx {
    macx: LIBS += -L$$PWD/bin/ -llog4qt.1
}
win32 {
    win32: LIBS += -L$$PWD/bin/ -llog4qt
}
DISTFILES += \
    $$PWD/log4qt.properties

对应的帮助类:

//1:log4qt_init_helper_by_coding.h 使用配置文件进行配置头文件
#ifndef LOG4QT_INIT_HELPER_BY_CODING_H
#define LOG4QT_INIT_HELPER_BY_CODING_H

//layouts
#include "log4qt/ttcclayout.h"
//appenders
#include "log4qt/consoleappender.h"
#include "log4qt/rollingfileappender.h"

extern void SetupLog4QtByCodingWithLogSavingDirAbsPath(QString log_saving_dir_abs_path);
extern void ShutDownLog4QtByCoding();

#endif // LOG4QT_INIT_HELPER_BY_CODING_H

//2:log4qt_init_helper_by_config.h 使用代码进行配置头文件
#ifndef LOG4QT_INIT_HELPER_BY_CONFIG_H
#define LOG4QT_INIT_HELPER_BY_CONFIG_H

//启动log4qt库,使用配置文件的方式配置log4qt
#include <QString>
extern void SetupLog4QtByConfigWithConfigFileAbsPath(QString config_file_abs_path);
extern void ShutDownLog4QtByConfig();//关闭log4qt库

#endif // LOG4QT_INIT_HELPER_BY_CONFIG_H

//3:log4qt_init_helper_by_coding.cpp
#include "log4qt_init_helper_by_coding.h"

#include "log4qt/logger.h"

#include "log4qt/propertyconfigurator.h"
#include "log4qt/loggerrepository.h"
#include "log4qt/consoleappender.h"
#include "log4qt/ttcclayout.h"
#include "log4qt/logmanager.h"
#include "log4qt/fileappender.h"

void SetupLog4QtByCodingWithLogSavingDirAbsPath(QString log_saving_dir_abs_path)
{
    QString absPath = log_saving_dir_abs_path;
    auto rootLogger = Log4Qt::Logger::rootLogger();

    // Create a layout
    auto *layout = new Log4Qt::TTCCLayout();
    layout->setName(QStringLiteral("My Layout"));
    layout->setDateFormat("dd.MM.yyyy hh:mm:ss.zzz");
    layout->activateOptions();

    // Create a console appender
    Log4Qt::ConsoleAppender *consoleAppender =
        new Log4Qt::ConsoleAppender(layout, Log4Qt::ConsoleAppender::STDOUT_TARGET);
    consoleAppender->setName(QStringLiteral("My console Appender"));
    consoleAppender->activateOptions();
    rootLogger->addAppender(consoleAppender);

    //Create a rolling file appender
    Log4Qt::RollingFileAppender *rollingFileAppender =
        new Log4Qt::RollingFileAppender(layout, absPath + "/basic.log", true);
    rollingFileAppender->setName(QStringLiteral("My rolling file appender"));
    //default is 10 MB (10 * 1024 * 1024).
    rollingFileAppender->setMaximumFileSize(20 * 1024 * 1024);
    rollingFileAppender->setThreshold(Log4Qt::Level::Value::INFO_INT);//设置子输出等级过滤
    rollingFileAppender->activateOptions();
    rootLogger->addAppender(rollingFileAppender);

    //设置根logger允许所以等级的消息被输出(子输出过滤是在根输出过滤的基础上)
    rootLogger->setLevel(Log4Qt::Level::ALL_INT);
    Log4Qt::LogManager::setHandleQtMessages(true);//设置监听qt自带的log输出
}

void ShutDownLog4QtByCoding()
{
    auto logger = Log4Qt::Logger::rootLogger();
    logger->removeAllAppenders();
    logger->loggerRepository()->shutdown();
}

//4:log4qt_init_helper_by_config.cpp
#include "log4qt_init_helper_by_config.h"

#include <QFile>
#include <QDebug>
#include "log4qt/logger.h"

#include "log4qt/propertyconfigurator.h"
#include "log4qt/loggerrepository.h"
#include "log4qt/consoleappender.h"
#include "log4qt/ttcclayout.h"
#include "log4qt/logmanager.h"
#include "log4qt/fileappender.h"

void SetupLog4QtByConfigWithConfigFileAbsPath(QString config_file_abs_path)
{
    if (QFile::exists(config_file_abs_path)) {
        Log4Qt::PropertyConfigurator::configure(config_file_abs_path);
    } else {
        qDebug() << "Can't find log4qt-config-file path:" << config_file_abs_path;
    }
}
void ShutDownLog4QtByConfig()
{
    auto logger = Log4Qt::Logger::rootLogger();
    logger->removeAllAppenders();
    logger->loggerRepository()->shutdown();
}

对应的配置文件:

#设置储存log文件的根目录
logpath=.

log4j.reset=true
log4j.Debug=WARN
log4j.threshold=NULL
#设置是否监听QDebug输出的字符串
log4j.handleQtMessages=true
#在运行中,是否监视此文件配置的变化
log4j.watchThisFile=false

#设置根Logger的输出log等级为All
#设置Log输出的几种输出源(appender):console, daily, rolling
log4j.rootLogger=ALL, console, daily

#设置终端打印记录器
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.target=STDOUT_TARGET
log4j.appender.console.layout=org.apache.log4j.TTCCLayout
log4j.appender.console.layout.dateFormat=dd.MM.yyyy hh:mm:ss.zzz
log4j.appender.console.layout.contextPrinting=true
log4j.appender.console.threshold=ALL

#设置一个每日储存一个log文件的记录器
log4j.appender.daily=org.apache.log4j.DailyFileAppender
log4j.appender.daily.file=${logpath}/propertyconfigurator.log
log4j.appender.daily.appendFile=true
log4j.appender.daily.datePattern=_yyyy_MM_dd
log4j.appender.daily.keepDays=10
log4j.appender.daily.layout=${log4j.appender.console.layout}
log4j.appender.daily.layout.dateFormat=${log4j.appender.console.layout.dateFormat}
log4j.appender.daily.layout.contextPrinting=${log4j.appender.console.layout.contextPrinting}

# 配置一个滚动文件记录器
log4j.appender.rolling=org.apache.log4j.RollingFileAppender
log4j.appender.rolling.file= ${logpath}/propertyconfigurator_rolling.log
log4j.appender.rolling.appendFile=true
log4j.appender.rolling.maxFileSize= 20MB
log4j.appender.rolling.maxBackupIndex= 10
log4j.appender.rolling.layout=${log4j.appender.console.layout}
log4j.appender.rolling.layout.dateFormat=${log4j.appender.console.layout.dateFormat}
log4j.appender.rolling.layout.contextPrinting=${log4j.appender.console.layout.contextPrinting}

#这里可以参考log4qt example下的实例,主要是类中LOG4QT_DECLARE_QCLASS_LOGGER 的使用
# 给“LoggerObjectPrio”这个类的Logger定义log输出等级为Error,
# 给“LoggerObjectPrio”这个类的Logger定义log输出源:rolling
log4j.logger.LoggerObjectPrio=ERROR, rolling
#设置为false,表示“LoggerObjectPrio”这个类的logger不继承的rootLogger输出源(appender)
log4j.additivity.LoggerObjectPrio=false

从配置文件进行设置 对应的main函数:

#include "widget.h"

#include <QApplication>

#include <QApplication>
#include <QStandardPaths>
#include <QDir>

#include "log4qt_init_helper_by_coding.h"
#include "log4qt_init_helper_by_config.h"
#include "log4qt/logger.h" //每个使用log4qt的类都需要包含此头文件

//在类的cpp文件中,使用此静态方法声明logger(此方法比较通用)
//第二个参数写类名字,因此,输出的log条目中包含其对应的类名
LOG4QT_DECLARE_STATIC_LOGGER(logger, Main)

#include <QDebug>

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

    //使用config 配置Log4Qt
    //把源代码目录中的"log4qt.properties"文件复制到编译好的可执行文件所在的目录
    //QString configFileAbsPath = QCoreApplication::applicationFilePath() + QStringLiteral(".log4qt.properties");//配置文件包括应用程序名称
    QString configFileAbsPath  = QCoreApplication::applicationDirPath() +"/"+ QStringLiteral("log4qt.properties");//配置文件不包括应用程序名称
    SetupLog4QtByConfigWithConfigFileAbsPath(configFileAbsPath);

    //可以使用以下三种方式编写Log输出条目
    //1.log4qt基本的logger输出
    logger()->debug() << "example ####11#####  logger()->debug()";
    logger()->error() << "example ####11#####  logger()->error()"<<1<<" xxx"<<3;
    logger()->error("xyz");
    
    //2.log4qt基本的宏定义输出
    l4qError() << "example ####22#####  l4qError() ";
    l4qError(QStringLiteral("example ####22#####  l4qError()  %1"), 10);

    //3.使用qt平台的Log库输出,(Log4Qt会监听qt的log的输出,并统一输出到Log文件中)
    qDebug() << "example ####33#####  qDebug()\n\n\n";
    ShutDownLog4QtByConfig();//exec()执行完成后,才关闭logger
    return ret;
}
//运行结果,注意  拷贝对应的配置文件到运行目录下、
//    这里和配置文件相对应,可以看到终端打印日志,生成两种日志文件,  propertyconfigurator_rolling.log 和propertyconfigurator_rolling.log

从代码进行设置的相关核心函数:

int main(int argc, char *argv[])
{
    //这里生成了日志文件的目录  可以结合QCoreApplication进行设置   这里核心还是看代码设置的逻辑 分析结果
    QString std_base_path = QCoreApplication::applicationDirPath();
    QString my_log_path = std_base_path + "/logs";
    // QString logSavingDirAbsPath  = QCoreApplication::applicationDirPath();
    qDebug() << "my_log_path = " << my_log_path;
    SetupLog4QtByCodingWithLogSavingDirAbsPath(my_log_path);

    //可以使用以下三种方式编写Log输出条目
    //1.log4qt基本的logger输出
    logger()->debug() << "example ####11#####  logger()->debug()";
    logger()->error() << "example ####11#####  logger()->error()"<<1<<" xxx"<<3;

    logger()->error("xyz");
    //2.log4qt基本的宏定义输出
    l4qError() << "example ####22#####  l4qError() ";
    l4qError(QStringLiteral("example ####22#####  l4qError()  %1"), 10);

    //3.使用qt平台的Log库输出,(Log4Qt会监听qt的log的输出,并统一输出到Log文件中)
    qDebug() << "example ####33#####  qDebug()\n\n\n";

      ShutDownLog4QtByCoding();//exec()执行完成后,才关闭logger
    return ret;
}
//从 中可以看到,这里设置两个输出,一个是终端,一个是以basic.log命名的文件。
//在运行目录下有logs/basic.log文件生成 日志对应的上 设置日志级别info,无debug日志。

更详细的设计布局,可以参考PatternLayout等其他布局,按要求打印日志。

4.3.2 封装一个单例帮助类,多个类,多线程调用试试。

也是参考其他网站,但是找不到了,想记录的是这里配置文件的配置。

#############
# 输出到控制台
#############
###############################################################################################
# 配置INFO CONSOLE输出到控制台
# log4j.rootLogger日志输出类别和级别:只输出不低于该级别的日志信息DEBUG < INFO < WARN < ERROR < FATAL
log4j.logger.all=ALL,all
log4j.appender.all=org.apache.log4j.ConsoleAppender
# 配置CONSOLE设置为自定义布局模式
log4j.appender.all.layout=org.apache.log4j.PatternLayout
# 配置CONSOLE日志的输出格式: %r耗费毫秒数 %p日志的优先级 %t线程名 %C所属类名通常为全类名 %L代码中的行号 %x线程相关联的NDC %m日志 %n换行
log4j.appender.all.layout.ConversionPattern=[ %d ] [ %p ] [ %C %t ] [ %l ] --> %m %n
###############################################################################################
 
################
# 输出到日志文件中
################
 
###############################################################################################
log4j.logger.info=INFO,info
# 配置logfile输出到文件中 文件大小到达指定尺寸的时候产生新的日志文件
log4j.appender.info=org.apache.log4j.RollingFileAppender
# 输出文件位置此为项目根目录下的logs文件夹中
log4j.appender.info.File=logs/INFO.log
#true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认值是false
log4j.appender.info.appendFile=true
# 后缀可以是KB,MB,GB达到该大小后创建新的日志文件
log4j.appender.info.MaxFileSize=10MB
# 设置滚定文件的最大值5
log4j.appender.info.MaxBackupIndex=5
# 配置logfile为自定义布局模式
log4j.appender.info.layout=org.apache.log4j.PatternLayout
log4j.appender.info.layout.ConversionPattern=[ %d ] [ %p ] [ %C %t ] [ %l ] --> %m %n
###############################################################################################
 
###############################################################################################
log4j.logger.warn=WARN,warn
# 配置logfile输出到文件中 文件大小到达指定尺寸的时候产生新的日志文件
log4j.appender.warn=org.apache.log4j.RollingFileAppender
# 输出文件位置此为项目根目录下的logs文件夹中
log4j.appender.warn.File=logs/WARN.log
#true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认值是false
log4j.appender.warn.appendFile=true
# 后缀可以是KB,MB,GB达到该大小后创建新的日志文件
log4j.appender.warn.MaxFileSize=10MB
# 设置滚定文件的最大值5
log4j.appender.warn.MaxBackupIndex=5
# 配置logfile为自定义布局模式
log4j.appender.warn.layout=org.apache.log4j.PatternLayout
log4j.appender.warn.layout.ConversionPattern=[ %d ] [ %p ] [ %C %t ] [ %l ] --> %m %n
###############################################################################################
 
###############################################################################################
log4j.logger.debug=DEBUG,debug
# 配置logfile输出到文件中 文件大小到达指定尺寸的时候产生新的日志文件
log4j.appender.debug=org.apache.log4j.RollingFileAppender
# 输出文件位置此为项目根目录下的logs文件夹中
log4j.appender.debug.File=logs/DEBUG.log
#true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认值是false
log4j.appender.debug.appendFile=true
# 后缀可以是KB,MB,GB达到该大小后创建新的日志文件
log4j.appender.debug.MaxFileSize=10MB
# 设置滚定文件的最大值5
log4j.appender.debug.MaxBackupIndex=5
# 配置logfile为自定义布局模式
log4j.appender.debug.layout=org.apache.log4j.PatternLayout
log4j.appender.debug.layout.ConversionPattern=[ %d ] [ %p ] [ %C %t ] [ %l ] --> %m %n
###############################################################################################
 
###############################################################################################
log4j.logger.error=ERROR,error
# 配置logfile输出到文件中 文件大小到达指定尺寸的时候产生新的日志文件
log4j.appender.error=org.apache.log4j.RollingFileAppender
# 输出文件位置此为项目根目录下的logs文件夹中
log4j.appender.error.File=logs/ERROR.log
#true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认值是false
log4j.appender.error.appendFile=true
# 后缀可以是KB,MB,GB达到该大小后创建新的日志文件
log4j.appender.error.MaxFileSize=10MB
# 设置滚定文件的最大值5
log4j.appender.error.MaxBackupIndex=5
# 配置logfile为自定义布局模式
log4j.appender.error.layout=org.apache.log4j.PatternLayout
log4j.appender.error.layout.ConversionPattern=[ %d ] [ %p ] [ %C %t ] [ %l ] --> %m %n
###############################################################################################

这里自定义布局PatternLayout,相关占位符以及这里的配置逻辑做记录。

log4j.appender.error.layout.ConversionPattern=[ %d ] [ %p ] [ %C %t ] [ %l ] --> %m %n
在这个配置中,使用了不同的占位符来表示不同的信息:

%d:日期和时间
%p:日志级别
%C:类名
%t:线程名
%l:日志位置(文件名、行号)
%m:消息内容
%n:换行符

采用单例的形式,对log4qt进行封装,使调用更方便,但是,会发现日志中无法打印相关函数名。

//头文件 :qloghelper.h
#ifndef QLOGHELPER_H
#define QLOGHELPER_H

#include <QObject>
#include <log4qt/logger.h>
#include <log4qt/basicconfigurator.h>
#include <log4qt/propertyconfigurator.h>

#include <QScopedPointer>
class QLogHelper : public QObject
{
    Q_OBJECT

private:
    explicit QLogHelper(QObject *parent = nullptr);
public:
    ~QLogHelper();
    static QLogHelper *Instance();

    int Init(QString configpath);
    Log4Qt::Logger* GetLogInfo();
    Log4Qt::Logger* GetLogWarn();
    Log4Qt::Logger* GetLogDebug();
    Log4Qt::Logger* GetLogError();
    Log4Qt::Logger* GetLogConsole();
    void LogInfo(QString);
    void LogWarn(QString);
    void LogDebug(QString);
    void LogError(QString);
signals:

public slots:

private:
    Log4Qt::Logger *logInfo=NULL;
    Log4Qt::Logger *logWarn=NULL;
    Log4Qt::Logger *logDebug=NULL;
    Log4Qt::Logger *logError=NULL;
    Log4Qt::Logger *logConsole=NULL;
    QString confFilePath=NULL;

//    static QLogHelper *m_Instance;
    static QScopedPointer<QLogHelper> self;
};

#endif // QLOGHELPER_H

//对应的.cpp文件  qloghelper.cpp
#include "qloghelper.h"
#include <QMutex>
#include <QFileInfo>
#include <QApplication>
using namespace Log4Qt;
QLogHelper::QLogHelper(QObject *parent) : QObject(parent)
{
}


QLogHelper::~QLogHelper(){
}

//QLogHelper * QLogHelper::m_Instance = nullptr;
//QLogHelper *QLogHelper::Instance(){
//    if(m_Instance == nullptr){
//        m_Instance = new QLogHelper();
//    }
//    return m_Instance;
//}

QScopedPointer<QLogHelper> QLogHelper::self;
QLogHelper *QLogHelper::Instance()
{
    if (self.isNull()) {
        static QMutex mutex;   //局部静态变量  第一次调用时初始化 只会初始化一次
        QMutexLocker locker(&mutex);
        if (self.isNull()) {
            self.reset(new QLogHelper);
        }
    }
    return self.data();
}

int QLogHelper::Init(QString configpath){

    confFilePath = configpath;
    if(confFilePath.isEmpty()){
        confFilePath=QApplication::applicationDirPath()+"/config/QLog4.properties";
    }

    //判断文件是否存在
    QFileInfo configFile(this->confFilePath);
    if(!this->confFilePath.isEmpty()&&configFile.exists()){
        PropertyConfigurator::configure(this->confFilePath);
        //实例化节点对象
        if(logInfo==NULL){ logInfo = Log4Qt::Logger::logger("info");}
        if(logWarn==NULL){ logWarn = Log4Qt::Logger::logger("warn");}
        if(logDebug==NULL){ logDebug = Log4Qt::Logger::logger("debug");}
        if(logError==NULL){ logError = Log4Qt::Logger::logger("error");}
        if(logConsole==NULL){ logConsole = Log4Qt::Logger::logger("all");}
        return 0;
    }
    return 1;
}

Logger* QLogHelper::GetLogInfo(){
    return this->logInfo;
}

Logger* QLogHelper::GetLogWarn(){
    return this->logWarn;
}

Logger* QLogHelper::GetLogDebug(){
    return this->logDebug;
}

Logger* QLogHelper::GetLogError(){
    return this->logError;
}

Logger* QLogHelper::GetLogConsole(){
    return this->logConsole;
}

void QLogHelper::LogInfo(QString txt){
    this->logConsole->info(txt);
    this->logInfo->info(txt);
}

void QLogHelper::LogWarn(QString txt){
    this->logConsole->warn(txt);
    this->logWarn->warn(txt);
}

void QLogHelper::LogDebug(QString txt){
    this->logConsole->debug(txt);
    this->logDebug->debug(txt);
}

void QLogHelper::LogError(QString txt){
    this->logConsole->error(txt);
    this->logError->error(txt);
}

//qt 项目main函数调用的地方
#include "qloghelper.h"
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    //日志系统初始化
    QString file_config = a.applicationDirPath() + "/configs/QLog4.properties";

    if(QLogHelper::Instance()->Init(file_config) != 0)
    {
        qDebug()<<"error init log4qt";
        return -1;
    }
    QLogHelper::Instance()->LogInfo("Logger Init Successful!  LogInfo");
    QLogHelper::Instance()->LogDebug("Logger Init Successful!  LogDebug");
    QLogHelper::Instance()->LogError("Logger Init Successful!  LogError");
//这里测试其他线程调用 能正常打印
//    workerThread thread;
//    thread.start(); //run函数中循环打印

//    QThread::sleep(2);
//    test_print_log t;  //测试其他类调用,能正常打印。

//    thread.wait();
    Widget w;
    w.show();
    return a.exec();
}

需要注意对应的配置文件得拷贝到对应的目录下。执行测试。

能执行成功,运行目录下的logs目录下生成 ,发现执行中无法打印函数名等必要信息

DELL@DESKTOP-QDIDMCO MINGW64 /e/job_qt_project/build-qt_test_log4qt-Desktop_Qt_5_14_2_MinGW_32_bit-Debug/logs
$ ls
DEBUG.log  ERROR.log  INFO.log  WARN.log
#取一个info日志  观察
DELL@DESKTOP-QDIDMCO MINGW64 /e/job_qt_project/build-qt_test_log4qt-Desktop_Qt_5_14_2_MinGW_32_bit-Debug/logs
$ cat INFO.log
[ 2024-07-22 17:29:18.120 ] [ INFO ] [  0x00aab010 ] [ :-1 -  ] --> Logger Init Successful!  LogInfo
[ 2024-07-22 17:29:18.122 ] [ INFO ] [  0x008efe00 ] [ :-1 -  ] --> workerThread  LogInfo
[ 2024-07-22 17:29:19.143 ] [ INFO ] [  0x008efe00 ] [ :-1 -  ] --> workerThread  LogInfo
[ 2024-07-22 17:29:20.124 ] [ INFO ] [  0x00aab010 ] [ :-1 -  ] --> test_print_log  LogInfo
[ 2024-07-22 17:29:20.154 ] [ INFO ] [  0x008efe00 ] [ :-1 -  ] --> workerThread  LogInfo
[ 2024-07-22 17:29:21.187 ] [ INFO ] [  0x008efe00 ] [ :-1 -  ] --> workerThread  LogInfo
[ 2024-07-22 17:29:22.198 ] [ INFO ] [  0x008efe00 ] [ :-1 -  ] --> workerThread  LogInfo
[ 2024-07-22 17:31:50.768 ] [ INFO ] [  0x001cb010 ] [ :-1 -  ] --> Logger Init Successful!  LogInfo
[ 2024-07-22 17:31:50.769 ] [ INFO ] [  0x0091fe00 ] [ :-1 -  ] --> workerThread  LogInfo
[ 2024-07-22 17:31:51.780 ] [ INFO ] [  0x0091fe00 ] [ :-1 -  ] --> workerThread  LogInfo
4.3.3 结合qt自带的消息处理器,把qdebug相关日志重定向到log4qt中。

开始探索过程中,发现log4qt能接管qdebug的相关日志,正常输出,看起来也满足基本功能,日志正常输出,但是不知道有没有隐患。

然后从网络了解到,单纯的接管qdebug日志,有重复输出的问题,有无法灵活控制日志级别,设置格式化问题。

可能把qt的输出重定向到log4qt,是不是好点?

测试了一下,确实也可行。

#include "qloghelper.h"
//这里用全局变量  使lamba能识别到  可以像上面用类的方式优化
Log4Qt::Logger *logger = Log4Qt::Logger::rootLogger();

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

    //日志系统初始化 这里应该对配置文件和配置结果进行判断
    QString file_config = a.applicationDirPath() + "/configs/QLog4.properties";
    Log4Qt::PropertyConfigurator::configure(file_config);

    // 将 Qt 的消息输出到 log4qt中 线程安全
    qInstallMessageHandler([](QtMsgType type, const QMessageLogContext &context, const QString &message) {
        QByteArray localMsg = message.toLocal8Bit();
        QString text = "";
        QString msg = text.append(" file:").append(context.file)
                      .append(" function:").append(context.function)
                      .append(" category:").append(context.category)
                       .append(" msg:").append(localMsg.constData())
                       .append(" line:").append(QString::number(context.line)
                       .append(" version:").append(QString::number(context.version)));

        switch (type) {
        case QtDebugMsg:
            logger->debug(msg);
            break;
        case QtInfoMsg:
            logger->info(msg);
            break;
        case QtWarningMsg:
            logger->warn(msg);
            break;
        case QtCriticalMsg:
            logger->error(msg);
            break;
        case QtFatalMsg:
            logger->fatal(msg);
        }
    });

    qDebug()<<"Logger Init Successful!  LogInfo";
    qInfo()<<"Logger Init Successful!  LogDebug";
    qWarning()<<"Logger Init Successful!  LogError";

        workerThread thread;
        thread.start();

        QThread::sleep(2);
        test_print_log t;

        thread.wait();

    return 0;
}
//配合log4qt的配置文件,确实倒也能打印日志,以及自己定制相关内容。

4.4:总结

初次接触qt相关的日志库,有需要用一个qt的日志库,就瞎折腾。

最终发现,好像最基本的是最合理的,直接用log4qt在cpp中LOG4QT_DECLARE_STATIC_LOGGER静态定义最方便吧,结合配置文件也能满足需求。

最终感觉,直接用log4qt应该完全能满足需求,日志文件中也能定制自己需要的布局,也支持多线程等。

啥也不是,先操作后再思考吧。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1951068.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

JAVA SE 类和对象

类和对象 类定义和使用类的定义格式 类的实例化什么是实例化 this 引用this引用的特性 对象的构造及初始化如何初始化对象构造方法概念特性 在这里插入图片描述 **注意**&#xff1a; 封装封装的概念封装扩展之包导入包中的类自定义包包的访问权限控制举例 static成员static修饰…

MOZHE SQL手工注入漏洞测试(MySQL数据库)

主界面URL没有参数&#xff0c;无法判断是否有注入点 点击公告 【浏览器不便于查看返回包可以用burp】 测试URL 参数后加上单引号&#xff0c;报错&#xff0c;说明存在注入点 http://124.70.64.48:40021/new_list.php?id1 获取表列数 order by 4 返回200 order by 5 …

鸿蒙应用框架开发【N-Body模拟程序】

N-Body模拟程序 介绍 在本示例中&#xff0c;使用ArkTS编程语言开发了业界编程语言基准测试项目[Benchmarks Game]中的[N体问题模拟程序]&#xff0c;实现类木星体轨道计算。 本示例用到了ohos.taskpool和ohos.worker 接口。示例中的程序可以用于AOT(Ahead Of Time)等性能测…

计科录取75人!常州大学计算机考研考情分析!

常州大学&#xff08;Changzhou University&#xff09;&#xff0c;简称“常大”&#xff0c;位于江苏省常州市&#xff0c;是江苏省人民政府与中国石油天然气集团有限公司、中国石油化工集团有限公司及中国海洋石油集团有限公司共建的省属全日制本科院校&#xff0c;为全国深…

⼤模型在⽹络安全⽅⾯的应⽤汇总

引⾔ ⼤语⾔模型&#xff08;Large Language Models, LLMs&#xff09;的横空出世&#xff0c;为传统计算机科学的各个细分领域带来了颠覆性的变⾰。这种变⾰的浪潮同样席卷了⽹络安全领域&#xff0c;引发了⼀系列深刻的变化和影响。GPT-4、Gemini、Llama 2 等⼤模型以其卓越的…

7月22日学习笔记 文件共享服务nfs,SAMBA文件共享与DNS域名服务

任务背景 由于业务驱动&#xff0c;为了提⾼⽤户的访问效率&#xff0c;现需要将原有web服务器上的静态资源 ⽂件分离出来&#xff0c;单独保存到⼀台⽂件服务器上。 任务要求 1. ⼀台应⽤服务器web-server部署apache&#xff0c;静态⽹⻚资源存放在另外⼀台NFS服 务器上 …

深入理解计算机系统 CSAPP 家庭作业11.7

静态内容是指在不同请求中访问到的数据都相同的静态文件。例如&#xff1a;图片、视频、网站中的文件&#xff08;html、css、js&#xff09;、软件安装包、apk文件、压缩包文件等。 /** get_filetype - derive file type from file name*/ void get_filetype(char *filename,…

12_TypeScript 模块 以及 模块化封装DB 库

TypeScript 模块 1、模块中暴露方法12、模块中暴露方法23、模块中暴露方法34、封装[上一节的db 库](https://blog.csdn.net/qq_46143850/article/details/140664100)5、TypeScript 命名空间 模块的概念&#xff08;官方&#xff09;&#xff1a; 关于术语的一点说明&#xff1a…

Linux网络-wget命令

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注我&#xff0c;我尽量把自己会的都分享给大家&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux服务器作为一个常用的网络服务器&#xff0c;主要的作用就是向客户端提供网络…

【工具推荐】强大的JS渗透测试工具:URLFinder

一、下载地址 https://github.com/pingc0y/URLFinder 二、工具原理 从表现中JS中提取URL或者敏感数据&#xff0c;一款用于快速提取检测页面中JS与URL的工具。功能类似于JSFinder&#xff0c;但JSFinder好久没更新了。 三、工具介绍 1、下载解压出来包含下面两个文件 2、直…

内网渗透—内网穿透工具NgrokFRPNPSSPP

前言 主要介绍一下常见的隧道搭建工具&#xff0c;以此来达到一个内网穿透的目的。简单说一下实验滴环境吧&#xff0c;kali作为攻击机&#xff0c;winserver2016作为目标靶机。 kali 192.168.145.171 winserver2016 10.236.44.127 显然它们处于两个不同的局域网&#xff0c…

SQLException:Operation not allowed after ResultSet closed

运行代码时出现的错误&#xff1a; 这是在运行简单的JDBC访问数据库时出现的问题&#xff0c;原因是在ResultSet方法中添加了close()关闭方法,如图&#xff1a; ResultSet 是通过 query 方法获得的&#xff0c;并且在 try-catch 块中没有显式地关闭它。这实际上是 一个常见的…

ServletContainerInitializer接口详解

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhlServletContainerInitializer概述 ServletContainerInitializer是Servlet 3.0规范中引入的一个接口,它的主要目的是允许开发者在Servlet容器(如Tomcat、Jetty等)启动时执行一些自定义的初始化代…

池化层pytorch最大池化练习

神经网络构建 class Tudui(nn.Module):def __init__(self):super(Tudui, self).__init__()self.maxpool1 MaxPool2d(kernel_size3, ceil_modeFalse)def forward(self, input):output self.maxpool1(input)return output Tensorboard 处理 writer SummaryWriter("./l…

Linux系统上安装Redis

百度网盘&#xff1a; 通过网盘分享的文件&#xff1a;redis_linux 链接: https://pan.baidu.com/s/1ZcECygWA15pQWCuiVdjCtg?pwd8888 提取码: 8888 1.把安装包拖拽到/ruanjian/redis/文件夹中&#xff08;自己选择&#xff09; 2.进入压缩包所在文件夹&#xff0c;解压压缩…

生信技能54 - WisecondorX多线程并行分析CNV

WisecondorX分析CNV,默认单样本分析,batch_analysis参数设置为True可启动多样本并行分析。 WisecondorX基本使用方法以及npz文件转换和reference构建参考文章: 生信技能53 - wiseconrdoX自动化批量npz转换和reference构建 github: https://github.com/CenterForMedicalGe…

微服务:解决复杂业务的妙方

1 微服务介绍 1)什么是微服务 ​ 微服务&#xff08;Microservices&#xff09;是一种软件架构风格&#xff0c;它将一个大型应用程序拆分成许多较小的、松散耦合的、独立运行的服务。这些服务通常围绕特定功能或业务领域组织&#xff0c;可以独立开发、部署、扩展和更新。微…

【网络安全学习】 SQL注入01:基础知识

&#x1f4bb; 1. 什么是SQL注入 SQL注入是一种针对Web程序中数据库层的安全漏洞的攻击方式。它利用了程序对用户输入数据合法性的判断或过滤不严&#xff0c;允许攻击者在设计不良的程序中添加额外的SQL语句&#xff0c;从而执行计划外的命令或访问未授权的数据。攻击者可以通…

[Unity] ShaderGraph实现不同贴图素材的同一材质球复用

无意间发现的ShaderGraph小技巧&#xff0c; 可以实现同一个ShaderGraph&#xff0c;同一个Material材质球&#xff0c; 但使用不同的Texture贴图&#xff0c;而Sprite显示不会相互覆盖。 具体实现方法如下&#xff1a; 声明Texture2D时&#xff0c;把名字命名成&#xff1a…

超燃!纯AI生成《泰坦尼克号》大片!浙大阿里发布MovieDreamer:超长电影生成“梦工厂“

论文链接&#xff1a;https://arxiv.org/pdf/2407.16655 项目主页&#xff1a;https://aim-uofa.github.io/MovieDreamer/ github链接&#xff1a;https://github.com/aim-uofa/MovieDreamer 亮点直击 MovieDreamer&#xff0c;一个新颖的分层框架&#xff0c;将自回归模型与扩…