HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
log.hpp
1// Copyright Take Vos 2019-2021.
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 "polymorphic_optional.hpp"
10#include "wfree_fifo.hpp"
11#include "atomic.hpp"
12#include "meta.hpp"
13#include "format.hpp"
14#include "source_location.hpp"
15#include "architecture.hpp"
16#include "delayed_format.hpp"
17#include "fixed_string.hpp"
18#include "subsystem.hpp"
19#include "global_state.hpp"
20#include "URL.hpp"
21#include "unfair_mutex.hpp"
22#include <chrono>
23#include <format>
24#include <string>
25#include <string_view>
26#include <tuple>
27#include <mutex>
28#include <atomic>
29#include <memory>
30#include <thread>
31
32namespace tt {
33namespace detail {
34
36public:
37 tt_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, basic_fixed_string SourceFile, int SourceLine, basic_fixed_string Fmt, typename... Values>
49public:
50 static_assert(std::popcount(to_underlying(Level)) == 1);
51 static_assert(std::is_same_v<decltype(SourceFile)::value_type, char>, "SourceFile must be a basic_fixed_string<char>");
52 static_assert(std::is_same_v<decltype(Fmt)::value_type, char>, "Fmt must be a basic_fixed_string<char>");
53
54 // clang-format off
55 static constexpr char const *log_level_name =
56 Level == global_state_type::log_fatal ? "fatal" :
57 Level == global_state_type::log_error ? "error" :
58 Level == global_state_type::log_warning ? "warning" :
59 Level == global_state_type::log_info ? "info" :
60 Level == global_state_type::log_debug ? "debug" :
61 Level == global_state_type::log_trace ? "trace" :
62 Level == global_state_type::log_audit ? "audit" :
63 Level == global_state_type::log_statistics ? "stats" :
64 "<unknown log level>";
65 // clang-format on
66
67 log_message(log_message const &) noexcept = default;
68 log_message &operator=(log_message const &) noexcept = default;
69
70 template<typename... Args>
71 tt_force_inline log_message(Args &&...args) noexcept :
72 _time_stamp(time_stamp_count::inplace_with_thread_id{}), _what(std::forward<Args>(args)...)
73 {
74 }
75
76 std::string format() const noexcept override
77 {
78 ttlet utc_time_point = time_stamp_utc::make(_time_stamp);
79 ttlet sys_time_point = std::chrono::clock_cast<std::chrono::system_clock>(utc_time_point);
80 ttlet local_time_point = zone->to_local(sys_time_point);
81
82 ttlet cpu_id = _time_stamp.cpu_id();
83 ttlet thread_id = _time_stamp.thread_id();
84
85 if constexpr (to_bool(Level & global_state_type::log_statistics)) {
86 return std::format("{} {:2}:{:<10} {:5} {}\n", local_time_point, cpu_id, thread_id, log_level_name, _what());
87 } else {
88 return std::format(
89 "{} {:2}:{:<10} {:5} {} ({}:{})\n",
90 local_time_point,
91 cpu_id,
92 thread_id,
93 log_level_name,
94 _what(),
95 URL::urlFromPath(SourceFile).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
111} // namespace detail
112
113class log {
114public:
123 template<global_state_type Level, basic_fixed_string SourceFile, int SourceLine, basic_fixed_string Fmt, typename... Args>
124 tt_force_inline void add(Args &&...args) noexcept
125 {
126 static_assert(std::popcount(to_underlying(Level)) == 1);
127
128 ttlet state = global_state.load(std::memory_order::relaxed);
129 if (not to_bool(state & Level)) {
130 return;
131 }
132
133 // Add messages in the queue, block when full.
134 // * This reduces amount of instructions needed to be executed during logging.
135 // * Simplifies logged_fatal_message logic.
136 // * Will make sure everything gets logged.
137 // * Blocking is bad in a real time thread, so maybe count the number of times it is blocked.
138
139 // Emplace a message directly on the queue.
141 std::forward<Args>(args)...);
142
143 if (to_bool(Level & global_state_type::log_fatal) or not to_bool(state & global_state_type::log_is_running)) {
144 // If the logger did not start we will log in degraded mode and log from the current thread.
145 // On fatal error we also want to log from the current thread.
146 [[unlikely]] flush();
147 }
148
149 if constexpr (to_bool(Level & global_state_type::log_fatal)) {
151 }
152 }
153
158 tt_no_inline void flush() noexcept;
159
167 static bool start_subsystem(global_state_type log_level = global_state_type::log_level_default)
168 {
169 set_log_level(log_level);
170 return tt::start_subsystem(global_state_type::log_is_running, log::subsystem_init, log::subsystem_deinit);
171 }
172
176 static void stop_subsystem()
177 {
178 return tt::stop_subsystem(log::subsystem_deinit);
179 }
180
181private:
182
186 mutable unfair_mutex _mutex;
187
192 void write(std::string const &str) const noexcept;
193
196 static inline std::jthread _log_thread;
197
200 static void log_thread_main(std::stop_token stop_token) noexcept;
201
204 static void subsystem_deinit() noexcept;
205
211 static bool subsystem_init() noexcept;
212};
213
214inline log log_global;
215
218[[nodiscard]] std::string get_last_error_message() noexcept;
219
220} // namespace tt
221
222#define tt_log_debug(fmt, ...) ::tt::log_global.add<::tt::global_state_type::log_debug, __FILE__, __LINE__, fmt>(__VA_ARGS__)
223#define tt_log_info(fmt, ...) ::tt::log_global.add<::tt::global_state_type::log_info, __FILE__, __LINE__, fmt>(__VA_ARGS__)
224#define tt_log_statistics(fmt, ...) \
225 ::tt::log_global.add<::tt::global_state_type::log_statistics, __FILE__, __LINE__, fmt>(__VA_ARGS__)
226#define tt_log_trace(fmt, ...) ::tt::log_global.add<::tt::global_state_type::log_trace, __FILE__, __LINE__, fmt>(__VA_ARGS__)
227#define tt_log_audit(fmt, ...) ::tt::log_global.add<::tt::global_state_type::log_audit, __FILE__, __LINE__, fmt>(__VA_ARGS__)
228#define tt_log_warning(fmt, ...) ::tt::log_global.add<::tt::global_state_type::log_warning, __FILE__, __LINE__, fmt>(__VA_ARGS__)
229#define tt_log_error(fmt, ...) ::tt::log_global.add<::tt::global_state_type::log_error, __FILE__, __LINE__, fmt>(__VA_ARGS__)
230#define tt_log_fatal(fmt, ...) \
231 ::tt::log_global.add<::tt::global_state_type::log_fatal, __FILE__, __LINE__, fmt>(__VA_ARGS__); \
232 tt_unreachable()
STL namespace.
Delayed formatting.
Definition delayed_format.hpp:19
example: ``` template<tt::basic_fixed_string Foo> class A { auto bar() { return std::string{Foo}; } }...
Definition fixed_string.hpp:31
Definition log.hpp:35
Definition log.hpp:48
Definition log.hpp:113
tt_force_inline void add(Args &&...args) noexcept
Log a message.
Definition log.hpp:124
static bool start_subsystem(global_state_type log_level=global_state_type::log_level_default)
Start the logger system.
Definition log.hpp:167
tt_no_inline void flush() noexcept
Flush all messages from the log_queue directly from this thread.
static void stop_subsystem()
Stop the logger system.
Definition log.hpp:176
Since Window's 10 QueryPerformanceCounter() counts at only 10MHz which is too low to measure performa...
Definition time_stamp_count.hpp:29
constexpr uint32_t thread_id() const noexcept
Get the thread id.
Definition time_stamp_count.hpp:103
ssize_t cpu_id() const noexcept
Get the logical CPU index.
Definition time_stamp_count.hpp:90
Definition time_stamp_count.hpp:33
static utc_nanoseconds make(time_stamp_count const &tsc) noexcept
Make a time point from a time stamp count.
A wait-free multiple-producer/single-consumer fifo designed for absolute performance.
Definition wfree_fifo.hpp:24
T terminate(T... args)