AUI Framework  develop
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
ALogger.h
1/*
2 * AUI Framework - Declarative UI toolkit for modern C++20
3 * Copyright (C) 2020-2024 Alex2772 and Contributors
4 *
5 * SPDX-License-Identifier: MPL-2.0
6 *
7 * This Source Code Form is subject to the terms of the Mozilla Public
8 * License, v. 2.0. If a copy of the MPL was not distributed with this
9 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 */
11
12#pragma once
13
14#include <AUI/Core.h>
15#include <AUI/IO/APath.h>
16#include <AUI/IO/IOutputStream.h>
17#include <AUI/Reflect/AReflect.h>
18#include <AUI/Util/ARaiiHelper.h>
19#include "AUI/Thread/AMutex.h"
20#include "AUI/IO/AFileOutputStream.h"
21#include <fmt/format.h>
22#include <fmt/chrono.h>
23#include <AUI/Thread/AMutexWrapper.h>
24
25class AString;
26
60class API_AUI_CORE ALogger final
61{
62public:
63 enum Level
64 {
65 INFO,
66 WARN,
67 ERR,
68 DEBUG,
69 };
70
71 struct LogWriter {
72 private:
73 ALogger& mLogger;
74 Level mLevel;
75 AString mPrefix;
76 struct Buffer {
77 private:
78 struct StackBuffer {
79 char buffer[2048];
80 char* currentIterator;
81 };
82 using HeapBuffer = AVector<char>;
83 std::variant<StackBuffer, HeapBuffer> mBuffer;
84
85 void switchToHeap() {
86 HeapBuffer h;
87 h.reserve(16384);
88 auto& stack = std::get<StackBuffer>(mBuffer);
89 h.insert(h.end(), stack.buffer, stack.currentIterator);
90 mBuffer = std::move(h);
91 }
92 public:
93 using value_type = char;
94
95 Buffer() noexcept: mBuffer({}) {
96 auto& sb = std::get<StackBuffer>(mBuffer);
97 sb.currentIterator = sb.buffer;
98 }
99 size_t write(const char* t, size_t s) {
100 if (std::holds_alternative<StackBuffer>(mBuffer)) {
101 auto& stack = std::get<StackBuffer>(mBuffer);
102 if (stack.currentIterator + s <= stack.buffer + sizeof(stack.buffer)) {
103 std::memcpy(stack.currentIterator, t, s);
104 stack.currentIterator += s;
105 return s;
106 }
107 switchToHeap();
108 }
109 auto& h = std::get<HeapBuffer>(mBuffer);
110 h.insert(h.end(), t, t + s);
111 return s;
112 }
113 void write(char c) {
114 if (std::holds_alternative<StackBuffer>(mBuffer)) {
115 auto& stack = std::get<StackBuffer>(mBuffer);
116 if (stack.currentIterator + sizeof(c) <= stack.buffer + sizeof(stack.buffer)) {
117 *stack.currentIterator = c;
118 stack.currentIterator += 1;
119 return;
120 }
121 switchToHeap();
122 }
123 std::get<HeapBuffer>(mBuffer).push_back(c);
124 }
125
126 void push_back(char c) { // for std::back_inserter
127 write(c);
128 }
129
130 [[nodiscard]]
131 std::string_view str() const {
132 // assuming there's a null terminator
133 if (std::holds_alternative<StackBuffer>(mBuffer)) {
134 auto& stack = std::get<StackBuffer>(mBuffer);
135 return {stack.buffer, static_cast<std::string_view::size_type>(stack.currentIterator - stack.buffer) - 1};
136 }
137 auto& h = std::get<HeapBuffer>(mBuffer);
138 return {h.data(), h.size()};
139 }
140 };
141
142 struct LazyStreamBuf final: std::streambuf {
143 private:
144 Buffer& stackBuffer;
145 public:
146 std::ostream stream;
147 LazyStreamBuf(Buffer& stackBuffer) : stackBuffer(stackBuffer), stream(this) {}
148
149 protected:
150 std::streamsize xsputn(const char_type* s, std::streamsize n) override {
151 return stackBuffer.write(s, n);
152 }
153
154 int overflow(int_type __c) override {
155 stackBuffer.write(__c);
156 return 1;
157 }
158 };
159 AOptional<LazyStreamBuf> mStreamBuf;
160
161 Buffer mBuffer;
162
163 void writeTimestamp(const char* fmt, std::chrono::system_clock::time_point t) noexcept {
164 fmt::format_to(std::back_inserter(mBuffer), "{}", t);
165 }
166
167 public:
168 LogWriter(ALogger& logger, Level level, AString prefix) :
169 mLogger(logger),
170 mLevel(level),
171 mPrefix(std::move(prefix)) {
172
173 }
174
175 ~LogWriter() {
176 mBuffer.write(0); // null terminator
177 auto s = mBuffer.str();
178 mLogger.log(mLevel, mPrefix.toStdString().c_str(), s);
179 }
180
181 template<typename T>
182 LogWriter& operator<<(const T& t) noexcept {
183 // avoid usage of std::ostream because it's expensive
184 if constexpr(std::is_constructible_v<std::string_view, T>) {
185 std::string_view stringView(t);
186 mBuffer.write(stringView.data(), stringView.size());
187 } else if constexpr(std::is_base_of_v<AString, T>) {
188 *this << t.toStdString();
189 } else if constexpr(std::is_base_of_v<std::exception, T> && !std::is_base_of_v<AException, T>) {
190 *this << "(" << AReflect::name(&t) << ") " << t.what();
191 } else if constexpr(std::is_same_v<std::chrono::seconds, T>) {
192 writeTimestamp("%D %T", std::chrono::system_clock::time_point(t));
193 } else if constexpr(std::is_same_v<std::chrono::minutes, T> || std::is_same_v<std::chrono::hours, T>) {
194 writeTimestamp("%D %R", std::chrono::system_clock::time_point(t));
195 } else {
196 if (!mStreamBuf) {
197 mStreamBuf.emplace(mBuffer);
198 }
199 mStreamBuf->stream << t;
200 }
201 return *this;
202 }
203 };
204
205
212 ALogger(AString filename) {
213 setLogFileImpl(std::move(filename));
214 }
215 ALogger();
216 ~ALogger();
217
218 static ALogger& global();
219
220 void setDebugMode(bool debug) {
221 global().mDebug = debug;
222 }
223 bool isDebug() {
224 return global().mDebug;
225 }
226
237 void setLogFile(APath path) {
238 setLogFileImpl(std::move(path));
239 }
240
246 static void setLogFileForGlobal(APath path);
247
248 [[nodiscard]]
249 APath logFile() {
250 return mLogFile.valueOrException().path();
251 }
252
253 void onLogged(std::function<void(const AString& prefix, const AString& message, Level level)> callback) {
254 std::unique_lock lock(mOnLogged);
255 mOnLogged = std::move(callback);
256 }
263 template <aui::invocable Callable>
264 void doLogFileAccessSafe(Callable action) {
265 std::unique_lock lock(mLogSync);
266 ARaiiHelper opener = [&] {
267 if (!mLogFile) return;
268 try {
269 mLogFile->open(true);
270 } catch (const AException& e) {
271 auto path = mLogFile->path();
272 mLogFile.reset();
273 lock.unlock();
274 log(WARN, "Logger", fmt::format("Unable to reopen file {}: {}", path, e.getMessage()));
275 }
276 };
277 if (!mLogFile || !mLogFile->nativeHandle()) {
278 action();
279 return;
280 }
281
282 mLogFile->close();
283 action();
284 }
285
286 static LogWriter info(const AString& str)
287 {
288 return {global(), INFO, str};
289 }
290 static LogWriter warn(const AString& str)
291 {
292 return {global(), WARN, str};
293 }
294 static LogWriter err(const AString& str)
295 {
296 return {global(), ERR, str};
297 }
298 static LogWriter debug(const AString& str)
299 {
300 return {global(), DEBUG, str};
301 }
302
308 LogWriter log(Level level, const AString& prefix)
309 {
310 return {*this, level, prefix};
311 }
312
313
314private:
315
317 AMutex mLogSync;
318 AMutexWrapper<std::function<void(const AString& prefix, const AString& message, Level level)>> mOnLogged;
319
320 bool mDebug = AUI_DEBUG;
321
322 void setLogFileImpl(AString path);
323
324
331 void log(Level level, std::string_view prefix, std::string_view message);
332
333};
334namespace glm {
335 template<glm::length_t L, typename T, glm::qualifier Q>
336 inline std::ostream& operator<<(std::ostream& o, vec<L, T, Q> vec) {
337 o << "{ ";
338 for (std::size_t i = 0; i < L; ++i) {
339 if (i != 0) o << ", ";
340 o << vec[i];
341 }
342 o << " }";
343
344 return o;
345 }
346
347}
348
349template<glm::length_t L, typename T, glm::qualifier Q>
350struct fmt::formatter<glm::vec<L, T, Q>> {
351 constexpr auto parse (format_parse_context& ctx) { return ctx.begin(); }
352
353 template <typename Context>
354 constexpr auto format(glm::vec<L, T, Q> vec, Context& ctx) const {
355 auto out = ctx.out();
356 out = format_to(out, FMT_STRING("{{ "));
357 for (glm::length_t i = 0; i < L; ++i) {
358 if (i == 0) {
359 out = format_to(out, FMT_STRING("{}"), vec[i]);
360 } else {
361 out = format_to(out, FMT_STRING(", {}"), vec[i]);
362 }
363 }
364 out = format_to(out, FMT_STRING(" }}"));
365 return out;
366 }
367
368private:
369};
370
371#define ALOG_DEBUG(str) if (ALogger::global().isDebug()) ALogger::debug(str)
372
373#include <AUI/Traits/strings.h>
Abstract AUI exception.
Definition AException.h:28
A logger class.
Definition ALogger.h:61
LogWriter log(Level level, const AString &prefix)
Writer a log entry with LogWriter helper.
Definition ALogger.h:308
void doLogFileAccessSafe(Callable action)
Allows to perform some action (access safely) on log file (which is opened all over the execution pro...
Definition ALogger.h:264
void setLogFile(APath path)
Sets log file.
Definition ALogger.h:237
ALogger(AString filename)
Constructor for an extra log file.
Definition ALogger.h:212
Wraps the object with mutex, providing thread-safety layer and a runtime check.
Definition AMutexWrapper.h:22
Utility wrapper implementing the stack-allocated (fast) optional idiom.
Definition AOptional.h:32
An add-on to AString with functions for working with the path.
Definition APath.h:107
Definition ARaiiHelper.h:17
Represents a Unicode character string.
Definition AString.h:37
A std::vector with AUI extensions.
Definition AVector.h:39
Definition ALogger.h:71
Basic syscall-based synchronization primitive.
Definition AMutex.h:33