xref: /openbmc/phosphor-fan-presence/logger.hpp (revision 9e9f599cece7fe6cdf99b1927a515fc8a552b7e9)
1 #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 
18 namespace 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  */
35 class 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
174