blob: 77ad13a45894be2939007be1b0341e6f423d53d5 [file] [log] [blame]
#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