From 5c3ce010828eb94236f5205a5c21c61acfb87b04 Mon Sep 17 00:00:00 2001 From: Borislav Stanimirov Date: Tue, 5 Nov 2024 14:03:54 +0200 Subject: [PATCH] build: merge jalog, closes #186 --- CMakeLists.txt | 1 - acord/asset-mgr/code/ac/asset/Logging.hpp | 8 +- common/CMakeLists.txt | 1 + common/astl/public/astl/mem_streambuf.hpp | 269 +++++ common/astl/public/astl/small_vector.hpp | 1041 +++++++++++++++++ common/astl/public/astl/time_t.hpp | 178 +++ common/jalog/CMakeLists.txt | 8 + common/jalog/LICENSE | 21 + common/jalog/code/CMakeLists.txt | 73 ++ common/jalog/code/ac/jalog/API.h | 15 + common/jalog/code/ac/jalog/BasicStream.hpp | 99 ++ common/jalog/code/ac/jalog/DefaultLogger.hpp | 11 + common/jalog/code/ac/jalog/DefaultScope.hpp | 12 + common/jalog/code/ac/jalog/Entry.hpp | 23 + common/jalog/code/ac/jalog/Instance.cpp | 49 + common/jalog/code/ac/jalog/Instance.hpp | 52 + common/jalog/code/ac/jalog/Level.hpp | 21 + common/jalog/code/ac/jalog/Log.hpp | 26 + common/jalog/code/ac/jalog/LogPrintf.hpp | 26 + common/jalog/code/ac/jalog/LogStream.hpp | 25 + common/jalog/code/ac/jalog/Logger.cpp | 136 +++ common/jalog/code/ac/jalog/Logger.hpp | 89 ++ common/jalog/code/ac/jalog/NoopStream.hpp | 17 + common/jalog/code/ac/jalog/Printf.cpp | 65 + common/jalog/code/ac/jalog/Printf.hpp | 33 + common/jalog/code/ac/jalog/PrintfWrap.hpp | 41 + common/jalog/code/ac/jalog/Scope.cpp | 54 + common/jalog/code/ac/jalog/Scope.hpp | 63 + common/jalog/code/ac/jalog/ScopeDesc.hpp | 32 + common/jalog/code/ac/jalog/Sink.cpp | 10 + common/jalog/code/ac/jalog/Sink.hpp | 25 + common/jalog/code/ac/jalog/SinkPtr.hpp | 12 + common/jalog/code/ac/jalog/Stream.hpp | 53 + common/jalog/code/ac/jalog/_qwrite.cpp | 53 + common/jalog/code/ac/jalog/_qwrite.hpp | 112 ++ .../jalog/code/ac/jalog/sinks/AndroidSink.cpp | 32 + .../jalog/code/ac/jalog/sinks/AndroidSink.hpp | 17 + .../code/ac/jalog/sinks/AnsiColorSink.cpp | 60 + .../code/ac/jalog/sinks/AnsiColorSink.hpp | 17 + .../jalog/code/ac/jalog/sinks/ColorSink.hpp | 12 + .../jalog/code/ac/jalog/sinks/DefaultSink.hpp | 26 + .../jalog/code/ac/jalog/sinks/NSLogSink.hpp | 17 + common/jalog/code/ac/jalog/sinks/NSLogSink.mm | 40 + .../code/ac/jalog/sinks/SimpleOStreamSink.hpp | 80 ++ .../code/ac/jalog/sinks/SimpleStdioSink.hpp | 70 ++ .../code/ac/jalog/sinks/WindowsColorSink.cpp | 126 ++ .../code/ac/jalog/sinks/WindowsColorSink.hpp | 22 + common/jalog/example/CMakeLists.txt | 14 + common/jalog/example/e-BasicShowcase.cpp | 41 + common/jalog/example/e-CustomStyleLogging.cpp | 20 + common/jalog/example/e-HelloWorld.cpp | 17 + common/jalog/example/e-PrintfStyleLogging.cpp | 59 + common/jalog/example/e-StreamStyleLogging.cpp | 21 + common/jalog/test/CMakeLists.txt | 19 + common/jalog/test/TestSink.hpp | 67 ++ common/jalog/test/TestTime.hpp | 15 + common/jalog/test/t-core.cpp | 461 ++++++++ common/jalog/test/t-defaults.cpp | 93 ++ common/jalog/test/t-qwrite.cpp | 122 ++ inference/dummy/code/CMakeLists.txt | 2 +- inference/dummy/code/ac/dummy/Logging.hpp | 6 +- inference/dummy/example/CMakeLists.txt | 3 +- inference/dummy/example/e-basic.cpp | 8 +- inference/dummy/local/example/CMakeLists.txt | 3 +- .../dummy/local/example/e-local-dummy.cpp | 8 +- inference/llama.cpp/code/CMakeLists.txt | 2 +- inference/llama.cpp/code/ac/llama/Logging.hpp | 6 +- inference/llama.cpp/example/CMakeLists.txt | 6 +- inference/llama.cpp/example/e-basic.cpp | 8 +- inference/llama.cpp/example/e-gui.cpp | 14 +- .../llama.cpp/local/example/CMakeLists.txt | 3 +- .../local/example/e-local-llama-chat.cpp | 8 +- .../local/example/e-local-llama-run.cpp | 8 +- inference/whisper/code/CMakeLists.txt | 2 +- inference/whisper/code/ac/whisper/Logging.hpp | 6 +- inference/whisper/example/CMakeLists.txt | 6 +- inference/whisper/example/e-basic.cpp | 8 +- inference/whisper/example/e-gui.cpp | 24 +- .../whisper/local/example/CMakeLists.txt | 3 +- .../whisper/local/example/e-local-whisper.cpp | 8 +- local/code/CMakeLists.txt | 2 +- local/code/ac/local/Logging.hpp | 6 +- wrapper/c/jalogc/CMakeLists.txt | 3 +- wrapper/c/jalogc/code/jalogc.cpp | 44 +- wrapper/c/jalogc/code/jalogc.h | 6 +- wrapper/c/jalogc/test/CMakeLists.txt | 2 +- wrapper/c/jalogc/test/t-more-logging.cpp | 10 +- wrapper/java/ac-jni/CMakeLists.txt | 3 +- wrapper/java/ac-jni/JniEntrypoint.cpp | 8 +- .../swift/code/CxxAlpacaCore/AlpacaCore.cpp | 8 +- .../swift/code/CxxAlpacaCore/CMakeLists.txt | 3 +- 91 files changed, 4330 insertions(+), 129 deletions(-) create mode 100644 common/astl/public/astl/mem_streambuf.hpp create mode 100644 common/astl/public/astl/small_vector.hpp create mode 100644 common/astl/public/astl/time_t.hpp create mode 100644 common/jalog/CMakeLists.txt create mode 100644 common/jalog/LICENSE create mode 100644 common/jalog/code/CMakeLists.txt create mode 100644 common/jalog/code/ac/jalog/API.h create mode 100644 common/jalog/code/ac/jalog/BasicStream.hpp create mode 100644 common/jalog/code/ac/jalog/DefaultLogger.hpp create mode 100644 common/jalog/code/ac/jalog/DefaultScope.hpp create mode 100644 common/jalog/code/ac/jalog/Entry.hpp create mode 100644 common/jalog/code/ac/jalog/Instance.cpp create mode 100644 common/jalog/code/ac/jalog/Instance.hpp create mode 100644 common/jalog/code/ac/jalog/Level.hpp create mode 100644 common/jalog/code/ac/jalog/Log.hpp create mode 100644 common/jalog/code/ac/jalog/LogPrintf.hpp create mode 100644 common/jalog/code/ac/jalog/LogStream.hpp create mode 100644 common/jalog/code/ac/jalog/Logger.cpp create mode 100644 common/jalog/code/ac/jalog/Logger.hpp create mode 100644 common/jalog/code/ac/jalog/NoopStream.hpp create mode 100644 common/jalog/code/ac/jalog/Printf.cpp create mode 100644 common/jalog/code/ac/jalog/Printf.hpp create mode 100644 common/jalog/code/ac/jalog/PrintfWrap.hpp create mode 100644 common/jalog/code/ac/jalog/Scope.cpp create mode 100644 common/jalog/code/ac/jalog/Scope.hpp create mode 100644 common/jalog/code/ac/jalog/ScopeDesc.hpp create mode 100644 common/jalog/code/ac/jalog/Sink.cpp create mode 100644 common/jalog/code/ac/jalog/Sink.hpp create mode 100644 common/jalog/code/ac/jalog/SinkPtr.hpp create mode 100644 common/jalog/code/ac/jalog/Stream.hpp create mode 100644 common/jalog/code/ac/jalog/_qwrite.cpp create mode 100644 common/jalog/code/ac/jalog/_qwrite.hpp create mode 100644 common/jalog/code/ac/jalog/sinks/AndroidSink.cpp create mode 100644 common/jalog/code/ac/jalog/sinks/AndroidSink.hpp create mode 100644 common/jalog/code/ac/jalog/sinks/AnsiColorSink.cpp create mode 100644 common/jalog/code/ac/jalog/sinks/AnsiColorSink.hpp create mode 100644 common/jalog/code/ac/jalog/sinks/ColorSink.hpp create mode 100644 common/jalog/code/ac/jalog/sinks/DefaultSink.hpp create mode 100644 common/jalog/code/ac/jalog/sinks/NSLogSink.hpp create mode 100644 common/jalog/code/ac/jalog/sinks/NSLogSink.mm create mode 100644 common/jalog/code/ac/jalog/sinks/SimpleOStreamSink.hpp create mode 100644 common/jalog/code/ac/jalog/sinks/SimpleStdioSink.hpp create mode 100644 common/jalog/code/ac/jalog/sinks/WindowsColorSink.cpp create mode 100644 common/jalog/code/ac/jalog/sinks/WindowsColorSink.hpp create mode 100644 common/jalog/example/CMakeLists.txt create mode 100644 common/jalog/example/e-BasicShowcase.cpp create mode 100644 common/jalog/example/e-CustomStyleLogging.cpp create mode 100644 common/jalog/example/e-HelloWorld.cpp create mode 100644 common/jalog/example/e-PrintfStyleLogging.cpp create mode 100644 common/jalog/example/e-StreamStyleLogging.cpp create mode 100644 common/jalog/test/CMakeLists.txt create mode 100644 common/jalog/test/TestSink.hpp create mode 100644 common/jalog/test/TestTime.hpp create mode 100644 common/jalog/test/t-core.cpp create mode 100644 common/jalog/test/t-defaults.cpp create mode 100644 common/jalog/test/t-qwrite.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 64f31c9a..8dc21f33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,7 +73,6 @@ mark_as_advanced(AC_BUILD_TESTS AC_BUILD_EXAMPLES AC_BUILD_POC) # global packages CPMAddPackage(gh:iboB/itlib@1.11.4) -CPMAddPackage(gh:iboB/jalog@0.4.5) if(AC_BUILD_TESTS) CPMAddPackage(gh:iboB/doctest-util@0.1.2) diff --git a/acord/asset-mgr/code/ac/asset/Logging.hpp b/acord/asset-mgr/code/ac/asset/Logging.hpp index a6ac3059..f13ef8c4 100644 --- a/acord/asset-mgr/code/ac/asset/Logging.hpp +++ b/acord/asset-mgr/code/ac/asset/Logging.hpp @@ -2,11 +2,11 @@ // SPDX-License-Identifier: MIT // #pragma once -#include -#include +#include +#include namespace ac::asset::log { -extern jalog::Scope scope; +extern ac::jalog::Scope scope; } -#define AC_ASSET_LOG(lvl, ...) JALOG_SCOPE(::ac::asset::log::scope, lvl, __VA_ARGS__) +#define AC_ASSET_LOG(lvl, ...) AC_JALOG_SCOPE(::ac::asset::log::scope, lvl, __VA_ARGS__) diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index fa4ae58d..cf156cf3 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -10,6 +10,7 @@ endif() # public add_subdirectory(astl) add_subdirectory(xxhash-cpp) +add_subdirectory(jalog) if(AC_BUILD_TESTS OR AC_BUILD_EXAMPLES OR AC_BUILD_POC) add_subdirectory(ac-repo-root) diff --git a/common/astl/public/astl/mem_streambuf.hpp b/common/astl/public/astl/mem_streambuf.hpp new file mode 100644 index 00000000..39575033 --- /dev/null +++ b/common/astl/public/astl/mem_streambuf.hpp @@ -0,0 +1,269 @@ +// itlib-mem-streambuf v1.03 +// +// std::streambuf implementations for working with contiguous memory +// +// SPDX-License-Identifier: MIT +// MIT License: +// Copyright(c) 2020-2022 Borislav Stanimirov +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files(the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and / or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions : +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// +// VERSION HISTORY +// +// 1.03 (2023-04-21) Minor rearangement to avoid ub of adding val to nullptr +// 1.02 (2022-02-01) Switched static assert from is_pod to is_trivial +// 1.01 (2021-11-18) Fixed mem_ostreambuf bug when used with containers whose +// data() returns non-null when empty +// 1.00 (2020-10-16) Initial release +// +// +// DOCUMENTATION +// +// Simply include this file wherever you need. +// It defines two classes which help working with std::stream-s with contguous +// memory. +// +// *** itlib::mem_ostreambuf *** +// +// Works with std::ostream. Has a template container which hold contiguous +// memory. The container must provide methods reserve, resize and data. +// Suitable containers are std::vector, std::string and itlib's small_vector +// or static_vector. It will grow the container appropriately so it holds the +// data which was written to the stream. +// +// mem_ostreambuf::peek_container +// Allows you to peek at the current data inside of the container +// +// mem_ostreambuf::get_container +// Transfer ownership of the container to the caller and clears the buffer +// +// Example: +// +// itlib::mem_ostreambuf buf; +// std::ostream out(&buf); +// out << "Hello world!"; +// auto str = buf.get_container(); +// assert(str == "Hello world!"); +// +// *** itlib::mem_istreambuf *** +// +// Works with std::istream. Works with a buffer of a given size provided by +// pointer and size. It makes no allocations whatsoever and doesn't touch +// the provided buffer. +// +// Example: +// +// std::string input = "1 2 3"; +// itlib::mem_istreambuf buf(input.data(), input.length()); +// std::istream in(&buf); +// int a, b, c; +// in >> a >> b >> c; +// assert(a == 1); +// assert(b == 2); +// assert(c == 3); +// +// +// TESTS +// +// You can find unit tests in the official repo: +// https://github.com/iboB/itlib/blob/master/test/ +// +#pragma once + +#include +#include +#include + +namespace astl +{ + +template +class mem_ostreambuf final : public std::basic_streambuf +{ +private: + Container m_data; + static_assert(std::is_trivial::value, "mem ostream must be of pod type"); + using super = std::basic_streambuf; +public: + using int_type = typename super::int_type; + using char_type = typename super::char_type; + using pos_type = typename super::pos_type; + using off_type = typename super::off_type; + + mem_ostreambuf(size_t reserve = 0) + { + this->setp(m_data.data(), m_data.data()); + cap_resize_by(reserve); + } + + // put offset + size_t poff() const + { + return size_t(this->pptr() - m_data.data()); + } + + const Container& peek_container() const + { + return m_data; + } + + Container get_container() + { + Container ret; + auto size = this->poff(); + ret.swap(m_data); + this->setp(nullptr, nullptr); + ret.resize(size); + return ret; + } + + void clear() + { + m_data.clear(); + this->setp(m_data.data(), m_data.data()); + } + +private: + + int_type overflow(int_type ch) override + { + cap_resize_by(1); + + *this->pptr() = char_type(ch); + this->pbump(1); + + return ch; + } + + std::streamsize remaining_buf_size() const noexcept { + // somewhat hacky check of size + return this->epptr() - this->pptr(); + } + + std::streamsize xsputn(const char_type* s, std::streamsize num) override + { + auto rem = remaining_buf_size(); + if (rem < num) + { + cap_resize_by(num - rem); + } + + memcpy(this->pptr(), s, num * sizeof(char_type)); + this->pbump(int_type(num)); + + return num; + } + + pos_type seekpos(pos_type sp, std::ios_base::openmode) override + { + this->setp(m_data.data() + int(sp), m_data.data() + m_data.size()); + return sp; + } + + pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode) override + { + if (way == std::ios_base::cur) return seekpos(poff() + off, std::ios_base::out); + if (way == std::ios_base::beg) return seekpos(off, std::ios_base::out); + if (way == std::ios_base::end) return seekpos(m_data.size() + off, std::ios_base::out); + return super::traits_type::eof(); + } + +private: + + void cap_resize_by(size_t by) + { + auto off = poff(); + + // we need two resize calls to adopt the container growth factor + m_data.resize(m_data.size() + by); + auto new_size = m_data.capacity(); + m_data.resize(new_size); + + this->setp(m_data.data() + off, m_data.data() + new_size); + } +}; + +template +class mem_istreambuf final : public std::basic_streambuf +{ +private: + static_assert(std::is_trivial::value, "mem istream must be of pod type"); + using super = std::basic_streambuf; +public: + using int_type = typename super::int_type; + using char_type = typename super::char_type; + using pos_type = typename super::pos_type; + using off_type = typename super::off_type; + + mem_istreambuf() = default; + + mem_istreambuf(const CharT* beg, size_t size) + { + reset(beg, size); + } + + void reset(const CharT* beg, size_t size) + { + auto ucbeg = const_cast(beg); + this->setg(ucbeg, ucbeg, ucbeg + size); + } + + // get offset + size_t goff() const + { + return this->gptr() - this->eback(); + } + + size_t size() const + { + return this->egptr() - this->eback(); + } + +private: + pos_type seekpos(pos_type sp, std::ios_base::openmode) override + { + this->setg(this->eback(), this->eback() + sp, this->egptr()); + return sp; + } + + pos_type seekoff(off_type off, std::ios_base::seekdir way, std::ios_base::openmode) override + { + if (way == std::ios_base::cur) return seekpos(goff() + off, std::ios_base::in); + if (way == std::ios_base::beg) return seekpos(off, std::ios_base::in); + if (way == std::ios_base::end) return seekpos(size() + off, std::ios_base::in); + return super::traits_type::eof(); + } + + std::streamsize xsgetn(char_type* s, std::streamsize count) override + { + if (this->gptr() + count > this->egptr()) + { + count = this->egptr() - this->gptr(); + } + + memcpy(s, this->gptr(), count * sizeof(char_type)); + this->setg(this->eback(), this->gptr() + count, this->egptr()); + + return count; + } +}; + +} diff --git a/common/astl/public/astl/small_vector.hpp b/common/astl/public/astl/small_vector.hpp new file mode 100644 index 00000000..2513730f --- /dev/null +++ b/common/astl/public/astl/small_vector.hpp @@ -0,0 +1,1041 @@ +// itlib-small-vector v2.05 +// +// std::vector-like class with a static buffer for initial capacity +// +// SPDX-License-Identifier: MIT +// MIT License: +// Copyright(c) 2016-2018 Chobolabs Inc. +// Copyright(c) 2020-2024 Borislav Stanimirov +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files(the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and / or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions : +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// +// VERSION HISTORY +// +// 2.05 (2024-03-06) Minor: Return bool from shrink_to_fit +// 2.04 (2022-04-29) Minor: Disable MSVC warning for constant conditional +// 2.03 (2022-10-31) Minor: Removed unused local var +// 2.02 (2022-09-24) Minor: Fixed leftover arguments in error handling macros +// 2.01 (2022-08-26) Minor: renames, doc +// 2.00 (2022-08-26) Redesign +// * Smaller size +// * Inherit from allocator to make use of EBO +// 1.04 (2022-04-14) Noxcept move construct and assign +// 1.03 (2021-10-05) Use allocator member instead of inheriting from allocator +// Allow compare with small_vector of different static_size +// Don't rely on operator!= from T. Use operator== instead +// 1.02 (2021-09-15) Bugfix! Fixed bad deallocation when reverting to +// static size on resize() +// 1.01 (2021-08-05) Bugfix! Fixed return value of erase +// 1.00 (2020-10-14) Rebranded release from chobo-small-vector +// +// +// DOCUMENTATION +// +// Simply include this file wherever you need. +// It defines the class itlib::small_vector, which is a drop-in replacement of +// std::vector, but with an initial capacity as a template argument. +// It gives you the benefits of using std::vector, at the cost of having a statically +// allocated buffer for the initial capacity, which gives you cache-local data +// when the vector is small (smaller than the initial capacity). +// +// When the size exceeds the capacity, the vector allocates memory via the provided +// allocator, falling back to classic std::vector behavior. +// +// The second size_t template argument, RevertToStaticBelow, is used when a +// small_vector which has already switched to dynamically allocated size reduces +// its size to a number smaller than that. In this case the vector's buffer +// switches back to the staticallly allocated one +// +// A default value for the initial static capacity is provided so a replacement +// in an existing code is possible with minimal changes to it. +// +// Example: +// +// itlib::small_vector myvec; // a small_vector of size 0, initial capacity 4, and revert size 4 (below 5) +// myvec.resize(2); // vector is {0,0} in static buffer +// myvec[1] = 11; // vector is {0,11} in static buffer +// myvec.push_back(7); // vector is {0,11,7} in static buffer +// myvec.insert(myvec.begin() + 1, 3); // vector is {0,3,11,7} in static buffer +// myvec.push_back(5); // vector is {0,3,11,7,5} in dynamically allocated memory buffer +// myvec.erase(myvec.begin()); // vector is {3,11,7,5} back in static buffer +// myvec.resize(5); // vector is {3,11,7,5,0} back in dynamically allocated memory +// +// +// Reference: +// +// itlib::small_vector is fully compatible with std::vector with +// the following exceptions: +// * when reducing the size with erase or resize the new size may fall below +// RevertToStaticBelow (if it is not 0). In such a case the vector will +// revert to using its static buffer, invalidating all iterators (contrary +// to the standard) +// * a method is added `revert_to_static()` which reverts to the static buffer +// if possible and does nothing if the size doesn't allow it +// +// Additionally, the following methods are added: +// +// * is_static() returns whether the vector is currently using its static buffer +// +// Other notes: +// +// * shrink_to_fit() returns a bool indicating whether the vector was shrunk (and the +// iterators invalidated) +// * the default value for RevertToStaticBelow is zero. This means that once a dynamic +// buffer is allocated the data will never be put into the static one, even if the +// size allows it. Even if clear() is called. The only way to do so is to call +// shrink_to_fit() or revert_to_static() +// * shrink_to_fit will free and reallocate if size != capacity and the data +// doesn't fit into the static buffer. It also will revert to the static buffer +// whenever possible regardless of the RevertToStaticBelow value +// +// +// Configuration +// +// The library has two configuration options. They can be set as #define-s +// before including the header file, but it is recommended to change the code +// of the library itself with the values you want, especially if you include +// the library in many compilation units (as opposed to, say, a precompiled +// header or a central header). +// +// Config out of range error handling +// +// An out of range error is a runtime error which is triggered when a method is +// called with an iterator that doesn't belong to the vector's current range. +// For example: vec.erase(vec.end() + 1); +// +// This is set by defining ITLIB_SMALL_VECTOR_ERROR_HANDLING to one of the +// following values: +// * ITLIB_SMALL_VECTOR_ERROR_HANDLING_NONE - no error handling. Crashes WILL +// ensue if the error is triggered. +// * ITLIB_SMALL_VECTOR_ERROR_HANDLING_THROW - std::out_of_range is thrown. +// * ITLIB_SMALL_VECTOR_ERROR_HANDLING_ASSERT - asserions are triggered. +// * ITLIB_SMALL_VECTOR_ERROR_HANDLING_ASSERT_AND_THROW - combines assert and +// throw to catch errors more easily in debug mode +// +// To set this setting by editing the file change the line: +// ``` +// # define ITLIB_SMALL_VECTOR_ERROR_HANDLING ITLIB_SMALL_VECTOR_ERROR_HANDLING_THROW +// ``` +// to the default setting of your choice +// +// Config bounds checks: +// +// By default bounds checks are made in debug mode (via an asser) when accessing +// elements (with `at` or `[]`). Iterators are not checked (yet...) +// +// To disable them, you can define ITLIB_SMALL_VECTOR_NO_DEBUG_BOUNDS_CHECK +// before including the header. +// +// +// TESTS +// +// You can find unit tests in the official repo: +// https://github.com/iboB/itlib/blob/master/test/ +// +#pragma once + +#include +#include +#include + +#define ITLIB_SMALL_VECTOR_ERROR_HANDLING_NONE 0 +#define ITLIB_SMALL_VECTOR_ERROR_HANDLING_THROW 1 +#define ITLIB_SMALL_VECTOR_ERROR_HANDLING_ASSERT 2 +#define ITLIB_SMALL_VECTOR_ERROR_HANDLING_ASSERT_AND_THROW 3 + +#if !defined(ITLIB_SMALL_VECTOR_ERROR_HANDLING) +# define ITLIB_SMALL_VECTOR_ERROR_HANDLING ITLIB_SMALL_VECTOR_ERROR_HANDLING_THROW +#endif + + +#if ITLIB_SMALL_VECTOR_ERROR_HANDLING == ITLIB_SMALL_VECTOR_ERROR_HANDLING_NONE +# define I_ITLIB_SMALL_VECTOR_OUT_OF_RANGE_IF(cond) +#elif ITLIB_SMALL_VECTOR_ERROR_HANDLING == ITLIB_SMALL_VECTOR_ERROR_HANDLING_THROW +# include +# define I_ITLIB_SMALL_VECTOR_OUT_OF_RANGE_IF(cond) if (cond) throw std::out_of_range("itlib::small_vector out of range") +#elif ITLIB_SMALL_VECTOR_ERROR_HANDLING == ITLIB_SMALL_VECTOR_ERROR_HANDLING_ASSERT +# include +# define I_ITLIB_SMALL_VECTOR_OUT_OF_RANGE_IF(cond) assert(!(cond) && "itlib::small_vector out of range") +#elif ITLIB_SMALL_VECTOR_ERROR_HANDLING == ITLIB_SMALL_VECTOR_ERROR_HANDLING_ASSERT_AND_THROW +# include +# include +# define I_ITLIB_SMALL_VECTOR_OUT_OF_RANGE_IF(cond) \ + do { if (cond) { assert(false && "itlib::small_vector out of range"); throw std::out_of_range("itlib::small_vector out of range"); } } while(false) +#else +#error "Unknown ITLIB_SMALL_VECTOR_ERRROR_HANDLING" +#endif + + +#if defined(ITLIB_SMALL_VECTOR_NO_DEBUG_BOUNDS_CHECK) +# define I_ITLIB_SMALL_VECTOR_BOUNDS_CHECK(i) +#else +# include +# define I_ITLIB_SMALL_VECTOR_BOUNDS_CHECK(i) assert((i) < this->size()) +#endif + +namespace astl +{ + +template> +struct small_vector : private Alloc +{ + static_assert(RevertToStaticBelow <= StaticCapacity + 1, "itlib::small_vector: the RevertToStaticBelow shouldn't exceed the static capacity by more than one"); + + using atraits = std::allocator_traits; +public: + using allocator_type = Alloc; + using value_type = typename atraits::value_type; + using size_type = typename atraits::size_type; + using difference_type = typename atraits::difference_type; + using reference = T&; + using const_reference = const T&; + using pointer = typename atraits::pointer; + using const_pointer = typename atraits::const_pointer; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + static constexpr size_t static_capacity = StaticCapacity; + static constexpr intptr_t revert_to_static_below = RevertToStaticBelow; + + small_vector() + : small_vector(Alloc()) + {} + + small_vector(const Alloc& alloc) + : Alloc(alloc) + , m_capacity(StaticCapacity) + { + m_begin = m_end = static_begin_ptr(); + } + + explicit small_vector(size_t count, const Alloc& alloc = Alloc()) + : small_vector(alloc) + { + resize(count); + } + + explicit small_vector(size_t count, const T& value, const Alloc& alloc = Alloc()) + : small_vector(alloc) + { + assign_fill(count, value); + } + + template ())> + small_vector(InputIterator first, InputIterator last, const Alloc& alloc = Alloc()) + : small_vector(alloc) + { + assign_copy(first, last); + } + + small_vector(std::initializer_list l, const Alloc& alloc = Alloc()) + : small_vector(alloc) + { + assign_move(l.begin(), l.end()); + } + + small_vector(const small_vector& v) + : small_vector(v, atraits::select_on_container_copy_construction(v.get_allocator())) + {} + + small_vector(const small_vector& v, const Alloc& alloc) + : small_vector(alloc) + { + assign_copy(v.begin(), v.end()); + } + + small_vector(small_vector&& v) noexcept + : Alloc(std::move(v.get_alloc())) + , m_capacity(v.m_capacity) + { + take_impl(v); + } + + ~small_vector() + { + destroy_all(); + + if (!is_static()) + { + atraits::deallocate(get_alloc(), m_begin, m_capacity); + } + } + + small_vector& operator=(const small_vector& v) + { + if (this == &v) + { + // prevent self usurp + return *this; + } + + destroy_all(); + assign_copy(v.begin(), v.end()); + + return *this; + } + + small_vector& operator=(small_vector&& v) noexcept + { + if (this == &v) + { + // prevent self usurp + return *this; + } + + destroy_all(); + if (!is_static()) + { + atraits::deallocate(get_alloc(), m_begin, m_capacity); + } + + get_alloc() = std::move(v.get_alloc()); + m_capacity = v.m_capacity; + + take_impl(v); + + return *this; + } + + void assign(size_type count, const T& value) + { + destroy_all(); + assign_fill(count, value); + } + + template ())> + void assign(InputIterator first, InputIterator last) + { + destroy_all(); + assign_copy(first, last); + } + + void assign(std::initializer_list ilist) + { + destroy_all(); + assign_move(ilist.begin(), ilist.end()); + } + + allocator_type get_allocator() const + { + return get_alloc(); + } + + const_reference at(size_type i) const + { + I_ITLIB_SMALL_VECTOR_BOUNDS_CHECK(i); + return *(m_begin + i); + } + + reference at(size_type i) + { + I_ITLIB_SMALL_VECTOR_BOUNDS_CHECK(i); + return *(m_begin + i); + } + + const_reference operator[](size_type i) const + { + return at(i); + } + + reference operator[](size_type i) + { + return at(i); + } + + const_reference front() const + { + return at(0); + } + + reference front() + { + return at(0); + } + + const_reference back() const + { + return *(m_end - 1); + } + + reference back() + { + return *(m_end - 1); + } + + const_pointer data() const noexcept + { + return m_begin; + } + + pointer data() noexcept + { + return m_begin; + } + + // iterators + iterator begin() noexcept + { + return m_begin; + } + + const_iterator begin() const noexcept + { + return m_begin; + } + + const_iterator cbegin() const noexcept + { + return m_begin; + } + + iterator end() noexcept + { + return m_end; + } + + const_iterator end() const noexcept + { + return m_end; + } + + const_iterator cend() const noexcept + { + return m_end; + } + + reverse_iterator rbegin() noexcept + { + return reverse_iterator(end()); + } + + const_reverse_iterator rbegin() const noexcept + { + return const_reverse_iterator(end()); + } + + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(end()); + } + + reverse_iterator rend() noexcept + { + return reverse_iterator(begin()); + } + + const_reverse_iterator rend() const noexcept + { + return const_reverse_iterator(begin()); + } + + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(begin()); + } + + // capacity + bool empty() const noexcept + { + return m_begin == m_end; + } + + size_t size() const noexcept + { + return m_end - m_begin; + } + + size_t max_size() const noexcept + { + return atraits::max_size(); + } + + void reserve(size_type new_cap) + { + if (new_cap <= m_capacity) return; + + const auto cdr = choose_data(new_cap); + + assert(cdr.ptr != m_begin); // should've been handled by new_cap <= m_capacity + assert(cdr.ptr != static_begin_ptr()); // we should never reserve into static memory + + auto s = size(); + + // now we need to transfer the existing elements into the new buffer + for (size_type i = 0; i < s; ++i) + { + atraits::construct(get_alloc(), cdr.ptr + i, std::move(*(m_begin + i))); + } + + // free old elements + for (size_type i = 0; i < s; ++i) + { + atraits::destroy(get_alloc(), m_begin + i); + } + + if (!is_static()) + { + // we've moved from dyn to dyn memory, so deallocate the old one + atraits::deallocate(get_alloc(), m_begin, m_capacity); + } + + m_begin = cdr.ptr; + m_end = m_begin + s; + m_capacity = cdr.cap; + } + + size_t capacity() const noexcept + { + return m_capacity; + } + + bool shrink_to_fit() + { + const auto s = size(); + + if (s == m_capacity) return false; // we're at max + if (is_static()) return false; // can't shrink static buf + + auto old_begin = m_begin; + auto old_end = m_end; + auto old_cap = m_capacity; + + if (s < StaticCapacity) + { + // revert to static capacity + m_begin = m_end = static_begin_ptr(); + m_capacity = StaticCapacity; + } + else + { + // alloc new smaller buffer + m_begin = m_end = atraits::allocate(get_alloc(), s); + m_capacity = s; + } + + for (auto p = old_begin; p != old_end; ++p) + { + atraits::construct(get_alloc(), m_end, std::move(*p)); + ++m_end; + atraits::destroy(get_alloc(), p); + } + + atraits::deallocate(get_alloc(), old_begin, old_cap); + return true; + } + + // only revert if possible + // otherwise don't shrink + // return true if the the vector is now static + bool revert_to_static() + { + const auto s = size(); + if (is_static()) return true; //we're already there + if (s > StaticCapacity) return false; // nothing we can do + + shrink_to_fit(); + return true; + } + + // modifiers + void clear() noexcept + { + destroy_all(); + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable : 4127) // conditional expression is constant +#endif + if (RevertToStaticBelow > 0 && !is_static()) + { + atraits::deallocate(get_alloc(), m_begin, m_capacity); + m_begin = m_end = static_begin_ptr(); + m_capacity = StaticCapacity; + } + else + { + m_end = m_begin; + } +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + } + + iterator insert(const_iterator position, const value_type& val) + { + auto pos = grow_at(position, 1); + atraits::construct(get_alloc(), pos, val); + return pos; + } + + iterator insert(const_iterator position, value_type&& val) + { + auto pos = grow_at(position, 1); + atraits::construct(get_alloc(), pos, std::move(val)); + return pos; + } + + iterator insert(const_iterator position, size_type count, const value_type& val) + { + auto pos = grow_at(position, count); + for (size_type i = 0; i < count; ++i) + { + atraits::construct(get_alloc(), pos + i, val); + } + return pos; + } + + template ())> + iterator insert(const_iterator position, InputIterator first, InputIterator last) + { + auto pos = grow_at(position, last - first); + auto np = pos; + for (auto p = first; p != last; ++p, ++np) + { + atraits::construct(get_alloc(), np, *p); + } + return pos; + } + + iterator insert(const_iterator position, std::initializer_list ilist) + { + auto pos = grow_at(position, ilist.size()); + size_type i = 0; + for (auto& elem : ilist) + { + atraits::construct(get_alloc(), pos + i, std::move(elem)); + ++i; + } + return pos; + } + + template + iterator emplace(const_iterator position, Args&&... args) + { + auto pos = grow_at(position, 1); + atraits::construct(get_alloc(), pos, std::forward(args)...); + return pos; + } + + iterator erase(const_iterator position) + { + return shrink_at(position, 1); + } + + iterator erase(const_iterator first, const_iterator last) + { + I_ITLIB_SMALL_VECTOR_OUT_OF_RANGE_IF(first > last); + return shrink_at(first, last - first); + } + + void push_back(const_reference val) + { + auto pos = grow_at(m_end, 1); + atraits::construct(get_alloc(), pos, val); + } + + void push_back(T&& val) + { + auto pos = grow_at(m_end, 1); + atraits::construct(get_alloc(), pos, std::move(val)); + } + + template + reference emplace_back(Args&&... args) + { + auto pos = grow_at(m_end, 1); + atraits::construct(get_alloc(), pos, std::forward(args)...); + return *pos; + } + + void pop_back() + { + shrink_at(m_end - 1, 1); + } + + void resize(size_type n, const value_type& v) + { + reserve(n); + + auto new_end = m_begin + n; + + while (m_end > new_end) + { + atraits::destroy(get_alloc(), --m_end); + } + + while (new_end > m_end) + { + atraits::construct(get_alloc(), m_end++, v); + } + } + + void resize(size_type n) + { + reserve(n); + + auto new_end = m_begin + n; + + while (m_end > new_end) + { + atraits::destroy(get_alloc(), --m_end); + } + + while (new_end > m_end) + { + atraits::construct(get_alloc(), m_end++); + } + } + + bool is_static() const + { + return m_begin == static_begin_ptr(); + } + +private: + const T* static_begin_ptr() const + { + return reinterpret_cast(m_static_data + 0); + } + + T* static_begin_ptr() + { + return reinterpret_cast(m_static_data + 0); + } + + void destroy_all() + { + for (auto p = m_begin; p != m_end; ++p) + { + atraits::destroy(get_alloc(), p); + } + } + + void take_impl(small_vector& v) + { + if (v.is_static()) + { + m_begin = m_end = static_begin_ptr(); + for (auto p = v.m_begin; p != v.m_end; ++p) + { + atraits::construct(get_alloc(), m_end, std::move(*p)); + ++m_end; + } + + v.destroy_all(); + } + else + { + m_begin = v.m_begin; + m_end = v.m_end; + } + + v.m_begin = v.m_end = v.static_begin_ptr(); + v.m_capacity = StaticCapacity; + } + + // increase the size by splicing the elements in such a way that + // a hole of uninitialized elements is left at position, with size num + // returns the (potentially new) address of the hole + T* grow_at(const T* cp, size_t num) + { + auto position = const_cast(cp); + + I_ITLIB_SMALL_VECTOR_OUT_OF_RANGE_IF(position < m_begin || position > m_end); + + const auto s = size(); + const auto cdr = choose_data(s + num); + + if (cdr.ptr == m_begin) + { + // no special transfers needed + + m_end = m_begin + s + num; + + for (auto p = m_end - num - 1; p >= position; --p) + { + atraits::construct(get_alloc(), p + num, std::move(*p)); + atraits::destroy(get_alloc(), p); + } + + return position; + } + else + { + // we need to transfer the elements into the new buffer + + position = cdr.ptr + (position - m_begin); + + auto p = m_begin; + auto np = cdr.ptr; + + for (; np != position; ++p, ++np) + { + atraits::construct(get_alloc(), np, std::move(*p)); + } + + np += num; // hole + for (; p != m_end; ++p, ++np) + { + atraits::construct(get_alloc(), np, std::move(*p)); + } + + // destroy old + for (p = m_begin; p != m_end; ++p) + { + atraits::destroy(get_alloc(), p); + } + + if (!is_static()) + { + // we've moved from dyn memory, so deallocate the old one + atraits::deallocate(get_alloc(), m_begin, m_capacity); + } + + m_begin = cdr.ptr; + m_end = m_begin + s + num; + m_capacity = cdr.cap; + + return position; + } + } + + T* shrink_at(const T* cp, size_t num) + { + auto position = const_cast(cp); + + I_ITLIB_SMALL_VECTOR_OUT_OF_RANGE_IF(position < m_begin || position > m_end || position + num > m_end); + + const auto s = size(); + if (s - num == 0) + { + clear(); + return m_end; + } + + const auto cdr = choose_data(s - num); + + if (cdr.ptr == m_begin) + { + // no special transfers needed + + for (auto p = position, np = position + num; np != m_end; ++p, ++np) + { + atraits::destroy(get_alloc(), p); + atraits::construct(get_alloc(), p, std::move(*np)); + } + + for (auto p = m_end - num; p != m_end; ++p) + { + atraits::destroy(get_alloc(), p); + } + + m_end -= num; + } + else + { + // we need to transfer the elements into the new buffer + + assert(cdr.ptr == static_begin_ptr()); // since we're shrinking that's the only way to have a new buffer + + auto p = m_begin, np = cdr.ptr; + for (; p != position; ++p, ++np) + { + atraits::construct(get_alloc(), np, std::move(*p)); + atraits::destroy(get_alloc(), p); + } + + for (; p != position + num; ++p) + { + atraits::destroy(get_alloc(), p); + } + + for (; np != cdr.ptr + s - num; ++p, ++np) + { + atraits::construct(get_alloc(), np, std::move(*p)); + atraits::destroy(get_alloc(), p); + } + + // we've moved from dyn memory, so deallocate the old one + atraits::deallocate(get_alloc(), m_begin, m_capacity); + + position = cdr.ptr + (position - m_begin); + m_begin = cdr.ptr; + m_end = np; + m_capacity = StaticCapacity; + } + + return position; + } + + void assign_fill(size_type count, const T& value) + { + const auto cdr = choose_data(count); + + m_end = cdr.ptr; + for (size_t i=0; i + void assign_copy(InputIterator first, InputIterator last) + { + const auto cdr = choose_data(last - first); + + m_end = cdr.ptr; + for (auto p = first; p != last; ++p) + { + atraits::construct(get_alloc(), m_end, *p); + ++m_end; + } + + if (!is_static() && m_begin != cdr.ptr) + { + atraits::deallocate(get_alloc(), m_begin, m_capacity); + } + + m_begin = cdr.ptr; + m_capacity = cdr.cap; + } + + template + void assign_move(InputIterator first, InputIterator last) + { + const auto cdr = choose_data(last - first); + + m_end = cdr.ptr; + for (auto p = first; p != last; ++p) + { + atraits::construct(get_alloc(), m_end, std::move(*p)); + ++m_end; + } + + if (!is_static() && m_begin != cdr.ptr) + { + atraits::deallocate(get_alloc(), m_begin, m_capacity); + } + + m_begin = cdr.ptr; + m_capacity = cdr.cap; + } + + struct choose_data_result { + T* ptr; + size_t cap; + }; + choose_data_result choose_data(size_t desired_capacity) + { + choose_data_result ret = {m_begin, m_capacity}; + + if (!is_static()) + { + // we're at the dyn buffer, so see if it needs resize or revert to static + + if (desired_capacity > m_capacity) + { + while (ret.cap < desired_capacity) + { + // grow by roughly 1.5 + ret.cap *= 3; + ++ret.cap; + ret.cap /= 2; + } + + ret.ptr = atraits::allocate(get_alloc(), ret.cap); + } + else if (desired_capacity < RevertToStaticBelow) + { + // we're reverting to the static buffer + ret.ptr = static_begin_ptr(); + ret.cap = StaticCapacity; + } + + // else, do nothing + // the capacity is enough and we don't revert to static + } + else if (desired_capacity > StaticCapacity) + { + // we must move to dyn memory + // first move to dyn memory, use desired cap + + ret.cap = desired_capacity; + ret.ptr = atraits::allocate(get_alloc(), ret.cap); + } + // else, do nothing + // the capacity is and we're in the static buffer + + return ret; + } + + allocator_type& get_alloc() { return *this; } + const allocator_type& get_alloc() const { return *this; } + + pointer m_begin; // begin either points to m_static_data or to new memory + pointer m_end; + size_t m_capacity; + typename std::aligned_storage::value>::type m_static_data[StaticCapacity]; +}; + +template +bool operator==(const small_vector& a, + const small_vector& b) +{ + if (a.size() != b.size()) + { + return false; + } + + for (size_t i = 0; i < a.size(); ++i) + { + if (!(a[i] == b[i])) + return false; + } + + return true; +} + +template +bool operator!=(const small_vector& a, + const small_vector& b) + +{ + return !operator==(a, b); +} + +} diff --git a/common/astl/public/astl/time_t.hpp b/common/astl/public/astl/time_t.hpp new file mode 100644 index 00000000..d5bf8934 --- /dev/null +++ b/common/astl/public/astl/time_t.hpp @@ -0,0 +1,178 @@ +// itlib-time_t v1.02 +// +// A thin wrapper of std::time_t which provides thread safe std::tm getters and +// type-safe (std::chrono::duration-based) arithmetic +// +// SPDX-License-Identifier: MIT +// MIT License: +// Copyright(c) 2020-2023 Borislav Stanimirov +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files(the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and / or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions : +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// +// +// VERSION HISTORY +// +// 1.02 (2023-04-29) Fix MSVC warning for assignment in while +// 1.01 (2021-04-29) Added named ctors: now, from_gmtime, from_localtime +// 1.00 (2020-10-36) Initial release +// +// +// DOCUMENTATION +// +// Simply include this file wherever you need. +// It defines the class itlib::time_t which is a thin wrapper of std::time_t +// +// It provides multiplatform thread-safe ops to convert to std::tm +// * std::tm itlib::time_t::gmtime() const +// * std::tm itlib::time_t::localtime() const +// +// It also provides type-safe arithmetic +// * itlib::time_t operators +,-,+= and -= with std::chrono_duration +// * std::chrono_duration operator-(itlib::time_t a, itlib::time_t b) +// +// The file also defines the function +// std::string itlib::strftime(const char* fmt, const std::tm& tm); +// It works exactly as std::strftime but returns a std::string with the +// appropriate size +// +// TESTS +// +// You can find unit tests in the official repo: +// https://github.com/iboB/itlib/blob/master/test/ +// +#pragma once + +#include +#include +#include +#include + +namespace astl +{ + +class time_t +{ +public: + using timestamp_type = int64_t; + using duration_type = std::chrono::duration; + + time_t() = default; + time_t(const time_t&) = default; + time_t& operator=(const time_t&) = default; + + explicit time_t(const std::time_t& st) + { + m_t = static_cast(st); + } + explicit operator std::time_t() const + { + return static_cast(m_t); + } + + timestamp_type seconds_since_epoch() const { return m_t; } + + static time_t from_seconds(timestamp_type s) { return time_t(s); } + + static time_t now() { return from_seconds(std::time(nullptr)); } + + // non-const argument - gets normalized internally + static time_t from_gmtime(std::tm& gmtm) + { + auto tt = +#ifdef _WIN32 + _mkgmtime(&gmtm); +#else + timegm(&gmtm); +#endif + return from_seconds(tt); + } + + // non-const argument - gets normalized internally + static time_t from_localtime(std::tm& localtm) + { + return from_seconds(mktime(&localtm)); + } + + // cmp + friend bool operator==(const time_t& a, const time_t& b) { return a.m_t == b.m_t; } + friend bool operator!=(const time_t& a, const time_t& b) { return a.m_t != b.m_t; } + friend bool operator<(const time_t& a, const time_t& b) { return a.m_t < b.m_t; } + friend bool operator<=(const time_t& a, const time_t& b) { return a.m_t <= b.m_t; } + friend bool operator>(const time_t& a, const time_t& b) { return a.m_t > b.m_t; } + friend bool operator>=(const time_t& a, const time_t& b) { return a.m_t >= b.m_t; } + + // arithmetic + template + time_t& operator+=(const std::chrono::duration& d) { m_t += dc(d); return *this; } + template + time_t operator+(const std::chrono::duration& d) const { return time_t(m_t + dc(d)); } + template + time_t& operator-=(const std::chrono::duration& d) { m_t -= dc(d); return *this; } + template + time_t operator-(const std::chrono::duration& d) const { return time_t(m_t - dc(d)); } + + friend duration_type operator-(const time_t& a, const time_t& b) { return duration_type(a.m_t - b.m_t); } + + // ops + std::tm gmtime() const + { + std::tm ret = {}; + auto mt = std::time_t(*this); +#if defined(_WIN32) + gmtime_s(&ret, &mt); +#else + gmtime_r(&mt, &ret); +#endif + return ret; + } + + std::tm localtime() const + { + std::tm ret = {}; + auto mt = std::time_t(*this); +#if defined(_WIN32) + localtime_s(&ret, &mt); +#else + localtime_r(&mt, &ret); +#endif + return ret; + } + +private: + template + static timestamp_type dc(const std::chrono::duration& d) + { + return std::chrono::duration_cast(d).count(); + } + + timestamp_type m_t = 0; +}; + +inline std::string strftime(const char* format, const std::tm& tm) +{ + std::string ret; + ret.resize(128); + size_t len; + while ((len = std::strftime(&ret.front(), ret.size(), format, &tm)) == 0) ret.resize(2 * ret.size()); + ret.resize(len); + return ret; +} + +} diff --git a/common/jalog/CMakeLists.txt b/common/jalog/CMakeLists.txt new file mode 100644 index 00000000..7c6b3301 --- /dev/null +++ b/common/jalog/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) Borislav Stanimirov +# SPDX-License-Identifier: MIT +# +ac_dep(xec) + +add_subdirectory(code) +ac_add_test_subdir() +ac_add_example_subdir() diff --git a/common/jalog/LICENSE b/common/jalog/LICENSE new file mode 100644 index 00000000..3aeb7347 --- /dev/null +++ b/common/jalog/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021-2024 Borislav Stanimirov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/common/jalog/code/CMakeLists.txt b/common/jalog/code/CMakeLists.txt new file mode 100644 index 00000000..77e9b6aa --- /dev/null +++ b/common/jalog/code/CMakeLists.txt @@ -0,0 +1,73 @@ +# Copyright (c) Borislav Stanimirov +# SPDX-License-Identifier: MIT +# +icm_add_lib(ac-jalog AC_JALOG) +add_library(ac::jalog ALIAS ac-jalog) + +target_sources(ac-jalog PRIVATE + ac/jalog/API.h + + ac/jalog/Level.hpp + ac/jalog/ScopeDesc.hpp + ac/jalog/Entry.hpp + + ac/jalog/Scope.hpp + ac/jalog/Scope.cpp + ac/jalog/Logger.hpp + ac/jalog/Logger.cpp + + ac/jalog/Sink.hpp + ac/jalog/Sink.cpp + + ac/jalog/_qwrite.hpp + ac/jalog/_qwrite.cpp + + ac/jalog/BasicStream.hpp + + ac/jalog/Printf.hpp + ac/jalog/Printf.cpp + ac/jalog/PrintfWrap.hpp + + ac/jalog/Log.hpp + ac/jalog/LogPrintf.hpp + + ac/jalog/Instance.hpp + ac/jalog/Instance.cpp + + ac/jalog/sinks/AnsiColorSink.hpp + ac/jalog/sinks/AnsiColorSink.cpp +) + +if(WIN32) + target_sources(ac-jalog PRIVATE + ac/jalog/sinks/WindowsColorSink.hpp + ac/jalog/sinks/WindowsColorSink.cpp + ) +elseif(ANDROID) + target_sources(ac-jalog PRIVATE + ac/jalog/sinks/AndroidSink.hpp + ac/jalog/sinks/AndroidSink.cpp + ) + target_link_libraries(ac-jalog PRIVATE log) +elseif(APPLE) + target_sources(ac-jalog PRIVATE + ac/jalog/sinks/NSLogSink.hpp + ac/jalog/sinks/NSLogSink.mm + ) + find_library(COCOA Cocoa) + if(COCOA) + # On iOS Cocoa does not exist as a separate framework + # so just ignore it + target_link_libraries(ac-jalog PRIVATE ${COCOA}) + endif() +endif() + +target_include_directories(ac-jalog INTERFACE .) +target_compile_definitions(ac-jalog PUBLIC -DAC_JALOG_NO_BUILTIN_ASYNC=1) +target_link_libraries(ac-jalog + PUBLIC + ac::astl-public + PRIVATE + itlib::itlib +) + diff --git a/common/jalog/code/ac/jalog/API.h b/common/jalog/code/ac/jalog/API.h new file mode 100644 index 00000000..eb0c2426 --- /dev/null +++ b/common/jalog/code/ac/jalog/API.h @@ -0,0 +1,15 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include + +#if AC_JALOG_SHARED +# if BUILDING_AC_JALOG +# define AC_JALOG_API SYMBOL_EXPORT +# else +# define AC_JALOG_API SYMBOL_IMPORT +# endif +#else +# define AC_JALOG_API +#endif diff --git a/common/jalog/code/ac/jalog/BasicStream.hpp b/common/jalog/code/ac/jalog/BasicStream.hpp new file mode 100644 index 00000000..1e810228 --- /dev/null +++ b/common/jalog/code/ac/jalog/BasicStream.hpp @@ -0,0 +1,99 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "API.h" + +#include "_qwrite.hpp" + +#include "Scope.hpp" + +#include +#include + +namespace ac::jalog +{ + +template +auto base(I i) -> qwrite::wrapped_integer { return {i}; } + +struct endl_t {}; +inline const endl_t endl; + +class BasicStream +{ +public: + BasicStream(Scope& scope, Level lvl) + : m_scope(scope) + , m_level(lvl) + , m_stream(&m_streambuf) + {} + + using Self = BasicStream; + + void flush() + { + // length before zero-termination + auto textLength = m_streambuf.poff(); + // we don't *have* to zero-terminate the string, but let's be friendly to the outside world + m_streambuf.sputc(0); + + m_scope.addEntryUnchecked(m_level, std::string_view(m_streambuf.peek_container().data(), textLength)); + m_streambuf.clear(); + } + + Self& operator,(endl_t) + { + flush(); + return *this; + } + + Self& operator,(bool b) + { + if (b) m_streambuf.sputn("true", 4); + else m_streambuf.sputn("false", 5); + return *this; + } + + Self& operator,(char c) { m_streambuf.sputc(c); return *this; } + Self& operator,(std::string_view s) { m_streambuf.sputn(s.data(), s.size()); return *this; } + Self& operator,(const char* s) { operator,(std::string_view(s)); return *this; } + Self& operator,(const std::string& s) { operator,(std::string_view(s)); return *this; } + + template + Self& operator,(T* p) + { + qwrite::write_integer(m_streambuf, + qwrite::wrapped_integer{reinterpret_cast(p)}); + return *this; + } + + // baseful and padded integers + template + Self& operator,(qwrite::wrapped_integer wi) + { + qwrite::write_integer(m_streambuf, wi); + return *this; + } + + template + Self& operator,(const T& t) + { + if constexpr (std::is_integral_v) + qwrite::write_integer(m_streambuf, qwrite::wrapped_integer{t}); + else if constexpr (std::is_floating_point_v) + qwrite::write_float(m_streambuf, t); + else // fallback to std::ostream operator << + m_stream << t; + return *this; + } + +protected: + Scope& m_scope; + Level m_level; + + qwrite::streambuf m_streambuf; + std::ostream m_stream; +}; + +} diff --git a/common/jalog/code/ac/jalog/DefaultLogger.hpp b/common/jalog/code/ac/jalog/DefaultLogger.hpp new file mode 100644 index 00000000..3253b03d --- /dev/null +++ b/common/jalog/code/ac/jalog/DefaultLogger.hpp @@ -0,0 +1,11 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "API.h" + +namespace ac::jalog +{ +class Logger; +AC_JALOG_API Logger& DefaultLogger(); +} diff --git a/common/jalog/code/ac/jalog/DefaultScope.hpp b/common/jalog/code/ac/jalog/DefaultScope.hpp new file mode 100644 index 00000000..f2927e67 --- /dev/null +++ b/common/jalog/code/ac/jalog/DefaultScope.hpp @@ -0,0 +1,12 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "API.h" + +#include "Scope.hpp" + +namespace ac::jalog +{ +extern AC_JALOG_API Scope Default_Scope; +} diff --git a/common/jalog/code/ac/jalog/Entry.hpp b/common/jalog/code/ac/jalog/Entry.hpp new file mode 100644 index 00000000..2ef6415f --- /dev/null +++ b/common/jalog/code/ac/jalog/Entry.hpp @@ -0,0 +1,23 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once + +#include "Level.hpp" +#include "ScopeDesc.hpp" + +#include +#include + +namespace ac::jalog +{ + +struct Entry +{ + ScopeDesc scope; + Level level; + std::chrono::time_point timestamp; + std::string_view text; +}; + +} diff --git a/common/jalog/code/ac/jalog/Instance.cpp b/common/jalog/code/ac/jalog/Instance.cpp new file mode 100644 index 00000000..773edcde --- /dev/null +++ b/common/jalog/code/ac/jalog/Instance.cpp @@ -0,0 +1,49 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "Instance.hpp" + +#include "Logger.hpp" +#include "DefaultLogger.hpp" +#if !AC_JALOG_NO_BUILTIN_ASYNC +#include "AsyncLogging.hpp" +#include "AsyncLoggingThread.hpp" +#endif + +namespace ac::jalog +{ +Instance::Instance() : Instance(DefaultLogger()) {} +Instance::Instance(Logger& l) : m_logger(l) {} +Instance::~Instance() = default; + +Instance::SetupDSL::~SetupDSL() { + m_instance.m_logger.initialize(); +} + +Instance::SetupDSL& Instance::SetupDSL::defaultLevel(Level lvl) { + m_instance.m_logger.setDefaultLevel(lvl); + return *this; +} + +Instance::SetupDSL& Instance::SetupDSL::add(SinkPtr sink) { +#if !AC_JALOG_NO_BUILTIN_ASYNC + if (m_instance.m_asyncLogging) + m_instance.m_asyncLogging->add(sink); + else +#endif + m_instance.m_logger.addSink(sink); + + return *this; +} + +Instance::SetupDSL& Instance::SetupDSL::async() { +#if !AC_JALOG_NO_BUILTIN_ASYNC + if (m_instance.m_asyncLogging) return *this; + m_instance.m_asyncLogging = std::make_shared(); + m_instance.m_logger.addSink(m_instance.m_asyncLogging); + m_instance.m_asyncLoggingThread.reset(new AsyncLoggingThread(*m_instance.m_asyncLogging)); +#endif + return *this; +} + +} diff --git a/common/jalog/code/ac/jalog/Instance.hpp b/common/jalog/code/ac/jalog/Instance.hpp new file mode 100644 index 00000000..160ff48c --- /dev/null +++ b/common/jalog/code/ac/jalog/Instance.hpp @@ -0,0 +1,52 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "API.h" + +#include "Level.hpp" +#include "SinkPtr.hpp" + +namespace ac::jalog +{ + +class Logger; +class AsyncLogging; +class AsyncLoggingThread; + +class AC_JALOG_API Instance { +public: + Instance(); // DefaultLogger instance + explicit Instance(Logger& l); + ~Instance(); + + class AC_JALOG_API SetupDSL { + public: + explicit SetupDSL(Instance& i) : m_instance(i) {} + ~SetupDSL(); + SetupDSL& defaultLevel(Level lvl); + SetupDSL& add(SinkPtr sink); + template + SetupDSL& add(Args&&... args) { return add(std::make_shared(std::forward(args)...)); } + SetupDSL& async(); + private: + Instance& m_instance; + }; + + SetupDSL setup() { return SetupDSL(*this); } + + Logger& logger() { return m_logger; } + +#if !AC_JALOG_NO_BUILTIN_ASYNC + AsyncLogging* asyncLogging() { return m_asyncLogging.get(); } +#endif + +private: + Logger& m_logger; +#if !AC_JALOG_NO_BUILTIN_ASYNC + std::shared_ptr m_asyncLogging; + std::unique_ptr m_asyncLoggingThread; +#endif +}; + +} diff --git a/common/jalog/code/ac/jalog/Level.hpp b/common/jalog/code/ac/jalog/Level.hpp new file mode 100644 index 00000000..342711f3 --- /dev/null +++ b/common/jalog/code/ac/jalog/Level.hpp @@ -0,0 +1,21 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once + +#include + +namespace ac::jalog +{ + +enum class Level : uint32_t +{ + Debug, + Info, + Warning, + Error, + Critical, + Off +}; + +} diff --git a/common/jalog/code/ac/jalog/Log.hpp b/common/jalog/code/ac/jalog/Log.hpp new file mode 100644 index 00000000..d5a46865 --- /dev/null +++ b/common/jalog/code/ac/jalog/Log.hpp @@ -0,0 +1,26 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#if !defined(AC_JALOG_ENABLED) +# define AC_JALOG_ENABLED 1 +#endif + +#if AC_JALOG_ENABLED + +#include "DefaultScope.hpp" +#include "BasicStream.hpp" + +#define AC_JALOG_SCOPE(scope, lvl, ...) \ + if (scope.enabled(::ac::jalog::Level::lvl)) \ + ::ac::jalog::BasicStream(scope, ::ac::jalog::Level::lvl), __VA_ARGS__, ::ac::jalog::endl + +#define AC_JALOG(lvl, ...) AC_JALOG_SCOPE(::ac::jalog::Default_Scope, lvl, __VA_ARGS__) + +#else + +#define AC_JALOG_SCOPE(...) +#define AC_JALOG(...) + +#endif + diff --git a/common/jalog/code/ac/jalog/LogPrintf.hpp b/common/jalog/code/ac/jalog/LogPrintf.hpp new file mode 100644 index 00000000..ea2d80c1 --- /dev/null +++ b/common/jalog/code/ac/jalog/LogPrintf.hpp @@ -0,0 +1,26 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#if !defined(AC_JALOG_ENABLED) +# define AC_JALOG_ENABLED 1 +#endif + +#if AC_JALOG_ENABLED + +#include "DefaultScope.hpp" +#include "Printf.hpp" + +#define AC_JALOG_PRINTF_SCOPE(scope, lvl, fmt, ...) \ + if (scope.enabled(::ac::jalog::Level::lvl)) \ + ::ac::jalog::PrintfUnchecked(scope, ::ac::jalog::Level::lvl, fmt, ##__VA_ARGS__) + +#define AC_JALOG_PRINTF(lvl, ...) AC_JALOG_PRINTF_SCOPE(::ac::jalog::Default_Scope, lvl, __VA_ARGS__) + +#else + +#define AC_JALOG_PRINTF_SCOPE(...) +#define AC_JALOG_PRINTF(...) + +#endif + diff --git a/common/jalog/code/ac/jalog/LogStream.hpp b/common/jalog/code/ac/jalog/LogStream.hpp new file mode 100644 index 00000000..4e505e45 --- /dev/null +++ b/common/jalog/code/ac/jalog/LogStream.hpp @@ -0,0 +1,25 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#if !defined(AC_JALOG_ENABLED) +# define AC_JALOG_ENABLED 1 +#endif + +#if AC_JALOG_ENABLED + +#include "DefaultScope.hpp" +#include "Stream.hpp" + +#define AC_JALOG_STREAM_SCOPE(scope, lvl) ::ac::jalog::Stream(scope, ::ac::jalog::Level::lvl) +#define AC_JALOG_STREAM(lvl) AC_JALOG_STREAM_SCOPE(::ac::jalog::Default_Scope, lvl) + +#else + +#include "NoopStream.hpp" + +#define AC_JALOG_STREAM_SCOPE(...) ::ac::jalog::NoopStream() +#define AC_JALOG_STREAM(...) ::ac::jalog::NoopStream() + +#endif + diff --git a/common/jalog/code/ac/jalog/Logger.cpp b/common/jalog/code/ac/jalog/Logger.cpp new file mode 100644 index 00000000..4f36653a --- /dev/null +++ b/common/jalog/code/ac/jalog/Logger.cpp @@ -0,0 +1,136 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "Logger.hpp" +#include "DefaultLogger.hpp" +#include "Scope.hpp" +#include "DefaultScope.hpp" +#include "Sink.hpp" + +#include + +#include + +namespace ac::jalog +{ + +Logger& DefaultLogger() +{ + static Logger l; + return l; +} + +Scope Default_Scope(std::string_view{}); + +Logger::Logger() = default; +Logger::~Logger() = default; + +void Logger::setDefaultLevel(Level lvl) +{ + std::lock_guard l(m_mutex); + m_defaultLevel = lvl; +} + +Level Logger::defaultLevel() const +{ + std::lock_guard l(m_mutex); + return m_defaultLevel; +} + +void Logger::flush() +{ + std::lock_guard l(m_mutex); + if (!m_initialized) return; // nothing to flush + for (auto& s : m_sinks) + { + s->flush(); + } +} + +void Logger::addSink(SinkPtr sink) +{ + std::lock_guard l(m_mutex); + assert(!m_initialized); + if (m_initialized) return; // defensive: Don't crash + m_sinks.push_back(std::move(sink)); +} + +void Logger::initialize() +{ + std::lock_guard l(m_mutex); + assert(!m_initialized); + if (m_initialized) return; // defensive: Don't crash + + m_initialized = true; + + for (auto scope : m_scopes) + { + if (!scope) continue; + initScope(*scope); + } +} + +void Logger::initScope(Scope& scope) +{ + scope.setLevel(m_defaultLevel); + scope.m_sinks.clear(); + scope.m_sinks.reserve(m_sinks.size()); + for (auto& s : m_sinks) + { + scope.m_sinks.emplace_back(s.get()); + } +} + +void Logger::registerScope(Scope& scope) +{ + std::lock_guard l(m_mutex); + + if (m_initialized) + { + // if the we're initialized, also initialize scope + initScope(scope); + } + else + { + // otherwise disable scope until we're initialized (if we ever are) + scope.setLevel(Level::Off); + } + + // place scope into sparse vector registry + auto slot = itlib::pfind(m_scopes, nullptr); + if (!slot) + { + slot = &m_scopes.emplace_back(); + } + *slot = &scope; +} + +void Logger::unregisterScope(Scope& scope) +{ + std::lock_guard l(m_mutex); + // free slot in sparse vector registry + auto slot = itlib::pfind(m_scopes, &scope); + assert(slot); // bug! Scope must be registered + *slot = nullptr; +} + +Logger::SetupDSL::SetupDSL(Logger& l) : m_logger(l) {}; + +Logger::SetupDSL::~SetupDSL() +{ + m_logger.initialize(); +} + +Logger::SetupDSL& Logger::SetupDSL::defaultLevel(Level lvl) +{ + m_logger.setDefaultLevel(lvl); + return *this; +} + +Logger::SetupDSL& Logger::SetupDSL::add(SinkPtr sink) +{ + m_logger.addSink(std::move(sink)); + return *this; +} + +} diff --git a/common/jalog/code/ac/jalog/Logger.hpp b/common/jalog/code/ac/jalog/Logger.hpp new file mode 100644 index 00000000..c73729f3 --- /dev/null +++ b/common/jalog/code/ac/jalog/Logger.hpp @@ -0,0 +1,89 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "API.h" + +#include "Level.hpp" +#include "SinkPtr.hpp" + +#include +#include + +namespace ac::jalog +{ + +class Sink; +class Scope; +class Instance; + +class AC_JALOG_API Logger +{ +public: + Logger(); + ~Logger(); + + Logger(const Logger&) = delete; + Logger& operator=(const Logger&) = delete; + Logger(Logger&&) = delete; + Logger& operator=(Logger&&) = delete; + + class AC_JALOG_API SetupDSL + { + public: + explicit SetupDSL(Logger& l); + ~SetupDSL(); + SetupDSL& defaultLevel(Level lvl); + SetupDSL& add(SinkPtr sink); + template + SetupDSL& add(Args&&... args) { return add(std::make_shared(std::forward(args)...)); } + private: + Logger& m_logger; + }; + + SetupDSL directSetup() { return SetupDSL(*this); } + + // default level for newly created scopes + // scopes set their own level afterwards + void setDefaultLevel(Level lvl); + Level defaultLevel() const; + + // iterate scopes with a functor + // if the functor returns true, stops the iteration + template + void foreachScope(F&& f) + { + std::lock_guard l(m_mutex); + for (auto scope : m_scopes) + { + if (!scope) continue; + if (f(*scope)) break; + } + } + + // flush sinks + // can be used in signal handlers or in other cases where + // abnormal termination is imminent and there is risk of losing log messages + void flush(); + +private: + mutable std::mutex m_mutex; + + // setup + friend class Instance; + void addSink(SinkPtr sink); + void initialize(); + std::vector m_sinks; + bool m_initialized = false; + + // scope registry + Level m_defaultLevel = Level::Debug; + friend class Scope; + void registerScope(Scope& scope); + void unregisterScope(Scope& scope); + std::vector m_scopes; // sparse vector + + void initScope(Scope& scope); +}; + +} diff --git a/common/jalog/code/ac/jalog/NoopStream.hpp b/common/jalog/code/ac/jalog/NoopStream.hpp new file mode 100644 index 00000000..252bbe17 --- /dev/null +++ b/common/jalog/code/ac/jalog/NoopStream.hpp @@ -0,0 +1,17 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once + +namespace ac::jalog +{ + +// do-nothing stream for cases where logging is disabled at compile time +class NoopStream +{ +public: + template + NoopStream& operator<<(const T&) { return *this; } +}; + +} diff --git a/common/jalog/code/ac/jalog/Printf.cpp b/common/jalog/code/ac/jalog/Printf.cpp new file mode 100644 index 00000000..ed4622f6 --- /dev/null +++ b/common/jalog/code/ac/jalog/Printf.cpp @@ -0,0 +1,65 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "Printf.hpp" + +#include "Scope.hpp" + +#include +#include + +namespace ac::jalog +{ + +void VPrintfUnchecked(Scope& scope, Level lvl, const char* format, va_list args) +{ + va_list acopy; + // we need to copy the va_list, because if our optimistic fit below doesn't fit, we would need to call + // vsnprintf again (and the first call would've consumed the original va_list) + va_copy(acopy, args); + + // optimistically try to fit in a small buffer + size_t size = 0; + { + char buf[1024]; + size = size_t(vsnprintf(buf, sizeof(buf), format, args)); + + if (size <= sizeof(buf)) + { + // success + scope.addEntryUnchecked(lvl, std::string_view(buf, size)); + return; + } + } + + std::vector dbuf(size + 1); + size = size_t(vsnprintf(dbuf.data(), dbuf.size(), format, acopy)); + + scope.addEntryUnchecked(lvl, std::string_view(dbuf.data(), size)); +} + +void VPrintf(Scope& scope, Level lvl, const char* format, va_list args) +{ + if (!scope.enabled(lvl)) return; + VPrintfUnchecked(scope, lvl, format, args); +} + +void Printf(Scope& scope, Level lvl, const char* format, ...) +{ + if (!scope.enabled(lvl)) return; + + va_list args; + va_start(args, format); + VPrintfUnchecked(scope, lvl, format, args); + va_end(args); +} + +void PrintfUnchecked(Scope& scope, Level lvl, const char* format, ...) +{ + va_list args; + va_start(args, format); + VPrintfUnchecked(scope, lvl, format, args); + va_end(args); +} + +} diff --git a/common/jalog/code/ac/jalog/Printf.hpp b/common/jalog/code/ac/jalog/Printf.hpp new file mode 100644 index 00000000..68e75406 --- /dev/null +++ b/common/jalog/code/ac/jalog/Printf.hpp @@ -0,0 +1,33 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "API.h" + +#include "Level.hpp" + +#include + +namespace ac::jalog +{ + +class Scope; + +#if defined(__GNUC__) +# define I_AC_JALOG_PRINTF_FMT __attribute__((format(printf, 3, 4))) +# define _Printf_format_string_ +#else +# define I_AC_JALOG_PRINTF_FMT +# if !defined(_MSC_VER) +# define _Printf_format_string_ +# endif +#endif + + +AC_JALOG_API void VPrintf(Scope& scope, Level lvl, const char* format, va_list args); +AC_JALOG_API void VPrintfUnchecked(Scope& scope, Level lvl, const char* format, va_list args); + +AC_JALOG_API I_AC_JALOG_PRINTF_FMT void Printf(Scope& scope, Level lvl, _Printf_format_string_ const char* format, ...); +AC_JALOG_API I_AC_JALOG_PRINTF_FMT void PrintfUnchecked(Scope& scope, Level lvl, _Printf_format_string_ const char* format, ...); + +} diff --git a/common/jalog/code/ac/jalog/PrintfWrap.hpp b/common/jalog/code/ac/jalog/PrintfWrap.hpp new file mode 100644 index 00000000..8cd8f8db --- /dev/null +++ b/common/jalog/code/ac/jalog/PrintfWrap.hpp @@ -0,0 +1,41 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "Printf.hpp" + +// Macro which defines a function with signature: +// template +// void(const char*, ...). +// +// It can be used for C libraries (or retro C++ ones) which have can be +// configured with a function pointer of this type. +// +// For example: +// struct CLibConfig { +// void (*logInfo)(const char*, ...); +// void (*logError)(const char*, ...); +// // ... +// }; +// AC_JALOG_DEFINE_PRINTF_FUNC(myprintf, myscope) +// ... +// CLibConfig cfg = {}; +// cfg.logInfo = myprintf; +// cfg.logError = myprintf; +// ... +// CLibInit(&cfg); + +#if defined(__GNUC__) +# define I_AC_JALOG_PRINTF_WRAP_FMT __attribute__((format(printf, 1, 2))) +#else +# define I_AC_JALOG_PRINTF_WRAP_FMT +#endif + +#define AC_JALOG_DEFINE_PRINTF_FUNC(name, scope) \ + template <::ac::jalog::Level lvl> \ + I_AC_JALOG_PRINTF_WRAP_FMT void name(_Printf_format_string_ const char* format, ...) { \ + va_list args; \ + va_start(args, format); \ + ::ac::jalog::VPrintf(scope, lvl, format, args); \ + va_end(args); \ + } diff --git a/common/jalog/code/ac/jalog/Scope.cpp b/common/jalog/code/ac/jalog/Scope.cpp new file mode 100644 index 00000000..6bfdd6b2 --- /dev/null +++ b/common/jalog/code/ac/jalog/Scope.cpp @@ -0,0 +1,54 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "Scope.hpp" + +#include "Logger.hpp" +#include "DefaultLogger.hpp" +#include "Sink.hpp" +#include "Entry.hpp" + +#include + +namespace ac::jalog +{ + +Scope::Scope(Logger& logger, std::string_view lbl, uintptr_t id, intptr_t userData) + : m_logger(logger) +{ + auto len = std::min(lbl.length(), sizeof(m_desc.m_labelBytes) - 1); + if (len > 0) { + std::memcpy(m_desc.m_labelBytes, lbl.data(), len); + } + m_desc.m_labelBytes[len] = 0; + m_desc.m_labelLength = uint32_t(len); + + m_desc.m_id = id; + m_desc.userData = userData; + + m_logger.registerScope(*this); +} + +Scope::Scope(std::string_view label, uintptr_t id, intptr_t userData) + : Scope(DefaultLogger(), label, id, userData) +{} + +Scope::~Scope() +{ + m_logger.unregisterScope(*this); +} + +void Scope::addEntryUnchecked(Level lvl, std::string_view text) +{ + Entry e; + e.scope = m_desc; + e.level = lvl; + e.timestamp = std::chrono::system_clock::now(); + e.text = text; + for (auto s : m_sinks) + { + s->record(e); + } +} + +} diff --git a/common/jalog/code/ac/jalog/Scope.hpp b/common/jalog/code/ac/jalog/Scope.hpp new file mode 100644 index 00000000..dfbe78df --- /dev/null +++ b/common/jalog/code/ac/jalog/Scope.hpp @@ -0,0 +1,63 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "API.h" + +#include "Level.hpp" +#include "ScopeDesc.hpp" + +#include +#include +#include + +namespace ac::jalog +{ + +class Logger; +class Sink; + +class AC_JALOG_API Scope +{ +public: + // creates a default logger scope + explicit Scope(std::string_view label, uintptr_t id = 0, intptr_t userData = -1); + + // creates a scope for a given logger + Scope(Logger& logger, std::string_view label, uintptr_t id = 0, intptr_t userData = -1); + + ~Scope(); + + Scope(const Scope&) = delete; + Scope& operator=(const Scope&) = delete; + Scope(Scope&&) = delete; + Scope& operator=(Scope&&) = delete; + + const ScopeDesc& desc() const { return m_desc; } + + void setLevel(Level lvl) { m_level.store(lvl, std::memory_order_relaxed); } + Level level() const { return m_level.load(std::memory_order_relaxed); } + + bool enabled(Level lvl) const { return lvl >= level(); } + + // add an entry but don't perform the enabled check + // assume it's performed from the outside + void addEntryUnchecked(Level lvl, std::string_view text); + + // add entry if the level is >= than our min level + void addEntry(Level lvl, std::string_view text) { if (enabled(lvl)) addEntryUnchecked(lvl, text); } + +private: + friend class Logger; + + std::atomic m_level = Level::Debug; + +protected: + Logger& m_logger; + + ScopeDesc m_desc; + + std::vector m_sinks; +}; + +} diff --git a/common/jalog/code/ac/jalog/ScopeDesc.hpp b/common/jalog/code/ac/jalog/ScopeDesc.hpp new file mode 100644 index 00000000..379883f5 --- /dev/null +++ b/common/jalog/code/ac/jalog/ScopeDesc.hpp @@ -0,0 +1,32 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include +#include + +namespace ac::jalog +{ + +class ScopeDesc +{ + friend class Scope; + + char m_labelBytes[16]; + uint32_t m_labelLength; + + uintptr_t m_id; + +public: + // label is zero-terminated just in case + std::string_view label() const { return {m_labelBytes, m_labelLength}; } + + const char* labelCStr() const { return m_labelBytes; } + + // unused by jalog. May be used by user-defined sinks + uintptr_t id() const { return m_id; } + + intptr_t userData; // unused by jalog. May be used by user-defined sinks +}; + +} diff --git a/common/jalog/code/ac/jalog/Sink.cpp b/common/jalog/code/ac/jalog/Sink.cpp new file mode 100644 index 00000000..bcf18e25 --- /dev/null +++ b/common/jalog/code/ac/jalog/Sink.cpp @@ -0,0 +1,10 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "Sink.hpp" + +namespace ac::jalog +{ +// export the vtable from here +void Sink::flush() {} +} diff --git a/common/jalog/code/ac/jalog/Sink.hpp b/common/jalog/code/ac/jalog/Sink.hpp new file mode 100644 index 00000000..4a652f66 --- /dev/null +++ b/common/jalog/code/ac/jalog/Sink.hpp @@ -0,0 +1,25 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "API.h" + +namespace ac::jalog +{ + +class ScopeDesc; +struct Entry; + +class AC_JALOG_API Sink +{ +protected: + ~Sink() = default; +public: + + virtual void record(const Entry& entry) = 0; + + // optionally override to flush recorded entries + virtual void flush(); +}; + +} diff --git a/common/jalog/code/ac/jalog/SinkPtr.hpp b/common/jalog/code/ac/jalog/SinkPtr.hpp new file mode 100644 index 00000000..73a1ff4a --- /dev/null +++ b/common/jalog/code/ac/jalog/SinkPtr.hpp @@ -0,0 +1,12 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once + +#include + +namespace ac::jalog +{ +class Sink; +using SinkPtr = std::shared_ptr; +} diff --git a/common/jalog/code/ac/jalog/Stream.hpp b/common/jalog/code/ac/jalog/Stream.hpp new file mode 100644 index 00000000..c6645e68 --- /dev/null +++ b/common/jalog/code/ac/jalog/Stream.hpp @@ -0,0 +1,53 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "BasicStream.hpp" + +namespace ac::jalog +{ + +class Stream : public BasicStream +{ +public: + Stream(Scope& scope, Level lvl = Level::Info) + : BasicStream(scope, lvl) + , m_enabled(scope.enabled(lvl)) + {} + + ~Stream() + { + flushIfNotEmpty(); + } + + bool enabled() const { return m_enabled; } + + Stream& operator<<(Level lvl) + { + flushIfNotEmpty(); + m_level = lvl; + m_enabled = m_scope.enabled(lvl); + return *this; + } + + template + Stream& operator<<(const T& t) + { + if (!m_enabled) return *this; + static_cast(*this), t; + return *this; + } + +private: + void flushIfNotEmpty() + { + if (m_streambuf.poff() != 0) + { + flush(); + } + } + + bool m_enabled; +}; + +} diff --git a/common/jalog/code/ac/jalog/_qwrite.cpp b/common/jalog/code/ac/jalog/_qwrite.cpp new file mode 100644 index 00000000..dbac1522 --- /dev/null +++ b/common/jalog/code/ac/jalog/_qwrite.cpp @@ -0,0 +1,53 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "_qwrite.hpp" + +#if JALOG_USE_MSCHARCONV +#include +#else +#include +#define msstl std +#endif + +#include + +namespace ac::jalog::qwrite { + +namespace { +template +void write_fp(streambuf& out, FP f, float_format fmt, pad padding) { + char buf[128]; // hopefully enough + + auto cf = [fmt]() { + switch (fmt) { + case float_format::general: return msstl::chars_format::general; + case float_format::fixed: return msstl::chars_format::fixed; + case float_format::scientific: return msstl::chars_format::scientific; + case float_format::hex: return msstl::chars_format::hex; + default: return msstl::chars_format::general; + } + }(); + + auto res = msstl::to_chars(buf, buf + sizeof(buf), f, cf); + if (res.ec != std::errc{}) { + // it was not enough :( + // rescue + res.ptr = buf + snprintf(buf, sizeof(buf), "%f", double(f)); + } + + write_padded(out, buf, int(res.ptr - buf), padding); +} +} + +void write_float(streambuf& out, float f, float_format fmt, pad padding) { + write_fp(out, f, fmt, padding); +} +void write_float(streambuf& out, double f, float_format fmt, pad padding) { + write_fp(out, f, fmt, padding); +} +void write_float(streambuf& out, long double f, float_format fmt, pad padding) { + write_fp(out, f, fmt, padding); +} + +} diff --git a/common/jalog/code/ac/jalog/_qwrite.hpp b/common/jalog/code/ac/jalog/_qwrite.hpp new file mode 100644 index 00000000..22924bc7 --- /dev/null +++ b/common/jalog/code/ac/jalog/_qwrite.hpp @@ -0,0 +1,112 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "API.h" + +#include +#include + +namespace ac::jalog::qwrite { +#if defined(I_AC_JALOG_TESTING_QWRITE) +// for the qwrite tests use std::streambuf as it makes test code simpler +using streambuf = std::streambuf; +#else +// for the actual library use the final type to avoid virtual callsstd::streambuf +using streambuf = astl::mem_ostreambuf>; +#endif + +struct pad { + int16_t size = 0; + enum pad_type : int8_t { + left, + right, + inner + } type = left; + char character = ' '; +}; + +inline void write_padded(streambuf& out, const char* numstr, int length, pad padding) { + padding.size -= int16_t(length); + if (padding.size <= 0) { + out.sputn(numstr, length); + } + else if (padding.type == pad::right) { + out.sputn(numstr, length); + while (padding.size --> 0) { + out.sputc(padding.character); + } + } + else { + if (padding.type == pad::inner && *numstr == '-') { + out.sputc('-'); + ++numstr; + --length; + } + while (padding.size --> 0) { + out.sputc(padding.character); + } + out.sputn(numstr, length); + } +} + +template +struct wrapped_integer { Integer i; }; + +template +void write_integer(streambuf& out, const wrapped_integer value, pad padding = {}) { + static_assert(Base >= 2 && Base <= 36, "Invalid base"); + + char buf[sizeof(Integer) * 8]; // enough for integers in base 2 or greater + const auto end = buf + sizeof(buf); + auto p = end; // writing from back to front + + using Unsigned = std::make_unsigned_t; + constexpr auto ubase = Unsigned(Base); // base in same size to avoid promotion warnings + + Unsigned uvalue = Unsigned(value.i); + if constexpr (std::is_signed_v) { + if (value.i < 0) { + uvalue = 0 - uvalue; + } + } + + do { + if constexpr(ubase <= 10) { + // instead of using the lookup dable we just add to '0' + *--p = char('0' + uvalue % ubase); + } + else { + static constexpr char char_from_digit[] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' + }; + *--p = char_from_digit[uvalue % ubase]; + } + uvalue /= ubase; + } while (uvalue != 0); + + int len = int(end - p); + + if constexpr (std::is_signed_v) { + if (value.i < 0) { + *--p = '-'; + ++len; + } + } + + write_padded(out, p, len, padding); +} + +enum class float_format { + general, + fixed, + scientific, + hex, +}; + +AC_JALOG_API void write_float(streambuf& out, float f, float_format fmt = float_format::general, pad padding = {}); +AC_JALOG_API void write_float(streambuf& out, double f, float_format fmt = float_format::general, pad padding = {}); +AC_JALOG_API void write_float(streambuf& out, long double f, float_format fmt = float_format::general, pad padding = {}); + +} diff --git a/common/jalog/code/ac/jalog/sinks/AndroidSink.cpp b/common/jalog/code/ac/jalog/sinks/AndroidSink.cpp new file mode 100644 index 00000000..a105372f --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/AndroidSink.cpp @@ -0,0 +1,32 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "AndroidSink.hpp" + +#include "../Entry.hpp" + +#include + +namespace ac::jalog::sinks { + +namespace { +android_LogPriority jalogToAndroid(ac::jalog::Level level) { + switch (level) { + case ac::jalog::Level::Debug: return ANDROID_LOG_DEBUG; + case ac::jalog::Level::Info: return ANDROID_LOG_INFO; + case ac::jalog::Level::Warning: return ANDROID_LOG_WARN; + case ac::jalog::Level::Error: return ANDROID_LOG_ERROR; + case ac::jalog::Level::Critical: return ANDROID_LOG_FATAL; + default: return ANDROID_LOG_DEFAULT; + } +} +} + +void AndroidSink::record(const Entry& entry) { + auto lbl = entry.scope.labelCStr(); + auto txt = entry.text; + + __android_log_print(jalogToAndroid(entry.level), lbl, "%.*s", int(txt.length()), txt.data()); +} + +} diff --git a/common/jalog/code/ac/jalog/sinks/AndroidSink.hpp b/common/jalog/code/ac/jalog/sinks/AndroidSink.hpp new file mode 100644 index 00000000..5f41e365 --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/AndroidSink.hpp @@ -0,0 +1,17 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "../API.h" +#include "../Sink.hpp" + +namespace ac::jalog::sinks +{ + +class AC_JALOG_API AndroidSink final : public Sink +{ +public: + virtual void record(const Entry& entry) override; +}; + +} diff --git a/common/jalog/code/ac/jalog/sinks/AnsiColorSink.cpp b/common/jalog/code/ac/jalog/sinks/AnsiColorSink.cpp new file mode 100644 index 00000000..76138d4b --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/AnsiColorSink.cpp @@ -0,0 +1,60 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "AnsiColorSink.hpp" + +#include "../Entry.hpp" + +#include + +#include + +namespace ac::jalog::sinks +{ + +void AnsiColorSink::record(const Entry& entry) +{ + FILE* out = entry.level < Level::Error ? stdout : stderr; + + // time + { + using namespace std::chrono; + itlib::time_t tt(system_clock::to_time_t(entry.timestamp)); + auto tm = tt.localtime(); + + char buf[32]; + strftime(buf, 32, "%m/%d %X", &tm); + + int ms = int(duration_cast(entry.timestamp.time_since_epoch()).count() % 1000); + + fprintf(out, "\033[34m%s.%03d\033[m ", buf, ms); + } + + // scope + { + auto lbl = entry.scope.label(); + if (!lbl.empty()) + { + fprintf(out, "\033[36m%.*s\033[m ", int(lbl.length()), lbl.data()); + } + } + + // level + { + static const char* const lstr[] = { + "\033[35m[debug]\033[m", + "\033[01;36m[Info ]\033[m", + "\033[01;33m[WARN ]\033[m", + "\033[31m[ERROR]\033[m", + "\033[01;31m[CRIT!]\033[m", + }; + static_assert(std::size(lstr) == static_cast(Level::Off)); + const char* str = (entry.level < Level::Off) ? lstr[static_cast(entry.level)] : "[ ? ]"; + fputs(str, out); + } + + // text + fprintf(out, " %.*s\n", int(entry.text.size()), entry.text.data()); +} + +} diff --git a/common/jalog/code/ac/jalog/sinks/AnsiColorSink.hpp b/common/jalog/code/ac/jalog/sinks/AnsiColorSink.hpp new file mode 100644 index 00000000..fa02df27 --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/AnsiColorSink.hpp @@ -0,0 +1,17 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "../API.h" +#include "../Sink.hpp" + +namespace ac::jalog::sinks +{ + +class AC_JALOG_API AnsiColorSink final : public Sink +{ +public: + virtual void record(const Entry& entry) override; +}; + +} diff --git a/common/jalog/code/ac/jalog/sinks/ColorSink.hpp b/common/jalog/code/ac/jalog/sinks/ColorSink.hpp new file mode 100644 index 00000000..96f823b1 --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/ColorSink.hpp @@ -0,0 +1,12 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once + +#if defined(_WIN32) +#include "WindowsColorSink.hpp" +namespace ac::jalog::sinks { using ColorSink = WindowsColorSink; } +#else +#include "AnsiColorSink.hpp" +namespace ac::jalog::sinks { using ColorSink = AnsiColorSink; } +#endif diff --git a/common/jalog/code/ac/jalog/sinks/DefaultSink.hpp b/common/jalog/code/ac/jalog/sinks/DefaultSink.hpp new file mode 100644 index 00000000..3ce70507 --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/DefaultSink.hpp @@ -0,0 +1,26 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once + +#if defined(_WIN32) +# include "WindowsColorSink.hpp" + namespace ac::jalog::sinks { using DefaultSink = WindowsColorSink; } +#elif defined(__ANDROID__) +# include "AndroidSink.hpp" + namespace ac::jalog::sinks { using DefaultSink = AndroidSink; } +#elif defined(__APPLE__) +# include + // only use NSLog as default on iOS + // on macOS there will be a terminal available and color is prettier +# if TARGET_OS_IPHONE +# include "NSLogSink.hpp" + namespace ac::jalog::sinks { using DefaultSink = NSLogSink; } +# else +# include "AnsiColorSink.hpp" + namespace ac::jalog::sinks { using DefaultSink = AnsiColorSink; } +# endif +#else +# include "AnsiColorSink.hpp" + namespace ac::jalog::sinks { using DefaultSink = AnsiColorSink; } +#endif diff --git a/common/jalog/code/ac/jalog/sinks/NSLogSink.hpp b/common/jalog/code/ac/jalog/sinks/NSLogSink.hpp new file mode 100644 index 00000000..679fbc18 --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/NSLogSink.hpp @@ -0,0 +1,17 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "../API.h" +#include "../Sink.hpp" + +namespace ac::jalog::sinks +{ + +class AC_JALOG_API NSLogSink final : public Sink +{ +public: + virtual void record(const Entry& entry) override; +}; + +} diff --git a/common/jalog/code/ac/jalog/sinks/NSLogSink.mm b/common/jalog/code/ac/jalog/sinks/NSLogSink.mm new file mode 100644 index 00000000..a5fc0cf4 --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/NSLogSink.mm @@ -0,0 +1,40 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "NSLogSink.hpp" + +#include "../Entry.hpp" + +#include + +namespace ac::jalog::sinks +{ + +namespace +{ +const NSString* lstr[] = { + @"[debug]", + @"[Info ]", + @"[WARN ]", + @"[ERROR]", + @"[CRIT!]", +}; +const NSString* q = @"[ ? ]"; +} + +void NSLogSink::record(const Entry& entry) +{ + auto sev = (entry.level < Level::Off) ? lstr[static_cast(entry.level)] : q; + + auto sv = entry.text; + auto txt = CFStringCreateWithBytes(nullptr, (const UInt8 *)sv.data(), CFIndex(sv.size()), + kCFStringEncodingUTF8, false); + + auto lbl = CFStringCreateWithCString(nullptr, entry.scope.labelCStr(), kCFStringEncodingUTF8); + NSLog(@"%@%@: %@", lbl, sev, txt); + + CFRelease(lbl); + CFRelease(txt); +} + +} diff --git a/common/jalog/code/ac/jalog/sinks/SimpleOStreamSink.hpp b/common/jalog/code/ac/jalog/sinks/SimpleOStreamSink.hpp new file mode 100644 index 00000000..b81ae910 --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/SimpleOStreamSink.hpp @@ -0,0 +1,80 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "../Sink.hpp" +#include "../Entry.hpp" + +#include +#include + +#include + +namespace ac::jalog::sinks +{ + +class SimpleOStreamSink final : public Sink +{ +public: + SimpleOStreamSink(std::ostream& out, std::ostream& err) + : m_out(out) + , m_err(err) + {} + + static constexpr std::string_view levelToString(Level l) + { + switch (l) + { + case Level::Debug: return "debug"; + case Level::Info: return "Info "; + case Level::Warning: return "Warn "; + case Level::Error: return "ERROR"; + case Level::Critical:return "CRIT!"; + default: return " ? "; + } + } + + virtual void record(const Entry& entry) override { + auto& out = [&, this]() -> std::ostream& { + if (entry.level < Level::Error) return m_out; + return m_err; + }(); + + // time + { + using namespace std::chrono; + itlib::time_t tt(system_clock::to_time_t(entry.timestamp)); + auto tm = tt.localtime(); + out << std::put_time(&tm, "%m/%d %X"); + + // milliseconds + auto ms = duration_cast(entry.timestamp.time_since_epoch()).count() % 1000; + const char* padding; + if (ms < 10) padding = "00"; + else if (ms < 100) padding = "0"; + else padding = ""; + out << "." << padding << ms; + } + + // scope + { + auto lbl = entry.scope.label(); + if (!lbl.empty()) + { + out << " " << lbl; + } + } + + // level + out << " [" << levelToString(entry.level) << "] "; + + // text + out << entry.text << "\n"; + } + +private: + std::ostream& m_out; + std::ostream& m_err; +}; + +} diff --git a/common/jalog/code/ac/jalog/sinks/SimpleStdioSink.hpp b/common/jalog/code/ac/jalog/sinks/SimpleStdioSink.hpp new file mode 100644 index 00000000..6d11a25e --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/SimpleStdioSink.hpp @@ -0,0 +1,70 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "../Sink.hpp" +#include "../Entry.hpp" + +#include + +#include + +namespace ac::jalog::sinks +{ + +class SimpleStdioSink final : public Sink +{ +public: + SimpleStdioSink(FILE* out = stdout, FILE* err = nullptr) + : m_out(out) + , m_err(err ? err : out) + {} + + static const char* levelToString(Level l) + { + switch (l) + { + case Level::Debug: return "debug"; + case Level::Info: return "Info "; + case Level::Warning: return "Warn "; + case Level::Error: return "ERROR"; + case Level::Critical:return "CRIT!"; + default: return " ? "; + } + } + + virtual void record(const Entry& entry) override { + FILE* out = entry.level < Level::Error ? m_out : m_err; + + // time + { + using namespace std::chrono; + astl::time_t tt(system_clock::to_time_t(entry.timestamp)); + auto tm = tt.localtime(); + char buf[32]; + strftime(buf, 32, "%m/%d %X", &tm); + + int ms = int(duration_cast(entry.timestamp.time_since_epoch()).count() % 1000); + + fprintf(out, "%s.%03d ", buf, ms); + } + + // scope + { + auto lbl = entry.scope.label(); + if (!lbl.empty()) + { + fprintf(out, "%.*s ", int(lbl.length()), lbl.data()); + } + } + + // level and text + fprintf(out, "[%.5s] %.*s\n", levelToString(entry.level), int(entry.text.size()), entry.text.data()); + } + +private: + FILE* m_out; + FILE* m_err; +}; + +} diff --git a/common/jalog/code/ac/jalog/sinks/WindowsColorSink.cpp b/common/jalog/code/ac/jalog/sinks/WindowsColorSink.cpp new file mode 100644 index 00000000..c2a4ba4b --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/WindowsColorSink.cpp @@ -0,0 +1,126 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "WindowsColorSink.hpp" + +#include "../Entry.hpp" +#include "SimpleStdioSink.hpp" + +#include + +#if !defined(NOMINMAX) +#define NOMINMAX +#endif +#define WIN32_LEAN_AND_MEAN +#include +#include + + +namespace ac::jalog::sinks +{ + +WindowsColorSink::WindowsColorSink() +{ + DWORD unused; + m_out = GetStdHandle(STD_OUTPUT_HANDLE); + if (!GetConsoleMode(m_out, &unused)) m_out = nullptr; // out is not a console + m_err = GetStdHandle(STD_ERROR_HANDLE); + if (!GetConsoleMode(m_err, &unused)) m_err = nullptr; // err is not a console +} + +namespace +{ +void printWithColor(HANDLE out, uint16_t color, std::string_view text) +{ + uint16_t originalAttribs; + + CONSOLE_SCREEN_BUFFER_INFO info; + if (!GetConsoleScreenBufferInfo(out, &info)) + { + // fallback to white + originalAttribs = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + } + else + { + originalAttribs = info.wAttributes; + } + + WORD newAttributes = color | (originalAttribs & 0xfff00); + SetConsoleTextAttribute(out, newAttributes); + + WriteConsoleA(out, text.data(), DWORD(text.size()), nullptr, nullptr); + + // return to normal + SetConsoleTextAttribute(out, originalAttribs); +} + +} + +void WindowsColorSink::record(const Entry& entry) +{ + auto out = entry.level < Level::Error ? m_out : m_err; + + if (!out) + { + // output is not a console + // do a redirect + sinks::SimpleStdioSink(stdout, stderr).record(entry); + return; + } + + // time + { + using namespace std::chrono; + itlib::time_t tt(system_clock::to_time_t(entry.timestamp)); + auto tm = tt.localtime(); + + char buf[26]; + strftime(buf, sizeof(buf), "%m/%d %X", &tm); + + int ms = int(duration_cast(entry.timestamp.time_since_epoch()).count() % 1000); + char buf2[32]; + int len = snprintf(buf2, sizeof(buf2), "%s.%03d ", buf, ms); + + printWithColor(out, FOREGROUND_BLUE, std::string_view(buf2, size_t(len))); + } + + // scope + { + auto lbl = entry.scope.label(); + if (!lbl.empty()) + { + printWithColor(out, FOREGROUND_GREEN | FOREGROUND_BLUE, lbl); + WriteConsoleA(out, " ", 1, nullptr, nullptr); + } + } + + // level + { + static const uint16_t colors[] = { + FOREGROUND_RED | FOREGROUND_BLUE, + FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY, + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_INTENSITY, + FOREGROUND_RED, + FOREGROUND_RED | FOREGROUND_INTENSITY + }; + static_assert(std::size(colors) == static_cast(Level::Off)); + uint16_t color = (entry.level < Level::Off) ? colors[static_cast(entry.level)] : (FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); + + static const std::string_view lstr[] = { + "[debug] ", + "[Info ] ", + "[WARN ] ", + "[ERROR] ", + "[CRIT!] ", + }; + static_assert(std::size(lstr) == static_cast(Level::Off)); + std::string_view str = (entry.level < Level::Off) ? lstr[static_cast(entry.level)] : std::string_view("[ ? ]"); + + printWithColor(out, color, str); + } + + WriteConsoleA(out, entry.text.data(), DWORD(entry.text.length()), nullptr, nullptr); + WriteConsoleA(out, "\n", 1, nullptr, nullptr); +} + +} diff --git a/common/jalog/code/ac/jalog/sinks/WindowsColorSink.hpp b/common/jalog/code/ac/jalog/sinks/WindowsColorSink.hpp new file mode 100644 index 00000000..652c854f --- /dev/null +++ b/common/jalog/code/ac/jalog/sinks/WindowsColorSink.hpp @@ -0,0 +1,22 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include "../API.h" +#include "../Sink.hpp" + +namespace ac::jalog::sinks +{ + +class AC_JALOG_API WindowsColorSink final : public Sink +{ +public: + WindowsColorSink(); + virtual void record(const Entry& entry) override; + +private: + void* m_out; + void* m_err; +}; + +} diff --git a/common/jalog/example/CMakeLists.txt b/common/jalog/example/CMakeLists.txt new file mode 100644 index 00000000..a0f6ca4a --- /dev/null +++ b/common/jalog/example/CMakeLists.txt @@ -0,0 +1,14 @@ +# Copyright (c) Borislav Stanimirov +# SPDX-License-Identifier: MIT +# +macro(jalog_example example) + set(ename jalog-example-${example}) + add_executable(${ename} ${ARGN}) + target_link_libraries(${ename} ac-jalog) +endmacro() + +jalog_example(HelloWorld e-HelloWorld.cpp) +jalog_example(BasicShowcase e-BasicShowcase.cpp) +jalog_example(StreamStyleLogging e-StreamStyleLogging.cpp) +jalog_example(PrintfStyleLogging e-PrintfStyleLogging.cpp) +jalog_example(CustomStyleLogging e-CustomStyleLogging.cpp) diff --git a/common/jalog/example/e-BasicShowcase.cpp b/common/jalog/example/e-BasicShowcase.cpp new file mode 100644 index 00000000..49a33c06 --- /dev/null +++ b/common/jalog/example/e-BasicShowcase.cpp @@ -0,0 +1,41 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include +#include +#include +#include + +#include +#include + +struct Person +{ + std::string name; + int age; +}; + +std::ostream& operator<<(std::ostream& o, const Person& p) +{ + return o << p.name << '(' << p.age << ')'; +} + +int main() +{ + ac::jalog::Instance jl; + jl.setup().add(); + + AC_JALOG(Debug, "Log integers: ", 34, ", or in a custom base: ", ac::jalog::base<16>(255)); + AC_JALOG(Info, "Log floating point numbers with no precision loss: ", 12.4356631); + + std::string str = "my string"; + std::string_view sv = std::string_view(str).substr(0, 6); + AC_JALOG(Warning, "Log strings: '", str, "' and string views '", sv, "'"); + + Person alice = {"Alice", 34}; + AC_JALOG(Error, "Log types with custom ostream output: ", alice); + + AC_JALOG_PRINTF(Critical, "Log printf style: %d, %.3f, %s", 43, 3.14159, str.c_str()); + + return 0; +} diff --git a/common/jalog/example/e-CustomStyleLogging.cpp b/common/jalog/example/e-CustomStyleLogging.cpp new file mode 100644 index 00000000..a5db4e49 --- /dev/null +++ b/common/jalog/example/e-CustomStyleLogging.cpp @@ -0,0 +1,20 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include +#include +#include + +#include + +int main() +{ + ac::jalog::Instance jl; + jl.setup().add(); + + std::ostringstream sout; + sout << "Producing a custom string " << 34 << ' ' << 4.2; + ac::jalog::Default_Scope.addEntry(ac::jalog::Level::Info, sout.str()); + + return 0; +} diff --git a/common/jalog/example/e-HelloWorld.cpp b/common/jalog/example/e-HelloWorld.cpp new file mode 100644 index 00000000..36cb116b --- /dev/null +++ b/common/jalog/example/e-HelloWorld.cpp @@ -0,0 +1,17 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include +#include +#include + +int main() +{ + ac::jalog::Instance jl; + jl.setup().add(); + + AC_JALOG(Debug, "Perparing to greet world"); + AC_JALOG(Info, "Hello, world"); + + return 0; +} diff --git a/common/jalog/example/e-PrintfStyleLogging.cpp b/common/jalog/example/e-PrintfStyleLogging.cpp new file mode 100644 index 00000000..f180b57a --- /dev/null +++ b/common/jalog/example/e-PrintfStyleLogging.cpp @@ -0,0 +1,59 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include +#include +#include +#include +#include + +/////////////////////////////////////////////////////////////////////////////// +// this is an imaginary c library +typedef void(*clib_log_func)(const char* fmt, ...); +clib_log_func clib_log_info; +clib_log_func clib_log_error; +void clib_do_x(int x) +{ + if (clib_log_info) clib_log_info("Doing X: %d", x); + // ... +} +void clib_do_y(double y) +{ + if (clib_log_info) clib_log_info("Doing something else: %.3f", y); + if (clib_log_error) clib_log_error("Uh-oh. An error has occurred: %s", "y is bad"); + // ... +} +/////////////////////////////////////////////////////////////////////////////// + +ac::jalog::Scope clibScope("clib"); + +void JalogForCLib_Info(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + ac::jalog::VPrintf(clibScope, ac::jalog::Level::Info, fmt, args); + va_end(args); +} + +void JalogForCLib_Error(const char* fmt, ...) +{ + va_list args; + va_start(args, fmt); + ac::jalog::VPrintf(clibScope, ac::jalog::Level::Error, fmt, args); + va_end(args); +} + +int main() +{ + ac::jalog::Instance jl; + jl.setup().add(); + + clib_log_info = JalogForCLib_Info; + clib_log_error = JalogForCLib_Error; + + AC_JALOG(Info, "Launching CLib"); + clib_do_x(43); + clib_do_y(3.141592); + + return 0; +} diff --git a/common/jalog/example/e-StreamStyleLogging.cpp b/common/jalog/example/e-StreamStyleLogging.cpp new file mode 100644 index 00000000..5f6bb7db --- /dev/null +++ b/common/jalog/example/e-StreamStyleLogging.cpp @@ -0,0 +1,21 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include +#include +#include + +int main() +{ + ac::jalog::Instance jl; + jl.setup().add(); + + auto log = AC_JALOG_STREAM(Info); + + log << "Hello"; + log << ", stream world!" << ac::jalog::endl; + + log << ac::jalog::Level::Debug << "ac::jalog::Stream was used"; + + return 0; +} diff --git a/common/jalog/test/CMakeLists.txt b/common/jalog/test/CMakeLists.txt new file mode 100644 index 00000000..214f9bf7 --- /dev/null +++ b/common/jalog/test/CMakeLists.txt @@ -0,0 +1,19 @@ +# Copyright (c) Borislav Stanimirov +# SPDX-License-Identifier: MIT +# + +icm_add_test( + NAME ac-jalog-qwrite + TARGET test-ac-jalog-qwrite + LIBRARIES + ac::astl-public + doctest::main + SOURCES t-qwrite.cpp +) + +macro(jalog_test test) + add_doctest_lib_test(${test} ac-jalog ${ARGN}) +endmacro() + +jalog_test(core t-core.cpp) +jalog_test(defaults t-defaults.cpp) diff --git a/common/jalog/test/TestSink.hpp b/common/jalog/test/TestSink.hpp new file mode 100644 index 00000000..b296f3cc --- /dev/null +++ b/common/jalog/test/TestSink.hpp @@ -0,0 +1,67 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include + +#include +#include + +#include "TestTime.hpp" + +#include +#include +#include +#include + +class TestSink final : public ac::jalog::Sink +{ +public: + virtual void record(const ac::jalog::Entry& entry) override + { + entries.emplace_back(entry); + } + + struct EntryCopy + { + EntryCopy() = default; + EntryCopy(const ac::jalog::Entry& e) + : scope(e.scope) + , level(e.level) + , timestamp(tonano(e.timestamp)) + , text(e.text) + {} + ac::jalog::ScopeDesc scope; + ac::jalog::Level level; + uint64_t timestamp; + std::string text; + }; + + std::vector entries; + + void checkSameEntries(const TestSink& other) const + { + auto& es0 = entries; + auto& es = other.entries; + CHECK(es0.size() == es.size()); + for (size_t ei = 0; ei < es0.size(); ++ei) + { + auto& e0 = es0[ei]; + auto& e = es[ei]; + CHECK(&e0 != &e); + + CHECK(e0.scope.label() == e.scope.label()); + CHECK(e0.scope.id() == e.scope.id()); + CHECK(e0.scope.userData == e.scope.userData); + CHECK(e0.level == e.level); + CHECK(e0.timestamp == e.timestamp); + CHECK(e0.text == e.text); + } + } + + static void checkSortedEntries(const std::vector& es) + { + CHECK(std::is_sorted(es.begin(), es.end(), [](auto& a, auto& b) { + return a.timestamp < b.timestamp; + })); + } +}; diff --git a/common/jalog/test/TestTime.hpp b/common/jalog/test/TestTime.hpp new file mode 100644 index 00000000..dc52b814 --- /dev/null +++ b/common/jalog/test/TestTime.hpp @@ -0,0 +1,15 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#pragma once +#include + +uint64_t tonano(std::chrono::system_clock::time_point t) +{ + return std::chrono::duration_cast(t.time_since_epoch()).count(); +} + +uint64_t nanotime() +{ + return tonano(std::chrono::system_clock::now()); +} diff --git a/common/jalog/test/t-core.cpp b/common/jalog/test/t-core.cpp new file mode 100644 index 00000000..4a4d5c25 --- /dev/null +++ b/common/jalog/test/t-core.cpp @@ -0,0 +1,461 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include "TestSink.hpp" + +#include +#include +#include + +#include +#include + +TEST_SUITE_BEGIN("jalog"); + +TEST_CASE("scopes") +{ + ac::jalog::Scope s1("s1", 1, 2); + + auto& s1d = s1.desc(); + CHECK(s1d.id() == 1); + CHECK(s1d.label() == "s1"); + CHECK(s1d.labelCStr() == s1d.label().data()); + CHECK(strlen(s1d.labelCStr()) == s1d.label().length()); + CHECK(s1d.userData == 2); + + CHECK(s1.level() == ac::jalog::Level::Off); + s1.setLevel(ac::jalog::Level::Error); + CHECK(s1.level() == ac::jalog::Level::Error); + CHECK_FALSE(s1.enabled(ac::jalog::Level::Debug)); + CHECK_FALSE(s1.enabled(ac::jalog::Level::Info)); + CHECK_FALSE(s1.enabled(ac::jalog::Level::Warning)); + CHECK(s1.enabled(ac::jalog::Level::Error)); + CHECK(s1.enabled(ac::jalog::Level::Critical)); + + ac::jalog::Scope longname("0123456789ABCDEFG"); + CHECK(longname.desc().label() == "0123456789ABCDE"); +} + +TEST_CASE("default scope") +{ + auto& s = ac::jalog::Default_Scope; + auto& sd = s.desc(); + CHECK(sd.id() == 0); + CHECK(sd.label().empty()); + CHECK(*sd.labelCStr() == 0); + CHECK(sd.userData == -1); +} + +struct TestHelper +{ + TestHelper(int numSinks = 2) + : scope(logger, "t1", 1, 2) + , scope2(logger, "t2", 3, 4) + { + REQUIRE(numSinks >= 1); + auto setup = logger.directSetup(); + for (int i = 0; i < numSinks; ++i) { + setup.add(sinks.emplace_back(std::make_shared())); + } + } + + ac::jalog::Logger logger; + ac::jalog::Scope scope; + ac::jalog::Scope scope2; + + std::vector> sinks; + + TestSink& sink() { + return *sinks.front(); + } + + static void checkT1(const TestSink::EntryCopy& e) + { + CHECK(e.scope.label() == "t1"); + CHECK(e.scope.id() == 1); + CHECK(e.scope.userData == 2); + } + + static void checkT2(const TestSink::EntryCopy& e) + { + CHECK(e.scope.label() == "t2"); + CHECK(e.scope.id() == 3); + CHECK(e.scope.userData == 4); + } + + void checkSinks() + { + TestSink::checkSortedEntries(sink().entries); + + // now check whether all sinks contain the same data + if (sinks.size() == 1) return; + for (size_t i = 1; i < sinks.size(); ++i) + { + sink().checkSameEntries(*sinks[i]); + } + } + + TestSink::EntryCopy popFront() + { + auto& es = sink().entries; + if (es.empty()) return {}; + auto ret = std::move(es.front()); + es.erase(es.begin()); + return ret; + } +}; + +#define tlog(lvl, ...) AC_JALOG_SCOPE(helper.scope, lvl, __VA_ARGS__) +#define plog(...) \ + tlog(Info, __VA_ARGS__); \ + e = helper.popFront() +#define tlog2(lvl, ...) AC_JALOG_SCOPE(helper.scope2, lvl, __VA_ARGS__) + +#define tlogf(lvl, ...) AC_JALOG_PRINTF_SCOPE(helper.scope, lvl, __VA_ARGS__) +#define tlogf2(lvl, ...) AC_JALOG_PRINTF_SCOPE(helper.scope2, lvl, __VA_ARGS__) + +// logging to an unsetupped logger is safe and does nothing +TEST_CASE("no setup") +{ + ac::jalog::Logger uLogger; + ac::jalog::Scope uScope(uLogger, "u", 1, 2); + + AC_JALOG_SCOPE(uScope, Info, "asdf"); + + int nope = 0; + auto f = [&nope]() { + ++nope; + return 64; + }; + AC_JALOG_SCOPE(uScope, Critical, f()); + + CHECK(nope == 0); +} + +TEST_CASE("log scopes") +{ + const uint64_t start = nanotime(); + + TestHelper helper(3); + tlog(Debug, "dbg"); + tlog(Info, "info"); + tlogf(Warning, "warn%d", 1); + + auto& es = helper.sink().entries; + REQUIRE(es.size() == 3); + + { + auto& e = es[0]; + helper.checkT1(e); + CHECK(e.level == ac::jalog::Level::Debug); + CHECK(e.text == "dbg"); + CHECK(e.timestamp >= start); + } + { + auto& e = es[1]; + helper.checkT1(e); + CHECK(e.level == ac::jalog::Level::Info); + CHECK(e.text == "info"); + } + { + auto& e = es[2]; + helper.checkT1(e); + CHECK(e.level == ac::jalog::Level::Warning); + CHECK(e.text == "warn1"); + } + + tlog2(Warning, "warn", 2); + tlogf2(Warning, "warn%d", 3); + tlog2(Error, "err"); + tlogf(Critical, "crit"); + + REQUIRE(es.size() == 7); + helper.checkSinks(); + + { + auto& e = es[3]; + helper.checkT2(e); + CHECK(e.level == ac::jalog::Level::Warning); + CHECK(e.text == "warn2"); + } + { + auto& e = es[4]; + helper.checkT2(e); + CHECK(e.level == ac::jalog::Level::Warning); + CHECK(e.text == "warn3"); + } + { + auto& e = es[5]; + helper.checkT2(e); + CHECK(e.level == ac::jalog::Level::Error); + CHECK(e.text == "err"); + } + { + auto& e = es[6]; + helper.checkT1(e); + CHECK(e.level == ac::jalog::Level::Critical); + CHECK(e.text == "crit"); + } +} + +TEST_CASE("levels") +{ + TestHelper helper; + auto& es = helper.sink().entries; + + // shouldn't affect already created scopes + helper.logger.setDefaultLevel(ac::jalog::Level::Off); + + int argCounter = 0; + auto arg = [&]() { return argCounter++; }; + + auto logAll = [&]() { + tlog(Debug, arg()); + tlog(Info, arg()); + tlog(Warning, arg()); + tlogf(Error, "%d", arg()); + tlog(Critical, arg()); + }; + + logAll(); + CHECK(argCounter == 5); + CHECK(es.size() == 5); + + argCounter = 0; + es.clear(); + helper.scope.setLevel(ac::jalog::Level::Error); + logAll(); + CHECK(argCounter == 2); + CHECK(es.size() == 2); + + argCounter = 0; + es.clear(); + helper.scope.setLevel(ac::jalog::Level::Off); + for (int i = 0; i < 10; ++i) logAll(); + CHECK(argCounter == 0); + CHECK(es.empty()); + + argCounter = 0; + es.clear(); + helper.scope.setLevel(ac::jalog::Level::Warning); + logAll(); + CHECK(argCounter == 3); + CHECK(es.size() == 3); +} + +TEST_CASE("new scopes") +{ + TestHelper helper; + tlog(Info, "foo"); + + { + helper.logger.setDefaultLevel(ac::jalog::Level::Warning); + ac::jalog::Scope newScope(helper.logger, "ns1", 10, 20); + AC_JALOG_SCOPE(newScope, Debug, "dbg1"); // should be skipped + AC_JALOG_SCOPE(newScope, Info, "info1"); // should be skipped + AC_JALOG_SCOPE(newScope, Warning, "warn1"); + tlog(Info, "bar"); + } + + { + helper.logger.setDefaultLevel(ac::jalog::Level::Info); + ac::jalog::Scope newScope(helper.logger, "ns2", 30, 40); + AC_JALOG_SCOPE(newScope, Debug, "dbg2"); // should be skipped + AC_JALOG_SCOPE(newScope, Info, "info2"); + AC_JALOG_SCOPE(newScope, Warning, "warn2"); + tlog(Debug, "baz"); + } + + auto& es = helper.sink().entries; + CHECK(es.size() == 6); + helper.checkSinks(); + + { + auto e = helper.popFront(); + helper.checkT1(e); + CHECK(e.level == ac::jalog::Level::Info); + CHECK(e.text == "foo"); + } + { + auto e = helper.popFront(); + CHECK(e.scope.label() == "ns1"); + CHECK(e.scope.id() == 10); + CHECK(e.scope.userData == 20); + CHECK(e.level == ac::jalog::Level::Warning); + CHECK(e.text == "warn1"); + } + { + auto e = helper.popFront(); + helper.checkT1(e); + CHECK(e.level == ac::jalog::Level::Info); + CHECK(e.text == "bar"); + } + { + auto e = helper.popFront(); + CHECK(e.scope.label() == "ns2"); + CHECK(e.scope.id() == 30); + CHECK(e.scope.userData == 40); + CHECK(e.level == ac::jalog::Level::Info); + CHECK(e.text == "info2"); + } + { + auto e = helper.popFront(); + CHECK(e.scope.label() == "ns2"); + CHECK(e.scope.id() == 30); + CHECK(e.scope.userData == 40); + CHECK(e.level == ac::jalog::Level::Warning); + CHECK(e.text == "warn2"); + } + { + auto e = helper.popFront(); + helper.checkT1(e); + CHECK(e.level == ac::jalog::Level::Debug); + CHECK(e.text == "baz"); + } +} + +TEST_CASE("printf") +{ + TestHelper helper; + auto& es = helper.sink().entries; + + helper.scope.setLevel(ac::jalog::Level::Info); + + tlogf(Debug, "foo"); // should be skipped + tlogf(Info, "%d hello %s %.2f", 1, "bb", 3.14159); + ac::jalog::Printf(helper.scope, ac::jalog::Level::Debug, "%dabc", 12); // should be skipped + ac::jalog::Printf(helper.scope, ac::jalog::Level::Error, "x%dy", 33); + ac::jalog::PrintfUnchecked(helper.scope2, ac::jalog::Level::Debug, "hax"); + + CHECK(es.size() == 3); + helper.checkSinks(); + + { + auto e = helper.popFront(); + helper.checkT1(e); + CHECK(e.level == ac::jalog::Level::Info); + CHECK(e.text == "1 hello bb 3.14"); + } + { + auto e = helper.popFront(); + helper.checkT1(e); + CHECK(e.level == ac::jalog::Level::Error); + CHECK(e.text == "x33y"); + } + { + auto e = helper.popFront(); + helper.checkT2(e); + CHECK(e.level == ac::jalog::Level::Debug); + CHECK(e.text == "hax"); + } +} + +namespace test { + +struct vec { float x, y; }; + +// have this overload to confitm that the log happens through BasicStream and through std::ostream +std::ostream& operator<<(std::ostream& o, const vec& v) +{ + return o << v.x << ' ' << v.y; +} + +ac::jalog::BasicStream& operator,(ac::jalog::BasicStream& s, const vec& v) +{ + return s, '(', v.x, ';', v.y, ')'; +} + +struct ivec { int x, y; }; + +std::ostream& operator<<(std::ostream& o, const ivec& v) +{ + return o << v.x << ' ' << v.y; +} + +} + +#define tstream(lvl) AC_JALOG_STREAM_SCOPE(helper.scope, lvl) + +TEST_CASE("stream") +{ + TestHelper helper; + auto& es = helper.sink().entries; + helper.scope.setLevel(ac::jalog::Level::Info); + helper.scope2.setLevel(ac::jalog::Level::Info); + + tstream(Info) << "hello " << test::ivec{ 1, 2 }; + tstream(Debug) << "dbg"; // skipped + + { + ac::jalog::Stream str(helper.scope2); + + str << "foo " << test::vec{ 1.2f, 2.1f } << ac::jalog::endl; + + str << ac::jalog::Level::Debug << "dbg2"; // skipped + str << ac::jalog::Level::Error << "err"; + str << ac::jalog::base<2>(2); + str << ac::jalog::Level::Info << "info" << 2; + } + + CHECK(es.size() == 4); + helper.checkSinks(); + + { + auto e = helper.popFront(); + helper.checkT1(e); + CHECK(e.level == ac::jalog::Level::Info); + CHECK(e.text == "hello 1 2"); + } + { + auto e = helper.popFront(); + helper.checkT2(e); + CHECK(e.level == ac::jalog::Level::Info); + CHECK(e.text == "foo (1.2;2.1)"); + } + { + auto e = helper.popFront(); + helper.checkT2(e); + CHECK(e.level == ac::jalog::Level::Error); + CHECK(e.text == "err10"); + } + { + auto e = helper.popFront(); + helper.checkT2(e); + CHECK(e.level == ac::jalog::Level::Info); + CHECK(e.text == "info2"); + } +} + +TEST_CASE("stream output") +{ + TestHelper helper; + TestSink::EntryCopy e; + + // bools + plog("b1 = ", true, ", b2 = ", false); + CHECK(e.text == "b1 = true, b2 = false"); + + // chars + plog("\t", 'j', 'a', "log"); + CHECK(e.text == "\tjalog"); + + // ints + plog(uint8_t(4), ", ", INT16_MIN, ", ", ac::jalog::base<2>(9), ", ", 1'000'000'000'000ull); + CHECK(e.text == "4, -32768, 1001, 1000000000000"); + + // floats + plog(3.14159f, ", ", 2.718281828); + CHECK(e.text == "3.14159, 2.718281828"); + + // strings + std::string str = "abcde"; + std::string_view sv(str.c_str() + 1, 3); + plog(str, " : ", sv); + CHECK(e.text == "abcde : bcd"); + + // customs + test::vec v = {3.00f, 1.12f}; + test::ivec iv = {5, 6}; + plog("j: ", v, ", std: ", iv); + CHECK(e.text == "j: (3;1.12), std: 5 6"); +} diff --git a/common/jalog/test/t-defaults.cpp b/common/jalog/test/t-defaults.cpp new file mode 100644 index 00000000..be63cd5f --- /dev/null +++ b/common/jalog/test/t-defaults.cpp @@ -0,0 +1,93 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include + +#include "TestSink.hpp" + +#include +#include +#include +#include +#include + +TEST_SUITE_BEGIN("jalog"); + +ac::jalog::Scope gscope("g", 10, 20); + +AC_JALOG_DEFINE_PRINTF_FUNC(log_gscope, gscope) + +TEST_CASE("default logger/scope") +{ + auto sink = std::make_shared(); + auto& es = sink->entries; + ac::jalog::Instance i; + i.setup() + .defaultLevel(ac::jalog::Level::Info) + .add(sink); + + AC_JALOG(Debug, "dbg", 1); + AC_JALOG_PRINTF(Info, "info%d", 1); + AC_JALOG(Error, "error", 1); + + ac::jalog::Scope scope("s1", 1, 2); + AC_JALOG_SCOPE(scope, Debug, "dbg", 2); + AC_JALOG_SCOPE(scope, Info, "info", 2); + AC_JALOG_PRINTF_SCOPE(scope, Critical, "crit%d", 2); + + AC_JALOG_PRINTF_SCOPE(gscope, Debug, "dbg%d", 3); + AC_JALOG_SCOPE(gscope, Error, "err", 3); + + log_gscope("wrn%d", 11); + + REQUIRE(es.size() == 6); + + { + auto& e = es[0]; + CHECK(e.scope.label().empty()); + CHECK(e.scope.id() == 0); + CHECK(e.scope.userData == -1); + CHECK(e.level == ac::jalog::Level::Info); + CHECK(e.text == "info1"); + } + { + auto& e = es[1]; + CHECK(e.scope.label().empty()); + CHECK(e.scope.id() == 0); + CHECK(e.scope.userData == -1); + CHECK(e.level == ac::jalog::Level::Error); + CHECK(e.text == "error1"); + } + { + auto& e = es[2]; + CHECK(e.scope.label() == "s1"); + CHECK(e.scope.id() == 1); + CHECK(e.scope.userData == 2); + CHECK(e.level == ac::jalog::Level::Info); + CHECK(e.text == "info2"); + } + { + auto& e = es[3]; + CHECK(e.scope.label() == "s1"); + CHECK(e.scope.id() == 1); + CHECK(e.scope.userData == 2); + CHECK(e.level == ac::jalog::Level::Critical); + CHECK(e.text == "crit2"); + } + { + auto& e = es[4]; + CHECK(e.scope.label() == "g"); + CHECK(e.scope.id() == 10); + CHECK(e.scope.userData == 20); + CHECK(e.level == ac::jalog::Level::Error); + CHECK(e.text == "err3"); + } + { + auto& e = es[5]; + CHECK(e.scope.label() == "g"); + CHECK(e.scope.id() == 10); + CHECK(e.scope.userData == 20); + CHECK(e.level == ac::jalog::Level::Warning); + CHECK(e.text == "wrn11"); + } +} diff --git a/common/jalog/test/t-qwrite.cpp b/common/jalog/test/t-qwrite.cpp new file mode 100644 index 00000000..6d88517e --- /dev/null +++ b/common/jalog/test/t-qwrite.cpp @@ -0,0 +1,122 @@ +// Copyright (c) Borislav Stanimirov +// SPDX-License-Identifier: MIT +// +#include + +#define I_AC_JALOG_TESTING_QWRITE +#include "../code/ac/jalog/_qwrite.hpp" +#include "../code/ac/jalog/_qwrite.cpp" + +#if JALOG_USE_MSCHARCONV +#include +#else +#include +#define msstl std +#endif + +#include +#include +#include + +TEST_SUITE_BEGIN("jalog"); + +template +std::string cc2s(I i, int base = 10) +{ + char buf[100]; + auto res = msstl::to_chars(buf, buf+100, i, base); + return {buf, res.ptr}; +} + +template +std::string wi2s(I i, ac::jalog::qwrite::pad padding = {}) +{ + std::stringstream sout; + + ac::jalog::qwrite::write_integer( + *sout.rdbuf(), + ac::jalog::qwrite::wrapped_integer{i}, + padding + ); + + return sout.str(); +} + +template +void itest(I i) +{ + CHECK(cc2s(i, int(Base)) == wi2s(i)); +} + +template +using lim = std::numeric_limits; + +TEST_CASE("wrapped int") +{ + // sanity + CHECK(cc2s(4373) == "4373"); + CHECK(wi2s(-14322) == "-14322"); + + itest(5); + itest<7>(4521); + itest<27>(-154521); + + itest<2>(lim::min()); + itest<2>(lim::max()); + itest<2>(lim::min()); + itest<2>(lim::max()); + itest<2>(lim::min()); + itest<2>(lim::max()); + itest<2>(lim::min()); + itest<2>(lim::max()); +} + +TEST_CASE("wrapped padding") +{ + using ac::jalog::qwrite::pad; + CHECK(wi2s(24, {5, pad::left, '0'}) == "00024"); + CHECK(wi2s(24, {4, pad::inner, ' '}) == " 24"); + CHECK(wi2s(24, {6, pad::right, '='}) == "24===="); + + CHECK(wi2s(-1256, {3, pad::left, 'z'}) == "-1256"); + CHECK(wi2s(-1256, {4, pad::inner, 'z'}) == "-1256"); + CHECK(wi2s(-1256, {5, pad::right, 'z'}) == "-1256"); + + CHECK(wi2s(-87, {4, pad::left, '0'}) == "0-87"); + CHECK(wi2s(-87, {7, pad::right, 'z'}) == "-87zzzz"); + CHECK(wi2s(-87, {5, pad::inner, 'l'}) == "-ll87"); +} + +template +std::string ccf2s(F f) +{ + char buf[128]; + auto res = msstl::to_chars(buf, buf+sizeof(buf), f); + return {buf, res.ptr}; +} + +template +std::string wf2s(F f) +{ + std::stringstream sout; + ac::jalog::qwrite::write_float(*sout.rdbuf(), f); + return sout.str(); +} + +template +void ftest(F f) +{ + CHECK(ccf2s(f) == wf2s(f)); +} + +TEST_CASE("float") +{ + // sanity + CHECK(ccf2s(3.14) == "3.14"); + CHECK(wf2s(-8.2f) == "-8.2"); + + ftest(-4.1235); + ftest(1e-11); + ftest(4.f); + ftest(-4.22f); +} diff --git a/inference/dummy/code/CMakeLists.txt b/inference/dummy/code/CMakeLists.txt index a5d4495c..36b6b29c 100644 --- a/inference/dummy/code/CMakeLists.txt +++ b/inference/dummy/code/CMakeLists.txt @@ -6,7 +6,7 @@ add_library(ac::dummy ALIAS ac-dummy) target_include_directories(ac-dummy INTERFACE .) target_link_libraries(ac-dummy PRIVATE - jalog::jalog + ac::jalog PUBLIC splat::splat ac::astl-private diff --git a/inference/dummy/code/ac/dummy/Logging.hpp b/inference/dummy/code/ac/dummy/Logging.hpp index 4040b4d2..5a73b8f6 100644 --- a/inference/dummy/code/ac/dummy/Logging.hpp +++ b/inference/dummy/code/ac/dummy/Logging.hpp @@ -2,11 +2,11 @@ // SPDX-License-Identifier: MIT // #pragma once -#include -#include +#include +#include namespace ac::dummy::log { extern jalog::Scope scope; } -#define DUMMY_LOG(lvl, ...) JALOG_SCOPE(::ac::dummy::log::scope, lvl, __VA_ARGS__) +#define DUMMY_LOG(lvl, ...) AC_JALOG_SCOPE(::ac::dummy::log::scope, lvl, __VA_ARGS__) diff --git a/inference/dummy/example/CMakeLists.txt b/inference/dummy/example/CMakeLists.txt index f8ed4cdb..f46ade8e 100644 --- a/inference/dummy/example/CMakeLists.txt +++ b/inference/dummy/example/CMakeLists.txt @@ -5,8 +5,7 @@ set(tgt example-ac-dummy-basic) add_executable(${tgt} e-basic.cpp) target_link_libraries(${tgt} PRIVATE ac::dummy - jalog::jalog - jalog::sinklib + ac::jalog ac-test-data::dummy ) set_target_properties(${tgt} PROPERTIES FOLDER example) diff --git a/inference/dummy/example/e-basic.cpp b/inference/dummy/example/e-basic.cpp index 30f1776e..4d3ef8dc 100644 --- a/inference/dummy/example/e-basic.cpp +++ b/inference/dummy/example/e-basic.cpp @@ -5,8 +5,8 @@ #include // logging -#include -#include +#include +#include // models #include "ac-test-data-dummy-models.h" @@ -14,8 +14,8 @@ #include int main() try { - jalog::Instance jl; - jl.setup().add(); + ac::jalog::Instance jl; + jl.setup().add(); ac::dummy::Model model(AC_DUMMY_MODEL_SMALL, {}); ac::dummy::Instance instance(model, {}); diff --git a/inference/dummy/local/example/CMakeLists.txt b/inference/dummy/local/example/CMakeLists.txt index 3e7cbb1d..e482e0d2 100644 --- a/inference/dummy/local/example/CMakeLists.txt +++ b/inference/dummy/local/example/CMakeLists.txt @@ -5,8 +5,7 @@ add_executable(example-ac-local-dummy e-local-dummy.cpp) target_link_libraries(example-ac-local-dummy PRIVATE ac::local ac::local-dummy - jalog::jalog - jalog::sinklib + ac::jalog ac-test-data::dummy ) set_target_properties(example-ac-local-dummy PROPERTIES FOLDER example) diff --git a/inference/dummy/local/example/e-local-dummy.cpp b/inference/dummy/local/example/e-local-dummy.cpp index 4203f404..584723f6 100644 --- a/inference/dummy/local/example/e-local-dummy.cpp +++ b/inference/dummy/local/example/e-local-dummy.cpp @@ -7,16 +7,16 @@ #include #include -#include -#include +#include +#include #include #include "ac-test-data-dummy-models.h" int main() try { - jalog::Instance jl; - jl.setup().add(); + ac::jalog::Instance jl; + jl.setup().add(); ac::local::ModelFactory factory; ac::local::addDummyInference(factory); diff --git a/inference/llama.cpp/code/CMakeLists.txt b/inference/llama.cpp/code/CMakeLists.txt index 159fd1ea..55279e37 100644 --- a/inference/llama.cpp/code/CMakeLists.txt +++ b/inference/llama.cpp/code/CMakeLists.txt @@ -7,7 +7,7 @@ target_include_directories(ac-llama INTERFACE .) target_link_libraries(ac-llama PRIVATE llama - jalog::jalog + ac::jalog PUBLIC splat::splat ac::astl-private diff --git a/inference/llama.cpp/code/ac/llama/Logging.hpp b/inference/llama.cpp/code/ac/llama/Logging.hpp index 66a84388..4add0cae 100644 --- a/inference/llama.cpp/code/ac/llama/Logging.hpp +++ b/inference/llama.cpp/code/ac/llama/Logging.hpp @@ -2,11 +2,11 @@ // SPDX-License-Identifier: MIT // #pragma once -#include -#include +#include +#include namespace ac::llama::log { extern jalog::Scope scope; } -#define LLAMA_LOG(lvl, ...) JALOG_SCOPE(::ac::llama::log::scope, lvl, __VA_ARGS__) +#define LLAMA_LOG(lvl, ...) AC_JALOG_SCOPE(::ac::llama::log::scope, lvl, __VA_ARGS__) diff --git a/inference/llama.cpp/example/CMakeLists.txt b/inference/llama.cpp/example/CMakeLists.txt index a044bf98..8867bf63 100644 --- a/inference/llama.cpp/example/CMakeLists.txt +++ b/inference/llama.cpp/example/CMakeLists.txt @@ -7,8 +7,7 @@ add_executable(example-ac-llama-basic e-basic.cpp) target_link_libraries(example-ac-llama-basic PRIVATE ac::llama ac-test-data::llama - jalog::jalog - jalog::sinklib + ac::jalog ) set_target_properties(example-ac-llama-basic PROPERTIES FOLDER example) @@ -17,8 +16,7 @@ if(TARGET ac-dev::imgui) target_link_libraries(example-ac-llama-gui PRIVATE ac::llama ac-test-data::llama - jalog::jalog - jalog::sinklib + ac::jalog ac-dev::imgui ) set_target_properties(example-ac-llama-gui PROPERTIES FOLDER example) diff --git a/inference/llama.cpp/example/e-basic.cpp b/inference/llama.cpp/example/e-basic.cpp index 259bff4e..12265c70 100644 --- a/inference/llama.cpp/example/e-basic.cpp +++ b/inference/llama.cpp/example/e-basic.cpp @@ -11,8 +11,8 @@ #include // logging -#include -#include +#include +#include // model source directory #include "ac-test-data-llama-dir.h" @@ -21,8 +21,8 @@ #include int main() try { - jalog::Instance jl; - jl.setup().add(); + ac::jalog::Instance jl; + jl.setup().add(); // initialize the library ac::llama::initLibrary(); diff --git a/inference/llama.cpp/example/e-gui.cpp b/inference/llama.cpp/example/e-gui.cpp index 0f64bad2..99514a4a 100644 --- a/inference/llama.cpp/example/e-gui.cpp +++ b/inference/llama.cpp/example/e-gui.cpp @@ -13,9 +13,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include #include @@ -177,7 +177,7 @@ class UModel { void unload() { m_state.reset(); - JALOG(Info, "unloaded ", m_name); + AC_JALOG(Info, "unloaded ", m_name); } void load() { ac::llama::Model::Params modelParams; @@ -192,8 +192,8 @@ class UModel { int main(int, char**) { // setup logging - jalog::Instance jl; - jl.setup().add(); + ac::jalog::Instance jl; + jl.setup().add(); // setup llama ac::llama::initLibrary(); @@ -220,7 +220,7 @@ int main(int, char**) { SDL_RendererInfo info; SDL_GetRendererInfo(renderer, &info); - JALOG(Info, "SDL_Renderer: ", info.name); + AC_JALOG(Info, "SDL_Renderer: ", info.name); // setup imgui IMGUI_CHECKVERSION(); diff --git a/inference/llama.cpp/local/example/CMakeLists.txt b/inference/llama.cpp/local/example/CMakeLists.txt index ddf97e44..47f17f31 100644 --- a/inference/llama.cpp/local/example/CMakeLists.txt +++ b/inference/llama.cpp/local/example/CMakeLists.txt @@ -9,8 +9,7 @@ function(add_local_llama_example name) target_link_libraries(${tgt} PRIVATE ac::local ac::local-llama - jalog::jalog - jalog::sinklib + ac::jalog ac-test-data::llama ) set_target_properties(${tgt} PROPERTIES FOLDER example) diff --git a/inference/llama.cpp/local/example/e-local-llama-chat.cpp b/inference/llama.cpp/local/example/e-local-llama-chat.cpp index e5f4339f..5d997f20 100644 --- a/inference/llama.cpp/local/example/e-local-llama-chat.cpp +++ b/inference/llama.cpp/local/example/e-local-llama-chat.cpp @@ -7,16 +7,16 @@ #include #include -#include -#include +#include +#include #include #include "ac-test-data-llama-dir.h" int main() try { - jalog::Instance jl; - jl.setup().add(); + ac::jalog::Instance jl; + jl.setup().add(); ac::local::ModelFactory factory; ac::local::addLlamaInference(factory); diff --git a/inference/llama.cpp/local/example/e-local-llama-run.cpp b/inference/llama.cpp/local/example/e-local-llama-run.cpp index 1d48b7b8..ff794979 100644 --- a/inference/llama.cpp/local/example/e-local-llama-run.cpp +++ b/inference/llama.cpp/local/example/e-local-llama-run.cpp @@ -7,16 +7,16 @@ #include #include -#include -#include +#include +#include #include #include "ac-test-data-llama-dir.h" int main() try { - jalog::Instance jl; - jl.setup().add(); + ac::jalog::Instance jl; + jl.setup().add(); ac::local::ModelFactory factory; ac::local::addLlamaInference(factory); diff --git a/inference/whisper/code/CMakeLists.txt b/inference/whisper/code/CMakeLists.txt index 7385f845..bc2ee47a 100644 --- a/inference/whisper/code/CMakeLists.txt +++ b/inference/whisper/code/CMakeLists.txt @@ -7,7 +7,7 @@ target_include_directories(ac-whisper INTERFACE .) target_link_libraries(ac-whisper PRIVATE whisper - jalog::jalog + ac::jalog PUBLIC splat::splat ac::astl-private diff --git a/inference/whisper/code/ac/whisper/Logging.hpp b/inference/whisper/code/ac/whisper/Logging.hpp index 7a62f3d9..99b313e3 100644 --- a/inference/whisper/code/ac/whisper/Logging.hpp +++ b/inference/whisper/code/ac/whisper/Logging.hpp @@ -2,11 +2,11 @@ // SPDX-License-Identifier: MIT // #pragma once -#include -#include +#include +#include namespace ac::whisper::log { extern jalog::Scope scope; } -#define WHISPER_LOG(lvl, ...) JALOG_SCOPE(::ac::whisper::log::scope, lvl, __VA_ARGS__) +#define WHISPER_LOG(lvl, ...) AC_JALOG_SCOPE(::ac::whisper::log::scope, lvl, __VA_ARGS__) diff --git a/inference/whisper/example/CMakeLists.txt b/inference/whisper/example/CMakeLists.txt index 848389c6..bc629e45 100644 --- a/inference/whisper/example/CMakeLists.txt +++ b/inference/whisper/example/CMakeLists.txt @@ -8,8 +8,7 @@ set(TARGET example-ac-whisper-basic) add_executable(${TARGET} e-basic.cpp) target_link_libraries(${TARGET} PRIVATE ac::whisper - jalog::jalog - jalog::sinklib + ac::jalog ac-test-data::whisper ac-dev::audio ) @@ -20,8 +19,7 @@ if(TARGET ac-dev::imgui) add_executable(${TARGET} e-gui.cpp) target_link_libraries(${TARGET} PRIVATE ac::whisper - jalog::jalog - jalog::sinklib + ac::jalog ac-test-data::whisper ac-dev::audio ac-dev::imgui diff --git a/inference/whisper/example/e-basic.cpp b/inference/whisper/example/e-basic.cpp index 5a4da4d7..8396a0e8 100644 --- a/inference/whisper/example/e-basic.cpp +++ b/inference/whisper/example/e-basic.cpp @@ -12,8 +12,8 @@ #include // logging -#include -#include +#include +#include // model source directory #include "ac-test-data-whisper-dir.h" @@ -23,8 +23,8 @@ #include int main() try { - jalog::Instance jl; - jl.setup().add(); + ac::jalog::Instance jl; + jl.setup().add(); std::cout << "Basic example\n"; diff --git a/inference/whisper/example/e-gui.cpp b/inference/whisper/example/e-gui.cpp index 74456cde..169df2e8 100644 --- a/inference/whisper/example/e-gui.cpp +++ b/inference/whisper/example/e-gui.cpp @@ -19,9 +19,9 @@ #include // logging -#include -#include -#include +#include +#include +#include // model source directory #include "ac-test-data-whisper-dir.h" @@ -121,7 +121,7 @@ int initSDL(WindowState& wState, AudioState& aState) { SDL_RendererInfo info; SDL_GetRendererInfo(wState.m_renderer, &info); - JALOG(Info, "SDL_Renderer: ", info.name); + AC_JALOG(Info, "SDL_Renderer: ", info.name); char* buff; SDL_GetDefaultAudioInfo(&buff, &aState.m_defaultSpec, SDL_TRUE); @@ -146,9 +146,9 @@ int initSDL(WindowState& wState, AudioState& aState) { //Get capture device name std::string deviceName(SDL_GetAudioDeviceName(i, SDL_TRUE)); if (deviceName == defaultDeviceName) { - JALOG(Info, "[Default]: %d - %s\n", i, deviceName); + AC_JALOG(Info, "[Default]: %d - %s\n", i, deviceName); } else { - JALOG(Info, "%d - %s\n", i, deviceName); + AC_JALOG(Info, "%d - %s\n", i, deviceName); } } #endif @@ -164,7 +164,7 @@ int initSDL(WindowState& wState, AudioState& aState) { if(aState.m_recordingDeviceId == 0) { //Report error - JALOG(Error, "Failed to open recording device! SDL Error: %s", SDL_GetError()); + AC_JALOG(Error, "Failed to open recording device! SDL Error: %s", SDL_GetError()); return 1; } @@ -184,7 +184,7 @@ int initSDL(WindowState& wState, AudioState& aState) { if(aState.m_playbackDeviceId == 0) { //Report error - JALOG(Error, "Failed to open playback device! SDL Error: %s", SDL_GetError()); + AC_JALOG(Error, "Failed to open playback device! SDL Error: %s", SDL_GetError()); return 1; } @@ -347,13 +347,13 @@ class UModel { void unload() { m_state->dropInstance(); m_state.reset(); - JALOG(Info, "unloaded ", m_name); + AC_JALOG(Info, "unloaded ", m_name); } void load() { ac::whisper::Model::Params modelParams; m_state.reset(new State(m_binPath, modelParams)); m_state->createInstance({}); - JALOG(Info, "loaded ", m_name); + AC_JALOG(Info, "loaded ", m_name); } private: std::string m_binPath; @@ -363,8 +363,8 @@ class UModel { }; int main(int, char**) { - jalog::Instance jl; - jl.setup().add(); + ac::jalog::Instance jl; + jl.setup().add(); WindowState wState; AudioState aState; diff --git a/inference/whisper/local/example/CMakeLists.txt b/inference/whisper/local/example/CMakeLists.txt index c051b9d5..e68c6eb9 100644 --- a/inference/whisper/local/example/CMakeLists.txt +++ b/inference/whisper/local/example/CMakeLists.txt @@ -8,8 +8,7 @@ target_link_libraries(example-ac-local-whisper PRIVATE ac::local ac::local-whisper - jalog::jalog - jalog::sinklib + ac::jalog ac-test-data::whisper ac-dev::audio ) diff --git a/inference/whisper/local/example/e-local-whisper.cpp b/inference/whisper/local/example/e-local-whisper.cpp index c0f5b6e5..c13dfdb5 100644 --- a/inference/whisper/local/example/e-local-whisper.cpp +++ b/inference/whisper/local/example/e-local-whisper.cpp @@ -7,8 +7,8 @@ #include #include -#include -#include +#include +#include #include @@ -24,8 +24,8 @@ ac::Blob convertF32ToBlob(std::span f32data) { } int main() try { - jalog::Instance jl; - jl.setup().add(); + ac::jalog::Instance jl; + jl.setup().add(); ac::local::ModelFactory factory; ac::local::addWhisperInference(factory); diff --git a/local/code/CMakeLists.txt b/local/code/CMakeLists.txt index 9670e43b..6e74863a 100644 --- a/local/code/CMakeLists.txt +++ b/local/code/CMakeLists.txt @@ -6,7 +6,7 @@ add_library(ac::local ALIAS ac-local) target_link_libraries(ac-local PRIVATE - jalog::jalog + ac::jalog ac::astl-private PUBLIC ac::astl-public diff --git a/local/code/ac/local/Logging.hpp b/local/code/ac/local/Logging.hpp index 5ba69197..9f508501 100644 --- a/local/code/ac/local/Logging.hpp +++ b/local/code/ac/local/Logging.hpp @@ -7,13 +7,13 @@ // don't document #if !defined(DOXYGEN) -#include -#include +#include +#include namespace ac::local::log { extern jalog::Scope scope; } -#define AC_LOCAL_LOG(lvl, ...) JALOG_SCOPE(::ac::local::log::scope, lvl, __VA_ARGS__) +#define AC_LOCAL_LOG(lvl, ...) AC_JALOG_SCOPE(::ac::local::log::scope, lvl, __VA_ARGS__) #endif diff --git a/wrapper/c/jalogc/CMakeLists.txt b/wrapper/c/jalogc/CMakeLists.txt index b9227d26..c6a77c8a 100644 --- a/wrapper/c/jalogc/CMakeLists.txt +++ b/wrapper/c/jalogc/CMakeLists.txt @@ -10,8 +10,7 @@ target_link_libraries(jalogc PUBLIC splat::splat PRIVATE - jalog::jalog - jalog::sinklib + ac::jalog ) add_library(ac-c::jalogc ALIAS jalogc) diff --git a/wrapper/c/jalogc/code/jalogc.cpp b/wrapper/c/jalogc/code/jalogc.cpp index d252380d..a17512bb 100644 --- a/wrapper/c/jalogc/code/jalogc.cpp +++ b/wrapper/c/jalogc/code/jalogc.cpp @@ -3,30 +3,30 @@ // #include "jalogc.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include // these must match as we simply cast one to the other -static_assert(JALOGC_LOG_LEVEL_DEBUG == int(jalog::Level::Debug)); -static_assert(JALOGC_LOG_LEVEL_INFO == int(jalog::Level::Info)); -static_assert(JALOGC_LOG_LEVEL_WARN == int(jalog::Level::Warning)); -static_assert(JALOGC_LOG_LEVEL_ERROR == int(jalog::Level::Error)); -static_assert(JALOGC_LOG_LEVEL_CRIT == int(jalog::Level::Critical)); -static_assert(JALOGC_LOG_LEVEL_OFF == int(jalog::Level::Off)); +static_assert(JALOGC_LOG_LEVEL_DEBUG == int(ac::jalog::Level::Debug)); +static_assert(JALOGC_LOG_LEVEL_INFO == int(ac::jalog::Level::Info)); +static_assert(JALOGC_LOG_LEVEL_WARN == int(ac::jalog::Level::Warning)); +static_assert(JALOGC_LOG_LEVEL_ERROR == int(ac::jalog::Level::Error)); +static_assert(JALOGC_LOG_LEVEL_CRIT == int(ac::jalog::Level::Critical)); +static_assert(JALOGC_LOG_LEVEL_OFF == int(ac::jalog::Level::Off)); namespace { -jalog::Instance* instance = nullptr; +ac::jalog::Instance* instance = nullptr; -struct CallbackSink final : public jalog::Sink { +struct CallbackSink final : public ac::jalog::Sink { jalogc_log_callback m_callback; CallbackSink(jalogc_log_callback callback) : m_callback(callback) {} - void record(const jalog::Entry& entry) override { + void record(const ac::jalog::Entry& entry) override { m_callback( entry.scope.labelCStr(), jalogc_log_level(entry.level), @@ -42,25 +42,25 @@ extern "C" { void jalogc_init(jalogc_init_params params) { assert(!instance); if (instance) { - jalog::Printf(jalog::Default_Scope, jalog::Level::Error, "Jalog is already initialized"); + ac::jalog::Printf(ac::jalog::Default_Scope, ac::jalog::Level::Error, "Jalog is already initialized"); return; } - instance = new jalog::Instance(); + instance = new ac::jalog::Instance(); auto setup = instance->setup(); if (params.async_logging) { setup.async(); } - setup.defaultLevel(jalog::Level(params.default_log_level)); + setup.defaultLevel(ac::jalog::Level(params.default_log_level)); if (params.add_default_sink) { - setup.add(); + setup.add(); } if (params.log_file) { - setup.add(params.log_file); + setup.add(params.log_file); } if (params.log_callback) { @@ -75,8 +75,8 @@ void jalogc_shutdown() { } void jalogc_log(jalogc_log_level level, _Printf_format_string_ const char* format, ...) { - auto lvl = jalog::Level(level); - auto& scope = jalog::Default_Scope; + auto lvl = ac::jalog::Level(level); + auto& scope = ac::jalog::Default_Scope; if (!scope.enabled(lvl)) return; va_list args; va_start(args, format); diff --git a/wrapper/c/jalogc/code/jalogc.h b/wrapper/c/jalogc/code/jalogc.h index 0fb58580..f5778835 100644 --- a/wrapper/c/jalogc/code/jalogc.h +++ b/wrapper/c/jalogc/code/jalogc.h @@ -45,16 +45,16 @@ JALOGC_EXPORT void jalogc_init(jalogc_init_params params); JALOGC_EXPORT void jalogc_shutdown(); // joins async thread if async_logging is enabled #if defined(__GNUC__) -# define I_JALOGC_PRINTF_FMT __attribute__((format(printf, 2, 3))) +# define I_AC_JALOGC_PRINTF_FMT __attribute__((format(printf, 2, 3))) # define _Printf_format_string_ #else -# define I_JALOGC_PRINTF_FMT +# define I_AC_JALOGC_PRINTF_FMT # if !defined(_MSC_VER) # define _Printf_format_string_ # endif #endif -JALOGC_EXPORT I_JALOGC_PRINTF_FMT void jalogc_log(jalogc_log_level level, _Printf_format_string_ const char* format, ...); +JALOGC_EXPORT I_AC_JALOGC_PRINTF_FMT void jalogc_log(jalogc_log_level level, _Printf_format_string_ const char* format, ...); #if defined(__cplusplus) } diff --git a/wrapper/c/jalogc/test/CMakeLists.txt b/wrapper/c/jalogc/test/CMakeLists.txt index 360ae190..4302397f 100644 --- a/wrapper/c/jalogc/test/CMakeLists.txt +++ b/wrapper/c/jalogc/test/CMakeLists.txt @@ -5,5 +5,5 @@ add_unity_lib_test(logging jalogc t-logging.c t-more-logging.cpp LIBRARIES - jalog::jalog + ac::jalog ) diff --git a/wrapper/c/jalogc/test/t-more-logging.cpp b/wrapper/c/jalogc/test/t-more-logging.cpp index 74c79496..d3fc41bb 100644 --- a/wrapper/c/jalogc/test/t-more-logging.cpp +++ b/wrapper/c/jalogc/test/t-more-logging.cpp @@ -1,19 +1,19 @@ // Copyright (c) Alpaca Core // SPDX-License-Identifier: MIT // -#include -#include +#include +#include namespace { void log_default(int n) { - JALOG(Error, "message from c++ ", n); + AC_JALOG(Error, "message from c++ ", n); } -jalog::Scope cpp_scope("cpp_scope"); +ac::jalog::Scope cpp_scope("cpp_scope"); void log_scope(int n) { - JALOG_SCOPE(cpp_scope, Critical, "scoped from c++ ", n); + AC_JALOG_SCOPE(cpp_scope, Critical, "scoped from c++ ", n); } } // namespace diff --git a/wrapper/java/ac-jni/CMakeLists.txt b/wrapper/java/ac-jni/CMakeLists.txt index dbb5e00d..190c90ef 100644 --- a/wrapper/java/ac-jni/CMakeLists.txt +++ b/wrapper/java/ac-jni/CMakeLists.txt @@ -27,6 +27,5 @@ target_link_libraries(ac-jni PRIVATE ac::local-dummy ac::local-llama ac::local-whisper - jalog::jalog - jalog::sinklib + ac::jalog ) diff --git a/wrapper/java/ac-jni/JniEntrypoint.cpp b/wrapper/java/ac-jni/JniEntrypoint.cpp index e21c5a3e..6e89d38c 100644 --- a/wrapper/java/ac-jni/JniEntrypoint.cpp +++ b/wrapper/java/ac-jni/JniEntrypoint.cpp @@ -1,14 +1,14 @@ #include "jni.hpp" #include "JniApi.hpp" -#include -#include +#include +#include #include extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* jvm, void*) { - jalog::Instance jl; - jl.setup().add(); + ac::jalog::Instance jl; + jl.setup().add(); auto& env = jni::GetEnv(*jvm); diff --git a/wrapper/swift/code/CxxAlpacaCore/AlpacaCore.cpp b/wrapper/swift/code/CxxAlpacaCore/AlpacaCore.cpp index c9bba02f..1991bbfe 100644 --- a/wrapper/swift/code/CxxAlpacaCore/AlpacaCore.cpp +++ b/wrapper/swift/code/CxxAlpacaCore/AlpacaCore.cpp @@ -9,16 +9,16 @@ #include #include -#include -#include +#include +#include namespace AC { static std::unique_ptr factorySingleton; void initSDK() { - jalog::Instance jl; - jl.setup().add(); + ac::jalog::Instance jl; + jl.setup().add(); factorySingleton = std::make_unique(); diff --git a/wrapper/swift/code/CxxAlpacaCore/CMakeLists.txt b/wrapper/swift/code/CxxAlpacaCore/CMakeLists.txt index 1bb61197..6d743050 100644 --- a/wrapper/swift/code/CxxAlpacaCore/CMakeLists.txt +++ b/wrapper/swift/code/CxxAlpacaCore/CMakeLists.txt @@ -37,8 +37,7 @@ target_link_libraries(AlpacaCoreSwift ac-local-dummy ac-local-whisper ac-local-llama - jalog::jalog - jalog::sinklib + ac::jalog ) if(CMAKE_GENERATOR STREQUAL Xcode)