fwt_software/src/core/Logger.cpp

157 lines
5.1 KiB
C++

#include "fgc/Logger.h"
#include <atomic>
#include <chrono>
#include <cstdio>
#include <ctime>
#include <iostream>
#include <mutex>
namespace fgc {
namespace {
std::atomic<LogLevel> g_level{LogLevel::Info};
std::atomic<unsigned> 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<char>(std::tolower(static_cast<unsigned char>(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<milliseconds>(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<int>(ms.count()));
return buf;
}
} // namespace
void Logger::setSink(LogSink sink) {
std::lock_guard<std::mutex> 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<unsigned>(cat)); }
void Logger::disableCategory(LogCat cat) { g_cats.fetch_and(~static_cast<unsigned>(cat)); }
bool Logger::categoryEnabled(LogCat cat) {
return (g_cats.load() & static_cast<unsigned>(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<unsigned>(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<std::mutex> lock(g_mutex);
if (g_sink) g_sink(level_, line);
else writeDefault(level_, line);
}
} // namespace fgc