项目中spdlog库的使用

目录

spdlog的基本概念

主要使用类

项目中引入使用

库主要结构

代码示例

引入代码

使用初始化

使用示例:

使用效果:

Qt源码中的消息重定向



spdlog的基本概念

        spdlog就是一个开源日志库,支持跨平台(Windows、Linux、Mac、Android),主要开发语言是C++ ,headonly引入方式,主打高性能,支持单线程/多线程、同步/异步阻塞非阻塞模式,也可自己扩展。

     spdlog可以分成三级结构,从上而下是logger registry、logger、sink,其各自功能如下:           logger registry(日志管理器):负责管理所有的logger,用户建立的所有logger都会在registry处进行登记然后统一管理   

        logger(日志记录器):是用户直接操作的对象,通过操作logger进行日志逻辑的生成   

        sink(日志记录器槽):受logger控制,执行具体的动作(动作包括写入日志文件/输出到控制台)

主要使用类


        spdlog日志库自身包含控制台日志记录、基础文件日志记录、循环文件日志记录、每日文件日志记录等记录方式,可以满足各种场景需求。

        使用时主要包括创建日志记录器(logger)、创建日志记录器槽(sink)、设置日志输出格式(pattern)、设置日志输出等级(level)等。


日志等级(leve_enum):trace\info\debug\warn\err\critical\off\n_levels
日志记录器槽(sink):进行底层操作(格式化日志输出到文件等),自带多种槽满足各种需求,也可以自定义槽进行定制化需求满足;sink的主要函数:
        set_pattern(const std::string&):设置日志输出格式;
        set_level(level_enum):设置日志输出最低等级。
        log(log_msg):由logger自动调用。
日志记录器(logger):被顶层调用来输出日志的类,一个logger可以存储多个sink,输出时遍历所有sink的log(log_msg)函数进行输出;spdlog也自带几种logger,主要函数有:
         set_pattern(const std::string&):设置logger所包含的所有sink日志输出格式;
         set_level(level_enum):设置logger日志输出最低等级,如果logger所包含的sink没有设置最低等级,则该设置起效。
对象版本(st/mt):spdlog中logger对象和sink都有两个版本,st结尾单线程版本,不用加锁效率高;mt结尾多线程版本,用于多线程程序是线程安全的。

项目中引入使用

库主要结构

代码示例

数据库升级项目中引入该项目。

引入代码

头文件DbUpgradeLog.h


#pragma once
#include <spdlog/logger.h>

 class QObject;
 void unInitLog();
 void initLog(const char*LogName);
 std::shared_ptr<spdlog::logger> cLogger(const std::string& loggerName);
 void setGuiLoggerReceive(QObject*);

DbUpgradeLog.cpp

#include "DbUpgradeLog.h"
#include <QString>
#include <QDir>
#include<QDateTime>
#include <QCoreApplication>
#include <memory>
#include <string>
#include <mutex>

#include "spdlog/spdlog.h"
#include "spdlog/sinks/base_sink.h"
#include "spdlog/sinks/rotating_file_sink.h"
#include "spdlog/sinks/msvc_sink.h"
#include "spdlog/sinks/UpDaterGUIsink.h"
#include "spdlog/fmt/bundled/ostream.h"
#include "spdlog/common.h"
#include "spdlog/details/registry.h"
#include"spdlog/async.h"
#include"spdlog/async_logger.h"



