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.mzph.cn/diannao/49047.shtml

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

相关文章

openmv学习笔记(24电赛备赛笔记)

#openmv简介 openmv一种小型&#xff0c;可编程机器视觉摄像头&#xff0c;设计应用嵌入式应用和计算边缘&#xff0c;是图传模块&#xff0c;或者认为是一种&#xff0c;具有图像处理功能的单片机&#xff0c;提供多种接口&#xff08;I2C SPI UART CAN ADC DAC &#xff0…

高翔【自动驾驶与机器人中的SLAM技术】学习笔记(三)基变换与坐标变换;微分方程;李群和李代数;雅可比矩阵

一、基变换与坐标变换 字小,事不小。 因为第一反应:坐标咋变,坐标轴就咋变呀。事实却与我们想象的相反。这俩互为逆矩阵。 第一次读没有读明白,后面到事上才明白。 起因是多传感器标定:多传感器,就代表了多个坐标系,多个基底。激光雷达和imu标定。这个标定程序,网上,…

Bootstrap5 Navbar多级下拉框

实现目标&#xff1a; 1、访问 Bootstrap5-navbar 2、修改dropdown为多级 <!DOCTYPE HTML> <html lang"en-US"> <head><meta charset"UTF-8"><title></title><link rel"stylesheet" href"https…

(7) cmake 编译C++程序(二)

文章目录 概要整体代码结构整体代码小结 概要 在ubuntu下&#xff0c;通过cmake编译一个稍微复杂的管理程序 整体代码结构 整体代码 boss.cpp #include "boss.h"Boss::Boss(int id, string name, int dId) {this->Id id;this->Name name;this->DeptId …

05 HTTP Tomcat Servlet

文章目录 HTTP1、简介2、请求数据格式3、响应数据格式 Tomcat1、简介2、基本使用3、Maven创建Web项目4、IDEA使用Tomcat Servlet1、简介2、方法介绍3、体系结构4、urlPattern配置5、XML配置 HTTP 1、简介 HTTP概念 HyperText Transfer Protocol&#xff0c;超文本传输协议&am…

鸿蒙 动态共享包HSP的创建和引用

1.什么是动态共享包HSP HSP&#xff08;Harmony Shared Package&#xff09;是动态共享包&#xff0c;可以包含代码、C库、资源和配置文件&#xff0c;通过HSP可以实现代码和资源的共享。HSP不支持独立发布&#xff0c;而是跟随其宿主应用的APP包一起发布&#xff0c;与宿主应…

【Django5】模板引擎

系列文章目录 第一章 Django使用的基础知识 第二章 setting.py文件的配置 第三章 路由的定义与使用 第四章 视图的定义与使用 第五章 二进制文件下载响应 第六章 Http请求&HttpRequest请求类 第七章 会话管理&#xff08;Cookies&Session&#xff09; 第八章 文件上传…

redis的学习(三):Java客户端jedis的例子和SpringDataRedis的简介

简介 Java客户端jedis的例子和SpringDataRedis的简介## Java客户端 常用的Java客户端有jedis&#xff0c;lettuce&#xff0c;redission。 优缺点&#xff1a; jedis简单实用&#xff0c;api名是redis的命令&#xff0c;学习成本低。不过jedis实例的线程是不安全的&#xff…

VideoAgent: Long-form Video Understanding with Large Language Model as Agent

VideoAgent: Long-form Video Understanding with Large Language Model as Agent 基本信息 博客贡献人 燕青 作者 Xiaohan Wang, Yuhui Zhang, et al. 标签 Large Language Model Agent, Long-form Video Understanding, Vision-Language Foundation Models 摘要 长视…

Android中systrace配置及注意问题

Android中systrace配置及注意问题 systrace配置的官方文档地址如下&#xff1a;优化启动时间 Systrace systrace 允许在启动期间收集内核和 Android 跟踪记录。systrace 的可视化可以帮助分析启动过程中的具体问题。&#xff08;不过&#xff0c;如果要查看整个启动过程中的平…

2024.7.22 作业

