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

项目中用到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应该完全能满足需求,日志文件中也能定制自己需要的布局,也支持多线程等。

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

  • 10
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值