Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update quill v7.3.0 #143

Open
wants to merge 35 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3cbbd92
Updated Quill to v7.3.0 for core files
jjhierck Oct 9, 2024
acedfde
Updated quill in tracing
jjhierck Oct 9, 2024
15ea07e
Marked TODOs for this Quill update to find them back easily
jjhierck Oct 9, 2024
dbfd560
Updated examples to use quill v7.3.0
jjhierck Oct 9, 2024
4f94380
Moved shared default logger implementation to dedicated header file a…
jjhierck Oct 9, 2024
d7d9ec0
Moved logging config from the thread constructor into the thread conf…
jjhierck Oct 10, 2024
863c878
Added todo to investigate if it is better to pass in logger names ins…
jjhierck Oct 10, 2024
0211588
Fixed timestamp in formatter which did not display months
jjhierck Oct 10, 2024
d0be365
Extended example for logging with examples for custom loggers, multip…
jjhierck Oct 10, 2024
846364d
Use custom frontend and logger everywhere, preallocate the trace aggr…
jjhierck Oct 10, 2024
46620fd
Reverted automatic header guard update
jjhierck Oct 10, 2024
baaa144
Reverted automatic header guard update
jjhierck Oct 10, 2024
f265b09
Reverted automatic header guard update
jjhierck Oct 10, 2024
d002607
Updated header guard to match other files
jjhierck Oct 10, 2024
3d7fe7a
Moved implementation to logging.cc file
jjhierck Oct 10, 2024
e19902c
Added missing include
jjhierck Oct 10, 2024
71156eb
Added missing include and math functions
jjhierck Oct 10, 2024
ab566ae
Removed solved todos
jjhierck Oct 10, 2024
c81ca7f
Updated thread config to use logger name instead of passing around lo…
jjhierck Oct 10, 2024
3374899
Added logging header to default includes
jjhierck Oct 10, 2024
901c8ed
Added the logging macros as convenience include to logging, to preven…
jjhierck Oct 10, 2024
1db852d
Removed old git tag
jjhierck Oct 10, 2024
fe0c3bd
Removed deprecated compile flag for Quill
jjhierck Oct 10, 2024
3d1f03b
Reverted accidental include guard update
jjhierck Oct 10, 2024
5734001
Set the default console handler sink name to be static. If multiple l…
jjhierck Oct 10, 2024
62ce284
Fixed clang-tidy warnings which were treated as errors in the CI test…
jjhierck Oct 10, 2024
63089eb
Added check that the quill Backend thread is running before flushing …
jjhierck Oct 11, 2024
0f14b5b
Updated logging in ROS 2 code
jjhierck Oct 11, 2024
49e13aa
Fix for warning which was treated as error by clang-tidy
jjhierck Oct 11, 2024
b27e712
Updated documentation in logging example
jjhierck Oct 11, 2024
f7947d0
Added type alias for custom FrontendOptions
jjhierck Oct 13, 2024
10649a4
Updated todo for fixing timestamp grace period in 7.4.0
jjhierck Oct 14, 2024
1444682
Synchronize with latest changes by merging upstream branch 'upstream/…
jjhierck Oct 14, 2024
ed2ddf6
Moved logger configuration to static class method to better align wit…
jjhierck Oct 14, 2024
85d1042
Removed the quill backend thread stop in the destructor of App, since…
jjhierck Oct 15, 2024
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
8 changes: 3 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ if(CACTUS_RT_ENABLE_FETCH_DEPENDENCIES)
FetchContent_Declare(
quill
GIT_REPOSITORY https://github.com/odygrd/quill.git
GIT_TAG 9a270d5d6f57a3ac19451292e3a9f370fcd744b1
# GIT_TAG v3.3.2
# GIT_TAG a058985253dffd6a77b4eeca3ba4aa5ad85a2d02
GIT_TAG v7.3.0
)

FetchContent_MakeAvailable(quill)
Expand Down Expand Up @@ -117,6 +117,7 @@ add_library(cactus_rt
src/cactus_rt/thread.cc
src/cactus_rt/cyclic_thread.cc
src/cactus_rt/signal_handler.cc
src/cactus_rt/logging.cc
src/cactus_rt/experimental/lockless/atomic_bitset.cc
)

Expand All @@ -133,9 +134,6 @@ target_link_libraries(cactus_rt
Threads::Threads
)

# Use a bounded queue
target_compile_definitions(cactus_rt PUBLIC QUILL_USE_BOUNDED_QUEUE)

