From 28df12a5b9e8d9c1624f68f901a933110f42de26 Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Tue, 24 Dec 2024 09:03:31 -0700 Subject: [PATCH 1/4] Changed default queue to farbot, added Multi and Single Writer variants --- CMakeLists.txt | 24 +- README.md | 24 +- examples/custom_queue_example/CMakeLists.txt | 23 +- .../custom_queue_example/customqueuemain.cpp | 25 +- include/rtlog/rtlog.h | 64 ++++- test/test_rtlog.cpp | 269 ++++++++++-------- 6 files changed, 252 insertions(+), 177 deletions(-) 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/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 From 464d622326e58311543b168b064ad76a0e2d1f41 Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Sat, 28 Dec 2024 11:11:12 -0800 Subject: [PATCH 2/4] Cleanup logging name for review legibility --- test/test_rtlog.cpp | 152 +++++++++++++++++++++++--------------------- 1 file changed, 79 insertions(+), 73 deletions(-) diff --git a/test/test_rtlog.cpp b/test/test_rtlog.cpp index 4db78a5..b66938d 100644 --- a/test/test_rtlog.cpp +++ b/test/test_rtlog.cpp @@ -104,33 +104,34 @@ TYPED_TEST_SUITE(TruncatedRtLogTest, TruncatedLoggerTypes); #ifdef RTLOG_USE_STB 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); + auto &logger = this->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, 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); + auto &logger = this->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); } template @@ -143,49 +144,51 @@ void vaArgsTest(LoggerType &&logger, ExampleLogData &&data, const char *format, } TYPED_TEST(RtLogTest, LogvVersionWorks) { - vaArgsTest(this->logger, {ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + auto &logger = this->logger; + vaArgsTest(logger, {ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, %lu!", 123ul); - vaArgsTest(this->logger, {ExampleLogLevel::Info, ExampleLogRegion::Game}, + vaArgsTest(logger, {ExampleLogLevel::Info, ExampleLogRegion::Game}, "Hello, %f!", 123.0); - vaArgsTest(this->logger, - {ExampleLogLevel::Warning, ExampleLogRegion::Network}, + vaArgsTest(logger, {ExampleLogLevel::Warning, ExampleLogRegion::Network}, "Hello, %lf!", 123.0); - vaArgsTest(this->logger, {ExampleLogLevel::Critical, ExampleLogRegion::Audio}, + vaArgsTest(logger, {ExampleLogLevel::Critical, ExampleLogRegion::Audio}, "Hello, %p!", (void *)123); - vaArgsTest(this->logger, {ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + vaArgsTest(logger, {ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, %d!", 123); - vaArgsTest(this->logger, {ExampleLogLevel::Critical, ExampleLogRegion::Audio}, + vaArgsTest(logger, {ExampleLogLevel::Critical, ExampleLogRegion::Audio}, "Hello, %s!", "world"); - EXPECT_EQ(this->logger.PrintAndClearLogQueue(PrintMessage), 6); + EXPECT_EQ(logger.PrintAndClearLogQueue(PrintMessage), 6); } TYPED_TEST(RtLogTest, LoggerThreadDoesItsJob) { - rtlog::LogProcessingThread thread(this->logger, PrintMessage, + auto &logger = this->logger; + rtlog::LogProcessingThread thread(logger, PrintMessage, std::chrono::milliseconds(10)); - 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"); + 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"); thread.Stop(); } TYPED_TEST(TruncatedRtLogTest, ErrorsReturnedFromLog) { - EXPECT_EQ(this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - "Hello, %lu", 12ul), + auto &logger = this->logger; + EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + "Hello, %lu", 12ul), rtlog::Status::Success); - EXPECT_EQ(this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - "Hello, %luxxxxxxxxxxxxxx", 123ul), + EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + "Hello, %luxxxxxxxxxxxxxx", 123ul), rtlog::Status::Error_MessageTruncated); // Inspect truncated message @@ -203,43 +206,46 @@ TYPED_TEST(TruncatedRtLogTest, ErrorsReturnedFromLog) { va_end(args); EXPECT_STREQ(buffer.data(), "Hello, 12"); - EXPECT_EQ(strlen(buffer.data()), this->maxMessageLength - 1); + EXPECT_EQ(strlen(buffer.data()), maxMessageLength - 1); }; - EXPECT_EQ(this->logger.PrintAndClearLogQueue(InspectLogMessage), 2); + EXPECT_EQ(logger.PrintAndClearLogQueue(InspectLogMessage), 2); } #endif // RTLOG_USE_STB #ifdef RTLOG_USE_FMTLIB 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); + auto &logger = this->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, LogReturnsSuccessOnNormalEnqueue) { - EXPECT_EQ(this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - FMT_STRING("Hello, {}!"), 123l), + auto &logger = this->logger; + EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + FMT_STRING("Hello, {}!"), 123l), rtlog::Status::Success); } TYPED_TEST(TruncatedRtLogTest, LogHandlesLongMessageTruncation) { - EXPECT_EQ(this->logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - FMT_STRING("Hello, {}"), 12ul), + auto &logger = this->logger; + EXPECT_EQ(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), + EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, + FMT_STRING("Hello, {}xxxxxxxxxxx"), 123ul), rtlog::Status::Error_MessageTruncated); auto InspectLogMessage = [=](const ExampleLogData &data, @@ -257,10 +263,10 @@ TYPED_TEST(TruncatedRtLogTest, LogHandlesLongMessageTruncation) { va_end(args); EXPECT_STREQ(buffer.data(), "Hello, 12"); - EXPECT_EQ(strlen(buffer.data()), this->maxMessageLength - 1); + EXPECT_EQ(strlen(buffer.data()), maxMessageLength - 1); }; - EXPECT_EQ(this->logger.PrintAndClearLogQueue(InspectLogMessage), 2); + EXPECT_EQ(logger.PrintAndClearLogQueue(InspectLogMessage), 2); } TEST(LoggerTest, SingleWriterLogHandlesQueueFullError) { From d8c4f2db6f2e2e4f9fb80dfa4661fc6ba69501ba Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Sat, 28 Dec 2024 11:28:00 -0800 Subject: [PATCH 3/4] Few tweaks from self review --- README.md | 4 ++-- include/rtlog/rtlog.h | 19 +++++-------------- test/test_rtlog.cpp | 4 ++-- 3 files changed, 9 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 82d40b1..ccc471f 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ 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/hogliux/farbot) +- Efficient thread-safe logging using a [lock free queue](https://github.com/hogliux/farbot). ## Requirements @@ -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), diff --git a/include/rtlog/rtlog.h b/include/rtlog/rtlog.h index 1481676..4932f5b 100644 --- a/include/rtlog/rtlog.h +++ b/include/rtlog/rtlog.h @@ -195,6 +195,7 @@ class Logger { using InternalLogData = detail::BasicLogData; using InternalQType = QType; + static_assert(MaxNumMessages > 0); static_assert((MaxNumMessages & (MaxNumMessages - 1)) == 0, "MaxNumMessages must be a power of 2"); static_assert( @@ -257,13 +258,8 @@ class Logger { // data loss const bool dataWasEnqueued = mQueue.try_enqueue(std::move(dataToQueue)); - constexpr bool isMultiRealtimeWriterQueueType = - std::is_same_v>; - if constexpr (!isMultiRealtimeWriterQueueType) { - if (!dataWasEnqueued) - retVal = Status::Error_QueueFull; - } + if (!dataWasEnqueued) + retVal = Status::Error_QueueFull; return retVal; } @@ -359,13 +355,8 @@ class Logger { // data loss const bool dataWasEnqueued = mQueue.try_enqueue(std::move(dataToQueue)); - constexpr bool isMultiRealtimeWriterQueueType = - std::is_same_v>; - if constexpr (!isMultiRealtimeWriterQueueType) { - if (!dataWasEnqueued) - retVal = Status::Error_QueueFull; - } + if (!dataWasEnqueued) + retVal = Status::Error_QueueFull; return retVal; }; diff --git a/test/test_rtlog.cpp b/test/test_rtlog.cpp index b66938d..365a6f0 100644 --- a/test/test_rtlog.cpp +++ b/test/test_rtlog.cpp @@ -206,7 +206,7 @@ TYPED_TEST(TruncatedRtLogTest, 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(logger.PrintAndClearLogQueue(InspectLogMessage), 2); } @@ -263,7 +263,7 @@ TYPED_TEST(TruncatedRtLogTest, 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), 2); From 59505509d9e9cdaf43783ad198a01718d1699090 Mon Sep 17 00:00:00 2001 From: Chris Apple Date: Sat, 11 Jan 2025 15:21:42 +0700 Subject: [PATCH 4/4] Few fixups from self review --- test/test_rtlog.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/test/test_rtlog.cpp b/test/test_rtlog.cpp index 365a6f0..8760687 100644 --- a/test/test_rtlog.cpp +++ b/test/test_rtlog.cpp @@ -75,7 +75,7 @@ using MultiWriterRtLoggerType = template class RtLogTest : public ::testing::Test { protected: - LoggerType logger; + LoggerType logger_; }; typedef ::testing::Types @@ -92,8 +92,8 @@ using TruncatedMultiWriterRtLoggerType = template class TruncatedRtLogTest : public ::testing::Test { protected: - LoggerType logger; - inline static const size_t maxMessageLength = 10; + LoggerType logger_; + inline static const size_t maxMessageLength_ = 10; }; typedef ::testing::Typeslogger; + auto &logger = this->logger_; logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, world!"); logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, "Hello, world!"); @@ -117,7 +117,7 @@ TYPED_TEST(RtLogTest, BasicConstruction) { } TYPED_TEST(RtLogTest, VaArgsWorksAsIntended) { - auto &logger = this->logger; + auto &logger = this->logger_; logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, %lu!", 123ul); logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, "Hello, %f!", @@ -144,7 +144,7 @@ void vaArgsTest(LoggerType &&logger, ExampleLogData &&data, const char *format, } TYPED_TEST(RtLogTest, LogvVersionWorks) { - auto &logger = this->logger; + auto &logger = this->logger_; vaArgsTest(logger, {ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, %lu!", 123ul); vaArgsTest(logger, {ExampleLogLevel::Info, ExampleLogRegion::Game}, @@ -162,7 +162,7 @@ TYPED_TEST(RtLogTest, LogvVersionWorks) { } TYPED_TEST(RtLogTest, LoggerThreadDoesItsJob) { - auto &logger = this->logger; + auto &logger = this->logger_; rtlog::LogProcessingThread thread(logger, PrintMessage, std::chrono::milliseconds(10)); @@ -183,12 +183,14 @@ TYPED_TEST(RtLogTest, LoggerThreadDoesItsJob) { } TYPED_TEST(TruncatedRtLogTest, ErrorsReturnedFromLog) { - auto &logger = this->logger; + auto &logger = this->logger_; + auto maxMessageLength = this->maxMessageLength_; EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, "Hello, %lu", 12ul), rtlog::Status::Success); + EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - "Hello, %luxxxxxxxxxxxxxx", 123ul), + "Hello, %lu! xxxxxxxxxxx", 123ul), rtlog::Status::Error_MessageTruncated); // Inspect truncated message @@ -206,7 +208,7 @@ TYPED_TEST(TruncatedRtLogTest, ErrorsReturnedFromLog) { va_end(args); EXPECT_STREQ(buffer.data(), "Hello, 12"); - EXPECT_EQ(strlen(buffer.data()), this->maxMessageLength - 1); + EXPECT_EQ(strlen(buffer.data()), maxMessageLength - 1); }; EXPECT_EQ(logger.PrintAndClearLogQueue(InspectLogMessage), 2); } @@ -215,7 +217,7 @@ TYPED_TEST(TruncatedRtLogTest, ErrorsReturnedFromLog) { #ifdef RTLOG_USE_FMTLIB TYPED_TEST(RtLogTest, FormatLibVersionWorksAsIntended) { - auto &logger = this->logger; + auto &logger = this->logger_; logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, FMT_STRING("Hello, {}!"), 123l); logger.Log({ExampleLogLevel::Info, ExampleLogRegion::Game}, @@ -233,19 +235,17 @@ TYPED_TEST(RtLogTest, FormatLibVersionWorksAsIntended) { } TYPED_TEST(RtLogTest, LogReturnsSuccessOnNormalEnqueue) { - auto &logger = this->logger; + auto &logger = this->logger_; EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, FMT_STRING("Hello, {}!"), 123l), rtlog::Status::Success); } TYPED_TEST(TruncatedRtLogTest, LogHandlesLongMessageTruncation) { - auto &logger = this->logger; - EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - FMT_STRING("Hello, {}"), 12ul), - rtlog::Status::Success); + auto &logger = this->logger_; + auto maxMessageLength = this->maxMessageLength_; EXPECT_EQ(logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Engine}, - FMT_STRING("Hello, {}xxxxxxxxxxx"), 123ul), + FMT_STRING("Hello, {}! xxxxxxxxxxx"), 123l), rtlog::Status::Error_MessageTruncated); auto InspectLogMessage = [=](const ExampleLogData &data, @@ -263,10 +263,10 @@ TYPED_TEST(TruncatedRtLogTest, LogHandlesLongMessageTruncation) { va_end(args); EXPECT_STREQ(buffer.data(), "Hello, 12"); - EXPECT_EQ(strlen(buffer.data()), this->maxMessageLength - 1); + EXPECT_EQ(strlen(buffer.data()), maxMessageLength - 1); }; - EXPECT_EQ(logger.PrintAndClearLogQueue(InspectLogMessage), 2); + EXPECT_EQ(logger.PrintAndClearLogQueue(InspectLogMessage), 1); } TEST(LoggerTest, SingleWriterLogHandlesQueueFullError) {