namespace speedLog
{
	bool bInit = false;
	QObject* gui_msgReceiver = nullptr;
	std::string logPathStr;
	std::vector<spdlog::sink_ptr> sinks;
	std::recursive_mutex logMutex;
	std::string defaultPattern = "[%t %Y-%m-%d %H:%M:%S.%e]<%L|%n> %v";
	//%t 线程号 %Y年 %m月 %d日 %H时 %M分 %S秒 %e毫秒   %L 日志登记缩写(I D...)  %n 日志名称   %v 实际日志内容
	std::shared_ptr<spdlog::logger> getLogger(const std::string& loggerName)
	{
		std::lock_guard<std::recursive_mutex> guard(logMutex);
		auto logger = spdlog::get(loggerName);
		if (logger == nullptr)
		{
			logger = std::make_shared<spdlog::logger>(loggerName, begin(sinks), end(sinks));
			logger->set_pattern(defaultPattern);//设置输出格式
			logger->set_level(spdlog::level::level_enum::info);
			//logger设置日志等级 开发阶段可设置trace debug和trace便于打印 发布阶段可调整
			//默认info等级后则低于info等级不进行打印
			//等级从小到大 trace debug info(默认) warn err critical off  

			//设置日志刷新策略 默认在程序退出时才刷新 此处采用按照日志严重等级刷新高于这个level触发时刷新
			//spdlog::details::registry::instance().flush_every(std::chrono::milliseconds(500)); //每隔500ms刷新一次缓存
			logger->flush_on(spdlog::level::level_enum::info);
			//register_logger 日志管理器 管理所有logger 退出前不会销毁
			//register <name, logger> 将日志对象和其名称对应起来,后面使用的时候可以直接通过名称获取对应的日志对象
			spdlog::register_logger(logger);	
		}
		return logger;
	}
}

void initLog(const char*LogName)
{
	std::lock_guard<std::recursive_mutex> guard(speedLog::logMutex);
	if (!speedLog::bInit)
	{
		//设置日志路径
		QString moduleName = QString::fromUtf8(LogName);
		QString appPath = QCoreApplication::applicationDirPath();
		QString logPath = QDir::toNativeSeparators(QDir::cleanPath(appPath + QDir::separator() + moduleName));
		QDir dir(logPath);
		if (!dir.exists(logPath))
		{
			dir.mkpath(".");
		}
		//添加槽
		speedLog::sinks.push_back(std::make_shared<spdlog::sinks::wincolor_stdout_sink_mt>());//std 标准输出sink 
		speedLog::sinks.push_back(std::make_shared<spdlog::sinks::msvc_sink_mt>()); //msvc输出窗口 多线程版本sink
		//采用rotating_file_sink_mt 1024 * 1024 * 4(4M大小后自动更换新日志文件) 15个文件后顶替掉最久的日志
		QString logfileName = QDir::cleanPath(logPath + QDir::separator() + "UpdaterLog_" + QDateTime::currentDateTime().toString("yyyy_MM_dd_hh_mm_ss") + ".log");
		speedLog::sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(logfileName.toStdString(), 1024 * 1024 * 4, 15, true));
		//自定义 UpDaterGuiSink_mt 主要做gui日志记录
		speedLog::sinks.push_back(std::make_shared<spdlog::sinks::UpDaterGuiSink_mt>()); 
		speedLog::bInit = true;
	}
}

void unInitLog()
{
	std::lock_guard<std::recursive_mutex> guard(speedLog::logMutex);
	if (speedLog::bInit)
	{
		spdlog::drop_all();
		speedLog::bInit = false;
	}
}


std::shared_ptr<spdlog::logger> cLogger(const std::string& loggerName)
{
	return speedLog::getLogger(loggerName);
}

//Qt中界面日志显示需求引入
void setGuiLoggerReceive(QObject* pReceive)
{
	speedLog::gui_msgReceiver = pReceive;
}

 自定义槽:UpDaterGuiSink.h

#pragma once

#include <spdlog/details/null_mutex.h>
#include <spdlog/details/synchronous_factory.h>
#include <spdlog/sinks/base_sink.h>

#include <string>
#include <QString>
namespace spdlog {
	namespace sinks {
		template <typename Mutex>
		class UpDaterGuiSink : public base_sink<Mutex> {//继承base_sink
		public:
			UpDaterGuiSink() = default;
			UpDaterGuiSink(const UpDaterGuiSink&) = delete;
			UpDaterGuiSink&operator=(const UpDaterGuiSink&) = delete;
			~UpDaterGuiSink() = default;

