1//! @file log.hpp
2//! @author ryftchen
3//! @brief The declarations (log) in the application module.
4//! @version 0.1.0
5//! @copyright Copyright (c) 2022-2025 ryftchen. All rights reserved.
6
7#pragma once
8
9#include "configure.hpp"
10
11#ifndef _PRECOMPILED_HEADER
12#include <forward_list>
13#include <iostream>
14#include <source_location>
15#include <syncstream>
16#else
17#include "application/pch/precompiled_header.hpp"
18#endif // _PRECOMPILED_HEADER
19
20#include "utility/include/common.hpp"
21#include "utility/include/fsm.hpp"
22#include "utility/include/io.hpp"
23
24//! @brief Log with debug level.
25#define LOG_DBG application::log::Holder<application::log::Log::OutputLevel::debug>()
26//! @brief Log with debug level (printf style).
27#define LOG_DBG_P(fmt, ...) \
28 application::log::printfStyle( \
29 application::log::Log::OutputLevel::debug, __FILE__, __LINE__, fmt __VA_OPT__(, ) __VA_ARGS__)
30//! @brief Log with debug level (format style).
31#define LOG_DBG_F(fmt, ...) \
32 application::log::formatStyle( \
33 application::log::Log::OutputLevel::debug, __FILE__, __LINE__, fmt __VA_OPT__(, ) __VA_ARGS__)
34//! @brief Log with info level.
35#define LOG_INF application::log::Holder<application::log::Log::OutputLevel::info>()
36//! @brief Log with info level (printf style).
37#define LOG_INF_P(fmt, ...) \
38 application::log::printfStyle( \
39 application::log::Log::OutputLevel::info, __FILE__, __LINE__, fmt __VA_OPT__(, ) __VA_ARGS__)
40//! @brief Log with info level (format style).
41#define LOG_INF_F(fmt, ...) \
42 application::log::formatStyle( \
43 application::log::Log::OutputLevel::info, __FILE__, __LINE__, fmt __VA_OPT__(, ) __VA_ARGS__)
44//! @brief Log with warning level.
45#define LOG_WRN application::log::Holder<application::log::Log::OutputLevel::warning>()
46//! @brief Log with warning level (printf style).
47#define LOG_WRN_P(fmt, ...) \
48 application::log::printfStyle( \
49 application::log::Log::OutputLevel::warning, __FILE__, __LINE__, fmt __VA_OPT__(, ) __VA_ARGS__)
50//! @brief Log with warning level (format style).
51#define LOG_WRN_F(fmt, ...) \
52 application::log::formatStyle( \
53 application::log::Log::OutputLevel::warning, __FILE__, __LINE__, fmt __VA_OPT__(, ) __VA_ARGS__)
54//! @brief Log with error level.
55#define LOG_ERR application::log::Holder<application::log::Log::OutputLevel::error>()
56//! @brief Log with error level (printf style).
57#define LOG_ERR_P(fmt, ...) \
58 application::log::printfStyle( \
59 application::log::Log::OutputLevel::error, __FILE__, __LINE__, fmt __VA_OPT__(, ) __VA_ARGS__)
60//! @brief Log with error level (format style).
61#define LOG_ERR_F(fmt, ...) \
62 application::log::formatStyle( \
63 application::log::Log::OutputLevel::error, __FILE__, __LINE__, fmt __VA_OPT__(, ) __VA_ARGS__)
64
65//! @brief The application module.
66namespace application // NOLINT(modernize-concat-nested-namespaces)
67{
68//! @brief Log-related functions in the application module.
69namespace log
70{
71//! @brief Directory of the source code.
72constexpr std::string_view sourceDirectory = "/foo/";
73
74//! @brief Logger.
75class Log final : public utility::fsm::FSM<Log>
76{
77public:
78 friend class FSM<Log>;
79 //! @brief Destroy the Log object.
80 ~Log() = default;
81 //! @brief Construct a new Log object.
82 Log(const Log&) = delete;
83 //! @brief Construct a new Log object.
84 Log(Log&&) = delete;
85 //! @brief The operator (=) overloading of Log class.
86 //! @return reference of the Log object
87 Log& operator=(const Log&) = delete;
88 //! @brief The operator (=) overloading of Log class.
89 //! @return reference of the Log object
90 Log& operator=(Log&&) = delete;
91
92 //! @brief Instance name.
93 static constexpr std::string name{configure::field::logger};
94 //! @brief Get the Log instance.
95 //! @return reference of the Log object
96 static std::shared_ptr<Log> getInstance();
97 //! @brief Service for running.
98 void service();
99
100 //! @brief Enumerate specific states for FSM.
101 enum State : std::uint8_t
102 {
103 //! @brief Initial.
104 initial,
105 //! @brief Active.
106 active,
107 //! @brief Established.
108 established,
109 //! @brief Inactive.
110 inactive,
111 //! @brief Idle.
112 idle
113 };
114 //! @brief Access for the instance.
115 class Access
116 {
117 public:
118 //! @brief Wait for the logger to start. Interface controller for external use.
119 void startup() const;
120 //! @brief Wait for the logger to stop. Interface controller for external use.
121 void shutdown() const;
122 //! @brief Request to reset the logger. Interface controller for external use.
123 void reload() const;
124
125 //! @brief Preview the log.
126 //! @param peeking - further handling for peeking
127 void onPreviewing(const std::function<void(const std::string&)>& peeking) const;
128
129 private:
130 //! @brief Instance to be accessed.
131 const std::shared_ptr<Log> inst{getInstance()};
132
133 //! @brief Wait until the logger reaches the target state.
134 //! @param state - target state
135 //! @param handling - handling if unexpected state
136 void waitOr(const State state, const std::function<void()>& handling) const;
137 //! @brief Notify the logger to change the state.
138 //! @param action - action to be executed
139 void notifyVia(const std::function<void()>& action) const;
140 //! @brief Keep countdown if the logger does not meet the condition in time.
141 //! @param condition - condition of countdown
142 //! @param handling - handling if timeout
143 void countdownIf(const std::function<bool()>& condition, const std::function<void()>& handling) const;
144 };
145
146 //! @brief Enumerate specific output levels.
147 enum class OutputLevel : std::uint8_t
148 {
149 //! @brief Debug.
150 debug,
151 //! @brief Info.
152 info,
153 //! @brief Warning.
154 warning,
155 //! @brief Error.
156 error
157 };
158 //! @brief Enumerate specific output modes.
159 enum class OutputMode : std::uint8_t
160 {
161 //! @brief Append.
162 append,
163 //! @brief Overwrite.
164 overwrite
165 };
166 //! @brief Enumerate specific output types.
167 enum class OutputType : std::uint8_t
168 {
169 //! @brief File.
170 file,
171 //! @brief Terminal.
172 terminal,
173 //! @brief All.
174 all
175 };
176 template <typename... Args>
177 friend void printfStyle(
178 const OutputLevel severity,
179 const std::string_view srcFile,
180 const std::uint32_t srcLine,
181 const std::string& format,
182 Args&&... args);
183 template <typename... Args>
184 friend void formatStyle(
185 const OutputLevel severity,
186 const std::string_view srcFile,
187 const std::uint32_t srcLine,
188 const std::string& format,
189 Args&&... args);
190 static_assert((sourceDirectory.front() == '/') && (sourceDirectory.back() == '/'));
191
192private:
193 //! @brief Construct a new Log object.
194 Log();
195
196 //! @brief Full path to the log file.
197 const std::string filePath{getFullLogPath(filename: configure::detail::filePath4Logger())};
198 //! @brief Priority level.
199 const OutputLevel priorityLevel{static_cast<OutputLevel>(configure::detail::priorityLevel4Logger())};
200 //! @brief Target type.
201 const OutputType targetType{static_cast<OutputType>(configure::detail::targetType4Logger())};
202 //! @brief Write mode.
203 const OutputMode writeMode{static_cast<OutputMode>(configure::detail::writeMode4Logger())};
204 //! @brief Timeout period (ms) to waiting for the logger to change to the target state.
205 const std::uint32_t timeoutPeriod{static_cast<std::uint32_t>(configure::detail::helperTimeout())};
206 //! @brief The queue of logs.
207 std::queue<std::string> logQueue;
208 //! @brief Mutex for controlling daemon.
209 mutable std::mutex daemonMtx;
210 //! @brief The synchronization condition for daemon. Use with daemonMtx.
211 std::condition_variable daemonCond;
212 //! @brief Flag to indicate whether it is logging.
213 std::atomic_bool isOngoing{false};
214 //! @brief Flag for rollback request.
215 std::atomic_bool inResetting{false};
216 //! @brief Writer of the log content.
217 utility::io::FileWriter logWriter{filePath};
218 //! @brief Operation lock for the log file.
219 mutable utility::common::ReadWriteLock fileLock;
220 //! @brief The cache logs that could not be processed properly.
221 std::forward_list<std::string> unprocessedCache;
222 //! @brief Spin lock for controlling cache.
223 mutable utility::common::SpinLock cacheSwitch;
224
225 //! @brief Alias for the lock guard.
226 using LockGuard = utility::common::LockGuard;
227 //! @brief Alias for the lock mode.
228 using LockMode = utility::common::ReadWriteLock::LockMode;
229 //! @brief Flush log to queue.
230 //! @param severity - level of severity
231 //! @param labelTpl - label template
232 //! @param formatted - formatted body
233 void flush(const OutputLevel severity, const std::string_view labelTpl, const std::string_view formatted);
234 //! @brief Create the dynamic label template.
235 //! @param srcFile - current code file
236 //! @param srcLine - current code line
237 //! @return dynamic label template
238 static std::string createLabelTemplate(const std::string_view srcFile, const std::uint32_t srcLine);
239 //! @brief Get the prefix corresponding to the level.
240 //! @param level - output level
241 //! @return output prefix
242 static std::string_view getPrefix(const OutputLevel level);
243 //! @brief Reformat log contents.
244 //! @param label - label information
245 //! @param formatted - formatted body
246 //! @return log contents
247 static std::vector<std::string> reformatContents(const std::string_view label, const std::string_view formatted);
248
249 //! @brief Check whether it is in the uninterrupted serving state.
250 //! @param state - target state
251 //! @return in the uninterrupted serving state or not
252 bool isInServingState(const State state) const;
253 //! @brief Get the full path to the log file.
254 //! @param filename - log file path
255 //! @return full path to the log file
256 static std::string getFullLogPath(const std::string_view filename = "log/foo.log");
257 //! @brief Try to create the log folder.
258 void tryCreateLogFolder() const;
259 //! @brief Back up the log file if needed.
260 void backUpLogFileIfNeeded() const;
261 //! @brief Try to clear the log file.
262 void tryClearLogFile() const;
263 //! @brief FSM event. Open file.
264 struct OpenFile
265 {
266 };
267 //! @brief FSM event. Close file.
268 struct CloseFile
269 {
270 };
271 //! @brief FSM event. Go logging.
272 struct GoLogging
273 {
274 };
275 //! @brief FSM event. No logging.
276 struct NoLogging
277 {
278 };
279 //! @brief FSM event. Standby.
280 struct Standby
281 {
282 };
283 //! @brief FSM event. Relaunch.
284 struct Relaunch
285 {
286 };
287 //! @brief Open the log file.
288 void openLogFile();
289 //! @brief Close the log file.
290 void closeLogFile();
291 //! @brief Start logging.
292 void startLogging();
293 //! @brief Stop logging.
294 void stopLogging();
295 //! @brief Do toggle.
296 void doToggle();
297 //! @brief Do rollback.
298 void doRollback();
299 //! @brief Check whether the log file is opened.
300 //! @param event - FSM event
301 //! @return whether the log file is open or not
302 bool isLogFileOpened(const GoLogging& event) const;
303 //! @brief Check whether the log file is closed.
304 //! @param event - FSM event
305 //! @return whether the log file is close or not
306 bool isLogFileClosed(const NoLogging& event) const;
307 // clang-format off
308 //! @brief Alias for the transition table of the logger.
309 using TransitionTable = Table
310 <
311 // +------ Source ------+-- Event --+------ Target ------+------ Action ------+-------- Guard --------+
312 // +--------------------+-----------+--------------------+--------------------+-----------------------+
313 Row< State::initial , OpenFile , State::active , &Log::openLogFile >,
314 Row< State::active , GoLogging , State::established , &Log::startLogging , &Log::isLogFileOpened >,
315 Row< State::established , CloseFile , State::active , &Log::closeLogFile >,
316 Row< State::active , NoLogging , State::inactive , &Log::stopLogging , &Log::isLogFileClosed >,
317 Row< State::initial , Standby , State::idle , &Log::doToggle >,
318 Row< State::active , Standby , State::idle , &Log::doToggle >,
319 Row< State::established , Standby , State::idle , &Log::doToggle >,
320 Row< State::established , Relaunch , State::initial , &Log::doRollback >,
321 Row< State::inactive , Relaunch , State::initial , &Log::doRollback >,
322 Row< State::idle , Relaunch , State::initial , &Log::doRollback >
323 // +--------------------+-----------+--------------------+--------------------+-----------------------+
324 >;
325 // clang-format on
326 //! @brief The notification loop.
327 void notificationLoop();
328 //! @brief Await notification to proceed.
329 void awaitNotification2Proceed();
330 //! @brief Await notification to retry.
331 //! @return whether retry is required or not
332 bool awaitNotification2Retry();
333
334protected:
335 friend std::ostream& operator<<(std::ostream& os, const State state);
336};
337
338//! @brief Log output for legacy (printf style).
339//! @tparam Args - type of arguments of log format
340//! @param severity - level of severity
341//! @param srcFile - current code file
342//! @param srcLine - current code line
343//! @param format - log format to be flushed
344//! @param args - arguments of log format
345template <typename... Args>
346void printfStyle(
347 const Log::OutputLevel severity,
348 const std::string_view srcFile,
349 const std::uint32_t srcLine,
350 const std::string& format,
351 Args&&... args)
352{
353 if (configure::detail::activateHelper()) [[likely]]
354 {
355 Log::getInstance()->flush(
356 severity,
357 labelTpl: Log::createLabelTemplate(srcFile, srcLine),
358 formatted: utility::common::printfString(fmt: format.c_str(), std::forward<Args>(args)...));
359 return;
360 }
361
362 const auto rows = Log::reformatContents(
363 label: std::string{sourceDirectory.substr(pos: 1, n: sourceDirectory.length() - 2)} + ": ",
364 formatted: utility::common::printfString(fmt: format.c_str(), std::forward<Args>(args)...));
365 std::for_each(
366 rows.cbegin(), rows.cend(), [](const auto& output) { std::osyncstream(std::clog) << output << std::endl; });
367}
368
369//! @brief Log output for modern (format style).
370//! @tparam Args - type of arguments of log format
371//! @param severity - level of severity
372//! @param srcFile - current code file
373//! @param srcLine - current code line
374//! @param format - log format to be flushed
375//! @param args - arguments of log format
376template <typename... Args>
377void formatStyle(
378 const Log::OutputLevel severity,
379 const std::string_view srcFile,
380 const std::uint32_t srcLine,
381 const std::string& format,
382 Args&&... args)
383{
384 if (configure::detail::activateHelper()) [[likely]]
385 {
386 Log::getInstance()->flush(
387 severity,
388 labelTpl: Log::createLabelTemplate(srcFile, srcLine),
389 formatted: utility::common::formatString(format, std::forward<Args>(args)...));
390 return;
391 }
392
393 const auto rows = Log::reformatContents(
394 label: std::string{sourceDirectory.substr(pos: 1, n: sourceDirectory.length() - 2)} + ": ",
395 formatted: utility::common::formatString(format, std::forward<Args>(args)...));
396 std::for_each(
397 rows.cbegin(), rows.cend(), [](const auto& output) { std::osyncstream(std::clog) << output << std::endl; });
398}
399
400//! @brief Log holder for flushing.
401//! @tparam Lv - output level
402template <Log::OutputLevel Lv>
403class Holder final
404{
405public:
406 //! @brief Construct a new Holder object.
407 //! @param srcLoc - current source location
408 explicit Holder(const std::source_location& srcLoc = std::source_location::current()) : location{srcLoc} {}
409 //! @brief Destroy the Holder object.
410 ~Holder();
411 //! @brief Construct a new Holder object.
412 Holder(const Holder&) = default;
413 //! @brief Construct a new Holder object.
414 Holder(Holder&&) noexcept = default;
415 //! @brief The operator (=) overloading of Holder class.
416 //! @return reference of the Holder object
417 Holder& operator=(const Holder&) = default;
418 //! @brief The operator (=) overloading of Holder class.
419 //! @return reference of the Holder object
420 Holder& operator=(Holder&&) noexcept = default;
421
422 //! @brief The operator (=) overloading of Holder class.
423 //! @tparam Input - type of input content
424 //! @param input - input content
425 //! @return reference of the Holder object
426 template <typename Input>
427 Holder& operator<<(Input&& input);
428
429private:
430 //! @brief Source location.
431 const std::source_location location;
432 //! @brief Output stream for flushing.
433 std::ostringstream buffer;
434};
435
436template <Log::OutputLevel Lv>
437template <typename Input>
438Holder<Lv>& Holder<Lv>::operator<<(Input&& input)
439{
440 buffer << std::forward<Input>(input);
441 return *this;
442}
443
444extern template class Holder<Log::OutputLevel::debug>;
445extern template class Holder<Log::OutputLevel::info>;
446extern template class Holder<Log::OutputLevel::warning>;
447extern template class Holder<Log::OutputLevel::error>;
448
449extern std::string& changeToLogStyle(std::string& line);
450} // namespace log
451} // namespace application
452