#include "fgc/Logger.h" #include #include #include #include #include #include namespace fgc { namespace { std::atomic g_level{LogLevel::Info}; std::atomic g_cats{0}; // every wire-trace category OFF by default std::mutex g_mutex; Logger::LogSink g_sink; // empty => default stdout/stderr writer (below) // Default writer: the stdout/stderr split this logger has always used. void writeDefault(LogLevel level, const std::string& line) { std::ostream& out = (level >= LogLevel::Warn) ? std::cerr : std::cout; out << line << '\n'; out.flush(); } const char* tag(LogLevel l) { switch (l) { case LogLevel::Trace: return "TRACE"; case LogLevel::Debug: return "DEBUG"; case LogLevel::Info: return "INFO "; case LogLevel::Warn: return "WARN "; case LogLevel::Error: return "ERROR"; case LogLevel::Off: return "OFF "; } return "?????"; } const char* catTag(LogCat c) { switch (c) { case LogCat::Serial: return "SERIAL"; case LogCat::Mqtt: return "MQTT"; case LogCat::Camera: return "CAMERA"; case LogCat::Control: return "CONTROL"; default: return "TRACE"; } } std::string lower(const std::string& s) { std::string v; for (char c : s) v += static_cast(std::tolower(static_cast(c))); return v; } // Trim leading/trailing ASCII whitespace. std::string trim(const std::string& s) { size_t b = s.find_first_not_of(" \t\r\n"); if (b == std::string::npos) return ""; size_t e = s.find_last_not_of(" \t\r\n"); return s.substr(b, e - b + 1); } std::string timestamp() { using namespace std::chrono; auto now = system_clock::now(); auto t = system_clock::to_time_t(now); auto ms = duration_cast(now.time_since_epoch()) % 1000; std::tm tm{}; localtime_r(&t, &tm); char buf[32]; std::snprintf(buf, sizeof(buf), "%02d:%02d:%02d.%03d", tm.tm_hour, tm.tm_min, tm.tm_sec, static_cast(ms.count())); return buf; } } // namespace void Logger::setSink(LogSink sink) { std::lock_guard lock(g_mutex); g_sink = std::move(sink); } void Logger::setLevel(LogLevel level) { g_level.store(level); } LogLevel Logger::level() { return g_level.load(); } bool Logger::enabled(LogLevel level) { return level >= g_level.load(); } bool Logger::setLevelFromString(const std::string& s) { std::string v = lower(s); if (v == "trace") { setLevel(LogLevel::Trace); return true; } if (v == "debug") { setLevel(LogLevel::Debug); return true; } if (v == "info") { setLevel(LogLevel::Info); return true; } if (v == "warn" || v == "warning") { setLevel(LogLevel::Warn); return true; } if (v == "error") { setLevel(LogLevel::Error); return true; } if (v == "off" || v == "none") { setLevel(LogLevel::Off); return true; } return false; } void Logger::setCategories(unsigned mask) { g_cats.store(mask); } unsigned Logger::categories() { return g_cats.load(); } void Logger::enableCategory(LogCat cat) { g_cats.fetch_or(static_cast(cat)); } void Logger::disableCategory(LogCat cat) { g_cats.fetch_and(~static_cast(cat)); } bool Logger::categoryEnabled(LogCat cat) { return (g_cats.load() & static_cast(cat)) != 0; } LogCat Logger::catFromString(const std::string& name, bool* ok) { std::string v = lower(trim(name)); if (ok) *ok = true; if (v == "serial") return LogCat::Serial; if (v == "mqtt") return LogCat::Mqtt; if (v == "camera") return LogCat::Camera; if (v == "control") return LogCat::Control; if (v == "all") return LogCat::All; if (v == "none" || v.empty()) return LogCat::None; if (ok) *ok = false; return LogCat::None; } unsigned Logger::parseCategories(const std::string& csv, bool* ok) { if (ok) *ok = true; unsigned mask = 0; size_t start = 0; while (start <= csv.size()) { size_t comma = csv.find(',', start); std::string token = csv.substr(start, comma == std::string::npos ? std::string::npos : comma - start); std::string t = trim(token); if (!t.empty()) { bool tok_ok = true; mask |= static_cast(catFromString(t, &tok_ok)); if (!tok_ok && ok) *ok = false; } if (comma == std::string::npos) break; start = comma + 1; } return mask; } LogStream::LogStream(LogLevel level) : level_(level), enabled_(Logger::enabled(level)) {} LogStream::LogStream(LogLevel level, LogCat cat) : level_(level), cat_(cat), enabled_(Logger::categoryEnabled(cat) && Logger::level() != LogLevel::Off) {} void LogStream::commit() { done_ = true; if (!enabled_) return; const char* label = (cat_ != LogCat::None) ? catTag(cat_) : tag(level_); std::string line = timestamp() + " [" + label + "] " + buffer_.str(); std::lock_guard lock(g_mutex); if (g_sink) g_sink(level_, line); else writeDefault(level_, line); } } // namespace fgc