目录
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¶m)
{
//是否删除数据
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();
}