fwt_software/include/fgc/Logger.h

97 lines
3.9 KiB
C++

#pragma once
#include <ostream>
#include <sstream>
#include <string>
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()