模块设计详解:myLog 与 common
一、日志模块(myLog)
1.1 设计概述
日志模块基于 spdlog 库封装,采用单例模式提供全局唯一的日志实例,支持同步/异步日志写入、多级别日志输出、文件/stdout 双输出方式。
设计目标:
- 统一日志接口,简化使用
- 线程安全的初始化与访问
- 支持不同输出目标(控制台/文件)
- 日志格式统一,包含文件名和行号
1.2 myLog.h 接口设计
1.2.1 Logger 类(单例封装)
cpp
class Logger
{
public:
static void initLogger(const std::string& logName,
const std::string& logFile,
spdlog::level::level_enum logLevel = spdlog::level::info);
static std::shared_ptr<spdlog::logger> getLogger();
private:
Logger();
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
private:
static std::shared_ptr<spdlog::logger> _logger;
static std::mutex _mutex;
};
| 成员 | 说明 |
|---|---|
initLogger() |
初始化日志器,设置名称、输出目标、日志级别 |
getLogger() |
获取日志器实例(返回 shared_ptr) |
_logger |
静态成员,存储全局唯一的日志器实例 |
_mutex |
静态互斥锁,保护初始化过程的线程安全 |
| 私有构造函数 | 禁止外部实例化,强制通过静态方法访问 |
单例模式实现要点:
- 构造函数声明为
private,禁止外部创建实例 - 拷贝构造和赋值运算符使用
= delete删除,防止通过拷贝创建新实例 - 所有成员函数均为静态,无需实例化即可调用
1.2.2 日志宏定义
cpp
//fmt格式化输出跟踪日志
#define TRACE(fmt, ...) bite::Logger::getLogger()->trace(std::string("[{:>10s}:{:<4d}]")+fmt, __FILE__, __LINE__, ##__VA_ARGS__)
//fmt格式化输出调试日志
#define DBG(fmt, ...) bite::Logger::getLogger()->debug(std::string("[{:>10s}:{:<4d}]")+fmt, __FILE__, __LINE__, ##__VA_ARGS__)
//fmt格式化输出信息日志
#define INFO(fmt, ...) bite::Logger::getLogger()->info(std::string("[{:>10s}:{:<4d}]")+fmt, __FILE__, __LINE__, ##__VA_ARGS__)
//fmt格式化输出警告日志
#define WARN(fmt, ...) bite::Logger::getLogger()->warn(std::string("[{:>10s}:{:<4d}]")+fmt, __FILE__, __LINE__, ##__VA_ARGS__)
//fmt格式化输出错误日志
#define ERR(fmt, ...) bite::Logger::getLogger()->error(std::string("[{:>10s}:{:<4d}]")+fmt, __FILE__, __LINE__, ##__VA_ARGS__)
//fmt格式化输出严重错误日志
#define CRIT(fmt, ...) bite::Logger::getLogger()->critical(std::string("[{:>10s}:{:<4d}]")+fmt, __FILE__, __LINE__, ##__VA_ARGS__)
宏设计亮点:
| 特性 | 实现方式 | 效果 |
|---|---|---|
| 文件行号追踪 | __FILE__、__LINE__ 宏 |
日志自动附带调用位置 |
| 格式化输出 | fmt 库语法 |
支持 {} 占位符格式化 |
| 可变参数 | ##__VA_ARGS__ |
兼容 C99 可变参数宏 |
| 对齐美化 | {:>10s}、{:<4d} |
文件名右对齐10位,行号左对齐4位 |
日志输出格式:
[14:32:58][ChatSDK][INFO ][ChatSDK.cpp: 45] Initialize deepseek-chat success
1.3 myLog.cpp 实现细节
文件:myLog.cpp(file:///workspace/CPP_AI_Model_Access/AIModelAccess/sdk/src/util/myLog.cpp)
1.3.1 静态成员初始化
cpp
std::shared_ptr<spdlog::logger> Logger::_logger = nullptr;
std::mutex Logger::_mutex;
静态成员必须在类外部初始化 ,初始值为 nullptr 表示日志器尚未初始化。
1.3.2 initLogger() 核心实现
cpp
void Logger::initLogger(const std::string& logName,
const std::string& logFile,
spdlog::level::level_enum logLevel)
{
if(_logger == nullptr)
{
std::lock_guard<std::mutex> lock(_mutex);
if(_logger == nullptr)
{
spdlog::flush_on(logLevel);
spdlog::init_thread_pool(32768, 1);
if("stdout" == logFile)
_logger = spdlog::stdout_color_mt(logName);
else
_logger = spdlog::basic_logger_mt(logName, logFile);
}
_logger->set_pattern("[%H:%M:%S][%n][%-7l]%v");
_logger->set_level(logLevel);
}
}
关键技术点:
| 技术点 | 实现方式 | 作用 |
|---|---|---|
| 双重检查锁 | 两次判断 _logger == nullptr |
保证多线程环境下只初始化一次,避免竞态条件 |
| lock_guard | RAII 自动加锁/解锁 | 异常安全,防止死锁 |
| flush_on | 设置自动刷新级别 | 级别 >= logLevel 的日志立即刷新到文件 |
| init_thread_pool | 初始化异步日志线程池 | 线程池大小 32KB,1 个工作线程 |
| stdout_color_mt | 彩色控制台输出(多线程安全) | 区分不同级别日志颜色 |
| basic_logger_mt | 文件输出(多线程安全) | 将日志写入指定文件 |
1.3.3 日志格式设置
cpp
_logger->set_pattern("[%H:%M:%S][%n][%-7l]%v");
格式说明:
[%H:%M:%S]- 时分秒时间戳(24小时制)[%n]- 日志器名称(初始化时指定)[%7l]- 日志级别,左对齐,占用7个字符%v- 具体日志内容(包含文件:行号信息)
1.3.4 getLogger() 实现
cpp
std::shared_ptr<spdlog::logger> Logger::getLogger()
{
return _logger;
}
直接返回静态成员 _logger,调用者通过返回的 shared_ptr 访问日志方法。
1.4 使用方式
cpp
// 在程序入口初始化日志
bite::Logger::initLogger("ChatServer", "chat_server.log", spdlog::level::debug);
// 在任意模块使用日志宏
INFO("Server started on port {}", 8080);
ERR("Failed to connect to database: {}", errorMsg);
WARN("API key not set, some models may not work");
二、公共数据结构(common.h)
2.1 设计概述
common.h 定义了项目中所有模块共享的数据结构,是整个 SDK 的数据契约层。设计遵循以下原则:
- 职责单一:每个结构体只负责一类数据
- 继承扩展:通过继承实现配置的多态性
- 默认构造:提供合理的默认值,便于对象创建
- 安全性:配置基类提供虚析构,支持安全的向下转型
2.2 数据结构体系
Config (基类)
├── ApiConfig (云端API配置)
└── OllamaConfig (本地Ollama配置)
Message (消息结构)
ModelInfo (模型元信息)
Session (会话结构)
2.3 Message 结构
cpp
struct Message
{
std::string _messageId; // 消息唯一标识
std::string _role; // 角色:"user" / "assistant" / "system"
std::string _content; // 消息内容
std::time_t _timestamp; // 消息发送时间戳
Message(const std::string& role = "", const std::string& content = "")
: _role(role), _content(content)
{}
};
设计要点:
_role字段遵循 OpenAI API 规范,支持 user/assistant/system 三种角色_messageId在SessionManager中生成,用于消息追踪和持久化- 提供默认参数的构造函数,便于快速创建用户消息
使用场景:
- LLMProvider 发送请求时,构造消息列表
- SessionManager 维护历史消息记录
- DataManager 持久化消息到 SQLite
2.4 Config 层次结构
2.4.1 Config 基类
cpp
struct Config
{
std::string _modelName; // 模型名称
double _temperature; // 温度参数,控制输出随机性(默认0.7)
int _maxTokens; // 最大令牌数,限制输出长度(默认2048)
virtual ~Config() = default; // 虚析构,支持安全的多态删除
};
设计要点:
| 字段 | 作用 | 取值范围 |
|---|---|---|
_modelName |
模型唯一标识 | 如 "deepseek-chat", "gpt-4o-mini" |
_temperature |
控制输出随机性 | 0~2,越大越随机,越小越确定 |
_maxTokens |
限制输出长度 | 正整数,防止输出过长 |
虚析构的必要性:
cpp
// 如果没有虚析构,以下代码会导致未定义行为
Config* config = new ApiConfig();
delete config; // 只调用 Config 的析构函数,不会调用 ApiConfig 的析构
2.4.2 ApiConfig(云端 API 模型配置)
cpp
struct ApiConfig : public Config
{
std::string _apiKey; // API 密钥,用于身份认证
};
设计要点:
- 继承自
Config,复用模型名称、温度、最大 token 配置 - 新增
_apiKey字段,用于云端模型的 API 认证 - 适用于 ChatGPT、DeepSeek、Gemini 等云端 API 模型
2.4.3 OllamaConfig(本地 Ollama 模型配置)
cpp
struct OllamaConfig : public Config
{
std::string _modelName; // Ollama 模型名称(如 "deepseek-r1:1.5b")
std::string _modelDesc; // 模型描述信息
std::string _endpoint; // Ollama 服务地址(如 "http://localhost:11434")
};
设计要点:
- 继承自
Config,但覆盖了_modelName字段(Ollama 模型名格式不同) - 无需 API Key(本地模型)
- 需要指定 Ollama 服务的 endpoint 地址
- 包含模型描述,用于展示和区分不同模型
2.4.4 配置层次结构总结
| 配置类型 | 适用模型 | 特有字段 |
|---|---|---|
Config |
所有模型(基类) | _modelName, _temperature, _maxTokens |
ApiConfig |
ChatGPT、DeepSeek、Gemini | _apiKey |
OllamaConfig |
Ollama 本地模型 | _modelDesc, _endpoint |
多态使用示例:
cpp
// 在 ChatSDK 中统一处理不同类型的配置
std::vector<std::shared_ptr<Config>> configs;
// 添加云端模型配置
auto deepseekConfig = std::make_shared<ApiConfig>();
deepseekConfig->_modelName = "deepseek-chat";
deepseekConfig->_apiKey = "sk-xxx";
configs.push_back(deepseekConfig);
// 添加本地模型配置
auto ollamaConfig = std::make_shared<OllamaConfig>();
ollamaConfig->_modelName = "deepseek-r1:1.5b";
ollamaConfig->_endpoint = "http://localhost:11434";
configs.push_back(ollamaConfig);
// 通过 dynamic_pointer_cast 区分处理
for (const auto& config : configs) {
if (auto apiConfig = std::dynamic_pointer_cast<ApiConfig>(config)) {
// 处理云端模型
} else if (auto ollamaConfig = std::dynamic_pointer_cast<OllamaConfig>(config)) {
// 处理本地模型
}
}
2.5 ModelInfo 结构
cpp
struct ModelInfo
{
std::string _modelName; // 模型名称
std::string _modelDesc; // 模型描述
std::string _provider; // 模型提供方
std::string _endpoint; // API endpoint 地址
bool _isAvailable = false; // 模型是否可用
ModelInfo(const std::string& modelName = "",
const std::string& modelDesc = "",
const std::string& provider = "",
const std::string& endpoint = "")
: _modelName(modelName), _modelDesc(modelDesc),
_provider(provider), _endpoint(endpoint)
{}
};
设计要点:
- 用于展示模型的元信息,供前端或 API 返回使用
_isAvailable标记模型是否已成功初始化- 提供全参数构造函数,便于快速创建
使用场景:
- LLMManager 维护已注册模型的状态信息
- ChatServer 的
/api/models接口返回可用模型列表
2.6 Session 结构
cpp
struct Session
{
std::string _sessionId; // 会话唯一标识
std::string _modelName; // 会话使用的模型名称
std::vector<Message> _messages; // 会话历史消息
std::time_t _createAt; // 会话创建时间
std::time_t _updateAt; // 会话最后更新时间
Session(const std::string& modelName = "")
: _modelName(modelName)
{}
};
设计要点:
_sessionId由SessionManager生成,全局唯一_messages存储完整的对话历史,用于上下文管理_createAt和_updateAt用于会话管理(如清理长时间未使用的会话)
使用场景:
- SessionManager 管理活跃会话
- DataManager 将会话持久化到 SQLite
- ChatServer 通过
/api/session/{id}/history返回会话历史
2.7 数据结构关系图
┌─────────────────────────────────────────────────────────────┐
│ common.h 数据结构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────┐ │
│ │ Config │ (基类,虚析构) │
│ │ ────────────────── │ │
│ │ _modelName │ │
│ │ _temperature │ │
│ │ _maxTokens │ │
│ └──────────┬──────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────────┐ │
│ │ApiConfig │ │OllamaConfig │ │
│ │ ─────── │ │ ─────────── │ │
│ │ _apiKey │ │ _modelDesc │ │
│ └──────────┘ │ _endpoint │ │
│ └──────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ Message │ (消息单元) │
│ │ ────────────────── │ │
│ │ _messageId │ │
│ │ _role │ │
│ │ _content │ │
│ │ _timestamp │ │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ ModelInfo │ (模型元信息) │
│ │ ────────────────── │ │
│ │ _modelName │ │
│ │ _modelDesc │ │
│ │ _provider │ │
│ │ _endpoint │ │
│ │ _isAvailable │ │
│ └─────────────────────┘ │
│ │
│ ┌─────────────────────┐ │
│ │ Session │ (会话上下文) │
│ │ ────────────────── │ │
│ │ _sessionId │ │
│ │ _modelName │ │
│ │ _messages[] │ ◀─── 包含多个 Message │
│ │ _createAt │ │
│ │ _updateAt │ │
│ └─────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
三、模块间依赖关系
3.1 myLog 的依赖关系
┌──────────────┐
│ myLog.h │
│ ─────────── │
│ Logger 类 │
│ 日志宏定义 │
└──────┬───────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ChatSDK │ │LLMManager│ │所有Provider│
└──────────┘ └──────────┘ └──────────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Session- │ │Data- │ │ChatServer │
│Manager │ │Manager │ └──────────┘
└──────────┘ └──────────┘
依赖特性:
- myLog 不依赖任何项目内部模块,仅依赖 spdlog 第三方库
- 项目中所有模块均依赖 myLog(日志输出)
- myLog 通过全局宏提供无侵入的日志记录能力
3.2 common.h 的依赖关系
┌──────────────┐
│ common.h │
│ ─────────── │
│ 数据结构定义 │
└──────┬───────┘
│
┌─────────────────┼─────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│LLMProvider│ │ChatSDK │ │Session- │
│(抽象接口) │ └──────────┘ │Manager │
└────┬─────┘ └────┬─────┘
│ │
▼ ▼
┌─────────────────┐ ┌──────────┐
│ 所有 Provider │ │Data- │
│ (4个实现类) │ │Manager │
└─────────────────┘ └──────────┘
依赖特性:
- common.h 不依赖任何项目内部模块,仅依赖标准库
- LLMProvider、ChatSDK、SessionManager、DataManager 均依赖 common.h
- 是整个 SDK 的数据契约,定义了模块间交互的数据格式
四、设计模式应用
4.1 myLog - 单例模式
| 要素 | 实现 |
|---|---|
| 私有构造 | Logger() 声明为 private |
| 静态实例 | static std::shared_ptr<spdlog::logger> _logger |
| 全局访问 | static getLogger() 方法 |
| 线程安全 | 双重检查锁 + std::lock_guard |
| 禁止拷贝 | Logger(const Logger&) = delete |
4.2 common.h - 模板方法模式(隐式)
通过配置基类 Config 和子类 ApiConfig、OllamaConfig,实现了配置的多态处理:
cpp
// ChatSDK 中统一处理配置(模板方法模式的应用)
void initProviders(const std::vector<std::shared_ptr<Config>>& configs) {
for (const auto& config : configs) {
if (auto apiConfig = std::dynamic_pointer_cast<ApiConfig>(config)) {
initAPIModelProvider(apiConfig->_modelName, apiConfig);
} else if (auto ollamaConfig = std::dynamic_pointer_cast<OllamaConfig>(config)) {
initOllamaModelProvider(ollamaConfig->_modelName, ollamaConfig);
}
}
}
五、总结
| 模块 | 核心职责 | 设计模式 | 依赖关系 |
|---|---|---|---|
| myLog | 全局日志管理 | 单例模式 | 仅依赖 spdlog |
| common.h | 公共数据结构定义 | 继承/多态 | 仅依赖标准库 |
这两个模块是项目的基础设施:
- myLog 提供统一的日志能力,是调试和运维的基础
- common.h 定义了模块间的数据契约,是 SDK 设计的核心
- 两者都不依赖项目内部其他模块,体现了良好的模块化设计原则