		protected:
		    //重写sink_it函数
			void sink_it_(const details::log_msg &msg) override
			{
				if (speedLog::gui_msgReceiver)
				{
					//spdlog::details::log_msg 跨线程拷贝偶现数据丢失 可能拷贝函数问题 在这里做解析
					QString tempSysLog = QString(std::string(msg.payload.data(), msg.payload.size()).c_str());
					QMetaObject::invokeMethod(speedLog::gui_msgReceiver, "insertLog", Qt::AutoConnection, Q_ARG(QString, tempSysLog),Q_ARG(int,msg.level));
				}
			}
			void flush_() override {}
		};

		using UpDaterGuiSink_mt = UpDaterGuiSink<std::mutex>;
		using UpDaterGuiSink_st = UpDaterGuiSink<details::null_mutex>;
	}  // namespace spdlog
}

使用初始化

void UpDater::initUi()
{
	initLog("PmsUpDaterLog");
	setGuiLoggerReceive(this);//设置receiverObj
	...
}

void UpDater::insertLog(QString logMsg, int level)
{
	if (!logMsg.isEmpty())
	{
		QString levelStr, TextColorStr;
		switch (level)
		{
		case spdlog::level::warn:
		{
			levelStr = "warn";
			TextColorStr = "yellow";
			break;
		}
		case spdlog::level::info:
		{
			levelStr = "info";
			TextColorStr = "green";
			break;
		}
		case spdlog::level::err:
		{
			levelStr = "error";
			TextColorStr = "red";
			break;
		}
		default:
		{
			levelStr = "error";
			TextColorStr = "red";
			break;
		}
		}

		QString logStr = QString("<span style = \"color:%1\">[%2]:%3").arg(TextColorStr).arg(levelStr).arg(logMsg) + "</span><br>";
		ui.textBrowser->append(logStr);
	}
}

使用示例:

void AsyBackUpTaskObj::invokeUpGradePg(const BackupAndReplacePram&param)
{
	//是否删除数据
	if (param.bDelete && 0 != DeletePassvehicle(param.iMonths))
	{
		return;
	}
	//磁盘空间检查
	qint64 totalSize = getDirSize(param.DbDataDirStr);
	{
		QStorageInfo storageInfo(param.dataBackDirStr);
		if (storageInfo.isValid())
		{
			qint64 needSpace = totalSize + EXTRASAPCE;
			if (storageInfo.bytesFree() < needSpace)
			{
				emit alarmDiskSpace(needSpace, param.dataBackDirStr);
				return;
			}
		}
		else
		{
			cLogger("AsyTask")->error(TR("检测%1所在磁盘空闲空间出错,注意是否空间不足").arg(param.dataBackDirStr).toStdString().c_str());
		}
	}

	bool status = false;
	QString sqlfilePath = QDir::cleanPath(param.dataBackDirStr + QDir::separator() + "pmsdb_dumpFile.sql");
	if (pg_dump_sqlfile(param.DbBinStr, sqlfilePath) == 0)
	{
		status = true;
		if (0 == stopDbServer(param.DbBinStr, param.DbDataDirStr))
		{		
			emit upgradeProgress(PG_REPLACE, UpGrading);
			QDir pgDir(param.DbBinStr);
			pgDir.cdUp();
			QString pgDirPathStr = pgDir.path();
			QDir destDir(pgDirPathStr);
			if (!destDir.exists())
			{
				destDir.mkdir(pgDirPathStr);
			}
			cLogger("AsyTask")->info(TR("PostgreSql 替换%1开始...").arg(pgDirPathStr).toStdString().c_str());
			status = copyFiles2Dir(TaskType::PG_REPLACE, param.PgUpgradeDirStr, pgDirPathStr);
			if (status)
			{
				emit upgradeProgress(PG_REPLACE, UpGradeSuccess);
				cLogger("AsyTask")->info(TR("PostgreSql 替换%1:%2").arg(pgDirPathStr).arg(TR("成功")).toStdString().c_str());
			}
			else
			{
				emit upgradeProgress(PG_REPLACE, UpGradeFail);
				cLogger("AsyTask")->error(TR("PostgreSql 替换 %1:%2").arg(pgDirPathStr).arg(TR("失败")).toStdString().c_str());
			}
			if (0 == startDbServer() && status)
			{	
				emit upgradeProgress(TaskType::PG_RESTORE, UpGradeProgress::UpGrading);
				cLogger("AsyTask")->info(TR("数据库数据开始恢复...").toStdString());
				if (0 == pg_restore_fromFile(param.DbBinStr, sqlfilePath))
				{
					QFile file(sqlfilePath);
					if (file.exists())
					{
						file.remove();
					}
					cLogger("AsyTask")->info(TR("数据库数据恢复成功").toStdString());
					emit upgradeProgress(TaskType::PG_RESTORE, UpGradeProgress::UpGradeSuccess);
				}
				else
				{
					cLogger("AsyTask")->error(TR("数据库数据恢复失败").toStdString());
					emit upgradeProgress(TaskType::PG_RESTORE, UpGradeProgress::UpGradeFail);
				}
			}
		}
	}
}

