/** Logging and debugging routines * * @author Steffen Vogel <post@steffenvogel.de> * @copyright 2014-2022, Institute for Automation of Complex Power Systems, EONERC * @license Apache License 2.0 *********************************************************************************/ #include <list> #include <algorithm> #include <map> #include <fnmatch.h> #include <spdlog/sinks/syslog_sink.h> #include <spdlog/sinks/basic_file_sink.h> #include <villas/log.hpp> #include <villas/terminal.hpp> #include <villas/exceptions.hpp> using namespace villas; // The global log instance Log villas::logging; static std::map<spdlog::level::level_enum, std::string> levelNames = { { spdlog::level::trace, "trc" }, { spdlog::level::debug, "dbg" }, { spdlog::level::info, "info" }, { spdlog::level::warn, "warn" }, { spdlog::level::err, "err" }, { spdlog::level::critical, "crit" }, { spdlog::level::off, "off" } }; class CustomLevelFlag : public spdlog::custom_flag_formatter { public: void format(const spdlog::details::log_msg &msg, const std::tm &, spdlog::memory_buf_t &dest) override { auto lvl = levelNames[msg.level]; auto lvlpad = std::string(padinfo_.width_ - lvl.size(), ' ') + lvl; dest.append(lvlpad.data(), lvlpad.data() + lvlpad.size()); } spdlog::details::padding_info get_padding_info() { return padinfo_; } std::unique_ptr<custom_flag_formatter> clone() const override { return spdlog::details::make_unique<CustomLevelFlag>(); } }; Log::Log(Level lvl) : level(lvl), pattern("%H:%M:%S %^%-4t%$ %-16n %v") { char *p = getenv("VILLAS_LOG_PREFIX"); sinks = std::make_shared<DistSink::element_type>(); setLevel(level); setFormatter(pattern, p ? p : ""); // Default sink sink = std::make_shared<spdlog::sinks::stderr_color_sink_mt>(); sinks->add_sink(sink); } int Log::getWidth() { int width = Terminal::getCols() - 50; if (!prefix.empty()) width -= prefix.length(); return width; } Logger Log::get(const std::string &name) { Logger logger = spdlog::get(name); if (not logger) { logger = std::make_shared<Logger::element_type>(name, sinks); logger->set_level(level); logger->set_formatter(formatter->clone()); for (auto &expr : expressions) { int flags = 0; #ifdef FNM_EXTMATCH // musl-libc doesnt support this flag yet flags |= FNM_EXTMATCH; #endif if (!fnmatch(expr.name.c_str(), name.c_str(), flags)) logger->set_level(expr.level); } spdlog::register_logger(logger); } return logger; } void Log::parse(json_t *json) { const char *level = nullptr; const char *path = nullptr; const char *pattern = nullptr; int ret, syslog = 0; json_error_t err; json_t *json_expressions = nullptr; ret = json_unpack_ex(json, &err, JSON_STRICT, "{ s?: s, s?: s, s?: o, s?: b, s?: s }", "level", &level, "file", &path, "expressions", &json_expressions, "syslog", &syslog, "pattern", &pattern ); if (ret) throw ConfigError(json, err, "node-config-logging"); if (level) setLevel(level); if (path) { auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path); sinks->add_sink(sink); } if (syslog) { #if SPDLOG_VERSION >= 10400 auto sink = std::make_shared<spdlog::sinks::syslog_sink_mt>("villas", LOG_PID, LOG_DAEMON, true); #else auto sink = std::make_shared<spdlog::sinks::syslog_sink_mt>("villas", LOG_PID, LOG_DAEMON); #endif sinks->add_sink(sink); } if (json_expressions) { if (!json_is_array(json_expressions)) throw ConfigError(json_expressions, "node-config-logging-expressions", "The 'expressions' setting must be a list of objects."); size_t i; json_t *json_expression; // cppcheck-suppress unknownMacro json_array_foreach(json_expressions, i, json_expression) expressions.emplace_back(json_expression); } } void Log::setFormatter(const std::string &pat, const std::string &pfx) { pattern = pat; prefix = pfx; formatter = std::make_shared<spdlog::pattern_formatter>(spdlog::pattern_time_type::utc); formatter->add_flag<CustomLevelFlag>('t'); formatter->set_pattern(prefix + pattern); sinks->set_formatter(formatter->clone()); } void Log::setLevel(Level lvl) { level = lvl; sinks->set_level(lvl); } void Log::setLevel(const std::string &lvl) { auto l = SPDLOG_LEVEL_NAMES; auto it = std::find(l.begin(), l.end(), lvl); if (it == l.end()) throw RuntimeError("Invalid log level {}", lvl); setLevel(spdlog::level::from_str(lvl)); } Log::Level Log::getLevel() const { return level; } std::string Log::getLevelName() const { auto sv = spdlog::level::to_string_view(level); return std::string(sv.data()); } Log::Expression::Expression(json_t *json) { int ret; const char *nme; const char *lvl; json_error_t err; ret = json_unpack_ex(json, &err, JSON_STRICT, "{ s: s, s: s }", "name", &nme, "level", &lvl ); if (ret) throw ConfigError(json, err, "node-config-logging-expressions"); level = spdlog::level::from_str(lvl); name = nme; }