logger.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #include "logger.h"
  2. #include <iostream>
  3. #include <ctime>
  4. #include <fstream>
  5. Logger& Logger::getInstance() {
  6. static Logger instance;
  7. return instance;
  8. }
  9. Logger::~Logger() {
  10. close();
  11. }
  12. void Logger::initialize(bool enableFileLogging, const std::string& logFilePath, LogLevel minLevel) {
  13. std::lock_guard<std::mutex> lock(m_mutex);
  14. m_fileLoggingEnabled = enableFileLogging;
  15. m_logFilePath = logFilePath;
  16. m_internalMinLevel = minLevel;
  17. m_httpMinLevel = minLevel;
  18. // Perform systemd detection once during initialization
  19. if (!m_systemdDetected) {
  20. m_isRunningUnderSystemd = detectSystemd();
  21. m_systemdDetected = true;
  22. }
  23. if (m_fileLoggingEnabled && !m_logFilePath.empty()) {
  24. m_logFile.open(m_logFilePath, std::ios::app);
  25. if (!m_logFile.is_open()) {
  26. std::cerr << "<3>ERROR: Failed to open log file: " << m_logFilePath << std::endl;
  27. m_fileLoggingEnabled = false;
  28. } else {
  29. // Write startup marker
  30. m_logFile << "\n=== Log started at " << getCurrentTimestamp() << " ===" << std::endl;
  31. m_logFile.flush();
  32. }
  33. }
  34. }
  35. void Logger::setLogLevel(LoggerType type, LogLevel level) {
  36. std::lock_guard<std::mutex> lock(m_mutex);
  37. if (type == LoggerType::HTTP) {
  38. m_httpMinLevel = level;
  39. } else {
  40. m_internalMinLevel = level;
  41. }
  42. }
  43. LogLevel Logger::getLogLevel(LoggerType type) const {
  44. std::lock_guard<std::mutex> lock(m_mutex);
  45. return (type == LoggerType::HTTP) ? m_httpMinLevel : m_internalMinLevel;
  46. }
  47. void Logger::close() {
  48. std::lock_guard<std::mutex> lock(m_mutex);
  49. if (m_logFile.is_open()) {
  50. m_logFile << "=== Log closed at " << getCurrentTimestamp() << " ===" << std::endl;
  51. m_logFile.close();
  52. }
  53. }
  54. std::string Logger::getCurrentTimestamp() {
  55. auto now = std::chrono::system_clock::now();
  56. auto time_t_now = std::chrono::system_clock::to_time_t(now);
  57. auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
  58. now.time_since_epoch()) % 1000;
  59. std::tm tm_buf;
  60. localtime_r(&time_t_now, &tm_buf);
  61. std::ostringstream oss;
  62. oss << std::put_time(&tm_buf, "%Y-%m-%d %H:%M:%S");
  63. oss << '.' << std::setfill('0') << std::setw(3) << ms.count();
  64. return oss.str();
  65. }
  66. std::string Logger::levelToString(LogLevel level) {
  67. switch (level) {
  68. case LogLevel::DEBUG: return "DEBUG";
  69. case LogLevel::INFO: return "INFO";
  70. case LogLevel::WARNING: return "WARNING";
  71. case LogLevel::ERROR: return "ERROR";
  72. default: return "UNKNOWN";
  73. }
  74. }
  75. std::string Logger::typeToString(LoggerType type) {
  76. switch (type) {
  77. case LoggerType::HTTP: return "HTTP";
  78. case LoggerType::INTERNAL: return "INTERNAL";
  79. default: return "UNKNOWN";
  80. }
  81. }
  82. std::string Logger::levelToSystemdPriority(LogLevel level) {
  83. // systemd journal priority levels:
  84. // 0=emerg, 1=alert, 2=crit, 3=err, 4=warning, 5=notice, 6=info, 7=debug
  85. switch (level) {
  86. case LogLevel::DEBUG: return "<7>"; // debug
  87. case LogLevel::INFO: return "<6>"; // info
  88. case LogLevel::WARNING: return "<4>"; // warning
  89. case LogLevel::ERROR: return "<3>"; // error
  90. default: return "<6>";
  91. }
  92. }
  93. void Logger::log(LogLevel level, const std::string& message, LoggerType type) {
  94. LogLevel minLevel = (type == LoggerType::HTTP) ? m_httpMinLevel : m_internalMinLevel;
  95. if (level < minLevel) {
  96. return;
  97. }
  98. std::lock_guard<std::mutex> lock(m_mutex);
  99. std::string timestamp = getCurrentTimestamp();
  100. std::string levelStr = levelToString(level);
  101. std::string typeStr = typeToString(type);
  102. std::string priority = levelToSystemdPriority(level);
  103. // Console output with systemd priority prefix and type
  104. std::cout << priority << "[" << timestamp << "] "
  105. << typeStr << " " << levelStr << ": " << message << std::endl;
  106. // File output (without systemd priority, with type)
  107. if (m_fileLoggingEnabled && m_logFile.is_open()) {
  108. m_logFile << "[" << timestamp << "] "
  109. << typeStr << " " << levelStr << ": " << message << std::endl;
  110. m_logFile.flush();
  111. }
  112. }
  113. bool Logger::isRunningUnderSystemd() {
  114. std::lock_guard<std::mutex> lock(m_mutex);
  115. // Perform detection if not already done
  116. if (!m_systemdDetected) {
  117. m_isRunningUnderSystemd = detectSystemd();
  118. m_systemdDetected = true;
  119. }
  120. return m_isRunningUnderSystemd;
  121. }
  122. bool Logger::detectSystemd() {
  123. // Method 1: Try sd_booted() if systemd development libraries are available
  124. // This is the most reliable method when available
  125. #ifdef HAVE_SYSTEMD
  126. // If we have systemd development libraries, we could use sd_booted()
  127. // But we'll implement fallback methods for broader compatibility
  128. #endif
  129. // Method 2: Check for NOTIFY_SOCKET environment variable
  130. // This is set by systemd when using sd_notify()
  131. const char* notifySocket = std::getenv("NOTIFY_SOCKET");
  132. if (notifySocket && notifySocket[0] != '\0') {
  133. return true;
  134. }
  135. // Method 3: Check for JOURNAL_STREAM environment variable
  136. // This is set by systemd for services with StandardOutput=journal
  137. const char* journalStream = std::getenv("JOURNAL_STREAM");
  138. if (journalStream && journalStream[0] != '\0') {
  139. return true;
  140. }
  141. // Method 4: Check cgroup filesystem for systemd
  142. // Look for systemd-specific cgroup paths
  143. std::ifstream cgroupFile("/proc/1/cgroup");
  144. if (cgroupFile.is_open()) {
  145. std::string line;
  146. while (std::getline(cgroupFile, line)) {
  147. if (line.find("systemd") != std::string::npos) {
  148. return true;
  149. }
  150. }
  151. cgroupFile.close();
  152. }
  153. // Method 5: Check if /sys/fs/cgroup/systemd exists
  154. struct stat sb;
  155. if (stat("/sys/fs/cgroup/systemd", &sb) == 0 && S_ISDIR(sb.st_mode)) {
  156. return true;
  157. }
  158. // Method 6: Check PID 1 command line for "systemd"
  159. std::ifstream cmdlineFile("/proc/1/cmdline");
  160. if (cmdlineFile.is_open()) {
  161. std::string cmdline;
  162. std::getline(cmdlineFile, cmdline);
  163. cmdlineFile.close();
  164. // cmdline contains null-separated arguments, replace nulls with spaces for searching
  165. for (size_t i = 0; i < cmdline.length(); ++i) {
  166. if (cmdline[i] == '\0') {
  167. cmdline[i] = ' ';
  168. }
  169. }
  170. if (cmdline.find("systemd") != std::string::npos) {
  171. return true;
  172. }
  173. }
  174. return false;
  175. }