if(${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME})
if (ENABLE_TRACING)
target_sources(cactus_rt
Expand Down
83 changes: 81 additions & 2 deletions examples/logging_example/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,85 @@
`logging_example`
================

A simple example showing how to configure logging. Cactus RT uses [Quill](https://github.com/odygrd/quill) as a logger.
A simple example showing how to configure logging. Cactus RT uses [Quill](https://github.com/odygrd/quill) as a logger. Read the [Quill documentation](https://quillcpp.readthedocs.io/en/latest/) for details and additional examples.

To configure logging, create your own `quill::Config` and pass it to the `App` constructor. Configuration options for Quill can be found in the [Quill documentation](https://quillcpp.readthedocs.io/en/latest/users-api.html#config-class). The log format can also be configured, as per the [formatting documetnation](https://quillcpp.readthedocs.io/en/latest/users-api.html#config-class).
## `cactus_rt/logging.h`

To use Quill in a real-time friendly manner, `include/cactus_rt/logging.h` defines important types, default settings and utilities.

**Important**: `cactus_rt::logging::Logger` and `cactus_rt::logging::Frontend` types which define a `quill::QueueType::BoundedDropping` queue to prevent delays in case the loggers overflow, which is essential in any real-time thread. These *must* be used throughout the application instead of the default `quill::Logger` and `quill::Frontend` types!

## Logging non-primitive STL types

Logging non-primitive types on the hot path (i.e. in a real-time thread) requires some additional work. Most often-used standard library types are already provided for you by Quill though, and only require a simple `#include`.

For example, when logging `std::chrono` types, you need to insert the following:

```cpp
#include "quill/LogMacros.h"
// Required to parse std::chrono inside logging macros
#include "quill/std/Chrono.h" // IWYU pragma: keep

...

LOG_INFO(logger, "Time: {}", std::chrono::seconds(3));
```

The [Quill Cheat Sheet](https://quillcpp.readthedocs.io/en/latest/cheat_sheet.html#) contains very useful information for logging various non-primitive types.

## Default logging

If unspecified, each thread will create a default logger for itself. This logger will write to a default console sink and uses a default format. All of these defaults can be found in `include/cactus_rt/logging.h`.

## Customize logger, sink(s) or pattern format

Customization of the quill Backend worker thread can be done by passing in logging options to the `cactus_rt::AppConfig`.

To use a custom sink or pattern format, define them before creating the logger. The sink(s) and pattern format are used to create the logger. Finally, the name of the created logger must be passed to the `cactus_rt::ThreadConfig`.

The snippets below might help

1. Create a sink (see the [Quill documentation on sink types](https://quillcpp.readthedocs.io/en/latest/sink_types.html) for the full code to create each sink).

```cpp
// Default console sink
auto console_sink = cactus_rt::logging::DefaultConsoleSink();

// Custom sink, e.g. a file sink
// !! Make sure to use `cactus_rt::logging::Frontend instead of quill::Frontend !!
auto file_sink = cactus_rt::logging::Frontend::create_or_get_sink<quill::FileSink>(...);
```

2. Create a pattern format

```cpp
// Default formatter pattern
auto pattern_format = cactus_rt::logging::DefaultPatternFormatterOptions();

// Custom formatter pattern
auto pattern_format = quill::PatternFormatterOptions(...);
```

3. Create the logger

```cpp
// Create custom logger using a single sink
// !! Make sure to use `cactus_rt::logging::Frontend instead of quill::Frontend !!
auto* custom_logger = cactus_rt::logging::Frontend::create_or_get_logger("CustomLogger", console_sink, pattern_format);

// Alternatively, you can add multiple sinks to a vector and pass that instead
const std::vector<std::shared_ptr<quill::Sink>> sinks = {console_sink, file_sink};
// !! Make sure to use `cactus_rt::logging::Frontend instead of quill::Frontend !!
auto* custom_logger = cactus_rt::logging::Frontend::create_or_get_logger("CustomLogger", sinks, pattern_format);
```

4. Pass the logger to the thread

```cpp
// Add the custom logger name to the thread config
cactus_rt::ThreadConfig thread_config;
thread_config.logger_name = custom_logger->get_logger_name();

// Finally, create the thread with using the thread config
auto thread = app.CreateThread<ExampleRTThread>("ExampleRTThread", thread_config);
```
95 changes: 90 additions & 5 deletions examples/logging_example/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
#include <chrono>
#include <iostream>

#include "quill/backend/BackendOptions.h"
#include "quill/sinks/ConsoleSink.h"
#include "quill/sinks/FileSink.h"

// Required to parse std::chrono inside logging macros
#include "quill/std/Chrono.h" // IWYU pragma: keep

using cactus_rt::App;
using cactus_rt::CyclicThread;

Expand All @@ -29,10 +36,82 @@ class ExampleRTThread : public CyclicThread {
}

protected:
LoopControl Loop(int64_t /*now*/) noexcept final {
LoopControl Loop(int64_t elapsed_ns) noexcept final {
loop_counter_++;
if (loop_counter_ % 1000 == 0) {
LOG_INFO(Logger(), "Loop {} ({})", loop_counter_, std::chrono::nanoseconds(elapsed_ns));
}

LOG_INFO_LIMIT(std::chrono::milliseconds{1500}, Logger(), "Log limit: Loop {}", loop_counter_);
return LoopControl::Continue;
}
};

/**
* Here we create a second real-time thread that runs at another frequency, to
* show that we can have multiple threads emitting logging data simultaneously.
*/
class SecondRTThread : public CyclicThread {
int64_t loop_counter_ = 0;

static cactus_rt::CyclicThreadConfig MakeConfig() {
cactus_rt::CyclicThreadConfig thread_config;
thread_config.period_ns = 1'200'000;
thread_config.cpu_affinity = std::vector<size_t>{2};
thread_config.SetFifoScheduler(80);

thread_config.logger_config.logger_name = MakeLogger();

return thread_config;
}

/**
* Create a logger with multiple sinks and return the logger name.
*/
static std::string MakeLogger() {
// Use the default console logger too
// Make sure to use cactus_rt's logging Frontend instead of Quill's default
auto console_sink = cactus_rt::logging::Frontend::create_or_get_sink<quill::ConsoleSink>("OtherConsoleSink", true);

// Create a new file sink and configuration
quill::FileSinkConfig file_sink_config;
file_sink_config.set_open_mode('w');
file_sink_config.set_filename_append_option(quill::FilenameAppendOption::StartDateTime);
// !! Make sure to use `cactus_rt::logging::Frontend instead of quill::Frontend !!
auto file_sink = cactus_rt::logging::Frontend::create_or_get_sink<quill::FileSink>(
"file_logging_example.log",
file_sink_config,
quill::FileEventNotifier{}
);

// Add both sinks to a vector
const std::vector<std::shared_ptr<quill::Sink>> sinks = {console_sink, file_sink};

// Create a custom formatter pattern
auto pattern_format = quill::PatternFormatterOptions(
"[%(time)][%(log_level_short_code)][%(logger)][%(process_id)][%(file_name):%(line_number)] - WOW custom message format - %(message)",
"%H:%M:%S.%Qns"
);

// Use the new sinks and pattern to create a custom logger for the other thread
auto* custom_thread_logger = cactus_rt::logging::Frontend::create_or_get_logger("CustomThreadLogger", sinks, pattern_format);

// Return the custom logger's name to pass into the configuration
return custom_thread_logger->get_logger_name();
}

public:
SecondRTThread() : CyclicThread("SecondRTThread", MakeConfig()) {}

int64_t GetLoopCounter() const {
return loop_counter_;
}

protected:
LoopControl Loop(int64_t elapsed_ns) noexcept final {
loop_counter_++;
if (loop_counter_ % 1000 == 0) {
LOG_INFO(Logger(), "Loop {}", loop_counter_);
LOG_INFO(Logger(), "Loop {} ({})", loop_counter_, std::chrono::nanoseconds(elapsed_ns));
}

LOG_INFO_LIMIT(std::chrono::milliseconds{1500}, Logger(), "Log limit: Loop {}", loop_counter_);
Expand All @@ -44,15 +123,21 @@ int main() {
// Create a cactus_rt app configuration
cactus_rt::AppConfig app_config;

// Disable strict timestamp order - this will be faster, but logs may appear out of order
app_config.logger_config.backend_thread_strict_log_timestamp_order = false;
// Create a Quill backend logging config to configure the Quill backend thread
app_config.logger_backend_options = cactus_rt::logging::DefaultBackendOptions();

// Disable strict timestamp order by setting the grace period to 0 - this will be faster, but logs may appear out of order
// See quill::BackendOptions documentation for more info
// TODO: There is a bug in quill where setting the grace period to 0 causes assertion error in Debug builds. This is fixed in 7.4.0 (https://github.com/odygrd/quill/issues/605)
app_config.logger_backend_options.log_timestamp_ordering_grace_period = std::chrono::microseconds(1);

// Set the background logging thread CPU affinity
app_config.logger_config.backend_thread_cpu_affinity = 1; // Different CPU than the CyclicThread CPU!
app_config.logger_backend_options.cpu_affinity = 1; // Different CPU than the CyclicThread CPU!

App app("LoggingExampleApp", app_config);

auto thread = app.CreateThread<ExampleRTThread>();
auto other_thread = app.CreateThread<SecondRTThread>();

constexpr unsigned int time = 5;

Expand Down
1 change: 1 addition & 0 deletions examples/message_passing_example/data_logger_thread.cc
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "data_logger_thread.h"

#include <chrono>
#include <iomanip>

DataLogger::DataLogger(
const std::string& data_file_path,
Expand Down
2 changes: 2 additions & 0 deletions examples/message_passing_example/rt_thread.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "rt_thread.h"

#include <cmath>

cactus_rt::CyclicThread::LoopControl RtThread::Loop(int64_t ellapsed_ns) noexcept {
const double ellapsed_ms = static_cast<double>(ellapsed_ns) / 1'000'000.0;

Expand Down
2 changes: 1 addition & 1 deletion examples/ros2/publisher/complex_data.cc
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class RTROS2PublisherThread : public cactus_rt::CyclicThread, public cactus_rt::

const auto elapsed_ns_f = static_cast<float>(elapsed_ns);

MyCoefficientData msg{
const MyCoefficientData msg{
elapsed_ns_f,
elapsed_ns_f / 10.0F,
elapsed_ns_f / 100.0F,
Expand Down
3 changes: 3 additions & 0 deletions examples/simple_example/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
#include <chrono>
#include <iostream>

// Required to parse std::chrono inside logging macros
#include "quill/std/Chrono.h" // IWYU pragma: keep

using cactus_rt::App;
using cactus_rt::CyclicThread;

Expand Down
4 changes: 3 additions & 1 deletion examples/tracing_example_no_rt/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include <memory>
#include <thread>

#include "quill/Backend.h" // Required to start/stop the quill backend thread

using namespace std::chrono_literals;
using FileSink = cactus_rt::tracing::FileSink;
using TraceAggregator = cactus_rt::tracing::TraceAggregator;
Expand All @@ -25,7 +27,7 @@ void StartTracing(const char* app_name, const char* filename) {
// Create the file sink so the data aggregated by the TraceAggregator will be written to somewhere.
auto file_sink = std::make_shared<FileSink>(filename);

quill::start();
quill::Backend::start();
trace_aggregator->Start(file_sink);
}

Expand Down
15 changes: 1 addition & 14 deletions include/cactus_rt/app.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
#include <vector>

#include "config.h"
#include "quill/Quill.h"
#include "thread.h"
#include "tracing/thread_tracer.h"
#include "tracing/trace_aggregator.h"
Expand All @@ -29,26 +28,14 @@ class App {
size_t heap_size_;

// Configuration for quill logging
quill::Config logger_config_;
quill::BackendOptions logger_backend_options_;

TracerConfig tracer_config_;

std::shared_ptr<tracing::TraceAggregator> trace_aggregator_; // Must be above threads_ to guarantee destructor order.

std::vector<std::shared_ptr<Thread>> threads_;

void SetDefaultLogFormat(quill::Config& cfg) {
// Create a handler of stdout
const std::shared_ptr<quill::Handler> handler = quill::stdout_handler();

// Enable console colours on the handler
static_cast<quill::ConsoleHandler*>(handler.get())->enable_console_colours();

// Set the default pattern
handler->set_pattern("[%(ascii_time)][%(level_id)][%(logger_name)][%(filename):%(lineno)] %(message)", "%Y-%m-%d %H:%M:%S.%Qns");
cfg.default_handlers.push_back(handler);
}

public:
explicit App(std::string name = "RTApp", AppConfig config = AppConfig());

Expand Down
26 changes: 22 additions & 4 deletions include/cactus_rt/config.h
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#ifndef CACTUS_RT_CONFIG_H_
#define CACTUS_RT_CONFIG_H_

#include <quill/Quill.h>

#include <memory>

#include "cactus_rt/logging.h"
#include "cactus_rt/scheduler.h"
#include "quill/backend/BackendOptions.h"

namespace cactus_rt {

Expand All @@ -29,9 +29,11 @@ struct AppConfig {
size_t heap_size = 0;

/**
* @brief The configuration for quill logging
* @brief The Quill Backend configuration for logging.
*
* It is set to sensible default values for real-time logging.
*/
quill::Config logger_config;
quill::BackendOptions logger_backend_options = cactus_rt::logging::DefaultBackendOptions();

/**
* @brief The config for the tracer if enabled (ENABLE_TRACING option in cmake)
Expand Down Expand Up @@ -99,6 +101,20 @@ struct ThreadTracerConfig {
bool trace_wakeup_latency = false;
};

/**
* @brief The configuration needed for Quill logging in a thread
*/
struct ThreadLoggerConfig {
/**
* @brief Name of the logger that is to be used as thread logger.
*
* @note If not given, a default thread logger is created with the name set to the thread name.
*
* @note If the name given does not correspond to a created logger, a default thread logger is created with the `logger_name`.
*/
std::string logger_name;
};

/**
* @brief The configuration required for a thread
*/
Expand All @@ -111,6 +127,8 @@ struct ThreadConfig {

ThreadTracerConfig tracer_config;

ThreadLoggerConfig logger_config;

// The scheduler type, default scheduler is SCHED_OTHER
std::shared_ptr<Scheduler> scheduler = std::make_shared<OtherScheduler>();

Expand Down
Loading