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 "delayed_format.hpp"
8#include "format_check.hpp"
9#include "../container/module.hpp"
10#include "../time/module.hpp"
11#include "../utility/utility.hpp"
12#include "../concurrency/concurrency.hpp"
13#include "../console/console.hpp"
14#include "../macros.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
26
27
28namespace hi { inline namespace v1 {
29namespace detail {
30
32public:
33 hi_force_inline log_message_base() noexcept = default;
34 virtual ~log_message_base() = default;
35
36 [[nodiscard]] virtual std::string format() const noexcept = 0;
37 [[nodiscard]] virtual std::unique_ptr<log_message_base> make_unique_copy() const noexcept = 0;
38};
39
42public:
43 static_assert(std::popcount(std::to_underlying(Level)) == 1);
44
45 // clang-format off
46 constexpr static char const *log_level_name =
47 Level == global_state_type::log_fatal ? "fatal" :
48 Level == global_state_type::log_error ? "error" :
49 Level == global_state_type::log_warning ? "warning" :
50 Level == global_state_type::log_info ? "info" :
51 Level == global_state_type::log_debug ? "debug" :
52 Level == global_state_type::log_trace ? "trace" :
53 Level == global_state_type::log_audit ? "audit" :
54 Level == global_state_type::log_statistics ? "stats" :
55 "<unknown log level>";
56 // clang-format on
57
58 log_message(log_message const&) noexcept = default;
59 log_message& operator=(log_message const&) noexcept = default;
60
61 template<typename... Args>
62 hi_force_inline log_message(Args&&...args) noexcept :
63 _time_stamp(time_stamp_count::inplace_with_thread_id{}), _what(std::forward<Args>(args)...)
64 {
65 }
66
67 std::string format() const noexcept override
68 {
69 hilet utc_time_point = time_stamp_utc::make(_time_stamp);
70 hilet sys_time_point = std::chrono::clock_cast<std::chrono::system_clock>(utc_time_point);
72
73 hilet cpu_id = _time_stamp.cpu_id();
74 hilet thread_id = _time_stamp.thread_id();
75 hilet thread_name = get_thread_name(thread_id);
76
77 if constexpr (to_bool(Level & global_state_type::log_statistics)) {
78 return std::format("{} {}({}) {:5} {}\n", local_time_point, thread_name, cpu_id, log_level_name, _what());
79 } else {
80 auto source_filename = std::filesystem::path{static_cast<std::string_view>(SourcePath)}.filename().generic_string();
81 return std::format(
82 "{} {}({}) {:5} {} ({}:{})\n",
85 cpu_id,
86 log_level_name,
87 _what(),
90 }
91 }
92
93 [[nodiscard]] std::unique_ptr<log_message_base> make_unique_copy() const noexcept override
94 {
95 return std::make_unique<log_message>(*this);
96 }
97
98private:
99 time_stamp_count _time_stamp;
100 delayed_format<Fmt, Values...> _what;
101};
102
103} // namespace detail
104
105class log {
106public:
115 hi_force_inline void add(Args&&...args) noexcept
116 {
117 static_assert(std::popcount(std::to_underlying(Level)) == 1);
118
119 hilet state = global_state.load(std::memory_order::relaxed);
120 if (not to_bool(state & Level)) {
121 return;
122 }
123
124 // Add messages in the queue, block when full.
125 // * This reduces amount of instructions needed to be executed during logging.
126 // * Simplifies logged_fatal_message logic.
127 // * Will make sure everything gets logged.
128 // * Blocking is bad in a real time thread, so maybe count the number of times it is blocked.
129
130 // Emplace a message directly on the queue.
132 std::forward<Args>(args)...);
133
134 if (to_bool(Level & global_state_type::log_fatal) or not to_bool(state & global_state_type::log_is_running)) {
135 // If the logger did not start we will log in degraded mode and log from the current thread.
136 // On fatal error we also want to log from the current thread.
137 [[unlikely]] flush();
138 }
139 }
140
145 hi_no_inline void flush() noexcept
146 {
147 bool wrote_message;
148 do {
150
151 {
152 hilet lock = std::scoped_lock(_mutex);
153
154 wrote_message = _fifo.take_one([&copy_of_message](auto& message) {
155 copy_of_message = message.make_unique_copy();
156 });
157 }
158
159 if (wrote_message) {
160 hi_assert_not_null(copy_of_message);
161 write(copy_of_message->format());
162 }
163 } while (wrote_message);
164 }
165
173 static bool start_subsystem(global_state_type log_level = global_state_type::log_level_default)
174 {
176 return hi::start_subsystem(global_state_type::log_is_running, log::subsystem_init, log::subsystem_deinit);
177 }
178
182 static void stop_subsystem()
183 {
184 return hi::stop_subsystem(log::subsystem_deinit);
185 }
186
187private:
191 mutable unfair_mutex _mutex;
192
197 void write(std::string const& str) const noexcept
198 {
199 print("{}", str);
200 }
201
204 static inline std::jthread _log_thread;
205
210 inline static void log_thread_main(std::stop_token stop_token) noexcept;
211
214 inline static void subsystem_deinit() noexcept;
215
221 static bool subsystem_init() noexcept
222 {
223 _log_thread = std::jthread(log_thread_main);
224 return true;
225 }
226};
227
228inline log log_global;
229
232inline void log::subsystem_deinit() noexcept
233{
234 if (global_state_disable(global_state_type::log_is_running)) {
235 if (_log_thread.joinable()) {
236 _log_thread.request_stop();
237 _log_thread.join();
238 }
239
240 log_global.flush();
241 }
242}
243
244}} // namespace hi::v1
std::string get_thread_name(thread_id id) noexcept
Get the thread name of a thread id.
Definition thread_intf.hpp:63
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:116
std::atomic< global_state_type > global_state
The global state of the hikogui framework.
Definition global_state.hpp:201
bool global_state_disable(global_state_type subsystem, std::memory_order order=std::memory_order::seq_cst) noexcept
Disable a subsystem.
Definition global_state.hpp:247
global_state_type
The flag-type used for global state.
Definition global_state.hpp:30
void set_log_level(global_state_type log_level) noexcept
Set the logging level.
Definition global_state.hpp:230
void stop_subsystem(void(*deinit_function)())
Stop a sub-system.
Definition subsystem.hpp:202
@ write
Allow write access to a file.
DOXYGEN BUG.
Definition algorithm.hpp:16
geometry/margins.hpp
Definition lookahead_iterator.hpp:5
hi_export void print(std::format_string< Args... > fmt, Args &&... args) noexcept
Output text to the console.
constexpr Out narrow_cast(In const &rhs) noexcept
Cast numeric values without loss of precision.
Definition cast.hpp:377
std::chrono::time_zone const & cached_current_zone() noexcept
Cached current time zone.
Definition time_zone.hpp:39
Definition cpu_id.hpp:24
Definition log.hpp:41
Definition log.hpp:105
static bool start_subsystem(global_state_type log_level=global_state_type::log_level_default)
Start the logger system.
Definition log.hpp:173
hi_force_inline void add(Args &&...args) noexcept
Log a message.
Definition log.hpp:115
static void stop_subsystem()
Stop the logger system.
Definition log.hpp:182
hi_no_inline void flush() noexcept
Flush all messages from the log_queue directly from this thread.
Definition log.hpp:145
A string which may be used as a none-type template parameter.
Definition fixed_string.hpp:41
T log(T... args)