1.将双向链表和循环链表自己实现一遍&#xff0c;至少要实现创建、增、删、改、查、销毁工作 循环链表 looplinklist.h #ifndef LOOPLINKLIST_H #define LOOPLINKLIST_H#include <myhead.h>typedef int datatype;typedef struct Node {union {int len;datatype data;}…

win10开启Linux子系统

打开win10开发人员模式&#xff0c;在设置–>更新和安全–>针对开发人员&#xff0c;中勾选开发人员模式。 然后在控制面板中勾选添加Linux子系统。依次进入控制面板–>程序–>启用或关闭windows功能&#xff0c;勾选适用于windows的linux的子系统&#xff0c;点击…

Mac清理垃圾的软件有哪些 怎么清理电脑上的缓存文件和垃圾清理

如果你发现你的Mac运行速度开始慢如蜗牛&#xff0c;或者硬盘空间快速减少&#xff0c;那么可能是时候使用一款好的清理软件来“洗个澡”了。市场上有许多优秀的Mac清理软件&#xff0c;包括一些出色的国产软件和国际知名软件。那么&#xff0c;mac电脑清理垃圾的软件有哪些&am…

Java---后端文件上传详解

袁门才俊志高远&#xff0c; 震古烁今意决然。 风采翩翩才情显&#xff0c; 雄姿英发立世间。 目录 一&#xff0c;简单案例演示 二&#xff0c;服务器本地存储 三&#xff0c;配置单个文件上传大小限制 一&#xff0c;简单案例演示 首先简单编写一个前端网页&#xff1a; &l…

知识图谱:知识图谱概述(一)

一、知识图谱简介 知识图谱&#xff0c;是结构化的语义知识库&#xff0c;主要用于描述现实世界中的实体及其相互关系&#xff0c;由节点和边组成。节点可以是实体&#xff0c;如汽车、街道等&#xff0c;或是抽象的概念&#xff0c;如AI、疾病等。边可以是实体的属性&#xff…

基于FPGA的以太网设计(2)----以太网的硬件架构(MAC+PHY)

1、概述 以太网的电路架构一般由MAC、PHY、变压器、RJ45和传输介质组成,示意图如下所示: 需要注意的是,上图是一个简化了的模型,它描述的是两台主机之间的直接连接,但在实际应用中基本都是多台主机构成的局域网,它们之间并不直接相连,而是通过交换机Switch来进行…

【PPT把当前页输出为图片】及【PPT导出图片模糊】的解决方法(sci论文图片清晰度)

【PPT把当前页输出为图片】及【PPT导出图片模糊】的解决方法 内容一&#xff1a;ppt把当前页输出为图片&#xff1a;内容二&#xff1a;ppt导出图片模糊的解决方法&#xff1a;方法&#xff1a;步骤1&#xff1a;打开注册表编辑器步骤2&#xff1a;修改注册表&#xff1a; 该文…

万字长文之分库分表里无分库分表键如何查询【后端面试题 | 中间件 | 数据库 | MySQL | 分库分表 | 其他查询】

在很多业务里&#xff0c;分库分表键都是根据主要查询筛选出来的&#xff0c;那么不怎么重要的查询怎么解决呢&#xff1f; 比如电商场景下&#xff0c;订单都是按照买家ID来分库分表的&#xff0c;那么商家该怎么查找订单呢&#xff1f;或是买家找客服&#xff0c;客服要找到对…

深入浅出WebRTC—ULPFEC

FEC 通过在发送端添加额外的冗余信息&#xff0c;使接收端即使在部分数据包丢失的情况下也能恢复原始数据&#xff0c;从而减轻网络丢包的影响。在 WebRTC 中&#xff0c;FEC 主要有两种实现方式&#xff1a;ULPFEC 和 FlexFEC&#xff0c;FlexFEC 是 ULPFEC 的扩展和升级&…

websocket实现进度条

websocket实现进度条 做一个简易的websocket实现进度条的练习&#xff0c;效果如下&#xff1a; 前端vue3 <template><el-progress type"circle" :percentage"this.progressValue" :status"this.perstatus" /><el-button cli…