initial commit
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user