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