HikoGUI
A low latency retained GUI
Loading...
Searching...
No Matches
logger.hpp
1// Copyright 2019 Pokitec
2// All rights reserved.
3
4#pragma once
5
6#include "TTauri/Foundation/counters.hpp"
7#include "TTauri/Foundation/cpu_counter_clock.hpp"
8#include "TTauri/Foundation/hires_utc_clock.hpp"
9#include "TTauri/Foundation/polymorphic_value.hpp"
10#include "TTauri/Foundation/wfree_message_queue.hpp"
11#include "TTauri/Foundation/atomic.hpp"
12#include "TTauri/Foundation/meta.hpp"
13#include "TTauri/Foundation/format.hpp"
14#include "TTauri/Foundation/os_detect.hpp"
15#include <date/tz.h>
16#include <fmt/format.h>
17#include <fmt/ostream.h>
18#include <string>
19#include <string_view>
20#include <tuple>
21#include <mutex>
22
23namespace tt {
24
25std::string getLastErrorMessage();
26
27[[noreturn]] void terminateOnFatalError(std::string &&message) noexcept;
28
29// Forward without including trace.hpp
30void trace_record() noexcept;
31
33 const char *source_path;
34 int source_line;
35
36 constexpr source_code_ptr(const char *source_path, int source_line) :
37 source_path(source_path), source_line(source_line) {}
38};
39
40std::ostream &operator<<(std::ostream &lhs, source_code_ptr const &rhs);
41
42enum class log_level: uint8_t {
44 Debug,
46 Info,
48 Trace,
50 Counter,
52 Exception,
54 Audit,
56 Warning,
58 Error,
60 Assert,
62 Critical,
64 Fatal,
65};
66
67constexpr char const *to_const_string(log_level level) noexcept
68{
69 switch (level) {
70 case log_level::Debug: return "DEBUG";
71 case log_level::Info: return "INFO";
72 case log_level::Trace: return "TRACE";
73 case log_level::Counter: return "COUNT";
74 case log_level::Exception: return "THROW";
75 case log_level::Audit: return "AUDIT";
76 case log_level::Warning: return "WARN";
77 case log_level::Error: return "ERROR";
78 case log_level::Assert: return "ASSERT";
79 case log_level::Critical: return "CRIT";
80 case log_level::Fatal: return "FATAL";
81 default: return "<unknown>";
82 }
83}
84
85inline int command_line_argument_to_log_level(std::string_view str) noexcept
86{
87 if (str == "debug") {
88 return static_cast<int>(log_level::Debug);
89 } else if (str == "info") {
90 return static_cast<int>(log_level::Info);
91 } else if (str == "audit") {
92 return static_cast<int>(log_level::Audit);
93 } else if (str == "warning") {
94 return static_cast<int>(log_level::Warning);
95 } else if (str == "error") {
96 return static_cast<int>(log_level::Error);
97 } else if (str == "critical") {
98 return static_cast<int>(log_level::Critical);
99 } else if (str == "fatal") {
100 return static_cast<int>(log_level::Fatal);
101 } else {
102 return -1;
103 }
104}
105
106constexpr bool operator<(log_level lhs, log_level rhs) noexcept { return static_cast<int>(lhs) < static_cast<int>(rhs); }
107constexpr bool operator>(log_level lhs, log_level rhs) noexcept { return rhs < lhs; }
108constexpr bool operator==(log_level lhs, log_level rhs) noexcept { return static_cast<int>(lhs) == static_cast<int>(rhs); }
109constexpr bool operator!=(log_level lhs, log_level rhs) noexcept { return !(lhs == rhs); }
110constexpr bool operator<=(log_level lhs, log_level rhs) noexcept { return !(lhs > rhs); }
111constexpr bool operator>=(log_level lhs, log_level rhs) noexcept { return !(lhs < rhs); }
112
115 char const *format;
116
117 log_message_base(cpu_counter_clock::time_point timestamp, char const *format) noexcept :
118 timestamp(timestamp), format(format) {}
119
120 std::string string() const noexcept;
121 virtual std::string message() const noexcept = 0;
122 virtual log_level level() const noexcept = 0;
123};
124
125template<log_level Level, typename... Args>
127 std::tuple<std::decay_t<Args>...> format_args;
128
129 log_message(cpu_counter_clock::time_point const timestamp, char const *const format, Args &&... args) noexcept :
130 log_message_base(timestamp, format), format_args(std::forward<Args>(args)...) {}
131
132 log_level level() const noexcept override {
133 return Level;
134 }
135
136 std::string message() const noexcept override {
137 std::string format_str = format;
138
139 if constexpr (count_type_if<source_code_ptr, Args...>() > 0) {
140 if (format_uses_arg_ids(format)) {
141 constexpr size_t source_index = index_of_type<source_code_ptr, Args...>();
142
143 format_str += " ({" + std::to_string(source_index) + "})";
144 } else {
145 format_str += " ({})";
146 }
147 }
148
149 auto f = [format_str=format_str](ttlet&... args) {
150 return fmt::format(format_str, args...);
151 };
152
153 try {
154 return std::apply(f, format_args);
155 } catch (fmt::format_error &e) {
156 return std::string("ERROR: Could not format '") + format_str + std::string("': ") + e.what();
157 }
158 }
159};
160
162
166 static constexpr size_t MAX_MESSAGE_SIZE = 224;
167 static constexpr size_t MESSAGE_ALIGNMENT = 256;
168 static constexpr size_t MAX_NR_MESSAGES = 4096;
169
172
174 message_queue_type message_queue;
175
176 hires_utc_clock::time_point next_gather_time = {};
177
178public:
179 log_level minimum_log_level = log_level::Debug;
180
181
182 void logger_tick() noexcept;
183 void gather_tick(bool last) noexcept;
184
185 template<log_level Level, typename... Args>
186 tt_force_inline void log(typename cpu_counter_clock::time_point timestamp, char const *format, Args &&... args) noexcept {
187 if (Level >= minimum_log_level) {
188 // Add messages in the queue, block when full.
189 // * This reduces amount of instructions needed to be executed during logging.
190 // * Simplifies logged_fatal_message logic.
191 // * Will make sure everything gets logged.
192 // * Blocking is bad in a real time thread, so maybe count the number of times it is blocked.
193 auto message = message_queue.write<logger_blocked_tag>();
194 // derefence the message so that we get the polymorphic_value, so this assignment will work correctly.
195 message->emplace<log_message<Level, Args...>>(timestamp, format, std::forward<Args>(args)...);
196
197 } else {
198 return;
199 }
200
201 if constexpr (Level >= log_level::Fatal) {
202 ttlet message = log_message<Level, Args...>(timestamp, format, std::forward<Args>(args)...);
203 // Make sure everything including this message and counters are logged.
204 terminateOnFatalError(message.message());
205
206 } else if constexpr (Level >= log_level::Error) {
207 // Actually logging of tracing will only work when we cleanly unwind the stack and destruct all trace objects
208 // this will not work on fatal messages.
209 trace_record();
210 }
211 }
212
213private:
214 void write(std::string const &str) noexcept;
215 void writeToFile(std::string str) noexcept;
216 void writeToConsole(std::string str) noexcept;
217 void display_time_calibration() noexcept;
218 void display_counters() noexcept;
219 void display_trace_statistics() noexcept;
220};
221
222// The constructor of logger only starts the logging thread.
223// The ring buffer of the logger is triviality constructed and can be used before the logger's constructor is stared.
224inline logger_type logger = {};
225
226#define TTAURI_LOG(level, ...) ::tt::logger.log<level>(::tt::cpu_counter_clock::now(), __VA_ARGS__, ::tt::source_code_ptr(__FILE__, __LINE__))
227
228#define LOG_DEBUG(...) TTAURI_LOG(::tt::log_level::Debug, __VA_ARGS__)
229#define LOG_INFO(...) TTAURI_LOG(::tt::log_level::Info, __VA_ARGS__)
230#define LOG_AUDIT(...) TTAURI_LOG(::tt::log_level::Audit, __VA_ARGS__)
231#define LOG_EXCEPTION(...) TTAURI_LOG(::tt::log_level::Exception, __VA_ARGS__)
232#define LOG_WARNING(...) TTAURI_LOG(::tt::log_level::Warning, __VA_ARGS__)
233#define LOG_ERROR(...) TTAURI_LOG(::tt::log_level::Error, __VA_ARGS__)
234#define LOG_ASSERT(...) TTAURI_LOG(::tt::log_level::Assert, __VA_ARGS__)
235#define LOG_CRITICAL(...) TTAURI_LOG(::tt::log_level::Critical, __VA_ARGS__)
236#define LOG_FATAL(...) TTAURI_LOG(::tt::log_level::Fatal, __VA_ARGS__); tt_unreachable()
237
238}
Definition logger.hpp:32
Definition logger.hpp:113
Definition logger.hpp:126
Definition logger.hpp:161
Definition logger.hpp:165
Definition polymorphic_value.hpp:20
scoped_write_operation write() noexcept
Definition wfree_message_queue.hpp:128
T operator>(T... args)
T to_string(T... args)