HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
log.hpp
1// Copyright Take Vos 2021-2022.
2// Distributed under the Boost Software License, Version 1.0.
3// (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
4
5#pragma once
6
7#include "../atomic.hpp"
8#include "../delayed_format.hpp"
9#include "../format_check.hpp"
10#include "../container/module.hpp"
11#include "../time/module.hpp"
12#include "../utility/module.hpp"
13#include "../concurrency/module.hpp"
14#include "counters.hpp"
15#include <chrono>
16#include <format>
17#include <string>
18#include <string_view>
19#include <tuple>
20#include <mutex>
21#include <atomic>
22#include <memory>
23#include <thread>
24#include <filesystem>
25
26namespace hi { inline namespace v1 {
27namespace detail {
28
30public:
31 hi_force_inline log_message_base() noexcept = default;
32 virtual ~log_message_base() = default;
33
34 [[nodiscard]] virtual std::string format() const noexcept = 0;
35 [[nodiscard]] virtual std::unique_ptr<log_message_base> make_unique_copy() const noexcept = 0;
36
37public:
38 static inline std::chrono::time_zone const *zone = nullptr;
39};
40
41template<global_state_type Level, fixed_string SourcePath, int SourceLine, fixed_string Fmt, typename... Values>
43public:
44 static_assert(std::popcount(std::to_underlying(Level)) == 1);
45
46 // clang-format off
47 static constexpr char const *log_level_name =
48 Level == global_state_type::log_fatal ? "fatal" :
49 Level == global_state_type::log_error ? "error" :
50 Level == global_state_type::log_warning ? "warning" :
51 Level == global_state_type::log_info ? "info" :
52 Level == global_state_type::log_debug ? "debug" :
53 Level == global_state_type::log_trace ? "trace" :
54 Level == global_state_type::log_audit ? "audit" :
55 Level == global_state_type::log_statistics ? "stats" :
56 "<unknown log level>";
57 // clang-format on
58
59 log_message(log_message const&) noexcept = default;
60 log_message& operator=(log_message const&) noexcept = default;
61
62 template<typename... Args>
63 hi_force_inline log_message(Args&&...args) noexcept :
64 _time_stamp(time_stamp_count::inplace_with_thread_id{}), _what(std::forward<Args>(args)...)
65 {
66 }
67
68 std::string format() const noexcept override
69 {
70 hilet utc_time_point = time_stamp_utc::make(_time_stamp);
71 hilet sys_time_point = std::chrono::clock_cast<std::chrono::system_clock>(utc_time_point);
72 hilet local_time_point = zone->to_local(sys_time_point);
73
74 hilet cpu_id = _time_stamp.cpu_id();
75 hilet thread_id = _time_stamp.thread_id();
76 hilet thread_name = get_thread_name(thread_id);
77
78 if constexpr (to_bool(Level & global_state_type::log_statistics)) {
79 return std::format("{} {}({}) {:5} {}\n", local_time_point, thread_name, cpu_id, log_level_name, _what());
80 } else {
81 auto source_filename = std::filesystem::path{static_cast<std::string_view>(SourcePath)}.filename().generic_string();
82 return std::format(
83 "{} {}({}) {:5} {} ({}:{})\n",
84 local_time_point,
85 thread_name,
86 cpu_id,
87 log_level_name,
88 _what(),
89 source_filename,
90 SourceLine);
91 }
92 }
93
94 [[nodiscard]] std::unique_ptr<log_message_base> make_unique_copy() const noexcept override
95 {
96 return std::make_unique<log_message>(*this);
97 }
98
99private:
100 time_stamp_count _time_stamp;
101 delayed_format<Fmt, Values...> _what;
102};
103
104} // namespace detail
105
106class log {
107public:
115 template<global_state_type Level, fixed_string SourcePath, int SourceLine, fixed_string Fmt, typename... Args>
116 hi_force_inline void add(Args&&...args) noexcept
117 {
118 static_assert(std::popcount(std::to_underlying(Level)) == 1);
119
120 hilet state = global_state.load(std::memory_order::relaxed);
121 if (not to_bool(state & Level)) {
122 return;
123 }
124
125 // Add messages in the queue, block when full.
126 // * This reduces amount of instructions needed to be executed during logging.
127 // * Simplifies logged_fatal_message logic.
128 // * Will make sure everything gets logged.
129 // * Blocking is bad in a real time thread, so maybe count the number of times it is blocked.
130
131 // Emplace a message directly on the queue.
133 std::forward<Args>(args)...);
134
135 if (to_bool(Level & global_state_type::log_fatal) or not to_bool(state & global_state_type::log_is_running)) {
136 // If the logger did not start we will log in degraded mode and log from the current thread.
137 // On fatal error we also want to log from the current thread.
138 [[unlikely]] flush();
139 }
140 }
141
146 hi_no_inline void flush() noexcept;
147
155 static bool start_subsystem(global_state_type log_level = global_state_type::log_level_default)
156 {
157 set_log_level(log_level);
158 return hi::start_subsystem(global_state_type::log_is_running, log::subsystem_init, log::subsystem_deinit);
159 }
160
164 static void stop_subsystem()
165 {
166 return hi::stop_subsystem(log::subsystem_deinit);
167 }
168
169private:
172 wfree_fifo<detail::log_message_base, 64> _fifo;
173 mutable unfair_mutex _mutex;
174
179 void write(std::string const& str) const noexcept;
180
183 static inline std::jthread _log_thread;
184
187 static void log_thread_main(std::stop_token stop_token) noexcept;
188
191 static void subsystem_deinit() noexcept;
192
198 static bool subsystem_init() noexcept;
199};
200
201inline log log_global;
202
203}} // namespace hi::v1
204
205#define hi_log(level, fmt, ...) \
206 hi_format_check(fmt __VA_OPT__(, ) __VA_ARGS__); \
207 ::hi::log_global.add<level, __FILE__, __LINE__, fmt>(__VA_ARGS__)
208
209#define hi_log_debug(fmt, ...) hi_log(::hi::global_state_type::log_debug, fmt __VA_OPT__(, ) __VA_ARGS__)
210#define hi_log_info(fmt, ...) hi_log(::hi::global_state_type::log_info, fmt __VA_OPT__(, ) __VA_ARGS__)
211#define hi_log_statistics(fmt, ...) hi_log(::hi::global_state_type::log_statistics, fmt __VA_OPT__(, ) __VA_ARGS__)
212#define hi_log_trace(fmt, ...) hi_log(::hi::global_state_type::log_trace, fmt __VA_OPT__(, ) __VA_ARGS__)
213#define hi_log_audit(fmt, ...) hi_log(::hi::global_state_type::log_audit, fmt __VA_OPT__(, ) __VA_ARGS__)
214#define hi_log_warning(fmt, ...) hi_log(::hi::global_state_type::log_warning, fmt __VA_OPT__(, ) __VA_ARGS__)
215#define hi_log_error(fmt, ...) hi_log(::hi::global_state_type::log_error, fmt __VA_OPT__(, ) __VA_ARGS__)
216#define hi_log_fatal(fmt, ...) \
217 hi_log(::hi::global_state_type::log_fatal, fmt __VA_OPT__(, ) __VA_ARGS__); \
218 hi_debug_abort()
219
220#define hi_log_info_once(name, fmt, ...) \
221 do { \
222 if (++::hi::global_counter<name> == 1) { \
223 hi_log(::hi::global_state_type::log_info, fmt __VA_OPT__(, ) __VA_ARGS__); \
224 } \
225 } while (false)
226
227#define hi_log_error_once(name, fmt, ...) \
228 do { \
229 if (++::hi::global_counter<name> == 1) { \
230 hi_log(::hi::global_state_type::log_error, fmt __VA_OPT__(, ) __VA_ARGS__); \
231 } \
232 } while (false)
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
std::string get_thread_name(thread_id id) noexcept
Get the thread name of a thread id.
T::value_type start_subsystem(T &check_variable, typename T::value_type off_value, typename T::value_type(*init_function)(), void(*deinit_function)())
Start a sub-system.
Definition subsystem.hpp:113
std::atomic< global_state_type > global_state
The global state of the hikogui framework.
Definition global_state.hpp:198
global_state_type
The flag-type used for global state.
Definition global_state.hpp:27
void set_log_level(global_state_type log_level) noexcept
Set the logging level.
Definition global_state.hpp:227
void stop_subsystem(void(*deinit_function)())
Stop a sub-system.
Definition subsystem.hpp:199
@ write
Allow write access to a file.
DOXYGEN BUG.
Definition algorithm.hpp:13
geometry/margins.hpp
Definition cache.hpp:11
Definition cpu_id.hpp:21
Definition log.hpp:42
Definition log.hpp:106
static bool start_subsystem(global_state_type log_level=global_state_type::log_level_default)
Start the logger system.
Definition log.hpp:155
hi_force_inline void add(Args &&...args) noexcept
Log a message.
Definition log.hpp:116
static void stop_subsystem()
Stop the logger system.
Definition log.hpp:164
hi_no_inline void flush() noexcept
Flush all messages from the log_queue directly from this thread.