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.txtvoid 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 为 nullptrif (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.txtif (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.txtlogFile = 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.logif (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();
#elseQByteArray localMsg = msg.toLocal8Bit();
#endifstd::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 loggingQLOG_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/includeSOURCES += \$$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.hmacx {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 layoutauto *layout = new Log4Qt::TTCCLayout();layout->setName(QStringLiteral("My Layout"));layout->setDateFormat("dd.MM.yyyy hh:mm:ss.zzz");layout->activateOptions();// Create a console appenderLog4Qt::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 appenderLog4Qt::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()执行完成后,才关闭loggerreturn 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()执行完成后,才关闭loggerreturn 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_OBJECTprivate: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.rhkb.cn/news/384425.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CogVideo 实测,智谱「清影」AI视频生成,全民免费,连 API 都开放了!

不得不说&#xff0c;AI 视频生成界最近非常火热~ 前有快手「可灵」开放内测&#xff0c;一下子带火了老照片修复&#xff0c;全网刷屏&#xff1a; 怕是你还没拿到内测资格&#xff0c;被称为 “国货之光” 的「可灵」就结束了免费无限量模式。每天只有66点的免费额度&#x…

看 Unity 组件的源码 —— ILSpy

ILSpy 是开源的 .NET 程序集浏览器和解编译器。 下载 ILSpy ILSpy Github 地址&#xff1a;icsharpcode/ILSpy: .NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) - cross-platform! (github.com) 它有 Release 包可以下载 也提供 IDE 的…

静态路由学习笔记

1. 静态路由应用场景 &#xff08;1&#xff09;静态路由由网络管理员手动配置&#xff0c;配置方便&#xff0c;对系统要求低&#xff0c;适用于拓扑结构简单并且稳定的小型网络。 &#xff08;2&#xff09;缺点是不能自动适应网络拓扑的变化&#xff0c;需要人工干预过多。…

Python爬虫技术 第13节 HTML和CSS选择器

在爬虫技术中&#xff0c;解析和提取网页数据是核心部分。HTML 和 CSS 选择器被广泛用于定位网页中的特定元素。下面将详细介绍这些选择器如何在 Python 中使用&#xff0c;特别是在使用像 Beautiful Soup 或 Scrapy 这样的库时。 HTML 选择器 HTML 选择器基于 HTML 元素的属性…

企业公户验证API如何使用JAVA、Python、PHP语言进行应用

在纷繁复杂的金融与商业领域&#xff0c;确保每笔交易的安全与合规是至关重要的。而企业公户验证API&#xff0c;正是这样一位默默守护的数字卫士&#xff0c;它通过智能化的手段&#xff0c;简化了企业对公账户验证流程&#xff0c;让繁琐的审核变得快捷且可靠。 什么是企业公…

ShadingModel一些分析

一、算法分级 二、DFGTexture 用拟合替换DFGTexture&#xff08;Ref&#xff1a;Cod-Black Ops II&#xff09; 优点&#xff1a;节省一张纹理的采样。 缺点&#xff1a; 1、效果上会变得暗一些&#xff0c;并且由于用拟合的原因&#xff0c;会多一点指令计算。 2、后续如…

《Milvus Cloud向量数据库指南》——BGE-M3:多功能、多语言、多粒度的文本表示学习模型

引言 在自然语言处理(NLP)领域,随着大数据时代的到来,对文本信息的精准处理与高效检索成为了研究热点。BERT(Bidirectional Encoder Representations from Transformers)作为近年来NLP领域的里程碑式模型,以其强大的上下文理解能力在多项任务中取得了显著成效。然而,面…

Android APP 音视频(01)MediaCodec解码H264码流

说明&#xff1a; 此MediaCodec解码H264实操主要针对Android12.0系统。通过读取sd卡上的H264码流Me获取视频数据&#xff0c;将数据通过mediacodec解码输出到surfaceview上。 1 H264码流和MediaCodec解码简介 1.1 H264码流简介 H.264&#xff0c;也被称为MPEG-4 AVC&#xff…

【Linux】一些基本指令

文章目录 前言Linux下基本指令Linux下一些常见的通配符Linux下的引号引用whoamiwholswhichaliaswhereisfindtouchmkdirrmdir & rmmancpmvcatmorelessheadtailechodatecalgrepzip & unziptarrz & szuname几个重要的热键关机 前言 在学习操作系统的时候&#xff0c;我…

Dav_笔记12:Automatic SQL Tuning 之 1 概述

自动调整优化器概述 Oracle数据库使用优化程序为已提交的SQL语句生成执行计划。优化器以以下模式运行&#xff1a; ■普通模式 优化器编译SQL并生成执行计划。正常模式为绝大多数SQL语句生成合理的计划。在正常模式下&#xff0c;优化器以非常严格的时间约束运行&#xff0c…

Python 高阶语法

前言&#xff1a; 我们通过上篇文章学习了Python的基础语法&#xff0c;接下来我们来学习Python的高阶语法 1.初识对象 在Python中我们可以做到和生活中那样&#xff0c;设计表格、生产表格、填写表格的组织形式的 面向对象包含 3 大主要特性&#xff1a;  封装  继承 …

Oracle系统表空间的加解密

实验环境 数据库选择的是orclpdb1&#xff0c;当前系统表空间未加密&#xff1a; SQL> show con_nameCON_NAME ------------------------------ ORCLPDB1SQL> select TABLESPACE_NAME, STATUS, ENCRYPTED from dba_tablespaces;TABLESPACE_NAME STATUS …

软件测试:Postman 工具的使用。开发及测试均需要掌握的测试工具

工具介绍 各个模块功能的介绍如下&#xff1a; 1、New&#xff1a;在这里创建新的请求、集合或环境&#xff1b;还可以创建更高级的文档、Mock Server 和 Monitor以及API。 2、Import&#xff1a;这用于导入集合或环境。有一些选项&#xff0c;例如从文件&#xff0c;文件夹导…

【Linux】远程连接Linux虚拟机(MobaXterm)

【Linux】远程连接Linux虚拟机&#xff08;MobaXterm&#xff09; 零、原因 有时候我们在虚拟机中操作Linux不太方便&#xff0c;比如不能复制粘贴&#xff0c;不能传文件等等&#xff0c;我们在主机上使用远程连接软件远程连接Linux虚拟机后可以解决上面的问题。 壹、软件下…

学习小型gpt源码(自用)

数据集构建_哔哩哔哩_bilibili &#xff08;b站上有一系列课&#xff0c;从数据处理到模型构建和训练使用&#xff09; 什么是batch&#xff1f; 为什么一个batch内的句子要一样长&#xff1f; 不同batch的长度可以不一样&#xff0c;但是同一个batch内长度一样&#xff01;…

回文子串转二维dp的方式

目录 写在最前&#xff1a; 1. 首先我们要考虑一个问题&#xff1a;如何判断一个字符串是回文子串 2.如何创建dp[i][j]表示回文子串 3. 如何初始化&#xff1f; 4. 如何实现 问题引入&#xff1a; LCR 020. 回文子串 给定一个字符串 s &#xff0c;请计算这个字符串中有…

Spring Boot入门指南:留言板

一.留言板 1.输⼊留⾔信息,点击提交.后端把数据存储起来. 2.⻚⾯展⽰输⼊的表⽩墙的信息 规范&#xff1a; 1.写一个类MessageInfo对象&#xff0c;添加构造方法 虽然有快捷键&#xff0c;但是还是不够偷懒 项目添加Lombok。 Lombok是⼀个Java⼯具库&#xff0c;通过添加注…

C语言 | Leetcode C语言题解之第279题完全平方数

题目&#xff1a; 题解&#xff1a; // 判断是否为完全平方数 bool isPerfectSquare(int x) {int y sqrt(x);return y * y x; }// 判断是否能表示为 4^k*(8m7) bool checkAnswer4(int x) {while (x % 4 0) {x / 4;}return x % 8 7; }int numSquares(int n) {if (isPerfect…

项目实战1(30小时精通C++和外挂实战)

项目实战1&#xff08;30小时精通C和外挂实战&#xff09; 01-MFC1-图标02-MFC2-按钮、调试、打开网页05-MFC5-checkbox及按钮绑定对象06--文件格式、OD序列号08-暴力破解09-CE10-秒杀僵尸 01-MFC1-图标 这个外挂只针对植物大战僵尸游戏 开发这个外挂&#xff0c;首先要将界面…

【SpringCloud】 微服务分布式环境下的事务问题,seata大合集

目录 微服务分布式环境下的事务问题 分布式事务 本地事务 BASE理论与强弱一致性 BASE理论 强弱一致性 常见分布式事务解决方案 - 2PC 常见分布式事务解决方案 - TCC 常见分布式事务解决方案 - 最大努力通知 常见分布式事务解决方案 - 最终一致性 Seata介绍与术语 Seata…