blob: 77ad13a45894be2939007be1b0341e6f423d53d5 [file] [log] [blame]
Matt Spinler635de8c2020-09-24 13:51:40 -05001#pragma once
2
Matt Spinlerc6745102020-10-22 10:52:02 -05003#include "utility.hpp"
4
Matt Spinler635de8c2020-09-24 13:51:40 -05005#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>
Patrick Williamsfbf47032023-07-17 12:27:34 -050013#include <format>
Matt Spinler635de8c2020-09-24 13:51:40 -050014#include <fstream>
15#include <iomanip>
16#include <sstream>
17#include <string>
18#include <vector>
19
20namespace 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 */
37class Logger
38{
39 public:
40 // timestamp, message
41 using LogEntry = std::tuple<std::string, std::string>;
42
43 enum Priority
44 {
45 error,
Matt Spinler27f6b682020-10-27 08:43:37 -050046 info,
47 quiet
Matt Spinler635de8c2020-09-24 13:51:40 -050048 };
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 }
Matt Spinler27f6b682020-10-27 08:43:37 -050082 else if (priority != Logger::quiet)
Matt Spinler635de8c2020-09-24 13:51:40 -050083 {
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 /**
Matt Spinlerc6745102020-10-22 10:52:02 -0500116 * @brief Writes the data to a temporary file and returns the path
Matt Spinler635de8c2020-09-24 13:51:40 -0500117 * 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";
Matt Spinlerc6745102020-10-22 10:52:02 -0500130 util::FileDescriptor fd{mkstemp(tmpFile)};
131 if (fd() == -1)
Matt Spinler635de8c2020-09-24 13:51:40 -0500132 {
133 throw std::runtime_error{"mkstemp failed!"};
134 }
135
136 std::filesystem::path path{tmpFile};
137
Matt Spinlerc6745102020-10-22 10:52:02 -0500138 for (const auto& [time, message] : _entries)
Matt Spinler635de8c2020-09-24 13:51:40 -0500139 {
Patrick Williamsfbf47032023-07-17 12:27:34 -0500140 auto line = std::format("{}: {}\n", time, message);
Matt Spinlerc6745102020-10-22 10:52:02 -0500141 auto rc = write(fd(), line.data(), line.size());
142 if (rc == -1)
143 {
144 auto e = errno;
Patrick Williamsfbf47032023-07-17 12:27:34 -0500145 auto msg = std::format(
Matt Spinlerc6745102020-10-22 10:52:02 -0500146 "Could not write to temp file {} errno {}", tmpFile, e);
147 log(msg, Logger::error);
148 throw std::runtime_error{msg};
149 }
Matt Spinler635de8c2020-09-24 13:51:40 -0500150 }
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