项目中用到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应该完全能满足需求,日志文件中也能定制自己需要的布局,也支持多线程等。
啥也不是,先操作后再思考吧。