xref: /openbmc/openpower-vpd-parser/vpd-manager/include/logger.hpp (revision 0384502076a5c09dd156d360a5227e523a8da2d7)
1 #pragma once
2 
3 #include "types.hpp"
4 
5 #include <condition_variable>
6 #include <filesystem>
7 #include <fstream>
8 #include <iostream>
9 #include <memory>
10 #include <mutex>
11 #include <queue>
12 #include <source_location>
13 #include <string_view>
14 
15 namespace vpd
16 {
17 
18 /**
19  * @brief Enum class defining placeholder tags.
20  *
21  * The tag will be used by APIs to identify the endpoint for a given log
22  * message.
23  */
24 enum class PlaceHolder
25 {
26     DEFAULT,    /* logs to the journal */
27     PEL,        /* Creates a PEL */
28     COLLECTION, /* Logs collection messages */
29     VPD_WRITE   /* Logs VPD write details */
30 };
31 
32 /**
33  * @brief Class to handle file operations w.r.t logging.
34  * Based on the placeholder the class will handle different file operations to
35  * log error messages.
36  */
37 class ILogFileHandler
38 {
39   protected:
40     // absolute file path of log file
41     std::filesystem::path m_filePath{};
42 
43     // max number of log entries in file
44     size_t m_maxEntries{256};
45 
46     // file stream object to do file operations
47     std::fstream m_fileStream;
48 
49     // current number of log entries in file
50     size_t m_currentNumEntries{0};
51 
52     // number of chars in a single log entry
53     size_t m_logEntrySize{512};
54 
55     /**
56      * @brief API to rotate file.
57      *
58      * This API rotates the logs within a file by repositioning the write file
59      * pointer to beginning of file. Rotation is achieved by overwriting the
60      * oldest log entries starting from the top of the file.
61      *
62      * @throw std::ios_base::failure
63      */
64     virtual inline void rotateFile()
65     {
66         // reset file pointer to beginning of file
67         m_fileStream.seekp(0);
68         m_currentNumEntries = 0;
69     }
70 
71     /**
72      * @brief Constructor.
73      * Private so that can't be initialized by class(es) other than friends.
74      *
75      * @param[in] i_filePath - Absolute path of the log file.
76      * @param[in] i_maxEntries - Maximum number of entries in the log file after
77      * which the file will be rotated.
78      */
79     ILogFileHandler(const std::filesystem::path& i_filePath,
80                     const size_t i_maxEntries);
81 
82     /**
83      * @brief API to generate timestamp in string format.
84      *
85      * @return Returns timestamp in string format on success, otherwise returns
86      * empty string in case of any error.
87      */
88     static inline std::string timestamp() noexcept
89     {
90         try
91         {
92             const auto l_now = std::chrono::system_clock::now();
93             const auto l_in_time_t =
94                 std::chrono::system_clock::to_time_t(l_now);
95             const auto l_ms =
96                 std::chrono::duration_cast<std::chrono::milliseconds>(
97                     l_now.time_since_epoch()) %
98                 1000;
99 
100             std::stringstream l_ss;
101             l_ss << std::put_time(std::localtime(&l_in_time_t),
102                                   "%Y-%m-%d %H:%M:%S")
103                  << "." << std::setfill('0') << std::setw(3) << l_ms.count();
104             return l_ss.str();
105         }
106         catch (const std::exception& l_ex)
107         {
108             return std::string{};
109         }
110     }
111 
112   public:
113     // deleted methods
114     ILogFileHandler() = delete;
115     ILogFileHandler(const ILogFileHandler&) = delete;
116     ILogFileHandler(const ILogFileHandler&&) = delete;
117     ILogFileHandler operator=(const ILogFileHandler&) = delete;
118     ILogFileHandler operator=(const ILogFileHandler&&) = delete;
119 
120     /**
121      * @brief API to log a message to file.
122      *
123      * @param[in] i_message - Message to log.
124      *
125      * @throw std::runtime_error
126      */
127     virtual void logMessage(
128         [[maybe_unused]] const std::string_view& i_message) = 0;
129 
130     // destructor
131     virtual ~ILogFileHandler()
132     {
133         if (m_fileStream.is_open())
134         {
135             m_fileStream.close();
136         }
137     }
138 };
139 
140 /**
141  * @brief A class to handle logging messages to file synchronously
142  *
143  * This class handles logging messages to a specific file in a synchronous
144  * manner.
145  * Note: The logMessage API of this class is not multi-thread safe.
146  */
147 class SyncFileLogger final : public ILogFileHandler
148 {
149     /**
150      * @brief Parameterized constructor.
151      * Private so that can't be initialized by class(es) other than friends.
152      *
153      * @param[in] i_filePath - Absolute path of the log file.
154      * @param[in] i_maxEntries - Maximum number of entries in the log file after
155      * which the file will be rotated.
156      */
157     SyncFileLogger(const std::filesystem::path& i_filePath,
158                    const size_t i_maxEntries) :
159         ILogFileHandler(i_filePath, i_maxEntries)
160     {}
161 
162   public:
163     // Friend class Logger.
164     friend class Logger;
165 
166     // deleted methods
167     SyncFileLogger() = delete;
168     SyncFileLogger(const SyncFileLogger&) = delete;
169     SyncFileLogger(const SyncFileLogger&&) = delete;
170     SyncFileLogger operator=(const SyncFileLogger&) = delete;
171     SyncFileLogger operator=(const SyncFileLogger&&) = delete;
172 
173     /**
174      * @brief API to log a message to file
175      *
176      * This API logs messages to file in a synchronous manner.
177      * Note: This API is not multi-thread safe.
178      *
179      * @param[in] i_message - Message to log
180      *
181      * @throw std::runtime_error
182      */
183     void logMessage(const std::string_view& i_message) override;
184 
185     // destructor
186     ~SyncFileLogger() = default;
187 };
188 
189 /**
190  * @brief A class to handle asynchronous logging of messages to file
191  *
192  * This class implements methods to log messages asynchronously to a desired
193  * file in the filesystem. It uses a queue for buffering the messages from
194  * caller. The actual file operations are handled by a worker thread.
195  */
196 class AsyncFileLogger final : public ILogFileHandler
197 {
198     // queue for log messages
199     std::queue<std::string> m_messageQueue;
200 
201     // mutex to control access to log message queue
202     std::mutex m_mutex;
203 
204     // flag which indicates log worker thread if logging is finished
205     std::atomic_bool m_stopLogging{false};
206 
207     // conditional variable to signal log worker thread
208     std::condition_variable m_cv;
209 
210     /**
211      * @brief Constructor
212      * Private so that can't be initialized by class(es) other than friends.
213      *
214      * @param[in] i_fileName - Name of the log file
215      * @param[in] i_maxEntries - Maximum number of entries in the log file after
216      * which the file will be rotated
217      */
218     AsyncFileLogger(const std::filesystem::path& i_fileName,
219                     const size_t i_maxEntries) :
220         ILogFileHandler(i_fileName, i_maxEntries)
221     {
222         // start worker thread in detached mode
223         std::thread{[this]() { this->fileWorker(); }}.detach();
224     }
225 
226     /**
227      * @brief Logger worker thread body
228      */
229     void fileWorker() noexcept;
230 
231   public:
232     // Friend class Logger.
233     friend class Logger;
234 
235     // deleted methods
236     AsyncFileLogger() = delete;
237     AsyncFileLogger(const AsyncFileLogger&) = delete;
238     AsyncFileLogger(const AsyncFileLogger&&) = delete;
239     AsyncFileLogger operator=(const AsyncFileLogger&) = delete;
240     AsyncFileLogger operator=(const AsyncFileLogger&&) = delete;
241 
242     /**
243      * @brief API to log a message to file
244      *
245      * This API logs given message to a file. This API is multi-thread safe.
246      *
247      * @param[in] i_message - Message to log
248      *
249      * @throw std::runtime_error
250      */
251     void logMessage(const std::string_view& i_message) override;
252 
253     // destructor
254     ~AsyncFileLogger()
255     {
256         std::unique_lock<std::mutex> l_lock(m_mutex);
257 
258         m_stopLogging = true;
259 
260         if (m_fileStream.is_open())
261         {
262             m_fileStream.close();
263         }
264 
265         m_cv.notify_one();
266     }
267 };
268 
269 /**
270  * @brief Singleton class to handle error logging for the repository.
271  */
272 class Logger
273 {
274   public:
275     /**
276      * @brief Deleted Methods
277      */
278     Logger(const Logger&) = delete;            // Copy constructor
279     Logger(const Logger&&) = delete;           // Move constructor
280     Logger operator=(const Logger&) = delete;  // Copy assignment operator
281     Logger operator=(const Logger&&) = delete; // Move assignment operator
282 
283     /**
284      * @brief Method to get instance of Logger class.
285      */
286     static std::shared_ptr<Logger> getLoggerInstance()
287     {
288         if (!m_loggerInstance)
289         {
290             m_loggerInstance = std::shared_ptr<Logger>(new Logger());
291         }
292         return m_loggerInstance;
293     }
294 
295     /**
296      * @brief API to log a given error message.
297      *
298      * @param[in] i_message - Message to be logged.
299      * @param[in] i_placeHolder - States where the message needs to be logged.
300      * Default is journal.
301      * @param[in] i_pelTuple - A structure only required in case message needs
302      * to be logged as PEL.
303      * @param[in] i_location - Locatuon from where message needs to be logged.
304      */
305     void logMessage(std::string_view i_message,
306                     const PlaceHolder& i_placeHolder = PlaceHolder::DEFAULT,
307                     const types::PelInfoTuple* i_pelTuple = nullptr,
308                     const std::source_location& i_location =
309                         std::source_location::current());
310 
311     /**
312      * @brief API to initiate VPD collection logging.
313      *
314      * This API initiates VPD collection logging. It checks for existing
315      * collection log files and if 3 such files are found, it deletes the oldest
316      * file and initiates a VPD collection logger object, so that every new VPD
317      * collection flow always gets logged into a new file.
318      */
319     void initiateVpdCollectionLogging() noexcept;
320 
321     /**
322      * @brief API to terminate VPD collection logging.
323      *
324      * This API terminates the VPD collection logging by destroying the
325      * associated VPD collection logger object.
326      */
327     void terminateVpdCollectionLogging() noexcept
328     {
329         m_collectionLogger.reset();
330     }
331 
332   private:
333     /**
334      * @brief Constructor
335      */
336     Logger() : m_vpdWriteLogger(nullptr), m_collectionLogger(nullptr) {}
337 
338     // Instance to the logger class.
339     static std::shared_ptr<Logger> m_loggerInstance;
340 
341     // logger object to handle VPD write logs
342     std::unique_ptr<ILogFileHandler> m_vpdWriteLogger;
343 
344     // logger object to handle VPD collection logs
345     std::unique_ptr<ILogFileHandler> m_collectionLogger;
346 };
347 
348 /**
349  * @brief The namespace defines logging related methods for VPD.
350  * Only for backward compatibility till new logger class comes up.
351  */
352 namespace logging
353 {
354 
355 /**
356  * @brief An api to log message.
357  * This API should be called to log message. It will auto append information
358  * like file name, line and function name to the message being logged.
359  *
360  * @param[in] message - Information that we want  to log.
361  * @param[in] location - Object of source_location class.
362  */
363 void logMessage(std::string_view message, const std::source_location& location =
364                                               std::source_location::current());
365 } // namespace logging
366 } // namespace vpd
367