C++项目 —— 基于多设计模式下的同步&异步日志系统(5)(单例模式)
我们在上次已经完成了建造者类的编写,建造者类的编写可以帮助我们很好的组建我们的对象。还没有看过上一次的小伙伴可以点击这里:
https://blog.csdn.net/qq_67693066/article/details/147361584?spm=1011.2415.3001.5331
但是又会有新的问题出现了:
一个问题
我们来看看这段测试代码:
// 测试函数
void testLocalLogger()
{
// 创建局部建造者
logs::LocalLogger local_logger;
local_logger.buildLoggerName("synclogger");
local_logger.buildLoggerlevel(logs::Loglevel::value::DEBUG);
local_logger.buildFormatter("abc[%d{%H:%M:%S}][%c]%T%m%n");
logs::BaseLogger::ptr logger = local_logger.build();
logger->debug("main.cc", 53, "%s","格式化功能测试....");
}
我们是在这个函数体的内部创建了logger,第一点,这个logger出了函数作用域就不能工作了(因为被销毁了);第二点,如果我有一个函数就创建一个logger,不仅资源利用低下,还特别不容易统一管理。
所以这时候我们想到了单例模式,想详细了解单例模式的小伙伴可以点击这里:
https://blog.csdn.net/qq_67693066/article/details/136603292?spm=1011.2415.3001.5331
单例模式可以保证全局只会有一个实例,可以做到资源利用的最大化:
单例模式实现
class LoggerManager
{
public:
static LoggerManager& getInstance()
{
// c++11之后,针对静态局部变量,编译器在编译的层面实现了线程安全
// 当静态局部变量在没有构造完成之前,其他的线程进入就会阻塞
static LoggerManager eton;
return eton;
}
void addLogger(logs::BaseLogger::ptr& logger)
{
if(hasLogger(logger->name()))
return;
std::unique_lock<std::mutex> lock(_mutex);
_loggers.insert(std::make_pair(logger->name(),logger));
}
bool hasLogger(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _loggers.find(name);
if (it == _loggers.end())
{
return false;
}
return true;
}
logs::BaseLogger::ptr getLogger(const std::string &name)
{
std::unique_lock<std::mutex> lock(_mutex);
auto it = _loggers.find(name);
if (it == _loggers.end())
{
return BaseLogger::ptr();
}
return it->second;
}
logs::BaseLogger::ptr rootLogger()
{
return _root_logger;
}
private:
LoggerManager()
{
std::unique_ptr<logs::LoggerBuilder> builder(new logs::LocalLogger());
builder->buildLoggerName("root");
_root_logger = builder->build();
_loggers.insert(std::make_pair("root", _root_logger));
}
std::mutex _mutex;
logs::BaseLogger::ptr _root_logger; // 默认日志器
std::unordered_map<std::string, BaseLogger::ptr> _loggers;
};
这段代码实现了一个 日志管理器(LoggerManager
),用于集中管理和操作日志器(BaseLogger
)。它的主要作用是提供一个全局唯一的日志管理器实例,负责创建、存储和检索日志器。以下是详细的功能说明和设计目的:
1. 单例模式:全局唯一实例
功能:
LoggerManager
使用单例模式,确保在整个应用程序中只有一个全局的LoggerManager
实例。- 通过静态方法
getInstance()
提供对这个唯一实例的访问。
实现细节:
static LoggerManager& getInstance()
{
static LoggerManager eton;
return eton;
}
- 静态局部变量
eton
在 C++11 及之后的标准中是线程安全的,编译器会自动处理多线程环境下的初始化问题。 - 这种设计使得
LoggerManager
的实例可以在任何地方轻松获取,而无需手动创建或管理。
作用:
- 提供一个全局访问点,方便集中管理所有日志器。
- 避免重复创建多个日志管理器实例,节省资源并减少潜在冲突。
2. 日志器的注册与查找
功能:
LoggerManager
提供了添加、查找和获取日志器的功能,用于统一管理所有的日志器。
实现细节:
添加日志器:
void addLogger(logs::BaseLogger::ptr& logger) { if (hasLogger(logger->name())) return; std::unique_lock<std::mutex> lock(_mutex); _loggers.insert(std::make_pair(logger->name(), logger)); }
- 检查是否已经存在同名的日志器,避免重复添加。
- 使用互斥锁
_mutex
确保多线程环境下的线程安全性。
检查日志器是否存在:
bool hasLogger(const std::string &name) { std::unique_lock<std::mutex> lock(_mutex); auto it = _loggers.find(name); return it != _loggers.end(); }
- 查找
_loggers
中是否存在指定名称的日志器。
- 查找
获取日志器:
logs::BaseLogger::ptr getLogger(const std::string &name) { std::unique_lock<std::mutex> lock(_mutex); auto it = _loggers.find(name); if (it == _loggers.end()) return BaseLogger::ptr(); return it->second; }
- 如果找到指定名称的日志器,则返回对应的智能指针;否则返回空指针。
作用:
- 提供了一种统一的方式来管理日志器的生命周期。
- 避免了日志器的重复创建和命名冲突。
- 支持动态添加和查找日志器,满足不同模块的需求。
3. 默认日志器(Root Logger)
功能:
LoggerManager
在构造函数中创建了一个默认的日志器(root logger
),用于记录未指定目标的日志。
实现细节:
LoggerManager()
{
std::unique_ptr<logs::LoggerBuilder> builder(new logs::LocalLogger());
builder->buildLoggerName("root");
_root_logger = builder->build();
_loggers.insert(std::make_pair("root", _root_logger));
}
- 使用
LoggerBuilder
创建一个名为"root"
的默认日志器。 - 将该日志器存储在
_root_logger
中,并将其注册到_loggers
中。
作用:
- 提供了一个全局可用的默认日志器,避免日志丢失的风险。
- 用户可以通过
rootLogger()
方法直接访问默认日志器。
4. 线程安全性
功能:
- 在多线程环境中,多个线程可能同时访问或修改
_loggers
,因此需要确保线程安全性。
实现细节:
使用
std::mutex
和std::unique_lock
来保护对_loggers
的访问:std::mutex _mutex;
在每个涉及
_loggers
的操作(如添加、查找)中,都加锁以确保数据一致性。
作用:
- 避免了多线程环境下的数据竞争问题。
- 提高了代码的健壮性和可靠性。
5. 数据结构
功能:
- 使用
std::unordered_map
存储日志器,键是日志器名称,值是日志器的智能指针。
实现细节:
std::unordered_map<std::string, BaseLogger::ptr> _loggers;
std::unordered_map
提供了高效的查找性能(平均时间复杂度为 O(1))。- 使用智能指针(
std::shared_ptr
)管理日志器的生命周期,避免内存泄漏。
作用:
- 提供了一种高效的方式来存储和检索日志器。
- 自动管理日志器的内存,减少手动释放资源的负担。
总结
LoggerManager
的主要作用是:
全局管理日志器:
- 通过单例模式提供一个全局唯一的日志管理器,集中管理所有日志器。
- 支持动态添加、查找和获取日志器。
默认日志器:
- 提供一个默认的日志器(
root logger
),用于记录未指定目标的日志。
- 提供一个默认的日志器(
线程安全性:
- 使用互斥锁保护对日志器集合的访问,确保多线程环境下的安全性。
灵活性与扩展性:
- 支持动态扩展,可以轻松添加新的日志器。
- 使用
LoggerBuilder
构建日志器,支持灵活的配置选项。
通过这种方式,LoggerManager
解决了日志系统中常见的全局管理、线程安全、默认日志器等问题,使得日志系统的使用更加方便、可靠和高效。
建造者类扩展
因为我们使用了单例模式,有了全局的变量,所以我们可以扩建我们的建造者类让它也能够帮我们建造全局的日志器:
// 设置全局日志器的建造者:在局部的基础上增加了一个功能,将日志器添加到单例对象中
class GlobaloggerBuild : public LoggerBuilder
{
public:
BaseLogger::ptr build() override
{
assert(_logger_name.empty() == false); // 必须要有日志器名称
if (_formetter.get() == nullptr)
{
_formetter = std::make_shared<Formetter>();
}
if (_sinks.empty())
{
buildSink<StdoutSink>();
}
BaseLogger::ptr logger;
if (_logger_type == loggerType::LOGGER_ASYNC)
{
logger = std::make_shared<AsyncLogger>(_logger_name, _limt_level, _formetter, _sinks, _looper_type);
}
else
{
logger = std::make_shared<SyncLogger>(_logger_name, _limt_level, _formetter, _sinks);
}
LoggerManager::getInstance().addLogger(logger);
return logger;
}
};
我们可以来测试一下:
#include "utils.hpp"
#include "level.hpp"
#include "message.hpp"
#include "fometter.hpp"
#include "sink.hpp"
#include "logger.hpp"
// 测试函数
void testLocalLogger()
{
logs::GlobaloggerBuild global_logger;
global_logger.buildLoggerName("synclogger");
logs::BaseLogger::ptr logger = global_logger.build();
logger->debug("main.cc", 53, "%s", "格式化功能测试....");
}
int main()
{
testLocalLogger() ;
}