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