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