使用效果:

Qt源码中的消息重定向

        Qt源码中会打印许多warning和debug等信息,为了便于现场问题排查,将这些信息也可打印到日志中进行保留。
        自定义消息处理器函数,拦截所有通过qDebug、qInfo、qWarning和qCritical发出的消息,
并根据需要对这些消息进行处理(格式化消息、决定消息的输出目的地、或者根据消息的重要性进行过滤)。

       qInstallMessageHandler

       qInstallMessageHandler(function);注册日志处理函数function

       qInstallMessageHandler(0);恢复消息处理函数。

        

//自定义消息处理函数 将Qt的消息重定向到日志文件,便于现场日志持久化保存
void MyQtMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
	std::string logStr = QString("qtMessage:%1").arg(msg).toStdString();
	switch (type)
	{ 
	case QtDebugMsg:
	case QtInfoMsg:
		cLogger("Qt")->info(logStr);
		break;
	case QtWarningMsg:
		cLogger("Qt")->warn(logStr);
		break;
	case QtCriticalMsg:
		cLogger("Qt")->error(logStr);
		break;
	case QtFatalMsg:
		cLogger("Qt")->critical(logStr);
		abort();
	}
}



int main(int argc, char *argv[])
{
	QApplication a(argc, argv);
    //终端程序单例
	QSharedMemory sharedMemory;
	sharedMemory.setKey(QString("MYDBUPDATERBUILD20241010"));
	bool _pmsAlreadyRunning = false;
	if (sharedMemory.attach())
	{
		_pmsAlreadyRunning = true;
	}
	if (!sharedMemory.create(1))
	{
		_pmsAlreadyRunning = true;
	}
	if (_pmsAlreadyRunning)
	{
		QMessageBox::critical(nullptr,TR("数据库备份升级程序"),TR("已经有数据库备份升级程序启动"));
		return -1;
	}

	PmsUpDater w;
    //注册日志处理函数 
	qInstallMessageHandler(MyQtMessageHandler);
    //以下也会重定向到日志中
	//qInfo() << "test1 info!!!";
	//qDebug() << "test1 qdebug!!!";
	//qWarning() << "test1 qwarning!!!";
	//qCritical() << "qCritical qwarning!!!";
	w.show();
	return a.exec();
}

