项目中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();
}

### 在 Qt 项目中集成和使用 spdlog 的方法 在 Qt 项目中集成和使用 spdlog可以通过以下方式实现,具体方法取决于项目的构建工具(如 CMake 或 qmake)。以下是基于 CMake 的集成方法以及手动下载的解决方案。 #### 方法一:通过 CMake 自动下载并集成 spdlog 此方法适用于使用 CMake 构建的 Qt 项目。以下是详细的步骤: 1. 确保 `CMakeLists.txt` 文件中包含以下内容: ```cmake cmake_minimum_required(VERSION 3.14) project(MyQtApp) set(CMAKE_CXX_STANDARD 17) # 引入 Qt find_package(Qt5 COMPONENTS Widgets REQUIRED) # 使用 FetchContent 下载 spdlog include(FetchContent) FetchContent_Declare( spdlog GIT_REPOSITORY https://github.com/gabime/spdlog.git GIT_TAG v1.11.0 # 选择一个稳定的版本 ) FetchContent_MakeAvailable(spdlog) # 添加可执行文件 add_executable(MyQtApp main.cpp mainwindow.cpp mainwindow.h mainwindow.ui) # 链接 Qtspdlog target_link_libraries(MyQtApp PRIVATE Qt5::Widgets spdlog::spdlog) ``` 上述代码会自动从 GitHub 下载 spdlog 并将其集成到项目中[^1]。 2. 在源文件中使用 spdlog: ```cpp #include <spdlog/spdlog.h> #include <spdlog/sinks/stdout_color_sinks.h> int main(int argc, char *argv[]) { auto console = spdlog::stdout_color_mt("console"); console->info("Hello, spdlog in Qt!"); return 0; } ``` #### 方法二:手动下载并集成 spdlog 如果不想使用 CMake 的自动下载功能,可以选择手动下载 spdlog 并将其添加到项目中。 1. 前往 [spdlog GitHub 仓](https://github.com/gabime/spdlog) 下载最新的 release 版本(ZIP 文件)。 2. 将 `spdlog/include` 文件夹复制到你的项目目录中(例如,放在 `external/spdlog`)。 3. 修改 `CMakeLists.txt` 文件以包含 spdlog 的头文件路径: ```cmake include_directories(${CMAKE_SOURCE_DIR}/external/spdlog/include) add_executable(MyQtApp main.cpp mainwindow.cpp mainwindow.h mainwindow.ui) target_link_libraries(MyQtApp PRIVATE Qt5::Widgets) ``` 4. 在源文件中使用 spdlog: ```cpp #include <spdlog/spdlog.h> #include <spdlog/sinks/rotating_file_sink.h> void initLogger() { try { // 创建一个旋转日志文件 std::vector<spdlog::sink_ptr> sinks; sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>("app.log", 1048576, 3)); // 创建 logger auto multi_logger = std::make_shared<spdlog::logger>("multi_logger", begin(sinks), end(sinks)); spdlog::register_logger(multi_logger); multi_logger->set_level(spdlog::level::debug); multi_logger->flush_on(spdlog::level::debug); multi_logger->info("Logging initialized!"); } catch (const spdlog::spdlog_ex& ex) { std::cerr << "Log initialization failed: " << ex.what() << std::endl; } } int main(int argc, char *argv[]) { initLogger(); spdlog::get("multi_logger")->info("Hello, spdlog in Qt!"); return 0; } ``` #### 示例:创建自定义日志类 为了更好地管理日志,可以创建一个自定义的日志类。以下是一个示例: ```cpp #pragma once #include <spdlog/logger.h> #include <memory> #include <string> class DbUpgradeLog { public: static void initLog(const char* logName); static void unInitLog(); static std::shared_ptr<spdlog::logger> getLogger(const std::string& loggerName); private: static std::shared_ptr<spdlog::logger> s_logger; }; ``` 实现文件: ```cpp #include "DbUpgradeLog.h" #include <spdlog/sinks/basic_file_sink.h> std::shared_ptr<spdlog::logger> DbUpgradeLog::s_logger; void DbUpgradeLog::initLog(const char* logName) { s_logger = spdlog::basic_logger_mt("file_logger", logName); } void DbUpgradeLog::unInitLog() { spdlog::drop_all(); } std::shared_ptr<spdlog::logger> DbUpgradeLog::getLogger(const std::string& loggerName) { return spdlog::get(loggerName); } ``` 以上代码展示了如何在 Qt 项目中集成和使用 spdlog [^3]。 ### 注意事项 - 确保项目支持 C++17 或更高版本。 - 如果使用旋转日志文件,请注意文件大小限制和文件数量限制。 - spdlog 是一个高性能的日志,适合需要频繁记录日志的应用场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值