generated from guanyuankai/bonus-edge-proxy
feat(config manager): 增强config manager的功能,提供观察者模式的回调,给未来的热重载做准备。
This commit is contained in:
parent
430a69ad51
commit
5db82885b5
|
|
@ -1,17 +1,18 @@
|
|||
{
|
||||
"alarm_rules_path": "alarms.json",
|
||||
"config_base_path": "/app/config/",
|
||||
"data_cache_db_path": "edge_data_cache.db",
|
||||
"data_storage_db_path": "edge_proxy_data.db",
|
||||
"device_id": "rk3588-proxy-002",
|
||||
"log_level": "info",
|
||||
"mqtt_broker": "tcp://localhost:1883",
|
||||
"mqtt_client_id_prefix": "vehicle-road-counter-",
|
||||
"piper_executable_path": "/usr/bin/piper",
|
||||
"piper_model_path": "/app/models/model.onnx",
|
||||
"tcp_server_ports": [
|
||||
12345
|
||||
],
|
||||
"video_config_path": "video_config.json",
|
||||
"web_server_port": 8080
|
||||
}
|
||||
"alarm_rules_path": "alarms.json",
|
||||
"config_base_path": "/app/config/",
|
||||
"data_cache_db_path": "edge_data_cache.db",
|
||||
"data_storage_db_path": "edge_proxy_data.db",
|
||||
"device_id": "rk3588-proxy-002",
|
||||
"log_level": "info",
|
||||
"mqtt_broker": "tcp://localhost:1883",
|
||||
"mqtt_client_id_prefix": "vehicle-road-counter-",
|
||||
"piper_executable_path": "/usr/bin/piper",
|
||||
"piper_model_path": "/app/models/model.onnx",
|
||||
"tcp_server_ports": [12345],
|
||||
"video_config_path": "video_config.json",
|
||||
"web_server_port": 8080,
|
||||
"db_user": "forlinx",
|
||||
"db_pwd": "forlinx",
|
||||
"db_database": "smart-car-dev"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,146 +1,216 @@
|
|||
// config_manager.cc (修改后)
|
||||
#include "config_manager.h"
|
||||
|
||||
#include <cstdio> // for std::rename, std::remove
|
||||
#include <fstream>
|
||||
|
||||
ConfigManager &ConfigManager::getInstance() {
|
||||
static ConfigManager instance;
|
||||
return instance;
|
||||
ConfigManager& ConfigManager::getInstance() {
|
||||
static ConfigManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
// 注册监听器
|
||||
void ConfigManager::monitorKey(const std::string& key, KeyUpdateCallback cb) {
|
||||
std::lock_guard<std::mutex> lock(m_callbackMutex);
|
||||
m_key_callbacks[key].push_back(cb);
|
||||
spdlog::debug("Registered observer for config key: '{}'", key);
|
||||
}
|
||||
|
||||
// 核心逻辑:对比新旧配置,触发回调
|
||||
void ConfigManager::checkDiffAndNotify(const json& old_config, const json& new_config) {
|
||||
// 复制一份监听列表,避免在遍历时加锁时间过长,或者回调中再次注册导致死锁
|
||||
std::map<std::string, std::vector<KeyUpdateCallback>> callbacks_snapshot;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_callbackMutex);
|
||||
callbacks_snapshot = m_key_callbacks;
|
||||
}
|
||||
|
||||
int trigger_count = 0;
|
||||
|
||||
for (const auto& [key, callbacks] : callbacks_snapshot) {
|
||||
if (!new_config.contains(key))
|
||||
continue;
|
||||
|
||||
// 获取新旧值 (如果旧配置没这个key,视为 null)
|
||||
json old_val = old_config.contains(key) ? old_config[key] : json();
|
||||
json new_val = new_config[key];
|
||||
|
||||
if (old_val != new_val) {
|
||||
spdlog::info("Config Hot-Reload: Key '{}' changed. Triggering {} callbacks.", key,
|
||||
callbacks.size());
|
||||
|
||||
for (const auto& cb : callbacks) {
|
||||
try {
|
||||
cb(new_val); // 执行回调
|
||||
trigger_count++;
|
||||
} catch (const std::exception& e) {
|
||||
spdlog::error("Exception inside config callback for key '{}': {}", key,
|
||||
e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trigger_count > 0) {
|
||||
spdlog::info("Config reload finished. {} callbacks triggered.", trigger_count);
|
||||
}
|
||||
}
|
||||
|
||||
json ConfigManager::createDefaultConfig() {
|
||||
// --- 修改: 添加新的 video_service 和 video_streams 默认值 ---
|
||||
return json{
|
||||
{"device_id", "default-edge-proxy-01"},
|
||||
{"config_base_path", "/app/config/"},
|
||||
{"mqtt_broker", "tcp://localhost:1883"},
|
||||
{"mqtt_client_id_prefix", "edge-proxy-"},
|
||||
{"data_storage_db_path", "edge_proxy_data.db"},
|
||||
{"data_cache_db_path", "edge_data_cache.db"},
|
||||
{"tcp_server_ports", {12345}},
|
||||
{"web_server_port", 8080},
|
||||
{"log_level", "debug"},
|
||||
{"alarm_rules_path", "alarms.json"},
|
||||
{"piper_executable_path", "/usr/bin/piper"},
|
||||
{"piper_model_path", "/app/models/model.onnx"},
|
||||
|
||||
// --- 新增: 视频服务配置 ---
|
||||
{"video_config_path", "/app/config/video_config.json"},
|
||||
};
|
||||
return json{
|
||||
{"device_id", "default-edge-proxy-01"},
|
||||
{"config_base_path", "/app/config/"},
|
||||
{"mqtt_broker", "tcp://localhost:1883"},
|
||||
{"mqtt_client_id_prefix", "edge-proxy-"},
|
||||
{"data_storage_db_path", "edge_proxy_data.db"},
|
||||
{"data_cache_db_path", "edge_data_cache.db"},
|
||||
{"tcp_server_ports", {12345}},
|
||||
{"web_server_port", 8080},
|
||||
{"log_level", "debug"},
|
||||
{"alarm_rules_path", "alarms.json"},
|
||||
{"piper_executable_path", "/usr/bin/piper"},
|
||||
{"piper_model_path", "/app/models/model.onnx"},
|
||||
{"video_config_path", "/app/config/video_config.json"},
|
||||
};
|
||||
}
|
||||
|
||||
// 原子写入实现
|
||||
bool ConfigManager::save_unlocked() {
|
||||
std::ofstream ofs(m_configFilePath);
|
||||
if (!ofs.is_open()) {
|
||||
spdlog::error("Failed to open config file '{}' for writing.",
|
||||
m_configFilePath);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
ofs << m_config_json.dump(4);
|
||||
return true;
|
||||
} catch (const json::exception &e) {
|
||||
spdlog::error("Failed to serialize config. Error: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
std::string tmpPath = m_configFilePath + ".tmp";
|
||||
|
||||
// 1. 写入临时文件
|
||||
{
|
||||
std::ofstream ofs(tmpPath);
|
||||
if (!ofs.is_open()) {
|
||||
spdlog::error("Failed to open temp config file '{}' for writing.", tmpPath);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
ofs << m_config_json.dump(4);
|
||||
ofs.flush(); // 确保缓冲区刷入内核
|
||||
// 在某些关键系统上,可能还需要 fsync
|
||||
} catch (const json::exception& e) {
|
||||
spdlog::error("Failed to serialize config. Error: {}", e.what());
|
||||
return false;
|
||||
}
|
||||
} // ofs 关闭
|
||||
|
||||
// 2. 原子重命名 (Atomic Rename)
|
||||
// 如果重命名失败,原文件不会损坏
|
||||
if (std::rename(tmpPath.c_str(), m_configFilePath.c_str()) != 0) {
|
||||
spdlog::error("Failed to rename temp config file to '{}'. Errno: {}", m_configFilePath,
|
||||
errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigManager::load(const std::string &configFilePath) {
|
||||
std::unique_lock<std::shared_mutex> lock(m_mutex);
|
||||
m_configFilePath = configFilePath;
|
||||
bool ConfigManager::load(const std::string& configFilePath) {
|
||||
// 准备两个容器,用于在锁外进行对比
|
||||
json old_config_copy;
|
||||
json new_config_copy;
|
||||
|
||||
std::ifstream ifs(m_configFilePath);
|
||||
if (!ifs.is_open()) {
|
||||
spdlog::warn("Config file '{}' not found. Creating with default values.",
|
||||
m_configFilePath);
|
||||
m_config_json = createDefaultConfig();
|
||||
if (save_unlocked()) {
|
||||
spdlog::info("Default config file created at '{}'.", m_configFilePath);
|
||||
return true;
|
||||
} else {
|
||||
spdlog::error("Failed to create default config file.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(m_mutex); // 获取写锁
|
||||
m_configFilePath = configFilePath;
|
||||
|
||||
try {
|
||||
ifs >> m_config_json;
|
||||
// **重要**:合并默认值。
|
||||
json defaults = createDefaultConfig();
|
||||
defaults.merge_patch(
|
||||
m_config_json); // <-- 关键: m_config_json 会覆盖 defaults
|
||||
m_config_json = defaults; // 将合并后的结果存回
|
||||
// 备份当前配置(如果是第一次加载,这里是空的)
|
||||
old_config_copy = m_config_json;
|
||||
|
||||
spdlog::info("Successfully loaded config from '{}'. Device ID: {}",
|
||||
m_configFilePath, m_config_json.value("device_id", "N/A"));
|
||||
std::ifstream ifs(m_configFilePath);
|
||||
if (!ifs.is_open()) {
|
||||
spdlog::warn("Config file '{}' not found. Creating default.", m_configFilePath);
|
||||
m_config_json = createDefaultConfig();
|
||||
// 保存默认文件
|
||||
if (!save_unlocked()) {
|
||||
spdlog::error("Failed to create default config file.");
|
||||
return false;
|
||||
}
|
||||
// 第一次加载,不需要触发“变更”回调
|
||||
return true;
|
||||
}
|
||||
|
||||
if (save_unlocked()) {
|
||||
spdlog::debug("Config file updated with new default keys if any.");
|
||||
}
|
||||
return true;
|
||||
} catch (const json::exception &e) {
|
||||
spdlog::error(
|
||||
"Failed to parse config file '{}'. Error: {}. Using default values.",
|
||||
m_configFilePath, e.what());
|
||||
m_config_json = createDefaultConfig();
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
json loaded_json;
|
||||
ifs >> loaded_json;
|
||||
|
||||
// 合并默认值
|
||||
json defaults = createDefaultConfig();
|
||||
defaults.merge_patch(loaded_json);
|
||||
m_config_json = defaults;
|
||||
|
||||
// 复制新配置用于后续对比
|
||||
new_config_copy = m_config_json;
|
||||
|
||||
spdlog::info("Config loaded from '{}'. DeviceID: {}", m_configFilePath,
|
||||
m_config_json.value("device_id", "N/A"));
|
||||
|
||||
// 可选:如果缺少key,回写文件
|
||||
save_unlocked();
|
||||
|
||||
} catch (const json::exception& e) {
|
||||
spdlog::error("Failed to parse config file. Error: {}. Using defaults.", e.what());
|
||||
m_config_json = createDefaultConfig();
|
||||
return false;
|
||||
}
|
||||
} // !!! 写锁在这里释放 !!!
|
||||
|
||||
// 锁释放后,执行回调通知
|
||||
// 这样做非常安全,不会因为回调里调用了 get() 而导致死锁
|
||||
checkDiffAndNotify(old_config_copy, new_config_copy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ... (save, getDeviceID, ... getPiperModelPath 保持不变) ...
|
||||
bool ConfigManager::save() {
|
||||
std::unique_lock<std::shared_mutex> lock(m_mutex);
|
||||
return save_unlocked();
|
||||
}
|
||||
std::string ConfigManager::getDeviceID() {
|
||||
return get<std::string>("device_id", "default-edge-proxy-01");
|
||||
}
|
||||
std::string ConfigManager::getConfigBasePath() {
|
||||
return get<std::string>("config_base_path", "/app/config/");
|
||||
}
|
||||
std::string ConfigManager::getMqttBroker() {
|
||||
return get<std::string>("mqtt_broker", "tcp://localhost:1883");
|
||||
}
|
||||
std::string ConfigManager::getMqttClientID() {
|
||||
return get<std::string>("mqtt_client_id_prefix", "edge-proxy-") +
|
||||
getDeviceID();
|
||||
}
|
||||
std::string ConfigManager::getDataStorageDbPath() {
|
||||
return getConfigBasePath() +
|
||||
get<std::string>("data_storage_db_path", "edge_proxy_data.db");
|
||||
}
|
||||
std::string ConfigManager::getDataCacheDbPath() {
|
||||
return getConfigBasePath() +
|
||||
get<std::string>("data_cache_db_path", "edge_data_cache.db");
|
||||
}
|
||||
std::string ConfigManager::getDevicesConfigPath() {
|
||||
return getConfigBasePath() + "devices.json";
|
||||
}
|
||||
int ConfigManager::getWebServerPort() {
|
||||
return get<int>("web_server_port", 8080);
|
||||
}
|
||||
std::vector<uint16_t> ConfigManager::getTcpServerPorts() {
|
||||
return get<std::vector<uint16_t>>("tcp_server_ports", {12345});
|
||||
}
|
||||
std::string ConfigManager::getLogLevel() {
|
||||
return get<std::string>("log_level", "debug");
|
||||
}
|
||||
std::string ConfigManager::getAlarmRulesPath() {
|
||||
return getConfigBasePath() +
|
||||
get<std::string>("alarm_rules_path", "alarms.json");
|
||||
}
|
||||
std::string ConfigManager::getPiperExecutablePath() {
|
||||
return get<std::string>("piper_executable_path", "/usr/bin/piper");
|
||||
}
|
||||
std::string ConfigManager::getPiperModelPath() {
|
||||
return get<std::string>("piper_model_path", "/app/models/model.onnx");
|
||||
}
|
||||
|
||||
std::string ConfigManager::getVideoConfigPath() {
|
||||
return getConfigBasePath() +
|
||||
get<std::string>("video_config_path", "video_config.json");
|
||||
std::unique_lock<std::shared_mutex> lock(m_mutex);
|
||||
return save_unlocked();
|
||||
}
|
||||
|
||||
std::string ConfigManager::getConfigFilePath() const {
|
||||
std::shared_lock<std::shared_mutex> lock(m_mutex);
|
||||
return m_configFilePath;
|
||||
std::shared_lock<std::shared_mutex> lock(m_mutex);
|
||||
return m_configFilePath;
|
||||
}
|
||||
|
||||
std::string ConfigManager::getDeviceID() {
|
||||
return get<std::string>("device_id", "default-edge-proxy-01");
|
||||
}
|
||||
std::string ConfigManager::getConfigBasePath() {
|
||||
return get<std::string>("config_base_path", "/app/config/");
|
||||
}
|
||||
std::string ConfigManager::getMqttBroker() {
|
||||
return get<std::string>("mqtt_broker", "tcp://localhost:1883");
|
||||
}
|
||||
std::string ConfigManager::getMqttClientID() {
|
||||
return get<std::string>("mqtt_client_id_prefix", "edge-proxy-") + getDeviceID();
|
||||
}
|
||||
std::string ConfigManager::getDataStorageDbPath() {
|
||||
return getConfigBasePath() + get<std::string>("data_storage_db_path", "edge_proxy_data.db");
|
||||
}
|
||||
std::string ConfigManager::getDataCacheDbPath() {
|
||||
return getConfigBasePath() + get<std::string>("data_cache_db_path", "edge_data_cache.db");
|
||||
}
|
||||
std::string ConfigManager::getDevicesConfigPath() {
|
||||
return getConfigBasePath() + "devices.json";
|
||||
}
|
||||
int ConfigManager::getWebServerPort() {
|
||||
return get<int>("web_server_port", 8080);
|
||||
}
|
||||
std::vector<uint16_t> ConfigManager::getTcpServerPorts() {
|
||||
return get<std::vector<uint16_t>>("tcp_server_ports", {12345});
|
||||
}
|
||||
std::string ConfigManager::getLogLevel() {
|
||||
return get<std::string>("log_level", "debug");
|
||||
}
|
||||
std::string ConfigManager::getAlarmRulesPath() {
|
||||
return getConfigBasePath() + get<std::string>("alarm_rules_path", "alarms.json");
|
||||
}
|
||||
std::string ConfigManager::getPiperExecutablePath() {
|
||||
return get<std::string>("piper_executable_path", "/usr/bin/piper");
|
||||
}
|
||||
std::string ConfigManager::getPiperModelPath() {
|
||||
return get<std::string>("piper_model_path", "/app/models/model.onnx");
|
||||
}
|
||||
std::string ConfigManager::getVideoConfigPath() {
|
||||
return getConfigBasePath() + get<std::string>("video_config_path", "video_config.json");
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
// config_manager.h (修改后)
|
||||
#pragma once
|
||||
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <shared_mutex>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
|
|
@ -12,82 +15,98 @@ using json = nlohmann::json;
|
|||
|
||||
class ConfigManager {
|
||||
public:
|
||||
struct VideoStreamConfig {
|
||||
std::string id;
|
||||
bool enabled;
|
||||
std::string input_url;
|
||||
std::string output_rtsp;
|
||||
// 定义回调函数类型:接收变化后的新值 (json格式)
|
||||
using KeyUpdateCallback = std::function<void(const json& new_value)>;
|
||||
|
||||
std::string module_type;
|
||||
json module_config;
|
||||
};
|
||||
static ConfigManager& getInstance();
|
||||
|
||||
static ConfigManager &getInstance();
|
||||
bool load(const std::string &configFilePath);
|
||||
bool save();
|
||||
// 加载配置(支持热重载)
|
||||
bool load(const std::string& configFilePath);
|
||||
|
||||
template <typename T> T get(const std::string &key, const T &default_value) {
|
||||
std::shared_lock<std::shared_mutex> lock(m_mutex);
|
||||
// 保存配置(原子写入)
|
||||
bool save();
|
||||
|
||||
if (!m_config_json.contains(key)) {
|
||||
spdlog::debug("Config key '{}' not found, using default value.", key);
|
||||
return default_value;
|
||||
}
|
||||
// --- 核心新功能:监听特定 Key 的变化 ---
|
||||
void monitorKey(const std::string& key, KeyUpdateCallback cb);
|
||||
|
||||
try {
|
||||
return m_config_json.at(key).get<T>();
|
||||
} catch (const json::type_error &e) {
|
||||
spdlog::warn("Config type mismatch for key '{}'. Expected '{}', found "
|
||||
"'{}'. Using default. Error: {}",
|
||||
key, typeid(T).name(), m_config_json.at(key).type_name(),
|
||||
e.what());
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
// 通用 Get 方法
|
||||
template <typename T>
|
||||
T get(const std::string& key, const T& default_value) {
|
||||
std::shared_lock<std::shared_mutex> lock(m_mutex); // 读锁
|
||||
|
||||
template <typename T> void set(const std::string &key, const T &value) {
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(m_mutex);
|
||||
m_config_json[key] = value;
|
||||
spdlog::info("Config updated: [{}] set.", key);
|
||||
}
|
||||
if (!m_config_json.contains(key)) {
|
||||
// debug 级别,避免刷屏
|
||||
// spdlog::debug("Config key '{}' not found, using default.", key);
|
||||
return default_value;
|
||||
}
|
||||
|
||||
save();
|
||||
try {
|
||||
return m_config_json.at(key).get<T>();
|
||||
} catch (const json::type_error& e) {
|
||||
spdlog::warn(
|
||||
"Config type mismatch for key '{}'. Expected '{}'. Using default. Error: {}", key,
|
||||
typeid(T).name(), e.what());
|
||||
return default_value;
|
||||
}
|
||||
}
|
||||
|
||||
if (key == "log_level") {
|
||||
spdlog::set_level(spdlog::level::from_str(value));
|
||||
}
|
||||
}
|
||||
// 通用 Set 方法
|
||||
template <typename T>
|
||||
void set(const std::string& key, const T& value) {
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(m_mutex); // 写锁
|
||||
m_config_json[key] = value;
|
||||
spdlog::info("Config updated in memory: [{}]", key);
|
||||
}
|
||||
// 注意:这里 set 后立即 save,但目前没有触发回调(通常回调用于响应外部文件的变化)
|
||||
// 如果需要 set 也触发回调,可以手动调用 checkDiffAndNotify,但稍微复杂
|
||||
save();
|
||||
}
|
||||
|
||||
std::string getDeviceID();
|
||||
std::string getConfigBasePath();
|
||||
std::string getMqttBroker();
|
||||
std::string getMqttClientID();
|
||||
std::string getDataStorageDbPath();
|
||||
std::string getDataCacheDbPath();
|
||||
std::string getDevicesConfigPath();
|
||||
int getWebServerPort();
|
||||
std::vector<uint16_t> getTcpServerPorts();
|
||||
std::string getLogLevel();
|
||||
std::string getAlarmRulesPath();
|
||||
std::string getPiperExecutablePath();
|
||||
std::string getPiperModelPath();
|
||||
|
||||
// bool getIsVideoServiceEnabled() const;
|
||||
// std::vector<VideoStreamConfig> getVideoStreamConfigs() const;
|
||||
std::string getVideoConfigPath();
|
||||
std::string getConfigFilePath() const;
|
||||
// --- 快捷 Getter (保持你原有的接口不变) ---
|
||||
std::string getDeviceID();
|
||||
std::string getConfigBasePath();
|
||||
std::string getMqttBroker();
|
||||
std::string getMqttClientID();
|
||||
std::string getDataStorageDbPath();
|
||||
std::string getDataCacheDbPath();
|
||||
std::string getDevicesConfigPath();
|
||||
int getWebServerPort();
|
||||
std::vector<uint16_t> getTcpServerPorts();
|
||||
std::string getLogLevel();
|
||||
std::string getAlarmRulesPath();
|
||||
std::string getPiperExecutablePath();
|
||||
std::string getPiperModelPath();
|
||||
std::string getVideoConfigPath();
|
||||
std::string getConfigFilePath() const;
|
||||
|
||||
private:
|
||||
ConfigManager() = default;
|
||||
~ConfigManager() = default;
|
||||
ConfigManager(const ConfigManager &) = delete;
|
||||
ConfigManager &operator=(const ConfigManager &) = delete;
|
||||
json createDefaultConfig();
|
||||
ConfigManager() = default;
|
||||
~ConfigManager() = default;
|
||||
ConfigManager(const ConfigManager&) = delete;
|
||||
ConfigManager& operator=(const ConfigManager&) = delete;
|
||||
|
||||
bool save_unlocked();
|
||||
json createDefaultConfig();
|
||||
bool save_unlocked(); // 内部使用的无锁保存
|
||||
|
||||
std::string m_configFilePath;
|
||||
json m_config_json;
|
||||
mutable std::shared_mutex m_mutex;
|
||||
};
|
||||
// 检查差异并通知观察者
|
||||
void checkDiffAndNotify(const json& old_config, const json& new_config);
|
||||
|
||||
std::string m_configFilePath;
|
||||
json m_config_json;
|
||||
|
||||
mutable std::shared_mutex m_mutex; // 保护 m_config_json 的读写锁
|
||||
|
||||
// 观察者相关
|
||||
std::mutex m_callbackMutex; // 专门保护 map 的互斥锁
|
||||
std::map<std::string, std::vector<KeyUpdateCallback>> m_key_callbacks;
|
||||
};
|
||||
|
||||
/*
|
||||
回调的示例
|
||||
config.monitorKey("log_level", [](const json& new_val) {
|
||||
std::string level = new_val.get<std::string>();
|
||||
spdlog::set_level(spdlog::level::from_str(level));
|
||||
spdlog::warn("System Log Level changed to: {}", level);
|
||||
});
|
||||
*/
|
||||
Loading…
Reference in New Issue