1 #pragma once 2 3 #include <fmt/format.h> 4 #include <unistd.h> 5 6 #include <nlohmann/json.hpp> 7 #include <phosphor-logging/log.hpp> 8 9 #include <cassert> 10 #include <ctime> 11 #include <filesystem> 12 #include <fstream> 13 #include <iomanip> 14 #include <sstream> 15 #include <string> 16 #include <vector> 17 18 namespace phosphor::fan 19 { 20 21 /** 22 * @class Logger 23 * 24 * A simple logging class that stores log messages in a vector along 25 * with their timestamp. When a messaged is logged, it will also be 26 * written to the journal. 27 * 28 * A saveToTempFile() function will write the log entries as JSON to 29 * a temporary file, so they can be added to event logs. 30 * 31 * The maximum number of entries to keep is specified in the 32 * constructor, and after that is hit the oldest entry will be 33 * removed when a new one is added. 34 */ 35 class Logger 36 { 37 public: 38 // timestamp, message 39 using LogEntry = std::tuple<std::string, std::string>; 40 41 enum Priority 42 { 43 error, 44 info 45 }; 46 47 Logger() = delete; 48 ~Logger() = default; 49 Logger(const Logger&) = default; 50 Logger& operator=(const Logger&) = default; 51 Logger(Logger&&) = default; 52 Logger& operator=(Logger&&) = default; 53 54 /** 55 * @brief Constructor 56 * 57 * @param[in] maxEntries - The maximum number of log entries 58 * to keep. 59 */ 60 explicit Logger(size_t maxEntries) : _maxEntries(maxEntries) 61 { 62 assert(maxEntries != 0); 63 } 64 65 /** 66 * @brief Places an entry in the log and writes it to the journal. 67 * 68 * @param[in] message - The log message 69 * 70 * @param[in] priority - The priority for the journal 71 */ 72 void log(const std::string& message, Priority priority = Logger::info) 73 { 74 if (priority == Logger::error) 75 { 76 phosphor::logging::log<phosphor::logging::level::ERR>( 77 message.c_str()); 78 } 79 else 80 { 81 phosphor::logging::log<phosphor::logging::level::INFO>( 82 message.c_str()); 83 } 84 85 if (_entries.size() == _maxEntries) 86 { 87 _entries.erase(_entries.begin()); 88 } 89 90 // Generate a timestamp 91 auto t = std::time(nullptr); 92 auto tm = *std::localtime(&t); 93 94 // e.g. Sep 22 19:56:32 95 auto timestamp = std::put_time(&tm, "%b %d %H:%M:%S"); 96 97 std::ostringstream stream; 98 stream << timestamp; 99 _entries.emplace_back(stream.str(), message); 100 } 101 102 /** 103 * @brief Returns the entries in a JSON array 104 * 105 * @return JSON 106 */ 107 const nlohmann::json getLogs() const 108 { 109 return _entries; 110 } 111 112 /** 113 * @brief Writes the JSON to a temporary file and returns the path 114 * to it. 115 * 116 * Uses a temp file because the only use case for this is for sending 117 * in to an event log where a temp file makes sense, and frankly it 118 * was just easier to encapsulate everything here. 119 * 120 * @return path - The path to the file. 121 */ 122 std::filesystem::path saveToTempFile() 123 { 124 using namespace std::literals::string_literals; 125 126 char tmpFile[] = "/tmp/loggertemp.XXXXXX"; 127 int fd = mkstemp(tmpFile); 128 if (fd == -1) 129 { 130 throw std::runtime_error{"mkstemp failed!"}; 131 } 132 133 std::filesystem::path path{tmpFile}; 134 135 nlohmann::json data; 136 data["Logs"] = _entries; 137 auto jsonString = data.dump(); 138 139 auto rc = write(fd, jsonString.c_str(), jsonString.size()); 140 auto e = errno; 141 close(fd); 142 if (rc == 0) 143 { 144 log(fmt::format("Could not write to temp file {} errno {}", tmpFile, 145 e), 146 Logger::error); 147 throw std::runtime_error{"Could not write to "s + path.string()}; 148 } 149 150 return std::filesystem::path{tmpFile}; 151 } 152 153 /** 154 * @brief Deletes all log entries 155 */ 156 void clear() 157 { 158 _entries.clear(); 159 } 160 161 private: 162 /** 163 * @brief The maximum number of entries to hold 164 */ 165 const size_t _maxEntries; 166 167 /** 168 * @brief The vector of <timestamp, message> entries 169 */ 170 std::vector<LogEntry> _entries; 171 }; 172 173 } // namespace phosphor::fan 174