1635de8c6SMatt Spinler #pragma once 2635de8c6SMatt Spinler 3c674510eSMatt Spinler #include "utility.hpp" 4c674510eSMatt Spinler 5635de8c6SMatt Spinler #include <unistd.h> 6635de8c6SMatt Spinler 7635de8c6SMatt Spinler #include <nlohmann/json.hpp> 8635de8c6SMatt Spinler #include <phosphor-logging/log.hpp> 9635de8c6SMatt Spinler 10635de8c6SMatt Spinler #include <cassert> 11635de8c6SMatt Spinler #include <ctime> 12635de8c6SMatt Spinler #include <filesystem> 13*fbf4703fSPatrick Williams #include <format> 14635de8c6SMatt Spinler #include <fstream> 15635de8c6SMatt Spinler #include <iomanip> 16635de8c6SMatt Spinler #include <sstream> 17635de8c6SMatt Spinler #include <string> 18635de8c6SMatt Spinler #include <vector> 19635de8c6SMatt Spinler 20635de8c6SMatt Spinler namespace phosphor::fan 21635de8c6SMatt Spinler { 22635de8c6SMatt Spinler 23635de8c6SMatt Spinler /** 24635de8c6SMatt Spinler * @class Logger 25635de8c6SMatt Spinler * 26635de8c6SMatt Spinler * A simple logging class that stores log messages in a vector along 27635de8c6SMatt Spinler * with their timestamp. When a messaged is logged, it will also be 28635de8c6SMatt Spinler * written to the journal. 29635de8c6SMatt Spinler * 30635de8c6SMatt Spinler * A saveToTempFile() function will write the log entries as JSON to 31635de8c6SMatt Spinler * a temporary file, so they can be added to event logs. 32635de8c6SMatt Spinler * 33635de8c6SMatt Spinler * The maximum number of entries to keep is specified in the 34635de8c6SMatt Spinler * constructor, and after that is hit the oldest entry will be 35635de8c6SMatt Spinler * removed when a new one is added. 36635de8c6SMatt Spinler */ 37635de8c6SMatt Spinler class Logger 38635de8c6SMatt Spinler { 39635de8c6SMatt Spinler public: 40635de8c6SMatt Spinler // timestamp, message 41635de8c6SMatt Spinler using LogEntry = std::tuple<std::string, std::string>; 42635de8c6SMatt Spinler 43635de8c6SMatt Spinler enum Priority 44635de8c6SMatt Spinler { 45635de8c6SMatt Spinler error, 4627f6b686SMatt Spinler info, 4727f6b686SMatt Spinler quiet 48635de8c6SMatt Spinler }; 49635de8c6SMatt Spinler 50635de8c6SMatt Spinler Logger() = delete; 51635de8c6SMatt Spinler ~Logger() = default; 52635de8c6SMatt Spinler Logger(const Logger&) = default; 53635de8c6SMatt Spinler Logger& operator=(const Logger&) = default; 54635de8c6SMatt Spinler Logger(Logger&&) = default; 55635de8c6SMatt Spinler Logger& operator=(Logger&&) = default; 56635de8c6SMatt Spinler 57635de8c6SMatt Spinler /** 58635de8c6SMatt Spinler * @brief Constructor 59635de8c6SMatt Spinler * 60635de8c6SMatt Spinler * @param[in] maxEntries - The maximum number of log entries 61635de8c6SMatt Spinler * to keep. 62635de8c6SMatt Spinler */ Logger(size_t maxEntries)63635de8c6SMatt Spinler explicit Logger(size_t maxEntries) : _maxEntries(maxEntries) 64635de8c6SMatt Spinler { 65635de8c6SMatt Spinler assert(maxEntries != 0); 66635de8c6SMatt Spinler } 67635de8c6SMatt Spinler 68635de8c6SMatt Spinler /** 69635de8c6SMatt Spinler * @brief Places an entry in the log and writes it to the journal. 70635de8c6SMatt Spinler * 71635de8c6SMatt Spinler * @param[in] message - The log message 72635de8c6SMatt Spinler * 73635de8c6SMatt Spinler * @param[in] priority - The priority for the journal 74635de8c6SMatt Spinler */ log(const std::string & message,Priority priority=Logger::info)75635de8c6SMatt Spinler void log(const std::string& message, Priority priority = Logger::info) 76635de8c6SMatt Spinler { 77635de8c6SMatt Spinler if (priority == Logger::error) 78635de8c6SMatt Spinler { 79635de8c6SMatt Spinler phosphor::logging::log<phosphor::logging::level::ERR>( 80635de8c6SMatt Spinler message.c_str()); 81635de8c6SMatt Spinler } 8227f6b686SMatt Spinler else if (priority != Logger::quiet) 83635de8c6SMatt Spinler { 84635de8c6SMatt Spinler phosphor::logging::log<phosphor::logging::level::INFO>( 85635de8c6SMatt Spinler message.c_str()); 86635de8c6SMatt Spinler } 87635de8c6SMatt Spinler 88635de8c6SMatt Spinler if (_entries.size() == _maxEntries) 89635de8c6SMatt Spinler { 90635de8c6SMatt Spinler _entries.erase(_entries.begin()); 91635de8c6SMatt Spinler } 92635de8c6SMatt Spinler 93635de8c6SMatt Spinler // Generate a timestamp 94635de8c6SMatt Spinler auto t = std::time(nullptr); 95635de8c6SMatt Spinler auto tm = *std::localtime(&t); 96635de8c6SMatt Spinler 97635de8c6SMatt Spinler // e.g. Sep 22 19:56:32 98635de8c6SMatt Spinler auto timestamp = std::put_time(&tm, "%b %d %H:%M:%S"); 99635de8c6SMatt Spinler 100635de8c6SMatt Spinler std::ostringstream stream; 101635de8c6SMatt Spinler stream << timestamp; 102635de8c6SMatt Spinler _entries.emplace_back(stream.str(), message); 103635de8c6SMatt Spinler } 104635de8c6SMatt Spinler 105635de8c6SMatt Spinler /** 106635de8c6SMatt Spinler * @brief Returns the entries in a JSON array 107635de8c6SMatt Spinler * 108635de8c6SMatt Spinler * @return JSON 109635de8c6SMatt Spinler */ getLogs() const110635de8c6SMatt Spinler const nlohmann::json getLogs() const 111635de8c6SMatt Spinler { 112635de8c6SMatt Spinler return _entries; 113635de8c6SMatt Spinler } 114635de8c6SMatt Spinler 115635de8c6SMatt Spinler /** 116c674510eSMatt Spinler * @brief Writes the data to a temporary file and returns the path 117635de8c6SMatt Spinler * to it. 118635de8c6SMatt Spinler * 119635de8c6SMatt Spinler * Uses a temp file because the only use case for this is for sending 120635de8c6SMatt Spinler * in to an event log where a temp file makes sense, and frankly it 121635de8c6SMatt Spinler * was just easier to encapsulate everything here. 122635de8c6SMatt Spinler * 123635de8c6SMatt Spinler * @return path - The path to the file. 124635de8c6SMatt Spinler */ saveToTempFile()125635de8c6SMatt Spinler std::filesystem::path saveToTempFile() 126635de8c6SMatt Spinler { 127635de8c6SMatt Spinler using namespace std::literals::string_literals; 128635de8c6SMatt Spinler 129635de8c6SMatt Spinler char tmpFile[] = "/tmp/loggertemp.XXXXXX"; 130c674510eSMatt Spinler util::FileDescriptor fd{mkstemp(tmpFile)}; 131c674510eSMatt Spinler if (fd() == -1) 132635de8c6SMatt Spinler { 133635de8c6SMatt Spinler throw std::runtime_error{"mkstemp failed!"}; 134635de8c6SMatt Spinler } 135635de8c6SMatt Spinler 136635de8c6SMatt Spinler std::filesystem::path path{tmpFile}; 137635de8c6SMatt Spinler 138c674510eSMatt Spinler for (const auto& [time, message] : _entries) 139635de8c6SMatt Spinler { 140*fbf4703fSPatrick Williams auto line = std::format("{}: {}\n", time, message); 141c674510eSMatt Spinler auto rc = write(fd(), line.data(), line.size()); 142c674510eSMatt Spinler if (rc == -1) 143c674510eSMatt Spinler { 144c674510eSMatt Spinler auto e = errno; 145*fbf4703fSPatrick Williams auto msg = std::format( 146c674510eSMatt Spinler "Could not write to temp file {} errno {}", tmpFile, e); 147c674510eSMatt Spinler log(msg, Logger::error); 148c674510eSMatt Spinler throw std::runtime_error{msg}; 149c674510eSMatt Spinler } 150635de8c6SMatt Spinler } 151635de8c6SMatt Spinler 152635de8c6SMatt Spinler return std::filesystem::path{tmpFile}; 153635de8c6SMatt Spinler } 154635de8c6SMatt Spinler 155635de8c6SMatt Spinler /** 156635de8c6SMatt Spinler * @brief Deletes all log entries 157635de8c6SMatt Spinler */ clear()158635de8c6SMatt Spinler void clear() 159635de8c6SMatt Spinler { 160635de8c6SMatt Spinler _entries.clear(); 161635de8c6SMatt Spinler } 162635de8c6SMatt Spinler 163635de8c6SMatt Spinler private: 164635de8c6SMatt Spinler /** 165635de8c6SMatt Spinler * @brief The maximum number of entries to hold 166635de8c6SMatt Spinler */ 167635de8c6SMatt Spinler const size_t _maxEntries; 168635de8c6SMatt Spinler 169635de8c6SMatt Spinler /** 170635de8c6SMatt Spinler * @brief The vector of <timestamp, message> entries 171635de8c6SMatt Spinler */ 172635de8c6SMatt Spinler std::vector<LogEntry> _entries; 173635de8c6SMatt Spinler }; 174635de8c6SMatt Spinler 175635de8c6SMatt Spinler } // namespace phosphor::fan 176