1 #pragma once 2 3 #include "utility.hpp" 4 5 #include <unistd.h> 6 7 #include <nlohmann/json.hpp> 8 #include <phosphor-logging/log.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&) = default; 53 Logger& operator=(const Logger&) = default; 54 Logger(Logger&&) = default; 55 Logger& operator=(Logger&&) = default; 56 57 /** 58 * @brief Constructor 59 * 60 * @param[in] maxEntries - The maximum number of log entries 61 * to keep. 62 */ 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 */ 75 void log(const std::string& message, Priority priority = Logger::info) 76 { 77 if (priority == Logger::error) 78 { 79 phosphor::logging::log<phosphor::logging::level::ERR>( 80 message.c_str()); 81 } 82 else if (priority != Logger::quiet) 83 { 84 phosphor::logging::log<phosphor::logging::level::INFO>( 85 message.c_str()); 86 } 87 88 if (_entries.size() == _maxEntries) 89 { 90 _entries.erase(_entries.begin()); 91 } 92 93 // Generate a timestamp 94 auto t = std::time(nullptr); 95 auto tm = *std::localtime(&t); 96 97 // e.g. Sep 22 19:56:32 98 auto timestamp = std::put_time(&tm, "%b %d %H:%M:%S"); 99 100 std::ostringstream stream; 101 stream << timestamp; 102 _entries.emplace_back(stream.str(), message); 103 } 104 105 /** 106 * @brief Returns the entries in a JSON array 107 * 108 * @return JSON 109 */ 110 const nlohmann::json getLogs() const 111 { 112 return _entries; 113 } 114 115 /** 116 * @brief Writes the data to a temporary file and returns the path 117 * to it. 118 * 119 * Uses a temp file because the only use case for this is for sending 120 * in to an event log where a temp file makes sense, and frankly it 121 * was just easier to encapsulate everything here. 122 * 123 * @return path - The path to the file. 124 */ 125 std::filesystem::path saveToTempFile() 126 { 127 using namespace std::literals::string_literals; 128 129 char tmpFile[] = "/tmp/loggertemp.XXXXXX"; 130 util::FileDescriptor fd{mkstemp(tmpFile)}; 131 if (fd() == -1) 132 { 133 throw std::runtime_error{"mkstemp failed!"}; 134 } 135 136 std::filesystem::path path{tmpFile}; 137 138 for (const auto& [time, message] : _entries) 139 { 140 auto line = std::format("{}: {}\n", time, message); 141 auto rc = write(fd(), line.data(), line.size()); 142 if (rc == -1) 143 { 144 auto e = errno; 145 auto msg = std::format( 146 "Could not write to temp file {} errno {}", tmpFile, e); 147 log(msg, Logger::error); 148 throw std::runtime_error{msg}; 149 } 150 } 151 152 return std::filesystem::path{tmpFile}; 153 } 154 155 /** 156 * @brief Deletes all log entries 157 */ 158 void clear() 159 { 160 _entries.clear(); 161 } 162 163 private: 164 /** 165 * @brief The maximum number of entries to hold 166 */ 167 const size_t _maxEntries; 168 169 /** 170 * @brief The vector of <timestamp, message> entries 171 */ 172 std::vector<LogEntry> _entries; 173 }; 174 175 } // namespace phosphor::fan 176