blob: f31ff36a61e51cf8d0d265becd54046baf2e9c4d [file] [log] [blame]
Matt Spinler635de8c2020-09-24 13:51:40 -05001#pragma once
2
3#include <fmt/format.h>
4#include <unistd.h>
5
6#include <nlohmann/json.hpp>
7#include <phosphor-logging/log.hpp>
8
9#include <cassert>
10#include <ctime>
11#include <filesystem>
12#include <fstream>
13#include <iomanip>
14#include <sstream>
15#include <string>
16#include <vector>
17
18namespace phosphor::fan
19{
20
21/**
22 * @class Logger
23 *
24 * A simple logging class that stores log messages in a vector along
25 * with their timestamp. When a messaged is logged, it will also be
26 * written to the journal.
27 *
28 * A saveToTempFile() function will write the log entries as JSON to
29 * a temporary file, so they can be added to event logs.
30 *
31 * The maximum number of entries to keep is specified in the
32 * constructor, and after that is hit the oldest entry will be
33 * removed when a new one is added.
34 */
35class Logger
36{
37 public:
38 // timestamp, message
39 using LogEntry = std::tuple<std::string, std::string>;
40
41 enum Priority
42 {
43 error,
44 info
45 };
46
47 Logger() = delete;
48 ~Logger() = default;
49 Logger(const Logger&) = default;
50 Logger& operator=(const Logger&) = default;
51 Logger(Logger&&) = default;
52 Logger& operator=(Logger&&) = default;
53
54 /**
55 * @brief Constructor
56 *
57 * @param[in] maxEntries - The maximum number of log entries
58 * to keep.
59 */
60 explicit Logger(size_t maxEntries) : _maxEntries(maxEntries)
61 {
62 assert(maxEntries != 0);
63 }
64
65 /**
66 * @brief Places an entry in the log and writes it to the journal.
67 *
68 * @param[in] message - The log message
69 *
70 * @param[in] priority - The priority for the journal
71 */
72 void log(const std::string& message, Priority priority = Logger::info)
73 {
74 if (priority == Logger::error)
75 {
76 phosphor::logging::log<phosphor::logging::level::ERR>(
77 message.c_str());
78 }
79 else
80 {
81 phosphor::logging::log<phosphor::logging::level::INFO>(
82 message.c_str());
83 }
84
85 if (_entries.size() == _maxEntries)
86 {
87 _entries.erase(_entries.begin());
88 }
89
90 // Generate a timestamp
91 auto t = std::time(nullptr);
92 auto tm = *std::localtime(&t);
93
94 // e.g. Sep 22 19:56:32
95 auto timestamp = std::put_time(&tm, "%b %d %H:%M:%S");
96
97 std::ostringstream stream;
98 stream << timestamp;
99 _entries.emplace_back(stream.str(), message);
100 }
101
102 /**
103 * @brief Returns the entries in a JSON array
104 *
105 * @return JSON
106 */
107 const nlohmann::json getLogs() const
108 {
109 return _entries;
110 }
111
112 /**
113 * @brief Writes the JSON to a temporary file and returns the path
114 * to it.
115 *
116 * Uses a temp file because the only use case for this is for sending
117 * in to an event log where a temp file makes sense, and frankly it
118 * was just easier to encapsulate everything here.
119 *
120 * @return path - The path to the file.
121 */
122 std::filesystem::path saveToTempFile()
123 {
124 using namespace std::literals::string_literals;
125
126 char tmpFile[] = "/tmp/loggertemp.XXXXXX";
127 int fd = mkstemp(tmpFile);
128 if (fd == -1)
129 {
130 throw std::runtime_error{"mkstemp failed!"};
131 }
132
133 std::filesystem::path path{tmpFile};
134
135 nlohmann::json data;
136 data["Logs"] = _entries;
137 auto jsonString = data.dump();
138
139 auto rc = write(fd, jsonString.c_str(), jsonString.size());
140 auto e = errno;
141 close(fd);
142 if (rc == 0)
143 {
144 log(fmt::format("Could not write to temp file {} errno {}", tmpFile,
145 e),
146 Logger::error);
147 throw std::runtime_error{"Could not write to "s + path.string()};
148 }
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