initial commit

This commit is contained in:
NeilClack
2026-08-07 14:51:41 -05:00
commit 9251b3a196
604 changed files with 464448 additions and 0 deletions
+273
View File
@@ -0,0 +1,273 @@
#include "logger.h"
#include <algorithm>
#include <cstdlib>
#include <filesystem>
#include <iomanip>
#include <iostream>
#include <pwd.h>
#include <sstream>
#include <unistd.h>
namespace radiantwave::logger {
const char* logLevelToString(LogLevel level) {
switch (level) {
case LogLevel::Debug: return "DEBUG";
case LogLevel::Info: return "INFO";
case LogLevel::Warning: return "WARNING";
case LogLevel::Error: return "ERROR";
case LogLevel::Fatal: return "FATAL";
}
return "INFO";
}
LogLevel stringToLogLevel(const std::string& str) {
std::string s = str;
std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c){ return std::toupper(c); });
if (s == "DEBUG") return LogLevel::Debug;
if (s == "INFO") return LogLevel::Info;
if (s == "WARNING" || s == "WARN") return LogLevel::Warning;
if (s == "ERROR" || s == "ERR") return LogLevel::Error;
if (s == "FATAL") return LogLevel::Fatal;
return LogLevel::Info;
}
// ─── Singleton ─────────────────────────────────────────────────────────
Logger& Logger::instance() {
static Logger s_instance;
return s_instance;
}
// ─── Constructor & Destructor ──────────────────────────────────────────
Logger::Logger() = default;
Logger::~Logger() {
std::lock_guard<std::mutex> lock(mutex_);
if (log_file_.is_open()) {
log_file_.close();
}
}
// ─── Path Resolution ───────────────────────────────────────────────────
std::string Logger::resolvePath(const std::string& raw_path) const {
std::string path = raw_path;
if (path.empty()) {
const char* home = std::getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
if (pw) {
home = pw->pw_dir;
}
}
if (home) {
path = std::string(home) + "/.local/radiantwave/log.txt";
} else {
path = "/tmp/radiantwave/log.txt";
}
} else if (path.front() == '~') {
const char* home = std::getenv("HOME");
if (!home) {
struct passwd* pw = getpwuid(getuid());
if (pw) {
home = pw->pw_dir;
}
}
if (home) {
path = std::string(home) + path.substr(1);
}
}
return path;
}
// ─── Initialization ────────────────────────────────────────────────────
bool Logger::initialize(const std::string& custom_path) {
std::lock_guard<std::mutex> lock(mutex_);
file_path_ = resolvePath(custom_path);
try {
std::filesystem::path p(file_path_);
if (p.has_parent_path()) {
std::filesystem::create_directories(p.parent_path());
}
} catch (const std::exception& e) {
std::cerr << "[Logger] Failed to create directories for log file: " << e.what() << std::endl;
return false;
}
// Load existing entries from log file
loadExistingLogs();
// Open stream in append mode
log_file_.open(file_path_, std::ios::out | std::ios::app);
if (!log_file_.is_open()) {
std::cerr << "[Logger] Failed to open log file: " << file_path_ << std::endl;
return false;
}
return true;
}
void Logger::loadExistingLogs() {
entries_.clear();
std::ifstream file(file_path_);
if (!file.is_open()) return;
std::string line;
while (std::getline(file, line)) {
if (line.empty()) continue;
// Line format: [2026-07-24 14:30:15 UTC] [INFO] [Component] Message
auto bracket1_end = line.find(']');
if (bracket1_end == std::string::npos) continue;
auto bracket2_start = line.find('[', bracket1_end);
auto bracket2_end = line.find(']', bracket2_start);
if (bracket2_start == std::string::npos || bracket2_end == std::string::npos) continue;
auto bracket3_start = line.find('[', bracket2_end);
auto bracket3_end = line.find(']', bracket3_start);
if (bracket3_start == std::string::npos || bracket3_end == std::string::npos) continue;
std::string time_str = line.substr(1, bracket1_end - 1);
std::string level_str = line.substr(bracket2_start + 1, bracket2_end - bracket2_start - 1);
std::string comp_str = line.substr(bracket3_start + 1, bracket3_end - bracket3_start - 1);
std::string msg_str = (bracket3_end + 2 < line.size()) ? line.substr(bracket3_end + 2) : "";
// Parse UTC time string (e.g. 2026-07-24 14:30:15 UTC)
struct tm tm_buf = {};
int year = 0, month = 0, day = 0, hour = 0, min = 0, sec = 0;
if (sscanf(time_str.c_str(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &min, &sec) >= 6) {
tm_buf.tm_year = year - 1900;
tm_buf.tm_mon = month - 1;
tm_buf.tm_mday = day;
tm_buf.tm_hour = hour;
tm_buf.tm_min = min;
tm_buf.tm_sec = sec;
time_t utc_epoch = timegm(&tm_buf);
LogEntry entry;
entry.timestamp = utc_epoch;
entry.level = stringToLogLevel(level_str);
entry.component = comp_str;
entry.message = msg_str;
entries_.push_back(entry);
}
}
file.close();
}
// ─── Logging Methods ───────────────────────────────────────────────────
void Logger::log(LogLevel level, const std::string& component, const std::string& message) {
LogEntry entry;
entry.timestamp = std::time(nullptr);
entry.level = level;
entry.component = component;
entry.message = message;
{
std::lock_guard<std::mutex> lock(mutex_);
entries_.push_back(entry);
writeToFile(entry);
}
// Console output
std::cout << "[" << logLevelToString(level) << "] [" << component << "] " << message << std::endl;
}
void Logger::writeToFile(const LogEntry& entry) {
if (!log_file_.is_open()) return;
struct tm gmt_buf;
#if defined(_WIN32)
gmtime_s(&gmt_buf, &entry.timestamp);
#else
gmtime_r(&entry.timestamp, &gmt_buf);
#endif
char time_buf[64];
std::strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S UTC", &gmt_buf);
log_file_ << "[" << time_buf << "] ["
<< logLevelToString(entry.level) << "] ["
<< entry.component << "] "
<< entry.message << "\n";
log_file_.flush();
}
void Logger::debug(const std::string& component, const std::string& message) {
log(LogLevel::Debug, component, message);
}
void Logger::info(const std::string& component, const std::string& message) {
log(LogLevel::Info, component, message);
}
void Logger::warning(const std::string& component, const std::string& message) {
log(LogLevel::Warning, component, message);
}
void Logger::error(const std::string& component, const std::string& message) {
log(LogLevel::Error, component, message);
}
void Logger::fatal(const std::string& component, const std::string& message) {
log(LogLevel::Fatal, component, message);
}
// ─── Retrieve Entries ──────────────────────────────────────────────────
std::vector<LogEntry> Logger::getEntries() const {
std::lock_guard<std::mutex> lock(mutex_);
return entries_;
}
std::vector<LogEntry> Logger::getEntriesFiltered(LogLevel min_level, bool exact_level_match) const {
std::lock_guard<std::mutex> lock(mutex_);
std::vector<LogEntry> result;
for (const auto& entry : entries_) {
if (exact_level_match) {
if (entry.level == min_level) {
result.push_back(entry);
}
} else {
if (static_cast<int>(entry.level) >= static_cast<int>(min_level)) {
result.push_back(entry);
}
}
}
return result;
}
void Logger::clear() {
std::lock_guard<std::mutex> lock(mutex_);
entries_.clear();
if (log_file_.is_open()) {
log_file_.close();
}
log_file_.open(file_path_, std::ios::out | std::ios::trunc);
}
std::string Logger::getFilePath() const {
return file_path_;
}
} // namespace radiantwave::logger
+75
View File
@@ -0,0 +1,75 @@
#pragma once
#include <ctime>
#include <fstream>
#include <mutex>
#include <string>
#include <vector>
namespace radiantwave::logger {
enum class LogLevel {
Debug,
Info,
Warning,
Error,
Fatal
};
const char* logLevelToString(LogLevel level);
LogLevel stringToLogLevel(const std::string& str);
struct LogEntry {
time_t timestamp = 0; // UTC epoch time
LogLevel level = LogLevel::Info;
std::string component;
std::string message;
};
class Logger {
public:
Logger();
~Logger();
// Singleton access or embedded instance
static Logger& instance();
// Non-copyable
Logger(const Logger&) = delete;
Logger& operator=(const Logger&) = delete;
// Initialize log file path (default: ~/.local/radiantwave/log.txt)
bool initialize(const std::string& custom_path = "");
// Log a message
void log(LogLevel level, const std::string& component, const std::string& message);
// Helper functions
void debug(const std::string& component, const std::string& message);
void info(const std::string& component, const std::string& message);
void warning(const std::string& component, const std::string& message);
void error(const std::string& component, const std::string& message);
void fatal(const std::string& component, const std::string& message);
// Retrieve log entries
std::vector<LogEntry> getEntries() const;
std::vector<LogEntry> getEntriesFiltered(LogLevel min_level, bool exact_level_match = false) const;
// Clear logs in memory and on disk
void clear();
// Get current log file path
std::string getFilePath() const;
private:
std::string resolvePath(const std::string& raw_path) const;
void loadExistingLogs();
void writeToFile(const LogEntry& entry);
std::string file_path_;
std::ofstream log_file_;
std::vector<LogEntry> entries_;
mutable std::mutex mutex_;
};
} // namespace radiantwave::logger