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