#include "logger.h" #include #include #include Logger& Logger::getInstance() { static Logger instance; return instance; } Logger::~Logger() { close(); } void Logger::initialize(bool enableFileLogging, const std::string& logFilePath, LogLevel minLevel) { std::lock_guard lock(m_mutex); m_fileLoggingEnabled = enableFileLogging; m_logFilePath = logFilePath; m_internalMinLevel = minLevel; m_httpMinLevel = minLevel; // Perform systemd detection once during initialization if (!m_systemdDetected) { m_isRunningUnderSystemd = detectSystemd(); m_systemdDetected = true; } if (m_fileLoggingEnabled && !m_logFilePath.empty()) { m_logFile.open(m_logFilePath, std::ios::app); if (!m_logFile.is_open()) { std::cerr << "<3>ERROR: Failed to open log file: " << m_logFilePath << std::endl; m_fileLoggingEnabled = false; } else { // Write startup marker m_logFile << "\n=== Log started at " << getCurrentTimestamp() << " ===" << std::endl; m_logFile.flush(); } } } void Logger::setLogLevel(LoggerType type, LogLevel level) { std::lock_guard lock(m_mutex); if (type == LoggerType::HTTP) { m_httpMinLevel = level; } else { m_internalMinLevel = level; } } LogLevel Logger::getLogLevel(LoggerType type) const { std::lock_guard lock(m_mutex); return (type == LoggerType::HTTP) ? m_httpMinLevel : m_internalMinLevel; } void Logger::close() { std::lock_guard lock(m_mutex); if (m_logFile.is_open()) { m_logFile << "=== Log closed at " << getCurrentTimestamp() << " ===" << std::endl; m_logFile.close(); } } std::string Logger::getCurrentTimestamp() { auto now = std::chrono::system_clock::now(); auto time_t_now = std::chrono::system_clock::to_time_t(now); auto ms = std::chrono::duration_cast( now.time_since_epoch()) % 1000; std::tm tm_buf; localtime_r(&time_t_now, &tm_buf); std::ostringstream oss; oss << std::put_time(&tm_buf, "%Y-%m-%d %H:%M:%S"); oss << '.' << std::setfill('0') << std::setw(3) << ms.count(); return oss.str(); } std::string Logger::levelToString(LogLevel level) { switch (level) { case LogLevel::DEBUG: return "DEBUG"; case LogLevel::INFO: return "INFO"; case LogLevel::WARNING: return "WARNING"; case LogLevel::ERROR: return "ERROR"; default: return "UNKNOWN"; } } std::string Logger::typeToString(LoggerType type) { switch (type) { case LoggerType::HTTP: return "HTTP"; case LoggerType::INTERNAL: return "INTERNAL"; default: return "UNKNOWN"; } } std::string Logger::levelToSystemdPriority(LogLevel level) { // systemd journal priority levels: // 0=emerg, 1=alert, 2=crit, 3=err, 4=warning, 5=notice, 6=info, 7=debug switch (level) { case LogLevel::DEBUG: return "<7>"; // debug case LogLevel::INFO: return "<6>"; // info case LogLevel::WARNING: return "<4>"; // warning case LogLevel::ERROR: return "<3>"; // error default: return "<6>"; } } void Logger::log(LogLevel level, const std::string& message, LoggerType type) { LogLevel minLevel = (type == LoggerType::HTTP) ? m_httpMinLevel : m_internalMinLevel; if (level < minLevel) { return; } std::lock_guard lock(m_mutex); std::string timestamp = getCurrentTimestamp(); std::string levelStr = levelToString(level); std::string typeStr = typeToString(type); std::string priority = levelToSystemdPriority(level); // Console output with systemd priority prefix and type std::cout << priority << "[" << timestamp << "] " << typeStr << " " << levelStr << ": " << message << std::endl; // File output (without systemd priority, with type) if (m_fileLoggingEnabled && m_logFile.is_open()) { m_logFile << "[" << timestamp << "] " << typeStr << " " << levelStr << ": " << message << std::endl; m_logFile.flush(); } } bool Logger::isRunningUnderSystemd() { std::lock_guard lock(m_mutex); // Perform detection if not already done if (!m_systemdDetected) { m_isRunningUnderSystemd = detectSystemd(); m_systemdDetected = true; } return m_isRunningUnderSystemd; } bool Logger::detectSystemd() { // Method 1: Try sd_booted() if systemd development libraries are available // This is the most reliable method when available #ifdef HAVE_SYSTEMD // If we have systemd development libraries, we could use sd_booted() // But we'll implement fallback methods for broader compatibility #endif // Method 2: Check for NOTIFY_SOCKET environment variable // This is set by systemd when using sd_notify() const char* notifySocket = std::getenv("NOTIFY_SOCKET"); if (notifySocket && notifySocket[0] != '\0') { return true; } // Method 3: Check for JOURNAL_STREAM environment variable // This is set by systemd for services with StandardOutput=journal const char* journalStream = std::getenv("JOURNAL_STREAM"); if (journalStream && journalStream[0] != '\0') { return true; } // Method 4: Check cgroup filesystem for systemd // Look for systemd-specific cgroup paths std::ifstream cgroupFile("/proc/1/cgroup"); if (cgroupFile.is_open()) { std::string line; while (std::getline(cgroupFile, line)) { if (line.find("systemd") != std::string::npos) { return true; } } cgroupFile.close(); } // Method 5: Check if /sys/fs/cgroup/systemd exists struct stat sb; if (stat("/sys/fs/cgroup/systemd", &sb) == 0 && S_ISDIR(sb.st_mode)) { return true; } // Method 6: Check PID 1 command line for "systemd" std::ifstream cmdlineFile("/proc/1/cmdline"); if (cmdlineFile.is_open()) { std::string cmdline; std::getline(cmdlineFile, cmdline); cmdlineFile.close(); // cmdline contains null-separated arguments, replace nulls with spaces for searching for (size_t i = 0; i < cmdline.length(); ++i) { if (cmdline[i] == '\0') { cmdline[i] = ' '; } } if (cmdline.find("systemd") != std::string::npos) { return true; } } return false; }