#pragma once #include #include #include namespace fgc { enum class LogLevel { Trace = 0, Debug, Info, Warn, Error, Off }; // Wire-tracing categories. Orthogonal to LogLevel: a category emits verbatim // TX/RX lines and is toggled independently of the linear level, so you can // watch e.g. all serial traffic without turning everything else to Trace. // Values are a bitmask (multiple categories enabled at once). enum class LogCat : unsigned { None = 0, Serial = 1u << 0, Mqtt = 1u << 1, Camera = 1u << 2, Control = 1u << 3, All = Serial | Mqtt | Camera | Control, }; // Minimal leveled, thread-safe logger. Each log line is assembled in a // per-statement buffer and written atomically under a shared mutex, so lines // from different threads never interleave. Level filtering is global. // // Usage: LOG_INFO << "starting, rate=" << rate; // Lines at Warn/Error go to stderr; everything else to stdout. class Logger { public: static void setLevel(LogLevel level); static LogLevel level(); static bool enabled(LogLevel level); // Parse "trace"|"debug"|"info"|"warn"|"error"|"off" (case-insensitive). // Returns false (and leaves the level unchanged) on an unknown string. static bool setLevelFromString(const std::string& s); // Wire-trace categories (independent of the linear level above). static void setCategories(unsigned mask); static unsigned categories(); static void enableCategory(LogCat cat); static void disableCategory(LogCat cat); static bool categoryEnabled(LogCat cat); // Parse a comma list: "serial,mqtt" | "all" | "none" (case-insensitive, // whitespace-trimmed). Recognized tokens always apply; an unknown token // sets *ok=false (when ok != nullptr) but does not discard the rest. static unsigned parseCategories(const std::string& csv, bool* ok = nullptr); // Map a single category name to its enum value. Unknown => LogCat::None and // *ok=false (when ok != nullptr). static LogCat catFromString(const std::string& name, bool* ok = nullptr); }; // RAII helper that buffers one log line and flushes it on commit(). // Designed to be driven by the LOG_* macros' for-loop guard. class LogStream { public: explicit LogStream(LogLevel level); LogStream(LogLevel level, LogCat cat); // category (wire-trace) line bool pending() const { return enabled_ && !done_; } void commit(); std::ostream& stream() { return buffer_; } private: LogLevel level_; LogCat cat_ = LogCat::None; // None => gated by level only bool enabled_; bool done_ = false; std::ostringstream buffer_; }; } // namespace fgc // Brace-safe: the whole macro is a single for-statement, so it composes // correctly inside if/else without dangling-else hazards, and the message // expression is never evaluated when the level is disabled. #define FGC_LOG_AT(level) \ for (::fgc::LogStream _fgc_ls(level); _fgc_ls.pending(); _fgc_ls.commit()) \ _fgc_ls.stream() #define LOG_TRACE FGC_LOG_AT(::fgc::LogLevel::Trace) #define LOG_DEBUG FGC_LOG_AT(::fgc::LogLevel::Debug) #define LOG_INFO FGC_LOG_AT(::fgc::LogLevel::Info) #define LOG_WARN FGC_LOG_AT(::fgc::LogLevel::Warn) #define LOG_ERROR FGC_LOG_AT(::fgc::LogLevel::Error) // Verbatim wire-trace line for one category. Prints when the category is // enabled (independent of the linear level, except level Off silences all). // The message expression is not evaluated when the category is off. #define LOG_TRACE_CAT(cat) \ for (::fgc::LogStream _fgc_ls(::fgc::LogLevel::Trace, (cat)); _fgc_ls.pending(); \ _fgc_ls.commit()) \ _fgc_ls.stream()