<think>我们正在讨论如何在Qt项目中集成和使用spdlog日志spdlog是一个快速的C++日志,支持多种日志输出方式。 根据引用[4]中提到的关于在Qt项目中添加外部的方法,我们可以通过以下步骤来集成spdlog: 1. 下载spdlog:可以从其GitHub仓(https://github.com/gabime/spdlog)获取。 2. 将spdlog包含到项目中:通常有两种方式: a. 将spdlog作为子模块(submodule)添加到项目中(如果使用git),或者直接复制源代码到项目目录。 b. 使用包管理工具(如vcpkg、conan)安装,然后在项目中引用。 3. 在Qt项目中配置包含路径和(如果需要链接编译后的spdlog,但通常spdlog是header-only的,所以只需包含头文件即可)。 但是注意,spdlog默认是header-only的,所以通常只需要包含头文件即可使用。但也可以编译为动态或静态。 步骤: 1. 获取spdlog: 例如,使用git克隆spdlog到本地,或者下载zip包解压。 2. 将spdlog添加到Qt项目中: - 将spdlog的include目录添加到项目的INCLUDEPATH中(在.pro文件中)。 - 或者,将spdlog源码放在项目目录中(比如创建一个third_party目录),然后在代码中包含相对路径。 3. 在代码中使用spdlog。 由于spdlog是header-only,所以不需要链接,但需要确保你的项目支持C++11或更高版本。 在.pro文件中添加: INCLUDEPATH += path/to/spdlog/include 然后,在代码中就可以使用spdlog了。 示例代码: #include <spdlog/spdlog.h> int main(int argc, char *argv[]) { // 初始化Qt应用 QApplication a(argc, argv); // 使用spdlog记录一条信息 spdlog::info("Welcome to spdlog!"); // ... 其他代码 return a.exec(); } 注意:spdlog默认是异步日志,性能较好,适合在Qt项目使用,不会阻塞主线程。 但是,如果需要在Qt中输出到界面(比如文本框),我们可以自定义一个sink(输出目标)来实现。不过通常我们使用文件或控制台输出即可。 另外,如果项目使用CMake构建,可以参考引用[5]设置CMAKE_PREFIX_PATH,但这里我们主要讨论qmake的情况。 如果遇到编译错误,可能是因为spdlog依赖的(如fmt)没有包含。spdlog可以单独使用,也可以和fmt一起使用(如果使用格式化字符串)。在最新版本中,spdlog已经将fmt作为了内部依赖,所以通常不需要额外包含。 但是,如果使用较旧的spdlog版本(1.8.0之前)可能需要单独安装fmt。因此,建议使用最新版本的spdlog。 总结步骤: 1. 下载spdlog源码。 2. 将spdlog的include目录添加到Qt项目的.pro文件中(使用INCLUDEPATH)。 3. 在代码中包含头文件并使用spdlog。 如果需要将日志输出到文件,可以这样初始化: #include <spdlog/sinks/basic_file_sink.h> auto logger = spdlog::basic_logger_mt("file_logger", "logs/basic-log.txt"); spdlog::set_default_logger(logger); 然后在程序中使用spdlog::info("This is an info message"); 注意:确保有创建文件的权限(在项目目录下创建logs目录,或者使用绝对路径)。 另外,在多线程环境中使用spdlog是安全的,所以Qt的多线程环境也可以使用。 相关问题: 1. 如何在Qt项目中添加第三方? 2. spdlog有哪些常用的日志输出方式(sinks)? 3. 如何自定义spdlog的日志格式? 4. 如何在Qt项目使用CMake集成spdlog? 5. 如何将spdlog的日志输出到Qt的文本编辑控件中?</think>### 在Qt项目中集成spdlog日志 #### 步骤1:获取spdlog 1. **推荐方式**:通过Git子模块集成 ```bash git submodule add https://github.com/gabime/spdlog.git third_party/spdlog ``` 2. **替代方式**:直接下载源码 从 [spdlog GitHub](https://github.com/gabime/spdlog) 下载ZIP包,解压到项目目录(如 `third_party/spdlog`) #### 步骤2:配置Qt项目文件(.pro) ```qmake # 添加头文件路径 INCLUDEPATH += $$PWD/third_party/spdlog/include # 启用C++11(必需) CONFIG += c++11 # 如果使用静态(可选) # LIBS += -L$$PWD/third_party/spdlog/build -lspdlog ``` #### 步骤3:在代码中使用spdlog ```cpp #include <QApplication> #include <spdlog/spdlog.h> #include <spdlog/sinks/basic_file_sink.h> // 文件输出 int main(int argc, char *argv[]) { QApplication a(argc, argv); // 初始化日志系统(输出到文件+控制台) try { auto file_sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("qt_app.log", true); auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>(); spdlog::logger logger("main", {file_sink, console_sink}); spdlog::set_default_logger(std::make_shared<spdlog::logger>(logger)); spdlog::set_pattern("[%Y-%m-%d %H:%M:%S] [%^%l%$] %v"); } catch (const spdlog::spdlog_ex& ex) { qFatal("Log init failed: %s", ex.what()); } spdlog::info("======= 应用启动 ======="); spdlog::warn("Qt版本: {}", qVersion()); // 示例:在按钮点击时记录日志 QPushButton btn("记录日志"); QObject::connect(&btn, &QPushButton::clicked, []{ spdlog::debug("按钮被点击 @ {}", QDateTime::currentDateTime().toString().toStdString()); }); btn.show(); return a.exec(); } ``` #### 关键配置说明 1. **多线程支持**: spdlog默认线程安全,适合Qt多线程环境,使用 `_mt`(multi-thread)后缀的sink[^4] 2. **常用输出目标**: - 文件:`spdlog::sinks::basic_file_sink_mt` - 控制台:`spdlog::sinks::stdout_color_sink_mt` - Qt控件:需自定义sink(见下文) 3. **性能优化**: ```cpp // 启用异步日志(避免阻塞UI线程) spdlog::init_thread_pool(8192, 1); // 队列大小=8192,线程数=1 auto async_file = std::make_shared<spdlog::sinks::basic_file_sink_mt>("qt_async.log"); spdlog::create_async_nb<async_file>("async_logger"); ``` #### 自定义Qt控件输出(如QTextEdit) ```cpp class QTextEditSink : public spdlog::sinks::base_sink<std::mutex> { public: explicit QTextEditSink(QTextEdit* edit) : text_edit(edit) {} protected: void sink_it_(const spdlog::details::log_msg& msg) override { spdlog::memory_buf_t formatted; formatter_->format(msg, formatted); QMetaObject::invokeMethod(text_edit, [=]{ text_edit->append(QString::fromStdString(fmt::to_string(formatted))); }); } void flush_() override {} private: QTextEdit* text_edit; }; // 使用方法 QTextEdit *logView = new QTextEdit; spdlog::default_logger()->sinks().push_back( std::make_shared<QTextEditSink>(logView) ); ``` #### 常见问题解决 1. **编译错误:C++11特性不支持** 在.pro中添加:`CONFIG += c++11` 2. **链接错误:未找到spdlog符号** 确保使用header-only模式(默认),或正确链接静态: ```qmake # 静态配置 LIBS += -L$$PWD/third_party/spdlog/build -lspdlog INCLUDEPATH += $$PWD/third_party/spdlog/include DEPENDPATH += $$PWD/third_party/spdlog/include ``` 3. **日志文件无写入权限** 在Windows/Linux检查应用目录写权限,或指定绝对路径: ```cpp auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>( QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/app.log" ); ``` --- ### 相关问题 1. **如何在Qt中实现日志分级过滤?** 2. **spdlog异步模式如何避免内存泄漏?** 3. **如何将spdlogQt的qDebug()整合?** 4. **在Android/iOS平台集成spdlog有哪些特殊配置?** 5. **如何实现日志文件的自动分割和压缩?** [^1]: 引用Qt项目配置示例 [^4]: 外部集成方法参考 [^5]: CMake路径配置参考
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值