1 #pragma once
2 
3 #include "utility.hpp"
4 
5 #include <unistd.h>
6 
7 #include <nlohmann/json.hpp>
8 #include <phosphor-logging/log.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&) = 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      */
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             phosphor::logging::log<phosphor::logging::level::ERR>(
80                 message.c_str());
81         }
82         else if (priority != Logger::quiet)
83         {
84             phosphor::logging::log<phosphor::logging::level::INFO>(
85                 message.c_str());
86         }
87 
88         if (_entries.size() == _maxEntries)
89         {
90             _entries.erase(_entries.begin());
91         }
92 
93         // Generate a timestamp
94         auto t = std::time(nullptr);
95         auto tm = *std::localtime(&t);
96 
97         // e.g. Sep 22 19:56:32
98         auto timestamp = std::put_time(&tm, "%b %d %H:%M:%S");
99 
100         std::ostringstream stream;
101         stream << timestamp;
102         _entries.emplace_back(stream.str(), message);
103     }
104 
105     /**
106      * @brief Returns the entries in a JSON array
107      *
108      * @return JSON
109      */
getLogs() const110     const nlohmann::json getLogs() const
111     {
112         return _entries;
113     }
114 
115     /**
116      * @brief Writes the data to a temporary file and returns the path
117      *        to it.
118      *
119      * Uses a temp file because the only use case for this is for sending
120      * in to an event log where a temp file makes sense, and frankly it
121      * was just easier to encapsulate everything here.
122      *
123      * @return path - The path to the file.
124      */
saveToTempFile()125     std::filesystem::path saveToTempFile()
126     {
127         using namespace std::literals::string_literals;
128 
129         char tmpFile[] = "/tmp/loggertemp.XXXXXX";
130         util::FileDescriptor fd{mkstemp(tmpFile)};
131         if (fd() == -1)
132         {
133             throw std::runtime_error{"mkstemp failed!"};
134         }
135 
136         std::filesystem::path path{tmpFile};
137 
138         for (const auto& [time, message] : _entries)
139         {
140             auto line = std::format("{}: {}\n", time, message);
141             auto rc = write(fd(), line.data(), line.size());
142             if (rc == -1)
143             {
144                 auto e = errno;
145                 auto msg = std::format(
146                     "Could not write to temp file {} errno {}", tmpFile, e);
147                 log(msg, Logger::error);
148                 throw std::runtime_error{msg};
149             }
150         }
151 
152         return std::filesystem::path{tmpFile};
153     }
154 
155     /**
156      * @brief Deletes all log entries
157      */
clear()158     void clear()
159     {
160         _entries.clear();
161     }
162 
163   private:
164     /**
165      * @brief The maximum number of entries to hold
166      */
167     const size_t _maxEntries;
168 
169     /**
170      * @brief The vector of <timestamp, message> entries
171      */
172     std::vector<LogEntry> _entries;
173 };
174 
175 } // namespace phosphor::fan
176