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