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-2025 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
  316    AOptional<AFileOutputStream> mLogFile;
  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
static void setLogFileForGlobal(APath path)
Sets log file for ALogger::global().
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:33
An add-on to AString with functions for working with the path.
Definition APath.h:128
Definition ARaiiHelper.h:17
Represents a Unicode character string.
Definition AString.h:38
A std::vector with AUI extensions.
Definition AVector.h:39
AString name(T *v)
Runtime reflection based on typeid.
Definition AReflect.h:29
Definition ALogger.h:71
Basic syscall-based synchronization primitive.
Definition AMutex.h:33