From 712c859d8986daed66cb5897a4646ab30be744ff Mon Sep 17 00:00:00 2001 From: Takatoshi Kondo Date: Fri, 25 Oct 2024 16:53:39 +0900 Subject: [PATCH] Added custom logger example. This example disables Boost.Log and uses simple std::log. Updated document. --- CHANGELOG.adoc | 1 + doc/modules/ROOT/nav.adoc | 1 + .../ROOT/pages/functionality/logging.adoc | 112 +++++++++- doc/modules/ROOT/pages/index.adoc | 3 +- example/CMakeLists.txt | 1 + example/cl_cpp17_mqtt_pub.cpp | 2 +- example/cl_cpp17_mqtt_sub.cpp | 2 +- example/custom_logger.cpp | 197 ++++++++++++++++++ include/async_mqtt/util/log.hpp | 38 +--- include/async_mqtt/util/log_severity.hpp | 48 +++++ 10 files changed, 360 insertions(+), 45 deletions(-) create mode 100644 example/custom_logger.cpp create mode 100644 include/async_mqtt/util/log_severity.hpp diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index f7ab295cd..200121a2e 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -3,6 +3,7 @@ = History == 9.1.0 +* Added custom logger example. #367 * Refined documents. #364, #365 * Made colored log selectable (by default true(colored)). #363 * Fixed misuse of bound allocator. #362 diff --git a/doc/modules/ROOT/nav.adoc b/doc/modules/ROOT/nav.adoc index 995eb431e..1e2592bf3 100644 --- a/doc/modules/ROOT/nav.adoc +++ b/doc/modules/ROOT/nav.adoc @@ -38,3 +38,4 @@ ** xref:tool/broker.adoc[] ** xref:tool/client_cli.adoc[] ** xref:tool/bench.adoc[] +* link:http://github.com/redboltz/async_mqtt[Repository] diff --git a/doc/modules/ROOT/pages/functionality/logging.adoc b/doc/modules/ROOT/pages/functionality/logging.adoc index 060683758..db845fc46 100644 --- a/doc/modules/ROOT/pages/functionality/logging.adoc +++ b/doc/modules/ROOT/pages/functionality/logging.adoc @@ -1,17 +1,113 @@ -= logging += Logging -async_mqtt supports Boost.Log style logging. The severity levels are `fatal`, `error`, `warning`, `info`, `debug`, and `trace`. +== Using Boost.Log +`async_mqtt` provides logging support with Boost.Log. Supported severity levels include `fatal`, `error`, `warning`, `info`, `debug`, and `trace`. -The easiest way to setup logging is +To quickly set up logging, you can use: ```cpp async_mqtt::setup_log( - async_mqtt::severity_level::info, // you can use any other severity_level - true // if true or omit the argument - // log is colored, otherwise non colored + async_mqtt::severity_level::info, // Choose any severity level + true // Pass `true` or omit to enable colored output ); ``` -You can setup your custom settings. +Custom configurations are also supported. -See https://github.com/redboltz/async_mqtt/blob/main/include/async_mqtt/util/setup_log.hpp[setup_log.hpp] +See `setup_log.hpp` for details: https://github.com/redboltz/async_mqtt/blob/main/include/async_mqtt/util/setup_log.hpp[setup_log.hpp] + +NOTE: Boost.Log-based logging is enabled when `ASYNC_MQTT_USE_LOG` is defined. For more information, see xref:config.adoc[] + +=== Compile time severity filtering + +`setup_log()` is for runtime severity level setting. + +`ASYNC_MQTT_LOG_SEV` is for compile-time severity level setting. +If you define as follows: + +```cpp +#define ASYNC_MQTT_LOG_SEV info +``` + +then, `fatal`, `error`, `warning`, and `info` logs are compiled. `debug` and `trace` logs are erased from the code. +It is useful for minimal code generation for logging. + +== Using a Custom Logger +Boost.Log offers extensive functionality, including logging to the console, syslog, and files. However, some users may prefer a simpler logging mechanism. + +All you need to do is defining two macros. +One is `ASYNC_MQTT_ADD_VALUE`, the other is `ASYNC_MQTT_LOG`. + +To create and use a custom logger, follow these steps: + +All steps should be completed before including any `async_mqtt` headers. + +Include the minimal header to implement your custom logger: + +```cpp +#include +``` + +[Optional] Remove the `ASYNC_MQTT_USE_LOG` definition: + +```cpp +#undef ASYNC_MQTT_USE_LOG +``` + +NOTE: Even if `ASYNC_MQTT_USE_LOG` is defined, your custom logger works well. But removing Boost.Log is often the one of the dominant motivation to introduce custom loggers. + +Define your custom logger class: + +```cpp +// Define logger class +struct custom_log { + explicit constexpr custom_log( + std::string chan, // chan indicates the log channel or part + async_mqtt::severity_level sev // sev represents the severity level + ) + { + // Setup filter + if (sev < async_mqtt::severity_level::info) { + print = false; + return; + } + // Output header + std::clog << "[" << sev << "]" << "(" << chan << ") "; + } + ~custom_log() { + // Output trailer + if (print) std::clog << std::endl; + } + + bool print = true; +}; + +// Define output stream operator +template +inline constexpr custom_log const& operator<<( + custom_log const& o, + T const& t +) { + // Output message body with filter + if (o.print) std::clog << t; + return o; +} +``` + +NOTE: This example demonstrates how to locate logging elements in the output. e.g.) channel, severity, tag, ... , so different type of brace is intentionally used. (`()<>{}`) + + +Apply the custom logger to the `ASYNC_MQTT_LOG()` macro. The `ASYNC_MQTT_LOG()` macro is used throughout the `async_mqtt` library. + +```cpp +// Output additional value. Stringized name(tag) and tagged value. +#define ASYNC_MQTT_ADD_VALUE(name, val) "<" << #name ">{" << val << "} " +``` + +```cpp +// Set ASYNC_MQTT_LOG macro to custom_log. +#define ASYNC_MQTT_LOG(chan, sev) custom_log(chan, async_mqtt::severity_level::sev) +``` + +=== Example +For a full example, see link:../example/custom_logger.cpp[custom_logger.cpp]. diff --git a/doc/modules/ROOT/pages/index.adoc b/doc/modules/ROOT/pages/index.adoc index 94851d059..5e672275b 100644 --- a/doc/modules/ROOT/pages/index.adoc +++ b/doc/modules/ROOT/pages/index.adoc @@ -1,6 +1,6 @@ = async_mqtt -Takatoshi Kondo +http://github.com/redboltz/async_mqtt Copyright © 2024 Takatoshi Kondo @@ -48,3 +48,4 @@ Distributed under the Boost Software License, Version 1.0. (See accompanying fil ** xref:tool/broker.adoc[] ** xref:tool/client_cli.adoc[] ** xref:tool/bench.adoc[] +* link:http://github.com/redboltz/async_mqtt[Repository] diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 78d7e8cd8..7bb31c1e1 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -4,6 +4,7 @@ list(APPEND exec_PROGRAMS ep_slcoro_mqtt_client.cpp cl_cpp17_mqtt_pub.cpp cl_cpp17_mqtt_sub.cpp + custom_logger.cpp ) if("cxx_std_20" IN_LIST CMAKE_CXX_COMPILE_FEATURES) diff --git a/example/cl_cpp17_mqtt_pub.cpp b/example/cl_cpp17_mqtt_pub.cpp index 0cc8a81e8..2a397a323 100644 --- a/example/cl_cpp17_mqtt_pub.cpp +++ b/example/cl_cpp17_mqtt_pub.cpp @@ -9,7 +9,7 @@ // Finally, it disconnects from the broker. // // Example: -// ./cl_cpp20coro_mqtt_pub mqtt.redboltz.net 1883 +// ./cl_cpp17_mqtt_pub mqtt.redboltz.net 1883 #include #include diff --git a/example/cl_cpp17_mqtt_sub.cpp b/example/cl_cpp17_mqtt_sub.cpp index 3c9ed6232..c15f41543 100644 --- a/example/cl_cpp17_mqtt_sub.cpp +++ b/example/cl_cpp17_mqtt_sub.cpp @@ -9,7 +9,7 @@ // Finally, it disconnects from the broker. // // Example: -// ./cl_cpp20coro_mqtt_pub mqtt.redboltz.net 1883 +// ./cl_cpp17_mqtt_sub mqtt.redboltz.net 1883 #include #include diff --git a/example/custom_logger.cpp b/example/custom_logger.cpp new file mode 100644 index 000000000..74e506008 --- /dev/null +++ b/example/custom_logger.cpp @@ -0,0 +1,197 @@ +// Copyright Takatoshi Kondo 2023 +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// This example connects to the specified MQTT broker. +// It then publishes to topic1, topic2, and topic3, and receives the publish results. +// Finally, it disconnects from the broker. +// +// Example: +// ./custom_logger_pub mqtt.redboltz.net 1883 + +#include +#include + +#include + +//////////////////////////////////////////////////////////////////////////////// +// Using custom logger BEGIN + +// Implement the following codes before any async_mqtt headers include. +// Exception: +// async_mqtt/util/log_severity.hpp is used for defining custom_log + +#include + +// Optional: +// Define custom logger instead of Boost.Log (default) +// undef ASYNC_MQTT_USE_LOG or remove from compiler option +// -DASYNC_MQTT_USE_LOG removes all Boost.Log related code. +// It can compile faster. +#undef ASYNC_MQTT_USE_LOG + +// Define simple custom logger +// This is example implementation. +// Just output channel, sevarity, and message body to std::clog +struct custom_log { + explicit constexpr custom_log(std::string_view chan, async_mqtt::severity_level sev) + { + // Setup filter + if (sev < async_mqtt::severity_level::info) { + print = false; + return; + } + // Output header + std::clog << "[" << sev << "]" << "(" << chan << ") "; + } + ~custom_log() { + // Output trailer + if (print) std::clog << std::endl; + } + + bool print = true; +}; + +template +inline constexpr custom_log const& operator<<( + custom_log const& o, + T const& t +) { + // Output message body with filter + if (o.print) std::clog << t; + return o; +} + +// Output additional value. Stringized name(tag) and tagged value. +#define ASYNC_MQTT_ADD_VALUE(name, val) "<" << #name ">{" << val << "} " + +// Set ASYNC_MQTT_LOG macro to custom_log. +#define ASYNC_MQTT_LOG(chan, sev) custom_log(chan, async_mqtt::severity_level::sev) + +// Using custom logger END +//////////////////////////////////////////////////////////////////////////////// + +// The following code is the same as cl_cpp17_mqtt_pub.cpp + +#include + + +namespace as = boost::asio; +namespace am = async_mqtt; + +using client_t = am::client; + +struct app { + app(as::any_io_executor exe, std::string_view host, std::string_view port) + : cli_{exe} + { + am::async_underlying_handshake( + cli_.next_layer(), + host, + port, + [this](auto&&... args) { + handle_underlying_handshake( + std::forward>(args)... + ); + } + ); + } + +private: + void handle_underlying_handshake( + am::error_code ec + ) { + std::cout << "underlying_handshake:" << ec.message() << std::endl; + if (ec) return; + cli_.async_start( + true, // clean_start + std::uint16_t(0), // keep_alive + "", // Client Identifier, empty means generated by the broker + std::nullopt, // will + "UserName1", + "Password1", + [this](auto&&... args) { + handle_start_response(std::forward(args)...); + } + ); + } + + void handle_start_response( + am::error_code ec, + std::optional connack_opt + ) { + std::cout << "start:" << ec.message() << std::endl; + if (ec) return; + if (connack_opt) { + std::cout << *connack_opt << std::endl; + cli_.async_publish( + "topic1", + "payload1", + am::qos::at_most_once, + [this](auto&&... args) { + handle_publish_response( + std::forward>(args)... + ); + } + ); + cli_.async_publish( + *cli_.acquire_unique_packet_id(), // sync version only works thread safe context + "topic2", + "payload2", + am::qos::at_least_once, + [this](auto&&... args) { + handle_publish_response( + std::forward>(args)... + ); + } + ); + cli_.async_publish( + *cli_.acquire_unique_packet_id(), // sync version only works thread safe context + "topic3", + "payload3", + am::qos::exactly_once, + [this](auto&&... args) { + handle_publish_response( + std::forward>(args)... + ); + } + ); + } + } + + void handle_publish_response( + am::error_code ec, + client_t::pubres_type pubres + ) { + std::cout << "publish:" << ec.message() << std::endl; + if (ec) return; + if (pubres.puback_opt) { + std::cout << *pubres.puback_opt << std::endl; + } + if (pubres.pubrec_opt) { + std::cout << *pubres.pubrec_opt << std::endl; + } + if (pubres.pubcomp_opt) { + std::cout << *pubres.pubcomp_opt << std::endl; + cli_.async_disconnect(as::detached); + } + } + + client_t cli_; +}; + +int main(int argc, char* argv[]) { + am::setup_log( + am::severity_level::warning, + true // log colored + ); + if (argc != 3) { + std::cout << "Usage: " << argv[0] << " host port" << std::endl; + return -1; + } + as::io_context ioc; + app a{ioc.get_executor(), argv[1], argv[2]}; + ioc.run(); +} diff --git a/include/async_mqtt/util/log.hpp b/include/async_mqtt/util/log.hpp index 5c00c0265..852dc6010 100644 --- a/include/async_mqtt/util/log.hpp +++ b/include/async_mqtt/util/log.hpp @@ -28,6 +28,8 @@ #include #include +#include + #endif // defined(ASYNC_MQTT_USE_LOG) /** @@ -40,38 +42,6 @@ struct channel : std::string { using std::string::string; }; -/** - * @ingroup log - * log severity level - * warning is recommended for actual operation because there is no output except something important. - * - * #### Requirements - * @li Header: async_mqtt/util/log.hpp - * @li Convenience header: async_mqtt/all.hpp - * - */ -enum class severity_level { - trace, ///< trace level for detaied behavior and reporting issue - debug, ///< debug level not used in async_mqtt, so far - info, ///< info level api call is output - warning, ///< warning level such as timeout - error, ///< error level error report such as connection is failed - fatal ///< fatal level it is logic error of async_mqtt -}; - -inline std::ostream& operator<<(std::ostream& o, severity_level sev) { - constexpr char const* const str[] { - "trace", - "debug", - "info", - "warning", - "error", - "fatal" - }; - o << str[static_cast(sev)]; - return o; -} - namespace detail { struct null_log { @@ -134,8 +104,8 @@ BOOST_LOG_ATTRIBUTE_KEYWORD(address, "MqttAddress", void const*) #define ASYNC_MQTT_LOG(chan, sev) \ BOOST_PP_IF( \ BOOST_PP_GREATER_EQUAL(ASYNC_MQTT_GET_LOG_SEV_NUM(sev), ASYNC_MQTT_GET_LOG_SEV_NUM(ASYNC_MQTT_LOG_SEV)), \ - ASYNC_MQTT_LOG_FP(chan, async_mqtt::severity_level::sev), \ - async_mqtt::detail::null_log(chan, async_mqtt::severity_level::sev) \ + ASYNC_MQTT_LOG_FP(chan, sev), \ + async_mqtt::detail::null_log(chan, async_mqtt::severity_level::sev) \ ) #endif // !defined(ASYNC_MQTT_LOG) diff --git a/include/async_mqtt/util/log_severity.hpp b/include/async_mqtt/util/log_severity.hpp new file mode 100644 index 000000000..e4ed5f75b --- /dev/null +++ b/include/async_mqtt/util/log_severity.hpp @@ -0,0 +1,48 @@ +// Copyright Takatoshi Kondo 2020 +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#if !defined(ASYNC_MQTT_UTIL_LOG_SEVERITY_HPP) +#define ASYNC_MQTT_UTIL_LOG_SEVERITY_HPP + +#include + +namespace async_mqtt { + +/** + * @ingroup log + * log severity level + * warning is recommended for actual operation because there is no output except something important. + * + * #### Requirements + * @li Header: async_mqtt/util/log.hpp + * @li Convenience header: async_mqtt/all.hpp + * + */ +enum class severity_level { + trace, ///< trace level for detaied behavior and reporting issue + debug, ///< debug level not used in async_mqtt, so far + info, ///< info level api call is output + warning, ///< warning level such as timeout + error, ///< error level error report such as connection is failed + fatal ///< fatal level it is logic error of async_mqtt +}; + +inline std::ostream& operator<<(std::ostream& o, severity_level sev) { + constexpr char const* const str[] { + "trace", + "debug", + "info", + "warning", + "error", + "fatal" + }; + o << str[static_cast(sev)]; + return o; +} + +} // namespace async_mqtt + +#endif // ASYNC_MQTT_UTIL_LOG_SEVERITY_HPP