diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 38bc493..f4fc3ec 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -41,4 +41,4 @@ jobs: - name: Test working-directory: build - run: ctest -C ${{ env.BUILD_TYPE }} + run: ctest -C ${{ env.BUILD_TYPE }} --timeout 10 diff --git a/CMakeLists.txt b/CMakeLists.txt index 4925ff3..a43966a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,14 +43,24 @@ target_include_directories(${PROJECT_NAME} INTERFACE $ ) -# Try to find the package ReaderWriterQueue, if not found fetch with FetchContent -find_package(ReaderWriterQueue QUIET) -if(NOT TARGET readerwriterqueue) +if (NOT TARGET farbot) include(FetchContent) - FetchContent_Declare(ReaderWriterQueue - GIT_REPOSITORY https://github.com/cameron314/readerwriterqueue + + FetchContent_Declare(farbot + GIT_REPOSITORY https://github.com/hogliux/farbot + GIT_TAG 0416705394720c12f0d02e55c144e4f69bb06912 + ) + # Note we do not "MakeAvailable" here, because farbot does not fully work via FetchContent + if(NOT farbot_POPULATED) + FetchContent_Populate(farbot) + endif() + add_library(farbot INTERFACE) + add_library(farbot::farbot ALIAS farbot) + + target_include_directories(farbot INTERFACE + $ + $ ) - FetchContent_MakeAvailable(ReaderWriterQueue) endif() if(NOT RTSAN_USE_FMTLIB AND NOT TARGET stb::stb) @@ -89,7 +99,7 @@ endif() target_link_libraries(rtlog INTERFACE - readerwriterqueue + farbot::farbot stb::stb $<$:fmt::fmt> ) diff --git a/README.md b/README.md index 4b5a8b4..82d40b1 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ Slides: - Ability to log messages of any type and size from the real-time thread - Statically allocated memory at compile time, no allocations in the real-time thread - Support for printf-style format specifiers (using [a version of the printf family](https://github.com/nothings/stb/blob/master/stb_sprintf.h) that doesn't hit the `localeconv` lock) OR support for modern libfmt formatting. -- Efficient thread-safe logging using a [lock free queue](https://github.com/cameron314/readerwriterqueue). +- Efficient thread-safe logging using a [lock free queue](https://github.com/hogliux/farbot) ## Requirements - A C++17 compatible compiler - The C++17 standard library -- moodycamel::ReaderWriterQueue (will be downloaded via cmake if not provided) +- farbot::fifo (will be downloaded via cmake if not provided) - stb's vsnprintf (will be downloaded via cmake if not provided) OR libfmt if cmake is run with the `RTSAN_USE_FMTLIB` option ## Installation via CMake @@ -102,7 +102,7 @@ static auto PrintMessage = [](const ExampleLogData& data, size_t sequenceNumber, vsnprintf(buffer.data(), buffer.size(), fstring, args); va_end(args); - printf("{%lu} [%s] (%s): %s\n", + printf("{%lu} [%s] (%s): %s\n", sequenceNumber, rtlog::test::to_string(data.level), rtlog::test::to_string(data.region), @@ -130,7 +130,23 @@ Or alternatively spin up a `rtlog::LogProcessingThread` ## Customizing the queue type -If you don't want to use the SPSC moodycamel queue, you can provide your own queue type. +rtlog provides two queue type variants: `rtlog::SingleRealtimeWriterQueueType` (SPSC - default) and `rtlog::MultiRealtimeWriterQueueType` (MPSC). It is always assummed that you have one log printing thread. These may be used by specifying them: + +```cpp +using SingleWriterRtLoggerType = rtlog::Logger; + +SingleWriterRtLoggerType logger; +logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Audio}, "Hello, world! %i", 42); + +... + +using MultiWriterRtLoggerType = rtlog::Logger; + +MultiWriterRtLoggerType logger; +logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Audio}, "Hello, world! %i", 42); +``` + +If you don't want to use either of these defaults, you may provide your own queue. ** IT IS UP TO YOU TO ENSURE THE QUEUE YOU PROVIDE IS LOCK-FREE AND REAL-TIME SAFE ** diff --git a/examples/custom_queue_example/CMakeLists.txt b/examples/custom_queue_example/CMakeLists.txt index 5e3ada1..7372176 100644 --- a/examples/custom_queue_example/CMakeLists.txt +++ b/examples/custom_queue_example/CMakeLists.txt @@ -1,21 +1,10 @@ -if (NOT TARGET farbot) +find_package(ReaderWriterQueue QUIET) +if(NOT TARGET readerwriterqueue) include(FetchContent) - - FetchContent_Declare(farbot - GIT_REPOSITORY https://github.com/hogliux/farbot - GIT_TAG 0416705394720c12f0d02e55c144e4f69bb06912 - ) - # Note we do not "MakeAvailable" here, because farbot does not fully work via FetchContent - if(NOT farbot_POPULATED) - FetchContent_Populate(farbot) - endif() - add_library(farbot INTERFACE) - add_library(farbot::farbot ALIAS farbot) - - target_include_directories(farbot INTERFACE - $ - $ + FetchContent_Declare(ReaderWriterQueue + GIT_REPOSITORY https://github.com/cameron314/readerwriterqueue ) + FetchContent_MakeAvailable(ReaderWriterQueue) endif() add_executable(custom_queue_example @@ -25,5 +14,5 @@ add_executable(custom_queue_example target_link_libraries(custom_queue_example PRIVATE rtlog::rtlog - farbot::farbot + readerwriterqueue ) diff --git a/examples/custom_queue_example/customqueuemain.cpp b/examples/custom_queue_example/customqueuemain.cpp index 0bf6973..9fdad36 100644 --- a/examples/custom_queue_example/customqueuemain.cpp +++ b/examples/custom_queue_example/customqueuemain.cpp @@ -1,24 +1,20 @@ -#include #include -template class FarbotMPSCQueueWrapper { - farbot::fifo // producer_failure_mode +#include - mQueue; +template class CustomQueue { + + // technically we could use readerwriterqueue "unwrapped" but showing this off + // in the CustomQueue wrapper for documentation purposes + moodycamel::ReaderWriterQueue mQueue; public: using value_type = T; - FarbotMPSCQueueWrapper(int capacity) : mQueue(capacity) {} + CustomQueue(int capacity) : mQueue(capacity) {} - bool try_enqueue(T &&item) { return mQueue.push(std::move(item)); } - bool try_dequeue(T &item) { return mQueue.pop(item); } + bool try_enqueue(T &&item) { return mQueue.try_enqueue(std::move(item)); } + bool try_dequeue(T &item) { return mQueue.try_dequeue(item); } }; struct LogData {}; @@ -26,8 +22,7 @@ struct LogData {}; std::atomic gSequenceNumber{0}; int main() { - rtlog::Logger - logger; + rtlog::Logger logger; logger.Log({}, "Hello, World!"); logger.PrintAndClearLogQueue( diff --git a/examples/everlog/everlogmain.cpp b/examples/everlog/everlogmain.cpp index f28c832..bbf4258 100644 --- a/examples/everlog/everlogmain.cpp +++ b/examples/everlog/everlogmain.cpp @@ -4,7 +4,7 @@ namespace evr { constexpr auto MAX_LOG_MESSAGE_LENGTH = 256; -constexpr auto MAX_NUM_LOG_MESSAGES = 100; +constexpr auto MAX_NUM_LOG_MESSAGES = 128; enum class LogLevel { Debug, Info, Warning, Critical }; diff --git a/include/rtlog/rtlog.h b/include/rtlog/rtlog.h index 351ed6f..1481676 100644 --- a/include/rtlog/rtlog.h +++ b/include/rtlog/rtlog.h @@ -17,7 +17,7 @@ #include #endif // RTLOG_USE_FMTLIB -#include +#include #ifdef RTLOG_USE_STB #ifndef STB_SPRINTF_IMPLEMENTATION @@ -125,12 +125,40 @@ template inline constexpr bool has_int_constructor_v = has_int_constructor::value; } // namespace detail -// On earlier versions of compilers (especially clang) you cannot -// rely on defaulted template template parameters working as intended -// This overload explicitly has 1 template paramter which is what -// `Logger` expects, it uses the default 512 from ReaderWriterQueue as -// the hardcoded MaxBlockSize -template using rtlog_SPSC = moodycamel::ReaderWriterQueue; +template +class FarbotFifoType { + farbot::fifo // producer_failure_mode + + mQueue; + +public: + using value_type = T; + + FarbotFifoType(int capacity) : mQueue(capacity) {} + + bool try_enqueue(T &&item) { return mQueue.push(std::move(item)); } + bool try_dequeue(T &item) { return mQueue.pop(item); } +}; + +template +using SingleRealtimeWriterQueueType = + FarbotFifoType; + +// NOTE: This version overwrites on full, which is a requirement to make writing +// real-time safe. +// This means it will never report Error_QueueFull. +template +using MultiRealtimeWriterQueueType = FarbotFifoType< + T, farbot::fifo_options::concurrency::multiple, + farbot::fifo_options::full_empty_failure_mode::overwrite_or_return_default>; /** * @brief A logger class for logging messages. @@ -161,12 +189,14 @@ template using rtlog_SPSC = moodycamel::ReaderWriterQueue; */ template &SequenceNumber, - template class QType = rtlog_SPSC> + template class QType = SingleRealtimeWriterQueueType> class Logger { public: using InternalLogData = detail::BasicLogData; using InternalQType = QType; + static_assert((MaxNumMessages & (MaxNumMessages - 1)) == 0, + "MaxNumMessages must be a power of 2"); static_assert( detail::has_int_constructor_v, "QType must have a constructor that takes an int - `QType(int)`"); @@ -227,8 +257,13 @@ class Logger { // data loss const bool dataWasEnqueued = mQueue.try_enqueue(std::move(dataToQueue)); - if (!dataWasEnqueued) - retVal = Status::Error_QueueFull; + constexpr bool isMultiRealtimeWriterQueueType = + std::is_same_v>; + if constexpr (!isMultiRealtimeWriterQueueType) { + if (!dataWasEnqueued) + retVal = Status::Error_QueueFull; + } return retVal; } @@ -324,8 +359,13 @@ class Logger { // data loss const bool dataWasEnqueued = mQueue.try_enqueue(std::move(dataToQueue)); - if (!dataWasEnqueued) - retVal = Status::Error_QueueFull; + constexpr bool isMultiRealtimeWriterQueueType = + std::is_same_v>; + if constexpr (!isMultiRealtimeWriterQueueType) { + if (!dataWasEnqueued) + retVal = Status::Error_QueueFull; + } return retVal; }; diff --git a/test/test_rtlog.cpp b/test/test_rtlog.cpp index a924a1c..4db78a5 100644 --- a/test/test_rtlog.cpp +++ b/test/test_rtlog.cpp @@ -7,7 +7,7 @@ namespace rtlog::test { static std::atomic gSequenceNumber{0}; constexpr auto MAX_LOG_MESSAGE_LENGTH = 256; -constexpr auto MAX_NUM_LOG_MESSAGES = 100; +constexpr auto MAX_NUM_LOG_MESSAGES = 128; enum class ExampleLogLevel { Debug, Info, Warning, Critical }; @@ -66,115 +66,127 @@ static auto PrintMessage = [](const ExampleLogData &data, size_t sequenceNumber, using namespace rtlog::test; +using SingleWriterRtLoggerType = + rtlog::Logger; +using MultiWriterRtLoggerType = + rtlog::Logger; + +template class RtLogTest : public ::testing::Test { +protected: + LoggerType logger; +}; + +typedef ::testing::Types + LoggerTypes; +TYPED_TEST_SUITE(RtLogTest, LoggerTypes); + +using TruncatedSingleWriterRtLoggerType = + rtlog::Logger; +using TruncatedMultiWriterRtLoggerType = + rtlog::Logger; + +template +class TruncatedRtLogTest : public ::testing::Test { +protected: + LoggerType logger; + inline static const size_t maxMessageLength = 10; +}; + +typedef ::testing::Types + TruncatedLoggerTypes; +TYPED_TEST_SUITE(TruncatedRtLogTest, TruncatedLoggerTypes); + #ifdef RTLOG_USE_STB -TEST(RtlogTest, BasicConstruction) { - rtlog::Logger - logger; - logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - "Hello, world!"); - logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, "Hello, world!"); - logger.Log({ExampleLogLevel::Warning, ExampleLogRegion::Network}, - "Hello, world!"); - logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, - "Hello, world!"); - - EXPECT_EQ(logger.PrintAndClearLogQueue(PrintMessage), 4); +TYPED_TEST(RtLogTest, BasicConstruction) { + this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + "Hello, world!"); + this->logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, + "Hello, world!"); + this->logger.Log({ExampleLogLevel::Warning, ExampleLogRegion::Network}, + "Hello, world!"); + this->logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, + "Hello, world!"); + + EXPECT_EQ(this->logger.PrintAndClearLogQueue(PrintMessage), 4); } -TEST(RtlogTest, VaArgsWorksAsIntended) { - rtlog::Logger - logger; - - logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, %lu!", - 123ul); - logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, "Hello, %f!", - 123.0); - logger.Log({ExampleLogLevel::Warning, ExampleLogRegion::Network}, - "Hello, %lf!", 123.0); - logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, "Hello, %p!", - (void *)123); - logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, %d!", - 123); - logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, "Hello, %s!", - "world"); - - EXPECT_EQ(logger.PrintAndClearLogQueue(PrintMessage), 6); +TYPED_TEST(RtLogTest, VaArgsWorksAsIntended) { + this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + "Hello, %lu!", 123ul); + this->logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, + "Hello, %f!", 123.0); + this->logger.Log({ExampleLogLevel::Warning, ExampleLogRegion::Network}, + "Hello, %lf!", 123.0); + this->logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, + "Hello, %p!", (void *)123); + this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + "Hello, %d!", 123); + this->logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, + "Hello, %s!", "world"); + + EXPECT_EQ(this->logger.PrintAndClearLogQueue(PrintMessage), 6); } -void vaArgsTest(rtlog::Logger &logger, - ExampleLogData &&data, const char *format, ...) { +template +void vaArgsTest(LoggerType &&logger, ExampleLogData &&data, const char *format, + ...) { va_list args; va_start(args, format); logger.Logv(std::move(data), format, args); va_end(args); } -TEST(RtlogTest, LogvVersionWorks) { - rtlog::Logger - logger; - - vaArgsTest(logger, {ExampleLogLevel::Debug, ExampleLogRegion::Engine}, +TYPED_TEST(RtLogTest, LogvVersionWorks) { + vaArgsTest(this->logger, {ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, %lu!", 123ul); - vaArgsTest(logger, {ExampleLogLevel::Info, ExampleLogRegion::Game}, + vaArgsTest(this->logger, {ExampleLogLevel::Info, ExampleLogRegion::Game}, "Hello, %f!", 123.0); - vaArgsTest(logger, {ExampleLogLevel::Warning, ExampleLogRegion::Network}, + vaArgsTest(this->logger, + {ExampleLogLevel::Warning, ExampleLogRegion::Network}, "Hello, %lf!", 123.0); - vaArgsTest(logger, {ExampleLogLevel::Critical, ExampleLogRegion::Audio}, + vaArgsTest(this->logger, {ExampleLogLevel::Critical, ExampleLogRegion::Audio}, "Hello, %p!", (void *)123); - vaArgsTest(logger, {ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + vaArgsTest(this->logger, {ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, %d!", 123); - vaArgsTest(logger, {ExampleLogLevel::Critical, ExampleLogRegion::Audio}, + vaArgsTest(this->logger, {ExampleLogLevel::Critical, ExampleLogRegion::Audio}, "Hello, %s!", "world"); - EXPECT_EQ(logger.PrintAndClearLogQueue(PrintMessage), 6); + EXPECT_EQ(this->logger.PrintAndClearLogQueue(PrintMessage), 6); } -TEST(RtlogTest, LoggerThreadDoesItsJob) { - rtlog::Logger - logger; - - rtlog::LogProcessingThread thread(logger, PrintMessage, +TYPED_TEST(RtLogTest, LoggerThreadDoesItsJob) { + rtlog::LogProcessingThread thread(this->logger, PrintMessage, std::chrono::milliseconds(10)); - logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, %lu!", - 123ul); - logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, "Hello, %f!", - 123.0); - logger.Log({ExampleLogLevel::Warning, ExampleLogRegion::Network}, - "Hello, %lf!", 123.0); - logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, "Hello, %p!", - (void *)123); - logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, %d!", - 123); - logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, "Hello, %s!", - "world"); + this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + "Hello, %lu!", 123ul); + this->logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, + "Hello, %f!", 123.0); + this->logger.Log({ExampleLogLevel::Warning, ExampleLogRegion::Network}, + "Hello, %lf!", 123.0); + this->logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, + "Hello, %p!", (void *)123); + this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + "Hello, %d!", 123); + this->logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, + "Hello, %s!", "world"); thread.Stop(); } -TEST(RtlogTest, ErrorsReturnedFromLog) { - rtlog::Logger - logger; - - EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - "Hello, %lu!", 123ul), +TYPED_TEST(TruncatedRtLogTest, ErrorsReturnedFromLog) { + EXPECT_EQ(this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + "Hello, %lu", 12ul), rtlog::Status::Success); - - const auto maxMessageLength = 10; - rtlog::Logger - truncatedLogger; - EXPECT_EQ( - truncatedLogger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - "Hello, %lu! xxxxxxxxxxx", 123ul), - rtlog::Status::Error_MessageTruncated); + EXPECT_EQ(this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + "Hello, %luxxxxxxxxxxxxxx", 123ul), + rtlog::Status::Error_MessageTruncated); // Inspect truncated message auto InspectLogMessage = [=](const ExampleLogData &data, @@ -191,52 +203,43 @@ TEST(RtlogTest, ErrorsReturnedFromLog) { va_end(args); EXPECT_STREQ(buffer.data(), "Hello, 12"); - EXPECT_EQ(strlen(buffer.data()), maxMessageLength - 1); + EXPECT_EQ(strlen(buffer.data()), this->maxMessageLength - 1); }; - EXPECT_EQ(truncatedLogger.PrintAndClearLogQueue(InspectLogMessage), 1); + EXPECT_EQ(this->logger.PrintAndClearLogQueue(InspectLogMessage), 2); } #endif // RTLOG_USE_STB #ifdef RTLOG_USE_FMTLIB -TEST(LoggerTest, FormatLibVersionWorksAsIntended) { - rtlog::Logger - logger; - - logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - FMT_STRING("Hello, {}!"), 123l); - logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, - FMT_STRING("Hello, {}!"), 123.0f); - logger.Log({ExampleLogLevel::Warning, ExampleLogRegion::Network}, - FMT_STRING("Hello, {}!"), 123.0); - logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, - FMT_STRING("Hello, {}!"), (void *)123); - logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - FMT_STRING("Hello, {}!"), 123); - logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, - FMT_STRING("Hello, {}!"), "world"); - - EXPECT_EQ(logger.PrintAndClearLogQueue(PrintMessage), 6); +TYPED_TEST(RtLogTest, FormatLibVersionWorksAsIntended) { + this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + FMT_STRING("Hello, {}!"), 123l); + this->logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, + FMT_STRING("Hello, {}!"), 123.0f); + this->logger.Log({ExampleLogLevel::Warning, ExampleLogRegion::Network}, + FMT_STRING("Hello, {}!"), 123.0); + this->logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, + FMT_STRING("Hello, {}!"), (void *)123); + this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + FMT_STRING("Hello, {}!"), 123); + this->logger.Log({ExampleLogLevel::Critical, ExampleLogRegion::Audio}, + FMT_STRING("Hello, {}!"), "world"); + + EXPECT_EQ(this->logger.PrintAndClearLogQueue(PrintMessage), 6); } -TEST(LoggerTest, LogReturnsSuccessOnNormalEnqueue) { - rtlog::Logger - logger; - EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - FMT_STRING("Hello, {}!"), 123l), +TYPED_TEST(RtLogTest, LogReturnsSuccessOnNormalEnqueue) { + EXPECT_EQ(this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + FMT_STRING("Hello, {}!"), 123l), rtlog::Status::Success); } -TEST(LoggerTest, LogHandlesLongMessageTruncation) { - const auto maxMessageLength = 10; - rtlog::Logger - logger; - - EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - FMT_STRING("Hello, {}! xxxxxxxxxxx"), 123l), +TYPED_TEST(TruncatedRtLogTest, LogHandlesLongMessageTruncation) { + EXPECT_EQ(this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + FMT_STRING("Hello, {}"), 12ul), + rtlog::Status::Success); + EXPECT_EQ(this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + FMT_STRING("Hello, {}xxxxxxxxxxx"), 123ul), rtlog::Status::Error_MessageTruncated); auto InspectLogMessage = [=](const ExampleLogData &data, @@ -254,16 +257,16 @@ TEST(LoggerTest, LogHandlesLongMessageTruncation) { va_end(args); EXPECT_STREQ(buffer.data(), "Hello, 12"); - EXPECT_EQ(strlen(buffer.data()), maxMessageLength - 1); + EXPECT_EQ(strlen(buffer.data()), this->maxMessageLength - 1); }; - EXPECT_EQ(logger.PrintAndClearLogQueue(InspectLogMessage), 1); + EXPECT_EQ(this->logger.PrintAndClearLogQueue(InspectLogMessage), 2); } -TEST(LoggerTest, LogHandlesQueueFullError) { - const auto maxNumMessages = 10; +TEST(LoggerTest, SingleWriterLogHandlesQueueFullError) { + const auto maxNumMessages = 16; rtlog::Logger + gSequenceNumber, rtlog::SingleRealtimeWriterQueueType> logger; auto status = rtlog::Status::Success; @@ -276,4 +279,26 @@ TEST(LoggerTest, LogHandlesQueueFullError) { EXPECT_EQ(status, rtlog::Status::Error_QueueFull); } +TEST(LoggerTest, MultipleWriterLogHandlesNeverReturnsFull) { + const auto maxNumMessages = 16; + rtlog::Logger + logger; + + auto status = rtlog::Status::Success; + + int messageCount = 0; + + while (status == rtlog::Status::Success && + messageCount < maxNumMessages + 10) { + status = logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + FMT_STRING("Hello, {} {}!"), "world", messageCount); + messageCount++; + } + + // We can never report full on a multi-writer queue, it is not realtime safe + // We will just happily spin forever in this loop unless we break + EXPECT_EQ(status, rtlog::Status::Success); +} + #endif // RTLOG_USE_FMTLIB