xref: /openbmc/phosphor-fan-presence/logger.hpp (revision 8a7fb53bbe61592d3fb54a32f2ac4d1a55095583)
1 #pragma once
2 
3 #include "utility.hpp"
4 
5 #include <unistd.h>
6 
7 #include <nlohmann/json.hpp>
8 #include <phosphor-logging/lg2.hpp>
9 
10 #include <cassert>
11 #include <ctime>
12 #include <filesystem>
13 #include <format>
14 #include <fstream>
15 #include <iomanip>
16 #include <sstream>
17 #include <string>
18 #include <vector>
19 
20 namespace 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  */
37 class Logger
38 {
39   public:
40     // timestamp, message
41     using LogEntry = std::tuple<std::string, std::string>;
42 
43     enum Priority
44     {
45         error,
46         info,
47         quiet
48     };
49 
50     Logger() = delete;
51     ~Logger() = default;
52     Logger(const Logger&) = delete;
53     Logger& operator=(const Logger&) = delete;
54     Logger(Logger&&) = delete;
55     Logger& operator=(Logger&&) = delete;
56 
57     /**
58      * @brief Constructor
59      *
60      * @param[in] maxEntries - The maximum number of log entries
61      *                         to keep.
62      */
Logger(size_t maxEntries)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      */
log(const std::string & message,Priority priority=Logger::info)75     void log(const std::string& message, Priority priority = Logger::info)
76     {
77         if (priority == Logger::error)
78         {
79             lg2::error("{MSG}", "MSG", message);
80         }
81         else if (priority != Logger::quiet)
82         {
83             lg2::info("{MSG}", "MSG", message);
84         }
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      */
getLogs() const108     const nlohmann::json getLogs() const
109     {
110         return _entries;
111     }
112 
113     /**
114      * @brief Writes the data to a temporary file and returns the path
115      *        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      */
saveToTempFile()123     std::filesystem::path saveToTempFile()
124     {
125         using namespace std::literals::string_literals;
126 
127         char tmpFile[] = "/tmp/loggertemp.XXXXXX";
128         util::FileDescriptor fd{mkstemp(tmpFile)};
129         if (fd() == -1)
130         {
131             throw std::runtime_error{"mkstemp failed!"};
132         }
133 
134         std::filesystem::path path{tmpFile};
135 
136         for (const auto& [time, message] : _entries)
137         {
138             auto line = std::format("{}: {}\n", time, message);
139             auto rc = write(fd(), line.data(), line.size());
140             if (rc == -1)
141             {
142                 auto e = errno;
143                 auto msg = std::format(
144                     "Could not write to temp file {} errno {}", tmpFile, e);
145                 log(msg, Logger::error);
146                 throw std::runtime_error{msg};
147             }
148         }
149 
150         return std::filesystem::path{tmpFile};
151     }
152 
153     /**
154      * @brief Deletes all log entries
155      */
clear()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
174