blob: bd40f559e2a92f9569d53e3ad4ddd7f32c6b7605 [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>
Anwaar Hadi32c4fef2025-04-02 16:08:27 +00008#include <phosphor-logging/lg2.hpp>
Matt Spinler635de8c2020-09-24 13:51:40 -05009
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 {
Anwaar Hadi32c4fef2025-04-02 16:08:27 +000079 lg2::error("{MSG}", "MSG", message);
Matt Spinler635de8c2020-09-24 13:51:40 -050080 }
Matt Spinler27f6b682020-10-27 08:43:37 -050081 else if (priority != Logger::quiet)
Matt Spinler635de8c2020-09-24 13:51:40 -050082 {
Anwaar Hadi32c4fef2025-04-02 16:08:27 +000083 lg2::info("{MSG}", "MSG", message);
Matt Spinler635de8c2020-09-24 13:51:40 -050084 }
85
86 if (_entries.size() == _maxEntries)
87 {
88 _entries.erase(_entries.begin());
89 }
90
91 // Generate a timestamp
92 auto t = std::time(nullptr);
93 auto tm = *std::localtime(&t);
94
95 // e.g. Sep 22 19:56:32
96 auto timestamp = std::put_time(&tm, "%b %d %H:%M:%S");
97
98 std::ostringstream stream;
99 stream << timestamp;
100 _entries.emplace_back(stream.str(), message);
101 }
102
103 /**
104 * @brief Returns the entries in a JSON array
105 *
106 * @return JSON
107 */
108 const nlohmann::json getLogs() const
109 {
110 return _entries;
111 }
112
113 /**
Matt Spinlerc6745102020-10-22 10:52:02 -0500114 * @brief Writes the data to a temporary file and returns the path
Matt Spinler635de8c2020-09-24 13:51:40 -0500115 * to it.
116 *
117 * Uses a temp file because the only use case for this is for sending
118 * in to an event log where a temp file makes sense, and frankly it
119 * was just easier to encapsulate everything here.
120 *
121 * @return path - The path to the file.
122 */
123 std::filesystem::path saveToTempFile()
124 {
125 using namespace std::literals::string_literals;
126
127 char tmpFile[] = "/tmp/loggertemp.XXXXXX";
Matt Spinlerc6745102020-10-22 10:52:02 -0500128 util::FileDescriptor fd{mkstemp(tmpFile)};
129 if (fd() == -1)
Matt Spinler635de8c2020-09-24 13:51:40 -0500130 {
131 throw std::runtime_error{"mkstemp failed!"};
132 }
133
134 std::filesystem::path path{tmpFile};
135
Matt Spinlerc6745102020-10-22 10:52:02 -0500136 for (const auto& [time, message] : _entries)
Matt Spinler635de8c2020-09-24 13:51:40 -0500137 {
Patrick Williamsfbf47032023-07-17 12:27:34 -0500138 auto line = std::format("{}: {}\n", time, message);
Matt Spinlerc6745102020-10-22 10:52:02 -0500139 auto rc = write(fd(), line.data(), line.size());
140 if (rc == -1)
141 {
142 auto e = errno;
Patrick Williamsfbf47032023-07-17 12:27:34 -0500143 auto msg = std::format(
Matt Spinlerc6745102020-10-22 10:52:02 -0500144 "Could not write to temp file {} errno {}", tmpFile, e);
145 log(msg, Logger::error);
146 throw std::runtime_error{msg};
147 }
Matt Spinler635de8c2020-09-24 13:51:40 -0500148 }
149
150 return std::filesystem::path{tmpFile};
151 }
152
153 /**
154 * @brief Deletes all log entries
155 */
156 void clear()
157 {
158 _entries.clear();
159 }
160
161 private:
162 /**
163 * @brief The maximum number of entries to hold
164 */
165 const size_t _maxEntries;
166
167 /**
168 * @brief The vector of <timestamp, message> entries
169 */
170 std::vector<LogEntry> _entries;
171};
172
173} // namespace phosphor::fan