// // Created by zhaohe on 19-5-31. // #include "logger_factory.hpp" #include #include #include #include #include #include #include "default_logger_config.hpp" #include "nlohmann/json.hpp" #include "spdlog/sinks/basic_file_sink.h" #include "spdlog/sinks/daily_file_sink.h" #include "spdlog/sinks/rotating_file_sink.h" #include "spdlog/sinks/stdout_sinks.h" // #include "zwtimecpp/core/utils/compliler.h" using namespace std; using namespace iflytop; using namespace core; using namespace nlohmann; using namespace spdlog; const static char* kRootLogerName = "root"; // const static char* kSpdDefaultConfigPaths[] = {"spd_logger_cfg.json"}; const static char* kDefaultPattern = "[%C-%m-%d %H:%M:%S.%e] [%-30n] [%^%L%$] %v"; // const static string kDefaultPattern = ""; // const string WEAK spdLoggerConfig() { return ""; } // const static string kDefaultLoggerBeforeConfig = "DefaultLoggerBeforeConfig"; // { // "name": "LoggerName", // "level": 2, // "type":"daily_logger_mt", // "filename":"fileName", // "hour":0, // "minute":0, // "truncate":false // } // { // "name": "LoggerName", // "level": 2, // "type":"rotating_logger_mt", // "filename":"fileName", // "max_file_size":1000, // "max_files":100, // "rotate_on_open":true // } // Rotate files: // log.txt -> log.1.txt // log.1.txt -> log.2.txt // log.2.txt -> log.3.txt // log.3.txt -> delete // 1. 没设置type,统统被当做logger处理 // 2. 所有的logger,sink loggerAndSink,name都不能重复 // 3. /** * @brief *loggerSupportList: * *-->: daily_logger_mt *-->: basic_logger_mt *-->: logger *-->: daily_file_sink_mt *-->: stderr_color_sink_mt *-->: stdout_color_sink_mt * * rotating_logger_mt * rotating_file_sink_mt * */ #define ASSININ_VALUE(value) \ if (config_item.key() == #value) item.at(#value).get_to(config.value); #if 0 static bool c_daily_logger_mt(json j) { try { string type = j.at("type").get(); if (type == "daily_logger_mt") { GET(string, name); GET(string, filename); mkdirIfNotExist(filename); TRY_GET(int, hour, 0); TRY_GET(int, minute, 0); TRY_GET(bool, truncate, false); auto var_logger = spdlog::daily_logger_mt(name, filename, hour, minute, truncate); logger_common_config(var_logger, j); insertLogger(var_logger); return true; } else { return false; } } catch (const std::exception& e) { spdlog::critical("c_daily_logger_mt fail {} reason {}", j.dump(1), e.what()); exit(-1); } }; #endif #if 0 static bool c_daily_file_sink_mt(json j) { try { string type = j.at("type").get(); if (type == "daily_file_sink_mt") { GET(string, name); GET(string, filename); mkdirIfNotExist(filename); TRY_GET(int, hour, 0); TRY_GET(int, minute, 0); TRY_GET(bool, truncate, false); auto sink = make_shared(filename, hour, minute, truncate); sink_common_config(sink, j); insertSink(name, sink); return true; } else { return false; } } catch (const std::exception& e) { spdlog::critical("c_daily_file_sink_mt fail {} reason {}", j.dump(1), e.what()); exit(-1); } }; #endif // 10485760 == 10M static string default_config = R"( [ { "name": "info-sink", "type": "rotating_file_sink_mt", "filename": "logs/infolog.log", "max_file_size":10485760, "max_files": 3, "rotate_on_open": false, "level" : 2 }, { "name": "debug-sink", "type": "rotating_file_sink_mt", "filename": "logs/debuglog.log", "max_file_size":10485760, "max_files": 3, "rotate_on_open": false, "level": 0 }, { "name": "terminal-sink", "type": "stdout_color_sink_mt" }, { "name": "root", "type": "logger", "level": 2, "sinks": [ "terminal-sink", "debug-sink", "info-sink" ] } ] )"; #define LOGGER_ENABLE_BEGIN(_name) \ static bool c_##_name(json j) { \ logger_t var_logger; \ try { \ string type = j.at("type").get(); \ if (type == #_name) { \ GET(string, name); #define LOGGER_ENABLE_END(name) \ logger_common_config(var_logger, j); \ insertLogger(var_logger); \ return true; \ } \ else { \ return false; \ } \ } \ catch (const std::exception& e) { \ spdlog::critical("c_{} fail {} reason {}", #name, j.dump(1), e.what()); \ exit(-1); \ } \ } \ ; #define SINK_DEFINE_BEGIN(var_name) \ static bool c_##var_name(json j) { \ sink_ptr sink; \ try { \ string type = j.at("type").get(); \ if (type == #var_name) { \ GET(string, name); #define SINK_DEFINE_END(var_name) \ sink_common_config(sink, j); \ insertSink(name, sink); \ return true; \ } \ else { \ return false; \ } \ } \ catch (const std::exception& e) { \ spdlog::critical("c_" #var_name " fail {} reason {}", j.dump(1), e.what()); \ exit(-1); \ } \ } \ ; template type tryGet(json j, string value_name, type defaultValue) { try { if (j.find(value_name) == j.end()) { return defaultValue; } type value = j.at(value_name).get(); return value; } catch (const std::exception& e) { return defaultValue; } } static bool exist(const string& path) { struct stat statInfo; if (stat(path.c_str(), &statInfo) == 0) { return true; } return false; } static bool mkdirIfNotExist(const string& path) { string::size_type sepPos = path.find_last_of("/"); if (sepPos == string::npos) { return false; } string dirPath = path.substr(0, sepPos); if (exist(dirPath)) { return true; } int ret = mkdir(dirPath.c_str(), S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); return 0 == ret ? true : false; } class LoggerAndSinks { public: string loggerName; set sinkNames; }; /****************************************************************************************************/ static map s_sinks = {}; static map> s_loggers = {}; static set> s_loggerAndSinks = {}; static void insertLogger(shared_ptr var_logger) { if (s_loggers.find(var_logger->name()) == s_loggers.end()) { s_loggers[var_logger->name()] = var_logger; } else { spdlog::critical("Add the logger {} fail", var_logger->name()); exit(-1); } }; static void insertSink(string name, sink_ptr sink) { if (s_sinks.find(name) == s_sinks.end()) { s_sinks[name] = sink; } else { spdlog::critical("Add the sink {} fail", name); exit(-1); } }; static level::level_enum to_level(int value) { switch (value) { case SPDLOG_LEVEL_TRACE: return level::trace; case SPDLOG_LEVEL_DEBUG: return level::debug; case SPDLOG_LEVEL_INFO: return level::info; case SPDLOG_LEVEL_WARN: return level::warn; case SPDLOG_LEVEL_ERROR: return level::err; case SPDLOG_LEVEL_CRITICAL: return level::critical; default: spdlog::critical("level is out of range {} level must in [{},{}]", value, SPDLOG_LEVEL_TRACE, SPDLOG_LEVEL_CRITICAL); exit(-1); break; } return level::info; } // #define GET(type,value,error_msg,...) #define TRY_GET(type, value_name, default_value) type value_name = tryGet(j, #value_name, default_value) #define GET(T, value_name) T value_name = j.at(#value_name).get(); static void logger_common_config(logger_t var_logger, json j) { TRY_GET(int, level, 2); TRY_GET(string, pattern, kDefaultPattern); TRY_GET(set, sinks, {}); var_logger->set_level(to_level(level)); if (!sinks.empty()) { shared_ptr las(new LoggerAndSinks()); las->loggerName = var_logger->name(); las->sinkNames = sinks; s_loggerAndSinks.insert(las); } } static void sink_common_config(sink_ptr sink, json j) { TRY_GET(int, level, 0); TRY_GET(string, pattern, kDefaultPattern); TRY_GET(set, sinks, {}); sink->set_level(to_level(level)); if (!pattern.empty()) sink->set_pattern(pattern); } /** * logger----------------------------------------------------------------------------------------------------- */ LOGGER_ENABLE_BEGIN(daily_logger_mt) { GET(string, filename); mkdirIfNotExist(filename); TRY_GET(int, hour, 0); TRY_GET(int, minute, 0); TRY_GET(bool, truncate, false); TRY_GET(int, max_files, 100); var_logger = spdlog::daily_logger_mt(name, filename, hour, minute, truncate, max_files); } LOGGER_ENABLE_END(daily_logger_mt) LOGGER_ENABLE_BEGIN(rotating_logger_mt) { GET(string, filename); mkdirIfNotExist(filename); TRY_GET(int, max_file_size, 10 * 1024 * 1024); TRY_GET(int, max_files, 3); TRY_GET(bool, rotate_on_open, true); var_logger = spdlog::rotating_logger_mt(name, filename, max_file_size, max_files, rotate_on_open); } LOGGER_ENABLE_END(rotating_logger_mt) LOGGER_ENABLE_BEGIN(basic_logger_mt) { GET(string, filename); mkdirIfNotExist(filename); var_logger = spdlog::basic_logger_mt(name, filename); } LOGGER_ENABLE_END(basic_logger_mt) static bool c_logger(json j) { try { string type = j.at("type").get(); if (type == "logger") { GET(string, name); GET(set, sinks); auto var_logger = make_shared(name, sinks_init_list{}); if (sinks.empty()) { spdlog::critical("c_logger fail {} reason {}", j.dump(1), "Not set sink"); exit(-1); } logger_common_config(var_logger, j); insertLogger(var_logger); return true; } else { return false; } } catch (const std::exception& e) { spdlog::critical("c_logger fail {} reason {}", j.dump(1), e.what()); exit(-1); } }; /** * sinks----------------------------------------------------------------------------------------------------- */ SINK_DEFINE_BEGIN(daily_file_sink_mt) { GET(string, filename); mkdirIfNotExist(filename); TRY_GET(int, hour, 0); TRY_GET(int, minute, 0); TRY_GET(bool, truncate, false); TRY_GET(int, max_files, 100); // printf("filename:%s,max_files %d\n", filename.c_str(), max_files); sink = make_shared(filename, hour, minute, truncate, max_files); } SINK_DEFINE_END(daily_file_sink_mt) SINK_DEFINE_BEGIN(rotating_file_sink_mt) { GET(string, filename); mkdirIfNotExist(filename); TRY_GET(int, max_file_size, 10 * 1024 * 1024); TRY_GET(int, max_files, 5); TRY_GET(bool, rotate_on_open, true); sink = make_shared(filename, max_file_size, max_files, rotate_on_open); } SINK_DEFINE_END(rotating_file_sink_mt) SINK_DEFINE_BEGIN(stdout_color_sink_mt) { sink = make_shared(); } SINK_DEFINE_END(stdout_color_sink_mt) SINK_DEFINE_BEGIN(stderr_color_sink_mt) { sink = make_shared(); } SINK_DEFINE_END(stderr_color_sink_mt) /** * default_root_logger----------------------------------------------------------------------------------------------------- */ static logger_t createRootLogger() { if (!get(kRootLogerName)) { auto rootLogger = spdlog::stdout_color_mt(kRootLogerName); if (!string(kDefaultPattern).empty()) { rootLogger->set_pattern(kDefaultPattern); } return rootLogger; } // auto stdoutsink = make_shared(); // stdoutsink->set_level(level::debug); // if (!kDefaultPattern.empty()) { // stdoutsink->set_pattern(kDefaultPattern); // } // auto rootLogger = // make_shared(kRootLogerName, sinks_init_list{stdoutsink}); // rootLogger->set_level(level::info); // rootLogger->set_pattern(kDefaultPattern); return get(kRootLogerName); } /** * @brief */ /** * @brief 当使用 daily_logger_mt时候会自动注册logger, 而使用make_shared * 则不会自动注册logger * @param var_logger */ static void myRegLogger(logger_t var_logger) { if (var_logger->name() == kRootLogerName) { spdlog::set_default_logger(var_logger); } if (!get(var_logger->name())) { register_logger(var_logger); } if (!get(var_logger->name())) { spdlog::critical("reg root logger fail {}"); exit(-1); } } static void __parseSphLogConfig(json var) { if (c_daily_logger_mt(var)) { } else if (c_rotating_logger_mt(var)) { } else if (c_basic_logger_mt(var)) { } else if (c_daily_file_sink_mt(var)) { } else if (c_logger(var)) { } else if (c_stderr_color_sink_mt(var)) { } else if (c_stdout_color_sink_mt(var)) { } else if (c_rotating_file_sink_mt(var)) { } else { spdlog::critical("no such type {}", var.dump()); exit(-1); } } static logger_t createLoggerWithoutType(json j) { TRY_GET(int, level, 2); GET(string, name); TRY_GET(string, pattern, kDefaultPattern); auto rootLogger = get(kRootLogerName); if (!rootLogger) { spdlog::critical("func: createLoggerWithoutType,can't find rootLogger"); exit(-1); } auto newLogger = rootLogger->clone(name); newLogger->set_level(to_level(level)); if (!pattern.empty()) newLogger->set_pattern(pattern); return newLogger; } /** * @brief * * 构造logger思路 * 1. 首先找到所有定义了type了的logger和sink * 2. 绑定所有的logger和对应的sink * 3. 查看是否创建rootLogger,没有则创建rootLogger * 4. 遍历所有没有定义type的logger,继承于rootLogger,并为其设置level * @param path */ void core::SpdLoggerFactory::parseSphLogConfig(string path) { try { // 这里必须清空,因为这个方法可能在main函数之前启动,所以数量可能未初始化 s_sinks.clear(); s_loggers.clear(); s_loggerAndSinks.clear(); fstream infile(path, ios::binary | ios::in); stringstream sstream; sstream << infile.rdbuf(); infile.close(); string jsonStr(sstream.str()); sstream.clear(); json configjson = json::parse(jsonStr); for (auto& j : configjson) { TRY_GET(string, type, ""); if (!type.empty()) { __parseSphLogConfig(j); } } // 组装logger and sink for (auto& las : s_loggerAndSinks) { logger_t logger = s_loggers[las->loggerName]; if (logger == nullptr) { spdlog::critical("can't find logger", las->loggerName); exit(-1); } set sinks; for (auto& sinkname : las->sinkNames) { auto result = s_sinks.find(sinkname); if (result == s_sinks.end()) { spdlog::critical("can't find sink {} ??", sinkname); exit(-1); } sinks.insert(result->second); } for (auto& sink : sinks) logger->sinks().push_back(sink); } for (auto& var : s_loggers) myRegLogger(var.second); // 如果没有rootLogger,构造rootLogger if (!get(kRootLogerName)) { for (auto& j : configjson) { GET(string, name); TRY_GET(string, type, ""); if (name == kRootLogerName) { if (type.empty()) { TRY_GET(int, level, 2); TRY_GET(string, pattern, kDefaultPattern); auto rootLogger = createRootLogger(); rootLogger->set_level(to_level(level)); if (!pattern.empty()) rootLogger->set_pattern(pattern); myRegLogger(rootLogger); } else { spdlog::critical("shouldn't go here"); exit(-1); } break; } } } // 如果依然没有构造rootLogger则构造默认logger if (!get(kRootLogerName)) myRegLogger(createRootLogger()); // 构造没有type的logger for (auto& j : configjson) { TRY_GET(string, type, ""); GET(string, name); if (type.empty() && name != kRootLogerName) { auto newlogger = createLoggerWithoutType(j); myRegLogger(newlogger); } } // spdlog::info("Logger initialize ok"); } catch (const exception& e) { spdlog::critical("parse logger config fail {}", e.what()); exit(-1); } } static string getConfigFilePath() { if (spdLoggerConfig) { if (exist(spdLoggerConfig())) { return spdLoggerConfig(); } else { spdlog::warn("can't find spdLoggerConfig file {},use deafult config", spdLoggerConfig()); } } return "spd_logger_cfg.json"; } class MonitoringSpdLoggerConfigTask { unique_ptr wthread; public: MonitoringSpdLoggerConfigTask() { wthread.reset(new thread([]() { })); } ~MonitoringSpdLoggerConfigTask() { wthread->join(); } }; void SpdLoggerFactory::initialize() { if (!initializeLogger) { string configFilePath = getConfigFilePath(); if (!configFilePath.empty() && exist(configFilePath)) { parseSphLogConfig(configFilePath); } else { spdlog::warn("can't find logger config file use default config {}", configFilePath); // 写字符串default_config到文件中configFilePath ofstream outfile(configFilePath); outfile << default_config; outfile.close(); parseSphLogConfig(configFilePath); } initializeLogger = true; } } shared_ptr SpdLoggerFactory::createLogger(string loggerName) { lock_guard lock_gu(createLogger_lock); if (!loggerName.empty()) { if (s_loggerNames.size() == 0) { s_loggerNames.insert(loggerName); } else { if (s_loggerNames.find(loggerName) == s_loggerNames.end()) { s_loggerNames.insert(loggerName); } } } // TODO:当使用gtest进行单元测试的时候,logger似乎会被清空,原因未知 if (!get(kRootLogerName)) { initializeLogger = false; if (!string(kDefaultPattern).empty()) { spdlog::set_pattern(kDefaultPattern); } } if (!initializeLogger) { string configFilePath = getConfigFilePath(); if (!configFilePath.empty() && exist(configFilePath)) { parseSphLogConfig(configFilePath); } else { spdlog::warn("can't find logger config file use default config {}", configFilePath); // 写字符串default_config到文件中configFilePath ofstream outfile(configFilePath); outfile << default_config; outfile.close(); parseSphLogConfig(configFilePath); } initializeLogger = true; } logger_t ret_logger = get(loggerName); if (ret_logger) { return ret_logger; } else { logger_t rootLogger = get(kRootLogerName); if (!rootLogger) { spdlog::critical("can't find root logger ?????"); exit(-1); } logger_t newLogger = rootLogger->clone(loggerName); myRegLogger(newLogger); newLogger->flush_on(spdlog::level::debug); return newLogger; } return nullptr; } set SpdLoggerFactory::loggerNames() { return s_loggerNames; } sink_ptr SpdLoggerFactory::getSink(string name) { auto result = s_sinks.find(name); if (result == s_sinks.end()) { return nullptr; } return result->second; } shared_ptr SpdLoggerFactory::createRotatingFileLogger(const std::string& logger_name, size_t max_file_size, size_t max_files, bool bindTerminal, bool bindDebug, bool bindInfo) { auto newlogger = spdlog::rotating_logger_mt(logger_name, fmt::format("logs/{}.log", logger_name), 5 * 1024 * 1024 /*5M*/, 3 /*times*/); newlogger->set_level(spdlog::level::info); if (bindTerminal && GET_SINK("terminal-sink")) newlogger->sinks().push_back(GET_SINK("terminal-sink")); if (bindDebug && GET_SINK("debug-sink")) newlogger->sinks().push_back(GET_SINK("debug-sink")); if (bindInfo && GET_SINK("info-sink")) newlogger->sinks().push_back(GET_SINK("info-sink")); myRegLogger(newlogger); return newlogger; }