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 "time_stamp_count.hpp"
8#include "time_stamp_utc.hpp"
9#include "wfree_fifo.hpp"
10#include "atomic.hpp"
11#include "meta.hpp"
12#include "architecture.hpp"
13#include "delayed_format.hpp"
14#include "fixed_string.hpp"
15#include "subsystem.hpp"
16#include "global_state.hpp"
17#include "unfair_mutex.hpp"
18#include "debugger.hpp"
19#include "format_check.hpp"
20#include "thread.hpp"
21#include <chrono>
22#include <format>
23#include <string>
24#include <string_view>
25#include <tuple>
26#include <mutex>
27#include <atomic>
28#include <memory>
29#include <thread>
30#include <filesystem>
31
32namespace hi { inline namespace v1 {
33namespace detail {
34
36public:
37 hi_force_inline log_message_base() noexcept = default;
38 virtual ~log_message_base() = default;
39
40 [[nodiscard]] virtual std::string format() const noexcept = 0;
41 [[nodiscard]] virtual std::unique_ptr<log_message_base> make_unique_copy() const noexcept = 0;
42
43public:
44 static inline std::chrono::time_zone const *zone = nullptr;
45};
46
47template<global_state_type Level, fixed_string SourcePath, int SourceLine, fixed_string Fmt, typename... Values>
49public:
50 static_assert(std::popcount(to_underlying(Level)) == 1);
51
52 // clang-format off
53 static constexpr char const *log_level_name =
54 Level == global_state_type::log_fatal ? "fatal" :
55 Level == global_state_type::log_error ? "error" :
56 Level == global_state_type::log_warning ? "warning" :
57 Level == global_state_type::log_info ? "info" :
58 Level == global_state_type::log_debug ? "debug" :
59 Level == global_state_type::log_trace ? "trace" :
60 Level == global_state_type::log_audit ? "audit" :
61 Level == global_state_type::log_statistics ? "stats" :
62 "<unknown log level>";
63 // clang-format on
64
65 log_message(log_message const&) noexcept = default;
66 log_message& operator=(log_message const&) noexcept = default;
67
68 template<typename... Args>
69 hi_force_inline log_message(Args&&...args) noexcept :
70 _time_stamp(time_stamp_count::inplace_with_thread_id{}), _what(std::forward<Args>(args)...)
71 {
72 }
73
74 std::string format() const noexcept override
75 {
76 hilet utc_time_point = time_stamp_utc::make(_time_stamp);
77 hilet sys_time_point = std::chrono::clock_cast<std::chrono::system_clock>(utc_time_point);
78 hilet local_time_point = zone->to_local(sys_time_point);
79
80 hilet cpu_id = _time_stamp.cpu_id();
81 hilet thread_id = _time_stamp.thread_id();
82 hilet thread_name = get_thread_name(thread_id);
83
84 if constexpr (to_bool(Level & global_state_type::log_statistics)) {
85 return std::format("{} {}({}) {:5} {}\n", local_time_point, thread_name, cpu_id, log_level_name, _what());
86 } else {
87 auto source_filename = std::filesystem::path{static_cast<std::string_view>(SourcePath)}.filename().generic_string();
88 return std::format(
89 "{} {}({}) {:5} {} ({}:{})\n",
90 local_time_point,
91 thread_name,
92 cpu_id,
93 log_level_name,
94 _what(),
95 source_filename,
96 SourceLine);
97 }
98 }
99
100 [[nodiscard]] std::unique_ptr<log_message_base> make_unique_copy() const noexcept override
101 {
102 return std::make_unique<log_message>(*this);
103 }
104
105private:
106 time_stamp_count _time_stamp;
107 delayed_format<Fmt, Values...> _what;
108};
109
110} // namespace detail
111
112class log {
113public:
121 template<global_state_type Level, fixed_string SourcePath, int SourceLine, fixed_string Fmt, typename... Args>
122 hi_force_inline void add(Args&&...args) noexcept
123 {
124 static_assert(std::popcount(to_underlying(Level)) == 1);
125
126 hilet state = global_state.load(std::memory_order::relaxed);
127 if (not to_bool(state & Level)) {
128 return;
129 }
130
131 // Add messages in the queue, block when full.
132 // * This reduces amount of instructions needed to be executed during logging.
133 // * Simplifies logged_fatal_message logic.
134 // * Will make sure everything gets logged.
135 // * Blocking is bad in a real time thread, so maybe count the number of times it is blocked.
136
137 // Emplace a message directly on the queue.
139 std::forward<Args>(args)...);
140
141 if (to_bool(Level & global_state_type::log_fatal) or not to_bool(state & global_state_type::log_is_running)) {
142 // If the logger did not start we will log in degraded mode and log from the current thread.
143 // On fatal error we also want to log from the current thread.
144 [[unlikely]] flush();
145 }
146 }
147
152 hi_no_inline void flush() noexcept;
153
161 static bool start_subsystem(global_state_type log_level = global_state_type::log_level_default)
162 {
163 set_log_level(log_level);
164 return hi::start_subsystem(global_state_type::log_is_running, log::subsystem_init, log::subsystem_deinit);
165 }
166
170 static void stop_subsystem()
171 {
172 return hi::stop_subsystem(log::subsystem_deinit);
173 }
174
175private:
178 wfree_fifo<detail::log_message_base, 64> _fifo;
179 mutable unfair_mutex _mutex;
180
185 void write(std::string const& str) const noexcept;
186
189 static inline std::jthread _log_thread;
190
193 static void log_thread_main(std::stop_token stop_token) noexcept;
194
197 static void subsystem_deinit() noexcept;
198
204 static bool subsystem_init() noexcept;
205};
206
207inline log log_global;
208
211[[nodiscard]] std::string get_last_error_message() noexcept;
212
213}} // namespace hi::v1
214
215#define hi_log(level, fmt, ...) \
216 hi_format_check(fmt __VA_OPT__(, ) __VA_ARGS__); \
217 ::hi::log_global.add<level, __FILE__, __LINE__, fmt>(__VA_ARGS__)
218
219#define hi_log_debug(fmt, ...) hi_log(::hi::global_state_type::log_debug, fmt __VA_OPT__(, ) __VA_ARGS__)
220#define hi_log_info(fmt, ...) hi_log(::hi::global_state_type::log_info, fmt __VA_OPT__(, ) __VA_ARGS__)
221#define hi_log_statistics(fmt, ...) hi_log(::hi::global_state_type::log_statistics, fmt __VA_OPT__(, ) __VA_ARGS__)
222#define hi_log_trace(fmt, ...) hi_log(::hi::global_state_type::log_trace, fmt __VA_OPT__(, ) __VA_ARGS__)
223#define hi_log_audit(fmt, ...) hi_log(::hi::global_state_type::log_audit, fmt __VA_OPT__(, ) __VA_ARGS__)
224#define hi_log_warning(fmt, ...) hi_log(::hi::global_state_type::log_warning, fmt __VA_OPT__(, ) __VA_ARGS__)
225#define hi_log_error(fmt, ...) hi_log(::hi::global_state_type::log_error, fmt __VA_OPT__(, ) __VA_ARGS__)
226#define hi_log_fatal(fmt, ...) \
227 hi_log(::hi::global_state_type::log_fatal, fmt __VA_OPT__(, ) __VA_ARGS__); \
228 hi_debug_abort()
229
230#define hi_log_info_once(name, fmt, ...) \
231 do { \
232 if (++global_counter<name> == 1) { \
233 hi_log(::hi::global_state_type::log_info, fmt __VA_OPT__(, ) __VA_ARGS__); \
234 } \
235 } while (false)
236
237#define hi_log_error_once(name, fmt, ...) \
238 do { \
239 if (++global_counter<name> == 1) { \
240 hi_log(::hi::global_state_type::log_error, fmt __VA_OPT__(, ) __VA_ARGS__); \
241 } \
242 } while (false)
#define hilet
Invariant should be the default for variables.
Definition utility.hpp:23
Functions and macros for handling architectural difference between compilers, CPUs and operating syst...
@ write
Allow write access to a file.
STL namespace.
DOXYGEN BUG.
Definition algorithm.hpp:15
The HikoGUI namespace.
Definition ascii.hpp:19
std::string get_last_error_message() noexcept
Get the OS error message from the last error received on this thread.
Definition log.hpp:48
Definition log.hpp:112
static bool start_subsystem(global_state_type log_level=global_state_type::log_level_default)
Start the logger system.
Definition log.hpp:161
hi_force_inline void add(Args &&...args) noexcept
Log a message.
Definition log.hpp:122
static void stop_subsystem()
Stop the logger system.
Definition log.hpp:170
hi_no_inline void flush() noexcept
Flush all messages from the log_queue directly from this thread.