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