diff --git a/broker/core/inc/com/centreon/broker/broker_impl.hh b/broker/core/inc/com/centreon/broker/broker_impl.hh index 06ea8cc3f16..af7785c8f81 100644 --- a/broker/core/inc/com/centreon/broker/broker_impl.hh +++ b/broker/core/inc/com/centreon/broker/broker_impl.hh @@ -88,6 +88,13 @@ class broker_impl final : public Broker::Service { __attribute__((unused)), ProcessingStats* response) override; + grpc::Status GetLogInfo(grpc::ServerContext* context [[maybe_unused]], + const GenericString* request, + LogInfo* response) override; + grpc::Status SetLogParam(grpc::ServerContext* context [[maybe_unused]], + const LogParam* request, + ::google::protobuf::Empty*) override; + public: void set_broker_name(const std::string& s); }; diff --git a/broker/core/inc/com/centreon/broker/log_v2.hh b/broker/core/inc/com/centreon/broker/log_v2.hh index ce17c6aa627..e70dc76341a 100644 --- a/broker/core/inc/com/centreon/broker/log_v2.hh +++ b/broker/core/inc/com/centreon/broker/log_v2.hh @@ -23,11 +23,11 @@ #include "com/centreon/broker/config/state.hh" #include "com/centreon/broker/namespace.hh" +#include "com/centreon/engine/log_v2_base.hh" CCB_BEGIN() -class log_v2 : public std::enable_shared_from_this { - std::string _log_name; +class log_v2 : public com::centreon::engine::log_v2_base { enum logger { log_bam, log_bbdo, @@ -53,7 +53,6 @@ class log_v2 : public std::enable_shared_from_this { std::mutex _load_m; asio::system_timer _flush_timer; - std::chrono::seconds _flush_interval; std::mutex _flush_timer_m; bool _flush_timer_active; std::shared_ptr _io_context; @@ -70,13 +69,17 @@ class log_v2 : public std::enable_shared_from_this { public: ~log_v2(); + std::shared_ptr shared_from_this() { + return std::static_pointer_cast( + com::centreon::engine::log_v2_base::shared_from_this()); + } + void stop_flush_timer(); static void load(const std::shared_ptr& io_context); static log_v2& instance(); void apply(const config::state& conf); - const std::string& log_name() const; static inline std::shared_ptr bam() { return get_logger(log_bam, "bam"); @@ -148,6 +151,8 @@ class log_v2 : public std::enable_shared_from_this { static bool contains_logger(const std::string& logger); static bool contains_level(const std::string& level); + std::vector> levels() const; + void set_level(const std::string& logger, const std::string& level); }; CCB_END(); diff --git a/broker/core/src/broker.proto b/broker/core/src/broker.proto index b9ccb073dcd..1df4797fb06 100644 --- a/broker/core/src/broker.proto +++ b/broker/core/src/broker.proto @@ -31,6 +31,24 @@ service Broker { */ rpc RebuildRRDGraphs(IndexIds) returns (google.protobuf.Empty) {} rpc RemoveGraphs(ToRemove) returns (google.protobuf.Empty) {} + /** + * @brief Retrieve some informations about loggers. If a name is specified, + * Informations are concentrated on the logger of that name. + * + * @param A logger name. + * + * @return A LogInfo message. + */ + rpc GetLogInfo(GenericString) returns (LogInfo) {} + + /** + * @brief Set a param of loggers. + * + * @param A message with a logger and a level as strings. + * + * @return nothing. + */ + rpc SetLogParam(LogParam) returns (google.protobuf.Empty) {} } message Version { @@ -39,6 +57,25 @@ message Version { int32 patch = 3; } +message LogInfo { + string log_name = 1; + string log_file = 2; + uint32 log_flush_period = 3; + map level = 4; +} + + +message LogParam { + enum LogParamType { + NONE =0; + FLUSH_PERIOD = 1; + LOG_LEVEL = 2; + } + LogParamType param = 1; + string name = 2; + string value = 3; +} + message GenericString { string str_arg = 1; } diff --git a/broker/core/src/broker_impl.cc b/broker/core/src/broker_impl.cc index d166f4e44d3..63fd01f027e 100644 --- a/broker/core/src/broker_impl.cc +++ b/broker/core/src/broker_impl.cc @@ -261,3 +261,67 @@ grpc::Status broker_impl::GetProcessingStats( stats::center::instance().get_processing_stats(response); return grpc::Status::OK; } + +grpc::Status broker_impl::GetLogInfo(grpc::ServerContext* context + [[maybe_unused]], + const GenericString* request, + LogInfo* response) { + auto& name{request->str_arg()}; + auto& map = *response->mutable_level(); + auto lvs = log_v2::instance().levels(); + response->set_log_name(log_v2::instance().log_name()); + response->set_log_file(log_v2::instance().file_path()); + response->set_log_flush_period( + log_v2::instance().get_flush_interval().count()); + if (!name.empty()) { + auto found = std::find_if(lvs.begin(), lvs.end(), + [&name](std::pair& p) { + return p.first == name; + }); + if (found != lvs.end()) { + map[name] = std::move(found->second); + return grpc::Status::OK; + } else { + std::string msg{fmt::format("'{}' is not a logger in broker", name)}; + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, msg); + } + } else { + for (auto& p : lvs) + map[p.first] = p.second; + return grpc::Status::OK; + } +} + +grpc::Status broker_impl::SetLogParam(grpc::ServerContext* context + [[maybe_unused]], + const LogParam* request, + ::google::protobuf::Empty*) { + switch (request->param()) { + case LogParam::LogParamType::LogParam_LogParamType_FLUSH_PERIOD: { + unsigned new_interval; + if (!absl::SimpleAtoi(request->value(), &new_interval)) { + return grpc::Status( + grpc::StatusCode::INVALID_ARGUMENT, + fmt::format("value must be a positive integer instead of {}", + request->value())); + } + log_v2::instance().set_flush_interval(new_interval); + break; + } + case LogParam::LogParamType::LogParam_LogParamType_LOG_LEVEL: { + const std::string& logger_name{request->name()}; + const std::string& level{request->value()}; + try { + log_v2::instance().set_level(logger_name, level); + } catch (const std::exception& e) { + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, e.what()); + } + break; + } + default: + return grpc::Status( + grpc::StatusCode::INVALID_ARGUMENT, + fmt::format("invalid ParamType:{}", request->param())); + } + return grpc::Status::OK; +} diff --git a/broker/core/src/config/applier/init.cc b/broker/core/src/config/applier/init.cc index 977dc3bf43c..8af1f474a2c 100644 --- a/broker/core/src/config/applier/init.cc +++ b/broker/core/src/config/applier/init.cc @@ -58,7 +58,7 @@ extern bool g_io_context_started; void config::applier::init(size_t n_thread, const std::string&) { // Load singletons. pool::load(g_io_context, n_thread); - g_io_context_started = n_thread > 0; + g_io_context_started = true; stats::center::load(); mysql_manager::load(); config::applier::state::load(); diff --git a/broker/core/src/log_v2.cc b/broker/core/src/log_v2.cc index cff3bad212c..ec06c8798f8 100644 --- a/broker/core/src/log_v2.cc +++ b/broker/core/src/log_v2.cc @@ -40,14 +40,15 @@ log_v2& log_v2::instance() { } log_v2::log_v2(const std::shared_ptr& io_context) - : _running{false}, + : log_v2_base("broker"), + _running{false}, _flush_timer(*io_context), _flush_timer_active(true), _io_context(io_context) { auto stdout_sink = std::make_shared(); - auto create_logger = [&stdout_sink](const std::string& name) { - std::shared_ptr log = - std::make_shared(name, stdout_sink); + auto create_logger = [&](const std::string& name) { + auto log = std::make_shared( + name, this, stdout_sink); log->flush_on(level::info); spdlog::register_logger(log); return log; @@ -86,31 +87,31 @@ void log_v2::apply(const config::state& conf) { const auto& log = conf.log_conf(); - _log_name = log.log_path(); // reset loggers to null sink auto null_sink = std::make_shared(); std::shared_ptr> file_sink; + _file_path = log.log_path(); if (log.max_size) file_sink = std::make_shared( - _log_name, log.max_size, 99); + _file_path, log.max_size, 99); else - file_sink = std::make_shared(_log_name); + file_sink = std::make_shared(_file_path); - auto create_log = [&file_sink, flush_period = log.flush_period]( - const std::string& name, level::level_enum lvl) { + auto create_log = [&](const std::string& name, level::level_enum lvl) { spdlog::drop(name); - auto log = std::make_shared(name, file_sink); - log->set_level(lvl); + auto logger = std::make_shared( + name, this, file_sink); + logger->set_level(lvl); if (lvl != level::off) { - if (flush_period) - log->flush_on(level::warn); + if (log.flush_period) + logger->flush_on(level::warn); else - log->flush_on(lvl); - log->set_pattern("[%Y-%m-%dT%H:%M:%S.%e%z] [%n] [%l] %v"); + logger->flush_on(lvl); + logger->set_pattern("[%Y-%m-%dT%H:%M:%S.%e%z] [%n] [%l] %v"); } - spdlog::register_logger(log); - return log; + spdlog::register_logger(logger); + return logger; }; _log[log_v2::log_core] = create_log("core", level::info); @@ -195,6 +196,25 @@ bool log_v2::contains_logger(const std::string& logger) { return std::find(loggers.begin(), loggers.end(), logger) != loggers.end(); } +/** + * @brief Accessor to the various levels of loggers + * + * @return A vector of pairs of strings. The first string is the logger name and + * the second string is its level. + */ +std::vector> log_v2::levels() const { + std::vector> retval; + if (_running) { + retval.reserve(_log.size()); + for (auto& l : _log) { + spdlog::level::level_enum level = l->level(); + auto& lv = to_string_view(level); + retval.emplace_back(l->name(), std::string(lv.data(), lv.size())); + } + } + return retval; +} + /** * @brief this private static method is used to access a specific logger * @@ -228,6 +248,30 @@ bool log_v2::contains_level(const std::string& level) { return l != level::off; } -const std::string& log_v2::log_name() const { - return _log_name; +/** + * @brief Set the level of a logger. + * + * @param logger The logger name + * @param level The level as a string + */ +void log_v2::set_level(const std::string& logger, const std::string& level) { + if (_running) { + bool found = false; + for (auto l : _log) { + if (l->name() == logger) { + found = true; + level::level_enum lvl = level::from_str(level); + if (lvl == level::off && level != "off") + throw msg_fmt("The '{}' level is unknown", level); + l->set_level(lvl); + break; + } + } + if (!found) + throw msg_fmt("The '{}' logger does not exist", logger); + } else + throw msg_fmt( + "Unable to change '{}' logger level, the logger is not running for now " + "- try later.", + logger); } diff --git a/ccc/client.cc b/ccc/client.cc index 7fef74b7bd7..085d39f9159 100644 --- a/ccc/client.cc +++ b/ccc/client.cc @@ -196,6 +196,11 @@ std::string client::call(const std::string& cmd, const std::string& args) { void* tag; bool ok = false; _cq.Next(&tag, &ok); + + if (!status_res.ok()) + throw com::centreon::exceptions::msg_fmt( + "In call of the '{}' method: {}", cmd_str, status_res.error_message()); + grpc::ProtoBufferReader reader(&resp_buf); const google::protobuf::Descriptor* output_desc = method->output_type(); diff --git a/engine/enginerpc/engine.proto b/engine/enginerpc/engine.proto index ef89ab4e846..3d19c0007b9 100644 --- a/engine/enginerpc/engine.proto +++ b/engine/enginerpc/engine.proto @@ -113,6 +113,16 @@ service Engine { rpc EnableNotifications(google.protobuf.Empty) returns (CommandSuccess) {} rpc DisableServiceNotifications(ServiceIdentifier) returns (CommandSuccess) {} rpc EnableServiceNotifications(ServiceIdentifier) returns (CommandSuccess) {} + + rpc GetLogInfo(google.protobuf.Empty) returns(LogInfo) {} + /** + * @brief Set a param of loggers. + * + * @param A message with a logger and a level as strings. + * + * @return nothing. + */ + rpc SetLogParam(LogParam) returns (google.protobuf.Empty) {} } message GenericString { @@ -534,3 +544,24 @@ message ChangeObjectCustomVar { string varname = 4; string varvalue = 5; } + +message LogInfo { + message LoggerInfo { + string log_name = 1; + string log_file = 2; + uint32 log_flush_period = 3; + map level = 4; + } + repeated LoggerInfo loggers = 1; +} + +message LogParam { + enum LogParamType { + NONE =0; + FLUSH_PERIOD = 1; + LOG_LEVEL = 2; + } + LogParamType param = 1; + string name = 2; + string value = 3; +} diff --git a/engine/enginerpc/engine_impl.cc b/engine/enginerpc/engine_impl.cc index 3b96a0476c9..6d476a625ce 100644 --- a/engine/enginerpc/engine_impl.cc +++ b/engine/enginerpc/engine_impl.cc @@ -24,8 +24,11 @@ #include +#include + #include #include +#include #include #include "com/centreon/engine/host.hh" @@ -1670,7 +1673,7 @@ grpc::Status engine_impl::DeleteHostDowntimeFull( CommandSuccess* response __attribute__((unused))) { std::string err; auto fn = std::packaged_task([&err, request]() -> int32_t { - std::list > dtlist; + std::list> dtlist; for (auto it = downtimes::downtime_manager::instance() .get_scheduled_downtimes() .begin(), @@ -3232,3 +3235,116 @@ engine_impl::get_serv( } } } + +/** + * @brief get log levels and infos + * + * @param context + * @param request + * @param response + * @return ::grpc::Status + */ +::grpc::Status engine_impl::GetLogInfo( + ::grpc::ServerContext* context, + const ::google::protobuf::Empty* request, + ::com::centreon::engine::LogInfo* response) { + using logger_by_log = + std::map>>; + + logger_by_log summary; + + spdlog::apply_all( + [&summary](const std::shared_ptr& logger_base) { + std::shared_ptr logger = + std::dynamic_pointer_cast(logger_base); + if (logger) { + summary[logger->get_parent()].push_back(logger); + } + }); + + for (const auto& by_parent_loggers : summary) { + LogInfo_LoggerInfo* loggers = response->add_loggers(); + loggers->set_log_name(by_parent_loggers.first->log_name()); + loggers->set_log_file(by_parent_loggers.first->file_path()); + loggers->set_log_flush_period( + by_parent_loggers.first->get_flush_interval().count()); + auto& levels = *loggers->mutable_level(); + for (const std::shared_ptr& logger : + by_parent_loggers.second) { + auto level = spdlog::level::to_string_view(logger->level()); + levels[logger->name()] = std::string(level.data(), level.size()); + } + } + return grpc::Status::OK; +} + +/** + * @brief set log levels and other + * + * @param context + * @param request + * @param response contain string returned to grpc client + * @return ::grpc::Status + */ + +::grpc::Status engine_impl::SetLogParam( + ::grpc::ServerContext* context, + const ::com::centreon::engine::LogParam* request, + ::google::protobuf::Empty* response) { + std::string err_detail; + switch (request->param()) { + case LogParam_LogParamType_FLUSH_PERIOD: { + std::shared_ptr search; + std::set available_loggers; + spdlog::apply_all([&search, &available_loggers, request]( + const std::shared_ptr logger) { + if (!search) { + std::shared_ptr test = + std::dynamic_pointer_cast(logger); + if (test) { + if (test->get_parent()->log_name() == request->name()) { + search = test; + } else { + available_loggers.insert(test->get_parent()->log_name()); + } + } + } + }); + if (!search) { + err_detail = + fmt::format("unknow logger name:{}, available loggers:{}", + request->name(), absl::StrJoin(available_loggers, " ")); + log_v2::external_command()->error(err_detail); + return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, err_detail); + } + unsigned new_interval; + if (!absl::SimpleAtoi(request->value(), &new_interval)) { + err_detail = fmt::format( + "value must be a positive integer instead of {}", request->value()); + log_v2::external_command()->error(err_detail); + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, err_detail); + } + search->get_parent()->set_flush_interval(new_interval); + return grpc::Status::OK; + } + case LogParam_LogParamType_LOG_LEVEL: { + std::shared_ptr logger = spdlog::get(request->name()); + if (!logger) { + err_detail = fmt::format("unknow logger:{}", request->name()); + log_v2::external_command()->error(err_detail); + return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, err_detail); + } else { + spdlog::level::level_enum lvl = + spdlog::level::from_str(request->value()); + if (lvl == spdlog::level::off && request->value() != "off") { + err_detail = fmt::format("unknow level:{}", request->value()); + log_v2::external_command()->error(err_detail); + return grpc::Status(::grpc::StatusCode::INVALID_ARGUMENT, err_detail); + } + logger->set_level(lvl); + return grpc::Status::OK; + } + } + } + return grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "unknown param"); +} diff --git a/engine/inc/com/centreon/engine/engine_impl.hh b/engine/inc/com/centreon/engine/engine_impl.hh index 3e3f19b0aeb..cf8dc4bcea3 100644 --- a/engine/inc/com/centreon/engine/engine_impl.hh +++ b/engine/inc/com/centreon/engine/engine_impl.hh @@ -234,6 +234,16 @@ class engine_impl final : public Engine::Service { static std::pair, std::string /*error*/> get_serv(const ::com::centreon::engine::ServiceIdentifier& serv_info); + + ::grpc::Status GetLogInfo( + ::grpc::ServerContext* context, + const ::google::protobuf::Empty* request, + ::com::centreon::engine::LogInfo* response) override; + + virtual ::grpc::Status SetLogParam( + ::grpc::ServerContext* context, + const ::com::centreon::engine::LogParam* request, + ::google::protobuf::Empty* response) override; }; CCE_END() diff --git a/engine/inc/com/centreon/engine/log_v2.hh b/engine/inc/com/centreon/engine/log_v2.hh index 8dce855c869..c438d57c97e 100644 --- a/engine/inc/com/centreon/engine/log_v2.hh +++ b/engine/inc/com/centreon/engine/log_v2.hh @@ -23,14 +23,13 @@ #include #include "com/centreon/engine/configuration/state.hh" +#include "log_v2_base.hh" CCE_BEGIN() -class log_v2 : public std::enable_shared_from_this { - std::string _log_name; +class log_v2 : public log_v2_base { std::array, 13> _log; std::atomic_bool _running; asio::system_timer _flush_timer; - std::chrono::seconds _flush_interval; std::mutex _flush_timer_m; bool _flush_timer_active; std::shared_ptr _io_context; @@ -63,6 +62,10 @@ class log_v2 : public std::enable_shared_from_this { public: ~log_v2() noexcept; + std::shared_ptr shared_from_this() { + return std::static_pointer_cast(log_v2_base::shared_from_this()); + } + void stop_flush_timer(); void apply(const configuration::state& config); static bool contains_level(const std::string& level_name); diff --git a/engine/inc/com/centreon/engine/log_v2_base.hh b/engine/inc/com/centreon/engine/log_v2_base.hh new file mode 100644 index 00000000000..eb54a58a20f --- /dev/null +++ b/engine/inc/com/centreon/engine/log_v2_base.hh @@ -0,0 +1,66 @@ +/* +** Copyright 2022 Centreon +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +** +** For more information : contact@centreon.com +*/ +#ifndef CCE_LOG_V2_BASE_HH +#define CCE_LOG_V2_BASE_HH + +#include +#include +#include + +#include "namespace.hh" + +CCE_BEGIN() + +class log_v2_base : public std::enable_shared_from_this { + protected: + std::string _log_name; + std::chrono::seconds _flush_interval; + std::string _file_path; + + public: + log_v2_base(const std::string& logger_name) : _log_name(logger_name) {} + + const std::string& log_name() { return _log_name; } + const std::string& file_path() const { return _file_path; } + + std::chrono::seconds get_flush_interval() const { return _flush_interval; } + void set_flush_interval(unsigned second_flush_interval) { + _flush_interval = std::chrono::seconds(second_flush_interval); + } +}; + +class log_v2_logger : public spdlog::logger { + log_v2_base* _parent; + + public: + template + log_v2_logger(std::string name, + log_v2_base* parent, + sink_iter begin, + sink_iter end) + : spdlog::logger(name, begin, end), _parent(parent) {} + + log_v2_logger(std::string name, log_v2_base* parent, spdlog::sink_ptr sink) + : spdlog::logger(name, sink), _parent(parent) {} + + log_v2_base* get_parent() { return _parent; } +}; + +CCE_END() + +#endif \ No newline at end of file diff --git a/engine/src/log_v2.cc b/engine/src/log_v2.cc index 4fab1328ac5..68ea66b4742 100644 --- a/engine/src/log_v2.cc +++ b/engine/src/log_v2.cc @@ -38,15 +38,15 @@ log_v2& log_v2::instance() { } log_v2::log_v2(const std::shared_ptr& io_context) - : _running{false}, + : log_v2_base("engine"), + _running{false}, _flush_timer(*io_context), _flush_timer_active(true), _io_context(io_context) { auto stdout_sink = std::make_shared(); - auto create_logger = [&stdout_sink](const std::string& name, - level::level_enum lvl) { + auto create_logger = [&](const std::string& name, level::level_enum lvl) { spdlog::drop(name); - auto log = std::make_shared(name, stdout_sink); + auto log = std::make_shared(name, this, stdout_sink); log->set_level(lvl); log->flush_on(lvl); log->set_pattern("[%Y-%m-%dT%H:%M:%S.%e%z] [%n] [%l] %v"); @@ -76,6 +76,9 @@ log_v2::log_v2(const std::shared_ptr& io_context) _log[log_v2::log_process] = create_logger("process", level::from_str("info")); _log[log_v2::log_runtime] = create_logger("runtime", level::from_str("error")); + + _log[log_v2::log_process]->info("{} : log started", _log_name); + _running = true; } @@ -96,8 +99,8 @@ void log_v2::apply(const configuration::state& config) { if (config.log_v2_enabled()) { if (config.log_v2_logger() == "file") { if (config.log_file() != "") { - sink_to_flush = - std::make_shared(config.log_file()); + _file_path = config.log_file(); + sink_to_flush = std::make_shared(_file_path); } else { log_v2::config()->error("log_file name is empty"); sink_to_flush = std::make_shared(); @@ -105,33 +108,33 @@ void log_v2::apply(const configuration::state& config) { } else if (config.log_v2_logger() == "syslog") sink_to_flush = std::make_shared("centreon-engine", 0, 0, true); - sinks.push_back(sink_to_flush); + if (sink_to_flush) { + sinks.push_back(sink_to_flush); + } auto broker_sink = std::make_shared(); broker_sink->set_level(spdlog::level::info); sinks.push_back(broker_sink); } else sinks.push_back(std::make_shared()); - auto create_logger = [&sinks, log_pid = config.log_pid(), - log_file_line = config.log_file_line(), - log_flush_period = config.log_flush_period()]( - const std::string& name, level::level_enum lvl) { + auto create_logger = [&](const std::string& name, level::level_enum lvl) { spdlog::drop(name); - auto log = std::make_shared(name, begin(sinks), end(sinks)); + auto log = + std::make_shared(name, this, begin(sinks), end(sinks)); log->set_level(lvl); - if (log_flush_period) + if (config.log_flush_period()) log->flush_on(level::warn); else log->flush_on(lvl); - if (log_pid) { - if (log_file_line) { + if (config.log_pid()) { + if (config.log_file_line()) { log->set_pattern("[%Y-%m-%dT%H:%M:%S.%e%z] [%s:%#] [%n] [%l] [%P] %v"); } else { log->set_pattern("[%Y-%m-%dT%H:%M:%S.%e%z] [%n] [%l] [%P] %v"); } } else { - if (log_file_line) { + if (config.log_file_line()) { log->set_pattern("[%Y-%m-%dT%H:%M:%S.%e%z] [%s:%#] [%n] [%l] %v"); } else { log->set_pattern("[%Y-%m-%dT%H:%M:%S.%e%z] [%n] [%l] %v");