| #pragma once |
| |
| #include "utility.hpp" |
| |
| #include <unistd.h> |
| |
| #include <nlohmann/json.hpp> |
| #include <phosphor-logging/log.hpp> |
| |
| #include <cassert> |
| #include <ctime> |
| #include <filesystem> |
| #include <format> |
| #include <fstream> |
| #include <iomanip> |
| #include <sstream> |
| #include <string> |
| #include <vector> |
| |
| namespace phosphor::fan |
| { |
| |
| /** |
| * @class Logger |
| * |
| * A simple logging class that stores log messages in a vector along |
| * with their timestamp. When a messaged is logged, it will also be |
| * written to the journal. |
| * |
| * A saveToTempFile() function will write the log entries as JSON to |
| * a temporary file, so they can be added to event logs. |
| * |
| * The maximum number of entries to keep is specified in the |
| * constructor, and after that is hit the oldest entry will be |
| * removed when a new one is added. |
| */ |
| class Logger |
| { |
| public: |
| // timestamp, message |
| using LogEntry = std::tuple<std::string, std::string>; |
| |
| enum Priority |
| { |
| error, |
| info, |
| quiet |
| }; |
| |
| Logger() = delete; |
| ~Logger() = default; |
| Logger(const Logger&) = default; |
| Logger& operator=(const Logger&) = default; |
| Logger(Logger&&) = default; |
| Logger& operator=(Logger&&) = default; |
| |
| /** |
| * @brief Constructor |
| * |
| * @param[in] maxEntries - The maximum number of log entries |
| * to keep. |
| */ |
| explicit Logger(size_t maxEntries) : _maxEntries(maxEntries) |
| { |
| assert(maxEntries != 0); |
| } |
| |
| /** |
| * @brief Places an entry in the log and writes it to the journal. |
| * |
| * @param[in] message - The log message |
| * |
| * @param[in] priority - The priority for the journal |
| */ |
| void log(const std::string& message, Priority priority = Logger::info) |
| { |
| if (priority == Logger::error) |
| { |
| phosphor::logging::log<phosphor::logging::level::ERR>( |
| message.c_str()); |
| } |
| else if (priority != Logger::quiet) |
| { |
| phosphor::logging::log<phosphor::logging::level::INFO>( |
| message.c_str()); |
| } |
| |
| if (_entries.size() == _maxEntries) |
| { |
| _entries.erase(_entries.begin()); |
| } |
| |
| // Generate a timestamp |
| auto t = std::time(nullptr); |
| auto tm = *std::localtime(&t); |
| |
| // e.g. Sep 22 19:56:32 |
| auto timestamp = std::put_time(&tm, "%b %d %H:%M:%S"); |
| |
| std::ostringstream stream; |
| stream << timestamp; |
| _entries.emplace_back(stream.str(), message); |
| } |
| |
| /** |
| * @brief Returns the entries in a JSON array |
| * |
| * @return JSON |
| */ |
| const nlohmann::json getLogs() const |
| { |
| return _entries; |
| } |
| |
| /** |
| * @brief Writes the data to a temporary file and returns the path |
| * to it. |
| * |
| * Uses a temp file because the only use case for this is for sending |
| * in to an event log where a temp file makes sense, and frankly it |
| * was just easier to encapsulate everything here. |
| * |
| * @return path - The path to the file. |
| */ |
| std::filesystem::path saveToTempFile() |
| { |
| using namespace std::literals::string_literals; |
| |
| char tmpFile[] = "/tmp/loggertemp.XXXXXX"; |
| util::FileDescriptor fd{mkstemp(tmpFile)}; |
| if (fd() == -1) |
| { |
| throw std::runtime_error{"mkstemp failed!"}; |
| } |
| |
| std::filesystem::path path{tmpFile}; |
| |
| for (const auto& [time, message] : _entries) |
| { |
| auto line = std::format("{}: {}\n", time, message); |
| auto rc = write(fd(), line.data(), line.size()); |
| if (rc == -1) |
| { |
| auto e = errno; |
| auto msg = std::format( |
| "Could not write to temp file {} errno {}", tmpFile, e); |
| log(msg, Logger::error); |
| throw std::runtime_error{msg}; |
| } |
| } |
| |
| return std::filesystem::path{tmpFile}; |
| } |
| |
| /** |
| * @brief Deletes all log entries |
| */ |
| void clear() |
| { |
| _entries.clear(); |
| } |
| |
| private: |
| /** |
| * @brief The maximum number of entries to hold |
| */ |
| const size_t _maxEntries; |
| |
| /** |
| * @brief The vector of <timestamp, message> entries |
| */ |
| std::vector<LogEntry> _entries; |
| }; |
| |
| } // namespace phosphor::fan |