Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 17 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,24 @@ target_include_directories(${PROJECT_NAME} INTERFACE
$<INSTALL_INTERFACE:include>
)

# 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
$<BUILD_INTERFACE:${farbot_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
)
FetchContent_MakeAvailable(ReaderWriterQueue)
endif()

if(NOT RTSAN_USE_FMTLIB AND NOT TARGET stb::stb)
Expand Down Expand Up @@ -89,7 +99,7 @@ endif()

target_link_libraries(rtlog
INTERFACE
readerwriterqueue
farbot::farbot
stb::stb
$<$<BOOL:${RTLOG_USE_FMTLIB}>:fmt::fmt>
)
Expand Down
22 changes: 19 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<ExampleLogData, MAX_NUM_LOG_MESSAGES, MAX_LOG_MESSAGE_LENGTH, gSequenceNumber, SingleRealtimeWriterQueueType>;

SingleWriterRtLoggerType logger;
logger.Log({ExampleLogLevel::Debug, ExampleLogRegion::Audio}, "Hello, world! %i", 42);

...

using MultiWriterRtLoggerType = rtlog::Logger<ExampleLogData, MAX_NUM_LOG_MESSAGES, MAX_LOG_MESSAGE_LENGTH, gSequenceNumber, MultiRealtimeWriterQueueType>;

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 **

Expand Down
23 changes: 6 additions & 17 deletions examples/custom_queue_example/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
$<BUILD_INTERFACE:${farbot_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
FetchContent_Declare(ReaderWriterQueue
GIT_REPOSITORY https://github.com/cameron314/readerwriterqueue
)
FetchContent_MakeAvailable(ReaderWriterQueue)
endif()

add_executable(custom_queue_example
Expand All @@ -25,5 +14,5 @@ add_executable(custom_queue_example
target_link_libraries(custom_queue_example
PRIVATE
rtlog::rtlog
farbot::farbot
readerwriterqueue
)
25 changes: 10 additions & 15 deletions examples/custom_queue_example/customqueuemain.cpp
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
#include <farbot/fifo.hpp>
#include <rtlog/rtlog.h>

template <typename T> class FarbotMPSCQueueWrapper {
farbot::fifo<T,
farbot::fifo_options::concurrency::single, // Consumer
farbot::fifo_options::concurrency::multiple, // Producer
farbot::fifo_options::full_empty_failure_mode::
return_false_on_full_or_empty, // consumer_failure_mode
farbot::fifo_options::full_empty_failure_mode::
overwrite_or_return_default> // producer_failure_mode
#include <readerwriterqueue.h>

mQueue;
template <typename T> class CustomQueue {

// technically we could use readerwriterqueue "unwrapped" but showing this off
// in the CustomQueue wrapper for documentation purposes
moodycamel::ReaderWriterQueue<T> 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 {};

std::atomic<size_t> gSequenceNumber{0};

int main() {
rtlog::Logger<LogData, 128, 128, gSequenceNumber, FarbotMPSCQueueWrapper>
logger;
rtlog::Logger<LogData, 128, 128, gSequenceNumber, CustomQueue> logger;
logger.Log({}, "Hello, World!");

logger.PrintAndClearLogQueue(
Expand Down
47 changes: 39 additions & 8 deletions include/rtlog/rtlog.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#include <fmt/format.h>
#endif // RTLOG_USE_FMTLIB

#include <readerwriterqueue.h>
#include <farbot/fifo.hpp>

#ifdef RTLOG_USE_STB
#ifndef STB_SPRINTF_IMPLEMENTATION
Expand Down Expand Up @@ -125,12 +125,40 @@ template <typename T>
inline constexpr bool has_int_constructor_v = has_int_constructor<T>::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 <typename T> using rtlog_SPSC = moodycamel::ReaderWriterQueue<T, 512>;
template <typename T, farbot::fifo_options::concurrency producer_concurrency,
farbot::fifo_options::full_empty_failure_mode producer_failure_mode>
class FarbotFifoType {
farbot::fifo<T,
farbot::fifo_options::concurrency::single, // Consumer
producer_concurrency, // Producer
farbot::fifo_options::full_empty_failure_mode::
return_false_on_full_or_empty, // consumer_failure_mode
producer_failure_mode> // 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 <typename T>
using SingleRealtimeWriterQueueType =
FarbotFifoType<T, farbot::fifo_options::concurrency::single,
farbot::fifo_options::full_empty_failure_mode::
return_false_on_full_or_empty>;

// 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 <typename T>
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.
Expand Down Expand Up @@ -161,12 +189,15 @@ template <typename T> using rtlog_SPSC = moodycamel::ReaderWriterQueue<T, 512>;
*/
template <typename LogData, size_t MaxNumMessages, size_t MaxMessageLength,
std::atomic<std::size_t> &SequenceNumber,
template <typename> class QType = rtlog_SPSC>
template <typename> class QType = SingleRealtimeWriterQueueType>
class Logger {
public:
using InternalLogData = detail::BasicLogData<LogData, MaxMessageLength>;
using InternalQType = QType<InternalLogData>;

static_assert(MaxNumMessages > 0);
static_assert((MaxNumMessages & (MaxNumMessages - 1)) == 0,
"MaxNumMessages must be a power of 2");
static_assert(
detail::has_int_constructor_v<InternalQType>,
"QType must have a constructor that takes an int - `QType(int)`");
Expand Down
Loading
Loading