mirror of
https://github.com/boostorg/redis.git
synced 2026-01-19 04:42:09 +00:00
Updates the Logger interface to allow extensibility and type erasure (#273)
Removes all the logger::on_xxx functions Removes the Logger template parameter to async_run Adds a logger constructor that allows passing a std::function to customize logging behavior Adds constructors to connection and basic_connection taking a logger Deprecates config::logger_prefix Deprecates the async_run overload taking a logger parameter Deprecates the basic_connection::async_run overload not taking any config object Deprecates the basic_connection::next_layer_type typedef Makes the default log level logger::info Makes the logging thread-safe Cleans up deprecated functionality from examples Adds docs on logging Adds an example on how to integrate spdlog into Boost.Redis logging close #213
This commit is contained in:
committed by
GitHub
parent
7304d99bf6
commit
f04d97ffa5
39
README.md
39
README.md
@@ -407,6 +407,44 @@ imported in the global namespace by the user. In the
|
||||
[Examples](#examples) section the reader can find examples showing how
|
||||
to serialize using json and [protobuf](https://protobuf.dev/).
|
||||
|
||||
<a name="logging"></a>
|
||||
|
||||
### Logging
|
||||
|
||||
`connection::async_run` is a complex algorithm, with features like built-in reconnection.
|
||||
This can make configuration problems, like a misconfigured hostname, difficult to debug -
|
||||
Boost.Redis will keep retrying to connect to the same hostname over and over.
|
||||
For this reason, Boost.Redis incorporates a lightweight logging solution, and
|
||||
**will log some status messages to stderr by default**.
|
||||
|
||||
Logging can be customized by passing a `logger` object to the `connection`'s constructor.
|
||||
|
||||
For example, logging can be disabled by writing:
|
||||
|
||||
```cpp
|
||||
asio::io_context ioc;
|
||||
redis::connection conn {ioc, redis::logger{redis::logger::level::disabled}};
|
||||
```
|
||||
|
||||
Every message logged by the library is attached a
|
||||
[syslog-like severity](https://en.wikipedia.org/wiki/Syslog#Severity_level)
|
||||
tag (a `logger::level`).
|
||||
You can filter messages by severity by creating a `logger` with a specific level:
|
||||
|
||||
```cpp
|
||||
asio::io_context ioc;
|
||||
|
||||
// Logs to stderr messages with severity >= level::error.
|
||||
// This will hide all informational output.
|
||||
redis::connection conn {ioc, redis::logger{redis::logger::level::error}};
|
||||
```
|
||||
|
||||
`logger`'s constructor accepts a `std::function<void(logger::level, std::string_view)>`
|
||||
as second argument. If supplied, Boost.Redis will call this function when logging
|
||||
instead of printing to stderr. This can be used to integrate third-party logging
|
||||
libraries. See our [spdlog integration example](example/cpp17_spdlog.cpp) for sample code.
|
||||
|
||||
|
||||
<a name="examples"></a>
|
||||
## Examples
|
||||
|
||||
@@ -424,6 +462,7 @@ The examples below show how to use the features discussed so far
|
||||
* cpp20_chat_room.cpp: A command line chat built on Redis pubsub.
|
||||
* cpp17_intro.cpp: Uses callbacks and requires C++17.
|
||||
* cpp17_intro_sync.cpp: Runs `async_run` in a separate thread and performs synchronous calls to `async_exec`.
|
||||
* cpp17_spdlog.cpp: Shows how to use third-party logging libraries like `spdlog` with Boost.Redis.
|
||||
|
||||
The main function used in some async examples has been factored out in
|
||||
the main.cpp file.
|
||||
|
||||
@@ -51,3 +51,12 @@ endif()
|
||||
if (NOT MSVC)
|
||||
make_example(cpp20_chat_room 20)
|
||||
endif()
|
||||
|
||||
# We build and test the spdlog integration example only if the library is found
|
||||
find_package(spdlog)
|
||||
if (spdlog_FOUND)
|
||||
make_testable_example(cpp17_spdlog 17)
|
||||
target_link_libraries(cpp17_spdlog PRIVATE spdlog::spdlog)
|
||||
else()
|
||||
message(STATUS "Skipping the spdlog example because the spdlog package couldn't be found")
|
||||
endif()
|
||||
|
||||
@@ -34,7 +34,7 @@ auto main(int argc, char* argv[]) -> int
|
||||
asio::io_context ioc;
|
||||
connection conn{ioc};
|
||||
|
||||
conn.async_run(cfg, {}, asio::detached);
|
||||
conn.async_run(cfg, asio::detached);
|
||||
|
||||
conn.async_exec(req, resp, [&](auto ec, auto) {
|
||||
if (!ec)
|
||||
|
||||
102
example/cpp17_spdlog.cpp
Normal file
102
example/cpp17_spdlog.cpp
Normal file
@@ -0,0 +1,102 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <iostream>
|
||||
#include <spdlog/spdlog.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace asio = boost::asio;
|
||||
namespace redis = boost::redis;
|
||||
|
||||
// Maps a Boost.Redis log level to a spdlog log level
|
||||
static spdlog::level::level_enum to_spdlog_level(redis::logger::level lvl)
|
||||
{
|
||||
switch (lvl) {
|
||||
// spdlog doesn't include the emerg and alert syslog levels,
|
||||
// so we convert them to the highest supported level.
|
||||
// Similarly, notice is similar to info
|
||||
case redis::logger::level::emerg:
|
||||
case redis::logger::level::alert:
|
||||
case redis::logger::level::crit: return spdlog::level::critical;
|
||||
case redis::logger::level::err: return spdlog::level::err;
|
||||
case redis::logger::level::warning: return spdlog::level::warn;
|
||||
case redis::logger::level::notice:
|
||||
case redis::logger::level::info: return spdlog::level::info;
|
||||
case redis::logger::level::debug:
|
||||
default: return spdlog::level::debug;
|
||||
}
|
||||
}
|
||||
|
||||
// This function glues Boost.Redis logging and spdlog.
|
||||
// It should have the signature shown here. It will be invoked
|
||||
// by Boost.Redis whenever a message is to be logged.
|
||||
static void do_log(redis::logger::level level, std::string_view msg)
|
||||
{
|
||||
spdlog::log(to_spdlog_level(level), "(Boost.Redis) {}", msg);
|
||||
}
|
||||
|
||||
auto main(int argc, char* argv[]) -> int
|
||||
{
|
||||
if (argc != 3) {
|
||||
std::cerr << "Usage: " << argv[0] << " <server-host> <server-port>\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
try {
|
||||
// Create an execution context, required to create any I/O objects
|
||||
asio::io_context ioc;
|
||||
|
||||
// Create a connection to connect to Redis, and pass it a custom logger.
|
||||
// Boost.Redis will call do_log whenever it needs to log a message.
|
||||
// Note that the function will only be called for messages with level >= info
|
||||
// (i.e. filtering is done by Boost.Redis).
|
||||
redis::connection conn{
|
||||
ioc,
|
||||
redis::logger{redis::logger::level::info, do_log}
|
||||
};
|
||||
|
||||
// Configuration to connect to the server
|
||||
redis::config cfg;
|
||||
cfg.addr.host = argv[1];
|
||||
cfg.addr.port = argv[2];
|
||||
|
||||
// Run the connection with the specified configuration.
|
||||
// This will establish the connection and keep it healthy
|
||||
conn.async_run(cfg, asio::detached);
|
||||
|
||||
// Execute a request
|
||||
redis::request req;
|
||||
req.push("PING", "Hello world");
|
||||
|
||||
redis::response<std::string> resp;
|
||||
|
||||
conn.async_exec(req, resp, [&](boost::system::error_code ec, std::size_t /* bytes_read*/) {
|
||||
if (ec) {
|
||||
spdlog::error("Request failed: {}", ec.what());
|
||||
exit(1);
|
||||
} else {
|
||||
spdlog::info("PING: {}", std::get<0>(resp).value());
|
||||
}
|
||||
conn.cancel();
|
||||
});
|
||||
|
||||
// Actually run our example. Nothing will happen until we call run()
|
||||
ioc.run();
|
||||
|
||||
} catch (std::exception const& e) {
|
||||
spdlog::error("Error: {}", e.what());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ auto co_main(config cfg) -> awaitable<void>
|
||||
|
||||
co_spawn(ex, receiver(conn), detached);
|
||||
co_spawn(ex, publisher(stream, conn), detached);
|
||||
conn->async_run(cfg, {}, consign(detached, conn));
|
||||
conn->async_run(cfg, consign(detached, conn));
|
||||
|
||||
signal_set sig_set{ex, SIGINT, SIGTERM};
|
||||
co_await sig_set.async_wait();
|
||||
|
||||
@@ -133,7 +133,7 @@ auto transaction(std::shared_ptr<connection> conn) -> awaitable<void>
|
||||
awaitable<void> co_main(config cfg)
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
conn->async_run(cfg, {}, consign(detached, conn));
|
||||
conn->async_run(cfg, consign(detached, conn));
|
||||
|
||||
co_await store(conn);
|
||||
co_await transaction(conn);
|
||||
|
||||
@@ -60,7 +60,7 @@ auto co_main(config cfg) -> asio::awaitable<void>
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
asio::co_spawn(ex, listener(conn), asio::detached);
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
signal_set sig_set(ex, SIGINT, SIGTERM);
|
||||
co_await sig_set.async_wait();
|
||||
|
||||
@@ -24,7 +24,7 @@ using boost::redis::connection;
|
||||
auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
// A request containing only a ping command.
|
||||
request req;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <boost/asio/consign.hpp>
|
||||
#include <boost/asio/detached.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/asio/use_awaitable.hpp>
|
||||
|
||||
#include <iostream>
|
||||
@@ -35,17 +36,18 @@ auto co_main(config cfg) -> asio::awaitable<void>
|
||||
cfg.addr.host = "db.occase.de";
|
||||
cfg.addr.port = "6380";
|
||||
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
asio::ssl::context ctx{asio::ssl::context::tlsv12_client};
|
||||
ctx.set_verify_mode(asio::ssl::verify_peer);
|
||||
ctx.set_verify_callback(verify_certificate);
|
||||
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor, std::move(ctx));
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
|
||||
response<std::string> resp;
|
||||
|
||||
conn->next_layer().set_verify_mode(asio::ssl::verify_peer);
|
||||
conn->next_layer().set_verify_callback(verify_certificate);
|
||||
|
||||
co_await conn->async_exec(req, resp);
|
||||
conn->cancel();
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ auto co_main(config cfg) -> asio::awaitable<void>
|
||||
{
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
// user object that will be stored in Redis in json format.
|
||||
user const u{"Joao", "58", "Brazil"};
|
||||
|
||||
@@ -64,7 +64,7 @@ asio::awaitable<void> co_main(config cfg)
|
||||
{
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
person p;
|
||||
p.set_name("Louis");
|
||||
|
||||
@@ -44,10 +44,9 @@ auto resolve_master_address(std::vector<address> const& addresses) -> asio::awai
|
||||
// TODO: async_run and async_exec should be lauched in
|
||||
// parallel here so we can wait for async_run completion
|
||||
// before eventually calling it again.
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
co_await conn->async_exec(req, resp, redir(ec));
|
||||
conn->cancel();
|
||||
conn->reset_stream();
|
||||
if (!ec && std::get<0>(resp))
|
||||
co_return address{
|
||||
std::get<0>(resp).value().value().at(0),
|
||||
|
||||
@@ -88,7 +88,7 @@ auto co_main(config cfg) -> net::awaitable<void>
|
||||
|
||||
// Disable health checks.
|
||||
cfg.health_check_interval = std::chrono::seconds::zero();
|
||||
conn->async_run(cfg, {}, net::consign(net::detached, conn));
|
||||
conn->async_run(cfg, net::consign(net::detached, conn));
|
||||
|
||||
signal_set sig_set(ex, SIGINT, SIGTERM);
|
||||
co_await sig_set.async_wait();
|
||||
|
||||
@@ -87,7 +87,7 @@ auto co_main(config cfg) -> asio::awaitable<void>
|
||||
auto ex = co_await asio::this_coro::executor;
|
||||
auto conn = std::make_shared<connection>(ex);
|
||||
asio::co_spawn(ex, receiver(conn), asio::detached);
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
signal_set sig_set(ex, SIGINT, SIGTERM);
|
||||
co_await sig_set.async_wait();
|
||||
|
||||
@@ -34,7 +34,7 @@ auto co_main(config cfg) -> asio::awaitable<void>
|
||||
cfg.unix_socket = "/tmp/redis-socks/redis.sock";
|
||||
|
||||
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
|
||||
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
|
||||
conn->async_run(cfg, asio::consign(asio::detached, conn));
|
||||
|
||||
request req;
|
||||
req.push("PING");
|
||||
|
||||
@@ -33,7 +33,7 @@ public:
|
||||
// Starts a thread that will can io_context::run on which the
|
||||
// connection will run.
|
||||
thread_ = std::thread{[this, cfg]() {
|
||||
conn_->async_run(cfg, {}, asio::detached);
|
||||
conn_->async_run(cfg, asio::detached);
|
||||
ioc_.run();
|
||||
}};
|
||||
}
|
||||
|
||||
@@ -60,7 +60,13 @@ struct config {
|
||||
/// Message used by the health-checker in `boost::redis::connection::async_run`.
|
||||
std::string health_check_id = "Boost.Redis";
|
||||
|
||||
/// Logger prefix, see `boost::redis::logger`.
|
||||
/**
|
||||
* @brief (Deprecated) Sets the logger prefix, a string printed before log messages.
|
||||
*
|
||||
* Setting a prefix in this struct is deprecated. If you need to change how log messages
|
||||
* look like, please construct a logger object passing a formatting function, and use that
|
||||
* logger in connection's constructor. This member will be removed in subsequent releases.
|
||||
*/
|
||||
std::string log_prefix = "(Boost.Redis) ";
|
||||
|
||||
/// Time the resolve operation is allowed to last.
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <boost/redis/adapter/adapt.hpp>
|
||||
#include <boost/redis/adapter/any_adapter.hpp>
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/redis/detail/connection_logger.hpp>
|
||||
#include <boost/redis/detail/exec_fsm.hpp>
|
||||
#include <boost/redis/detail/health_checker.hpp>
|
||||
#include <boost/redis/detail/helper.hpp>
|
||||
@@ -49,6 +50,7 @@
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace boost::redis {
|
||||
@@ -150,10 +152,9 @@ struct exec_op {
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Logger>
|
||||
template <class Conn>
|
||||
struct writer_op {
|
||||
Conn* conn_;
|
||||
Logger logger_;
|
||||
asio::coroutine coro{};
|
||||
|
||||
template <class Self>
|
||||
@@ -170,10 +171,10 @@ struct writer_op {
|
||||
asio::buffer(conn_->mpx_.get_write_buffer()),
|
||||
std::move(self));
|
||||
|
||||
logger_.on_write(ec, conn_->mpx_.get_write_buffer());
|
||||
conn_->logger_.on_write(ec, conn_->mpx_.get_write_buffer().size());
|
||||
|
||||
if (ec) {
|
||||
logger_.trace("writer_op (1)", ec);
|
||||
conn_->logger_.trace("writer_op (1)", ec);
|
||||
conn_->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
return;
|
||||
@@ -185,7 +186,7 @@ struct writer_op {
|
||||
// successful write might had already been queued, so we
|
||||
// have to check here before proceeding.
|
||||
if (!conn_->is_open()) {
|
||||
logger_.trace("writer_op (2): connection is closed.");
|
||||
conn_->logger_.trace("writer_op (2): connection is closed.");
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
@@ -194,7 +195,7 @@ struct writer_op {
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->writer_timer_.async_wait(std::move(self));
|
||||
if (!conn_->is_open()) {
|
||||
logger_.trace("writer_op (3): connection is closed.");
|
||||
conn_->logger_.trace("writer_op (3): connection is closed.");
|
||||
// Notice this is not an error of the op, stoping was
|
||||
// requested from the outside, so we complete with
|
||||
// success.
|
||||
@@ -205,7 +206,7 @@ struct writer_op {
|
||||
}
|
||||
};
|
||||
|
||||
template <class Conn, class Logger>
|
||||
template <class Conn>
|
||||
struct reader_op {
|
||||
using dyn_buffer_type = asio::dynamic_string_buffer<
|
||||
char,
|
||||
@@ -216,7 +217,6 @@ struct reader_op {
|
||||
static constexpr std::size_t buffer_growth_hint = 4096;
|
||||
|
||||
Conn* conn_;
|
||||
Logger logger_;
|
||||
std::pair<tribool, std::size_t> res_{std::make_pair(std::nullopt, 0)};
|
||||
asio::coroutine coro{};
|
||||
|
||||
@@ -233,11 +233,11 @@ struct reader_op {
|
||||
conn_->mpx_.get_parser().get_suggested_buffer_growth(buffer_growth_hint),
|
||||
std::move(self));
|
||||
|
||||
logger_.on_read(ec, n);
|
||||
conn_->logger_.on_read(ec, n);
|
||||
|
||||
// The connection is not viable after an error.
|
||||
if (ec) {
|
||||
logger_.trace("reader_op (1)", ec);
|
||||
conn_->logger_.trace("reader_op (1)", ec);
|
||||
conn_->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
return;
|
||||
@@ -246,7 +246,7 @@ struct reader_op {
|
||||
// The connection might have been canceled while this op was
|
||||
// suspended or after queueing so we have to check.
|
||||
if (!conn_->is_open()) {
|
||||
logger_.trace("reader_op (2): connection is closed.");
|
||||
conn_->logger_.trace("reader_op (2): connection is closed.");
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
@@ -255,7 +255,7 @@ struct reader_op {
|
||||
res_ = conn_->mpx_.consume_next(ec);
|
||||
|
||||
if (ec) {
|
||||
logger_.trace("reader_op (3)", ec);
|
||||
conn_->logger_.trace("reader_op (3)", ec);
|
||||
conn_->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
return;
|
||||
@@ -273,14 +273,14 @@ struct reader_op {
|
||||
}
|
||||
|
||||
if (ec) {
|
||||
logger_.trace("reader_op (4)", ec);
|
||||
conn_->logger_.trace("reader_op (4)", ec);
|
||||
conn_->cancel(operation::run);
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!conn_->is_open()) {
|
||||
logger_.trace("reader_op (5): connection is closed.");
|
||||
conn_->logger_.trace("reader_op (5): connection is closed.");
|
||||
self.complete(asio::error::operation_aborted);
|
||||
return;
|
||||
}
|
||||
@@ -302,20 +302,18 @@ inline system::error_code check_config(const config& cfg)
|
||||
return system::error_code{};
|
||||
}
|
||||
|
||||
template <class Conn, class Logger>
|
||||
template <class Conn>
|
||||
class run_op {
|
||||
private:
|
||||
Conn* conn_ = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro_{};
|
||||
system::error_code stored_ec_;
|
||||
|
||||
using order_t = std::array<std::size_t, 5>;
|
||||
|
||||
public:
|
||||
run_op(Conn* conn, Logger l)
|
||||
run_op(Conn* conn) noexcept
|
||||
: conn_{conn}
|
||||
, logger_{l}
|
||||
{ }
|
||||
|
||||
template <class Self>
|
||||
@@ -339,7 +337,7 @@ public:
|
||||
// Check config
|
||||
ec0 = check_config(conn_->cfg_);
|
||||
if (ec0) {
|
||||
logger_.log_error("Invalid configuration", ec0);
|
||||
conn_->logger_.log(logger::level::err, "Invalid configuration", ec0);
|
||||
stored_ec_ = ec0;
|
||||
BOOST_ASIO_CORO_YIELD asio::async_immediate(self.get_io_executor(), std::move(self));
|
||||
self.complete(stored_ec_);
|
||||
@@ -349,7 +347,7 @@ public:
|
||||
for (;;) {
|
||||
// Try to connect
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->stream_.async_connect(&conn_->cfg_, logger_, std::move(self));
|
||||
conn_->stream_.async_connect(&conn_->cfg_, &conn_->logger_, std::move(self));
|
||||
|
||||
// If we failed, try again
|
||||
if (ec0) {
|
||||
@@ -365,19 +363,19 @@ public:
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::experimental::make_parallel_group(
|
||||
[this](auto token) {
|
||||
return conn_->handshaker_.async_hello(*conn_, logger_, token);
|
||||
return conn_->handshaker_.async_hello(*conn_, token);
|
||||
},
|
||||
[this](auto token) {
|
||||
return conn_->health_checker_.async_ping(*conn_, logger_, token);
|
||||
return conn_->health_checker_.async_ping(*conn_, token);
|
||||
},
|
||||
[this](auto token) {
|
||||
return conn_->health_checker_.async_check_timeout(*conn_, logger_, token);
|
||||
return conn_->health_checker_.async_check_timeout(*conn_, token);
|
||||
},
|
||||
[this](auto token) {
|
||||
return conn_->reader(logger_, token);
|
||||
return conn_->reader(token);
|
||||
},
|
||||
[this](auto token) {
|
||||
return conn_->writer(logger_, token);
|
||||
return conn_->writer(token);
|
||||
})
|
||||
.async_wait(asio::experimental::wait_for_one_error(), std::move(self));
|
||||
|
||||
@@ -420,6 +418,8 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
logger make_stderr_logger(logger::level lvl, std::string prefix);
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/** @brief A SSL connection to the Redis server.
|
||||
@@ -438,7 +438,8 @@ public:
|
||||
using this_type = basic_connection<Executor>;
|
||||
|
||||
/// Type of the next layer
|
||||
using next_layer_type = asio::ssl::stream<asio::basic_stream_socket<asio::ip::tcp, Executor>>;
|
||||
BOOST_DEPRECATED("This typedef is deprecated, and will be removed with next_layer().")
|
||||
typedef asio::ssl::stream<asio::basic_stream_socket<asio::ip::tcp, Executor>> next_layer_type;
|
||||
|
||||
/// Executor type
|
||||
using executor_type = Executor;
|
||||
@@ -457,25 +458,55 @@ public:
|
||||
*
|
||||
* @param ex Executor on which connection operation will run.
|
||||
* @param ctx SSL context.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
* and higher are logged to `stderr`.
|
||||
*/
|
||||
explicit basic_connection(
|
||||
executor_type ex,
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client})
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
logger lgr = {})
|
||||
: stream_{ex, std::move(ctx)}
|
||||
, writer_timer_{ex}
|
||||
, reconnect_timer_{ex}
|
||||
, receive_channel_{ex, 256}
|
||||
, health_checker_{ex}
|
||||
, logger_{std::move(lgr)}
|
||||
{
|
||||
set_receive_response(ignore);
|
||||
writer_timer_.expires_at((std::chrono::steady_clock::time_point::max)());
|
||||
}
|
||||
|
||||
/** @brief Constructor
|
||||
*
|
||||
* @param ex Executor on which connection operation will run.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
* and higher are logged to `stderr`.
|
||||
*
|
||||
* An SSL context with default settings will be created.
|
||||
*/
|
||||
basic_connection(executor_type ex, logger lgr)
|
||||
: basic_connection(
|
||||
std::move(ex),
|
||||
asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
std::move(lgr))
|
||||
{ }
|
||||
|
||||
/// Constructs from a context.
|
||||
explicit basic_connection(
|
||||
asio::io_context& ioc,
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client})
|
||||
: basic_connection(ioc.get_executor(), std::move(ctx))
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
logger lgr = {})
|
||||
: basic_connection(ioc.get_executor(), std::move(ctx), std::move(lgr))
|
||||
{ }
|
||||
|
||||
/// Constructs from a context.
|
||||
basic_connection(asio::io_context& ctx, logger lgr)
|
||||
: basic_connection(
|
||||
ctx.get_executor(),
|
||||
asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
std::move(lgr))
|
||||
{ }
|
||||
|
||||
/** @brief Starts underlying connection operations.
|
||||
@@ -503,7 +534,6 @@ public:
|
||||
* `boost::redis::connection::cancel(operation::reconnection)`.
|
||||
*
|
||||
* @param cfg Configuration paramters.
|
||||
* @param l Logger object. The interface expected is specified in the class `boost::redis::logger`.
|
||||
* @param token Completion token.
|
||||
*
|
||||
* The completion token must have the following signature
|
||||
@@ -515,22 +545,62 @@ public:
|
||||
* For example on how to call this function refer to
|
||||
* cpp20_intro.cpp or any other example.
|
||||
*/
|
||||
template <
|
||||
class Logger = logger,
|
||||
class CompletionToken = asio::default_completion_token_t<executor_type>>
|
||||
auto async_run(config const& cfg = {}, Logger l = Logger{}, CompletionToken&& token = {})
|
||||
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
|
||||
auto async_run(config const& cfg, CompletionToken&& token = {})
|
||||
{
|
||||
cfg_ = cfg;
|
||||
health_checker_.set_config(cfg);
|
||||
handshaker_.set_config(cfg);
|
||||
l.set_prefix(cfg.log_prefix);
|
||||
|
||||
return asio::async_compose<CompletionToken, void(system::error_code)>(
|
||||
detail::run_op<this_type, Logger>{this, l},
|
||||
detail::run_op<this_type>{this},
|
||||
token,
|
||||
writer_timer_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc async_run
|
||||
*
|
||||
* This function accepts an extra logger parameter. The passed `logger::lvl`
|
||||
* will be used, but `logger::fn` will be ignored. Instead, a function
|
||||
* that logs to `stderr` using `config::prefix` will be used.
|
||||
* This keeps backwards compatibility with previous versions.
|
||||
* Any logger configured in the constructor will be overriden.
|
||||
*
|
||||
* @par Deprecated
|
||||
* The logger should be passed to the connection's constructor instead of using this
|
||||
* function. Use the overload without a logger parameter, instead. This function is
|
||||
* deprecated and will be removed in subsequent releases.
|
||||
*/
|
||||
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
|
||||
BOOST_DEPRECATED(
|
||||
"The async_run overload taking a logger argument is deprecated. "
|
||||
"Please pass the logger to the connection's constructor, instead, "
|
||||
"and use the other async_run overloads.")
|
||||
auto async_run(config const& cfg, logger l, CompletionToken&& token = {})
|
||||
{
|
||||
set_stderr_logger(l.lvl, cfg);
|
||||
return async_run(cfg, std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
/**
|
||||
* @copydoc async_run
|
||||
*
|
||||
* Uses a default-constructed config object to run the connection.
|
||||
*
|
||||
* @par Deprecated
|
||||
* This function is deprecated and will be removed in subsequent releases.
|
||||
* Use the overload taking an explicit config object, instead.
|
||||
*/
|
||||
template <class CompletionToken = asio::default_completion_token_t<executor_type>>
|
||||
BOOST_DEPRECATED(
|
||||
"Running without an explicit config object is deprecated."
|
||||
"Please create a config object and pass it to async_run.")
|
||||
auto async_run(CompletionToken&& token = {})
|
||||
{
|
||||
return async_run(config{}, std::forward<CompletionToken>(token));
|
||||
}
|
||||
|
||||
/** @brief Receives server side pushes asynchronously.
|
||||
*
|
||||
* When pushes arrive and there is no `async_receive` operation in
|
||||
@@ -755,25 +825,35 @@ private:
|
||||
mpx_.cancel_on_conn_lost();
|
||||
}
|
||||
|
||||
template <class, class> friend struct detail::reader_op;
|
||||
template <class, class> friend struct detail::writer_op;
|
||||
template <class> friend struct detail::exec_op;
|
||||
template <class, class> friend class detail::run_op;
|
||||
// Used by both this class and connection
|
||||
void set_stderr_logger(logger::level lvl, const config& cfg)
|
||||
{
|
||||
logger_.reset(detail::make_stderr_logger(lvl, cfg.log_prefix));
|
||||
}
|
||||
|
||||
template <class CompletionToken, class Logger>
|
||||
auto reader(Logger l, CompletionToken&& token)
|
||||
template <class> friend struct detail::reader_op;
|
||||
template <class> friend struct detail::writer_op;
|
||||
template <class> friend struct detail::exec_op;
|
||||
template <class, class> friend struct detail::hello_op;
|
||||
template <class, class> friend class detail::ping_op;
|
||||
template <class> friend class detail::run_op;
|
||||
template <class, class> friend class detail::check_timeout_op;
|
||||
friend class connection;
|
||||
|
||||
template <class CompletionToken>
|
||||
auto reader(CompletionToken&& token)
|
||||
{
|
||||
return asio::async_compose<CompletionToken, void(system::error_code)>(
|
||||
detail::reader_op<this_type, Logger>{this, l},
|
||||
detail::reader_op<this_type>{this},
|
||||
std::forward<CompletionToken>(token),
|
||||
writer_timer_);
|
||||
}
|
||||
|
||||
template <class CompletionToken, class Logger>
|
||||
auto writer(Logger l, CompletionToken&& token)
|
||||
template <class CompletionToken>
|
||||
auto writer(CompletionToken&& token)
|
||||
{
|
||||
return asio::async_compose<CompletionToken, void(system::error_code)>(
|
||||
detail::writer_op<this_type, Logger>{this, l},
|
||||
detail::writer_op<this_type>{this},
|
||||
std::forward<CompletionToken>(token),
|
||||
writer_timer_);
|
||||
}
|
||||
@@ -793,6 +873,7 @@ private:
|
||||
|
||||
config cfg_;
|
||||
detail::multiplexer mpx_;
|
||||
detail::connection_logger logger_;
|
||||
};
|
||||
|
||||
/** \brief A basic_connection that type erases the executor.
|
||||
@@ -809,31 +890,94 @@ public:
|
||||
/// Executor type.
|
||||
using executor_type = asio::any_io_executor;
|
||||
|
||||
/// Contructs from an executor.
|
||||
/** @brief Constructor
|
||||
*
|
||||
* @param ex Executor on which connection operation will run.
|
||||
* @param ctx SSL context.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
* and higher are logged to `stderr`.
|
||||
*/
|
||||
explicit connection(
|
||||
executor_type ex,
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client});
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
logger lgr = {});
|
||||
|
||||
/// Contructs from a context.
|
||||
/** @brief Constructor
|
||||
*
|
||||
* @param ex Executor on which connection operation will run.
|
||||
* @param lgr Logger configuration. It can be used to filter messages by level
|
||||
* and customize logging. By default, `logger::level::info` messages
|
||||
* and higher are logged to `stderr`.
|
||||
*
|
||||
* An SSL context with default settings will be created.
|
||||
*/
|
||||
connection(executor_type ex, logger lgr)
|
||||
: connection(
|
||||
std::move(ex),
|
||||
asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
std::move(lgr))
|
||||
{ }
|
||||
|
||||
/// Constructs from a context.
|
||||
explicit connection(
|
||||
asio::io_context& ioc,
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client});
|
||||
asio::ssl::context ctx = asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
logger lgr = {})
|
||||
: connection(ioc.get_executor(), std::move(ctx), std::move(lgr))
|
||||
{ }
|
||||
|
||||
/// Constructs from a context.
|
||||
connection(asio::io_context& ioc, logger lgr)
|
||||
: connection(
|
||||
ioc.get_executor(),
|
||||
asio::ssl::context{asio::ssl::context::tlsv12_client},
|
||||
std::move(lgr))
|
||||
{ }
|
||||
|
||||
/// Returns the underlying executor.
|
||||
executor_type get_executor() noexcept { return impl_.get_executor(); }
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_run`.
|
||||
/**
|
||||
* @brief Calls `boost::redis::basic_connection::async_run`.
|
||||
*
|
||||
* This function accepts an extra logger parameter. The passed logger
|
||||
* will be used by the connection, overwriting any logger passed to the connection's
|
||||
* constructor.
|
||||
*
|
||||
* @par Deprecated
|
||||
* The logger should be passed to the connection's constructor instead of using this
|
||||
* function. Use the overload without a logger parameter, instead. This function is
|
||||
* deprecated and will be removed in subsequent releases.
|
||||
*/
|
||||
template <class CompletionToken = asio::deferred_t>
|
||||
BOOST_DEPRECATED(
|
||||
"The async_run overload taking a logger argument is deprecated. "
|
||||
"Please pass the logger to the connection's constructor, instead, "
|
||||
"and use the other async_run overloads.")
|
||||
auto async_run(config const& cfg, logger l, CompletionToken&& token = {})
|
||||
{
|
||||
return asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
|
||||
[](auto handler, connection* self, config const* cfg, logger l) {
|
||||
self->async_run_impl(*cfg, l, std::move(handler));
|
||||
self->async_run_impl(*cfg, std::move(l), std::move(handler));
|
||||
},
|
||||
token,
|
||||
this,
|
||||
&cfg,
|
||||
l);
|
||||
std::move(l));
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_run`.
|
||||
template <class CompletionToken = asio::deferred_t>
|
||||
auto async_run(config const& cfg, CompletionToken&& token = {})
|
||||
{
|
||||
return asio::async_initiate<CompletionToken, void(boost::system::error_code)>(
|
||||
[](auto handler, connection* self, config const* cfg) {
|
||||
self->async_run_impl(*cfg, std::move(handler));
|
||||
},
|
||||
token,
|
||||
this,
|
||||
&cfg);
|
||||
}
|
||||
|
||||
/// Calls `boost::redis::basic_connection::async_receive`.
|
||||
@@ -877,13 +1021,13 @@ public:
|
||||
BOOST_DEPRECATED(
|
||||
"Accessing the underlying stream is deprecated and will be removed in the next release. Use "
|
||||
"the other member functions to interact with the connection.")
|
||||
auto& next_layer() noexcept { return impl_.next_layer(); }
|
||||
auto& next_layer() noexcept { return impl_.stream_.next_layer(); }
|
||||
|
||||
/// Calls `boost::redis::basic_connection::next_layer`.
|
||||
BOOST_DEPRECATED(
|
||||
"Accessing the underlying stream is deprecated and will be removed in the next release. Use "
|
||||
"the other member functions to interact with the connection.")
|
||||
auto const& next_layer() const noexcept { return impl_.next_layer(); }
|
||||
auto const& next_layer() const noexcept { return impl_.stream_.next_layer(); }
|
||||
|
||||
/// Calls `boost::redis::basic_connection::reset_stream`.
|
||||
BOOST_DEPRECATED(
|
||||
@@ -905,12 +1049,16 @@ public:
|
||||
BOOST_DEPRECATED(
|
||||
"ssl::context has no const methods, so this function should not be called. Set up any "
|
||||
"required TLS configuration before passing the ssl::context to the connection's constructor.")
|
||||
auto const& get_ssl_context() const noexcept { return impl_.get_ssl_context(); }
|
||||
auto const& get_ssl_context() const noexcept { return impl_.stream_.get_ssl_context(); }
|
||||
|
||||
private:
|
||||
void async_run_impl(
|
||||
config const& cfg,
|
||||
logger l,
|
||||
logger&& l,
|
||||
asio::any_completion_handler<void(boost::system::error_code)> token);
|
||||
|
||||
void async_run_impl(
|
||||
config const& cfg,
|
||||
asio::any_completion_handler<void(boost::system::error_code)> token);
|
||||
|
||||
void async_exec_impl(
|
||||
|
||||
53
include/boost/redis/detail/connection_logger.hpp
Normal file
53
include/boost/redis/detail/connection_logger.hpp
Normal file
@@ -0,0 +1,53 @@
|
||||
/* Copyright (c) 2018-2025 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#ifndef BOOST_REDIS_CONNECTION_LOGGER_HPP
|
||||
#define BOOST_REDIS_CONNECTION_LOGGER_HPP
|
||||
|
||||
#include <boost/redis/logger.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace boost::redis::detail {
|
||||
|
||||
// Wraps a logger and a string buffer for re-use, and provides
|
||||
// utility functions to format the log messages that we use.
|
||||
// The long-term trend will be moving most of this class to finite state
|
||||
// machines as we write them
|
||||
class connection_logger {
|
||||
logger logger_;
|
||||
std::string msg_;
|
||||
|
||||
public:
|
||||
connection_logger(logger&& logger) noexcept
|
||||
: logger_(std::move(logger))
|
||||
{ }
|
||||
|
||||
void reset(logger&& logger) { logger_ = std::move(logger); }
|
||||
|
||||
void on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res);
|
||||
void on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep);
|
||||
void on_connect(system::error_code const& ec, std::string_view unix_socket_ep);
|
||||
void on_ssl_handshake(system::error_code const& ec);
|
||||
void on_write(system::error_code const& ec, std::size_t n);
|
||||
void on_read(system::error_code const& ec, std::size_t n);
|
||||
void on_hello(system::error_code const& ec, generic_response const& resp);
|
||||
void log(logger::level lvl, std::string_view msg);
|
||||
void log(logger::level lvl, std::string_view op, system::error_code const& ec);
|
||||
void trace(std::string_view message) { log(logger::level::debug, message); }
|
||||
void trace(std::string_view op, system::error_code const& ec)
|
||||
{
|
||||
log(logger::level::debug, op, ec);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace boost::redis::detail
|
||||
|
||||
#endif // BOOST_REDIS_LOGGER_HPP
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
#include <boost/redis/adapter/any_adapter.hpp>
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/redis/detail/connection_logger.hpp>
|
||||
#include <boost/redis/operation.hpp>
|
||||
#include <boost/redis/request.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
@@ -20,16 +21,14 @@
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
namespace boost::redis::detail {
|
||||
|
||||
template <class HealthChecker, class Connection, class Logger>
|
||||
template <class HealthChecker, class Connection>
|
||||
class ping_op {
|
||||
public:
|
||||
HealthChecker* checker_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
@@ -38,7 +37,7 @@ public:
|
||||
BOOST_ASIO_CORO_REENTER(coro_) for (;;)
|
||||
{
|
||||
if (checker_->ping_interval_ == std::chrono::seconds::zero()) {
|
||||
logger_.trace("ping_op (1): timeout disabled.");
|
||||
conn_->logger_.trace("ping_op (1): timeout disabled.");
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::post(std::move(self));
|
||||
self.complete({});
|
||||
@@ -46,7 +45,7 @@ public:
|
||||
}
|
||||
|
||||
if (checker_->checker_has_exited_) {
|
||||
logger_.trace("ping_op (2): checker has exited.");
|
||||
conn_->logger_.trace("ping_op (2): checker has exited.");
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
@@ -54,7 +53,7 @@ public:
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
conn_->async_exec(checker_->req_, any_adapter(checker_->resp_), std::move(self));
|
||||
if (ec) {
|
||||
logger_.trace("ping_op (3)", ec);
|
||||
conn_->logger_.trace("ping_op (3)", ec);
|
||||
checker_->wait_timer_.cancel();
|
||||
self.complete(ec);
|
||||
return;
|
||||
@@ -66,7 +65,7 @@ public:
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
checker_->ping_timer_.async_wait(std::move(self));
|
||||
if (ec) {
|
||||
logger_.trace("ping_op (4)", ec);
|
||||
conn_->logger_.trace("ping_op (4)", ec);
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
@@ -74,12 +73,11 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
template <class HealthChecker, class Connection, class Logger>
|
||||
template <class HealthChecker, class Connection>
|
||||
class check_timeout_op {
|
||||
public:
|
||||
HealthChecker* checker_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
@@ -88,7 +86,7 @@ public:
|
||||
BOOST_ASIO_CORO_REENTER(coro_) for (;;)
|
||||
{
|
||||
if (checker_->ping_interval_ == std::chrono::seconds::zero()) {
|
||||
logger_.trace("check_timeout_op (1): timeout disabled.");
|
||||
conn_->logger_.trace("check_timeout_op (1): timeout disabled.");
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
asio::post(std::move(self));
|
||||
self.complete({});
|
||||
@@ -100,20 +98,20 @@ public:
|
||||
BOOST_ASIO_CORO_YIELD
|
||||
checker_->wait_timer_.async_wait(std::move(self));
|
||||
if (ec) {
|
||||
logger_.trace("check_timeout_op (2)", ec);
|
||||
conn_->logger_.trace("check_timeout_op (2)", ec);
|
||||
self.complete(ec);
|
||||
return;
|
||||
}
|
||||
|
||||
if (checker_->resp_.has_error()) {
|
||||
// TODO: Log the error.
|
||||
logger_.trace("check_timeout_op (3): Response error.");
|
||||
conn_->logger_.trace("check_timeout_op (3): Response error.");
|
||||
self.complete({});
|
||||
return;
|
||||
}
|
||||
|
||||
if (checker_->resp_.value().empty()) {
|
||||
logger_.trace("check_timeout_op (4): pong timeout.");
|
||||
conn_->logger_.trace("check_timeout_op (4): pong timeout.");
|
||||
checker_->ping_timer_.cancel();
|
||||
conn_->cancel(operation::run);
|
||||
checker_->checker_has_exited_ = true;
|
||||
@@ -157,30 +155,30 @@ public:
|
||||
wait_timer_.cancel();
|
||||
}
|
||||
|
||||
template <class Connection, class Logger, class CompletionToken>
|
||||
auto async_ping(Connection& conn, Logger l, CompletionToken token)
|
||||
template <class Connection, class CompletionToken>
|
||||
auto async_ping(Connection& conn, CompletionToken token)
|
||||
{
|
||||
return asio::async_compose<CompletionToken, void(system::error_code)>(
|
||||
ping_op<health_checker, Connection, Logger>{this, &conn, l},
|
||||
ping_op<health_checker, Connection>{this, &conn},
|
||||
token,
|
||||
conn,
|
||||
ping_timer_);
|
||||
}
|
||||
|
||||
template <class Connection, class Logger, class CompletionToken>
|
||||
auto async_check_timeout(Connection& conn, Logger l, CompletionToken token)
|
||||
template <class Connection, class CompletionToken>
|
||||
auto async_check_timeout(Connection& conn, CompletionToken token)
|
||||
{
|
||||
checker_has_exited_ = false;
|
||||
return asio::async_compose<CompletionToken, void(system::error_code)>(
|
||||
check_timeout_op<health_checker, Connection, Logger>{this, &conn, l},
|
||||
check_timeout_op<health_checker, Connection>{this, &conn},
|
||||
token,
|
||||
conn,
|
||||
wait_timer_);
|
||||
}
|
||||
|
||||
private:
|
||||
template <class, class, class> friend class ping_op;
|
||||
template <class, class, class> friend class check_timeout_op;
|
||||
template <class, class> friend class ping_op;
|
||||
template <class, class> friend class check_timeout_op;
|
||||
|
||||
timer_type ping_timer_;
|
||||
timer_type wait_timer_;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#define BOOST_REDIS_REDIS_STREAM_HPP
|
||||
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/redis/detail/connection_logger.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
|
||||
#include <boost/asio/basic_waitable_timer.hpp>
|
||||
@@ -67,11 +68,10 @@ class redis_stream {
|
||||
}
|
||||
}
|
||||
|
||||
template <class Logger>
|
||||
struct connect_op {
|
||||
redis_stream& obj;
|
||||
const config* cfg;
|
||||
Logger lgr;
|
||||
connection_logger* lgr;
|
||||
asio::coroutine coro{};
|
||||
|
||||
// This overload will be used for connects. We only need the endpoint
|
||||
@@ -82,7 +82,7 @@ class redis_stream {
|
||||
system::error_code ec,
|
||||
const asio::ip::tcp::endpoint& selected_endpoint)
|
||||
{
|
||||
lgr.on_connect(ec, selected_endpoint);
|
||||
lgr->on_connect(ec, selected_endpoint);
|
||||
(*this)(self, ec);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ class redis_stream {
|
||||
asio::cancel_after(obj.timer_, cfg->connect_timeout, std::move(self)));
|
||||
|
||||
// Log it
|
||||
lgr.on_connect(ec, cfg->unix_socket);
|
||||
lgr->on_connect(ec, cfg->unix_socket);
|
||||
|
||||
// If this failed, we can't continue
|
||||
if (ec) {
|
||||
@@ -130,7 +130,7 @@ class redis_stream {
|
||||
asio::cancel_after(obj.timer_, cfg->resolve_timeout, std::move(self)));
|
||||
|
||||
// Log it
|
||||
lgr.on_resolve(ec, resolver_results);
|
||||
lgr->on_resolve(ec, resolver_results);
|
||||
|
||||
// If this failed, we can't continue
|
||||
if (ec) {
|
||||
@@ -162,7 +162,7 @@ class redis_stream {
|
||||
asio::ssl::stream_base::client,
|
||||
asio::cancel_after(obj.timer_, cfg->ssl_handshake_timeout, std::move(self)));
|
||||
|
||||
lgr.on_ssl_handshake(ec);
|
||||
lgr->on_ssl_handshake(ec);
|
||||
|
||||
// If this failed, we can't continue
|
||||
if (ec) {
|
||||
@@ -208,11 +208,11 @@ public:
|
||||
const auto& next_layer() const { return stream_; }
|
||||
|
||||
// I/O
|
||||
template <class Logger, class CompletionToken>
|
||||
auto async_connect(const config* cfg, Logger l, CompletionToken&& token)
|
||||
template <class CompletionToken>
|
||||
auto async_connect(const config* cfg, connection_logger* l, CompletionToken&& token)
|
||||
{
|
||||
return asio::async_compose<CompletionToken, void(system::error_code)>(
|
||||
connect_op<Logger>{*this, cfg, l},
|
||||
connect_op{*this, cfg, l},
|
||||
token);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,17 +8,15 @@
|
||||
#define BOOST_REDIS_RUNNER_HPP
|
||||
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/redis/detail/connection_logger.hpp>
|
||||
#include <boost/redis/error.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
#include <boost/redis/operation.hpp>
|
||||
#include <boost/redis/request.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
|
||||
#include <boost/asio/compose.hpp>
|
||||
#include <boost/asio/coroutine.hpp>
|
||||
//#include <boost/asio/ip/tcp.hpp>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace boost::redis::detail {
|
||||
@@ -28,11 +26,10 @@ void push_hello(config const& cfg, request& req);
|
||||
// TODO: Can we avoid this whole function whose only purpose is to
|
||||
// check for an error in the hello response and complete with an error
|
||||
// so that the parallel group that starts it can exit?
|
||||
template <class Handshaker, class Connection, class Logger>
|
||||
template <class Handshaker, class Connection>
|
||||
struct hello_op {
|
||||
Handshaker* handshaker_ = nullptr;
|
||||
Connection* conn_ = nullptr;
|
||||
Logger logger_;
|
||||
asio::coroutine coro_{};
|
||||
|
||||
template <class Self>
|
||||
@@ -47,7 +44,7 @@ struct hello_op {
|
||||
handshaker_->hello_req_,
|
||||
any_adapter(handshaker_->hello_resp_),
|
||||
std::move(self));
|
||||
logger_.on_hello(ec, handshaker_->hello_resp_);
|
||||
conn_->logger_.on_hello(ec, handshaker_->hello_resp_);
|
||||
|
||||
if (ec) {
|
||||
conn_->cancel(operation::run);
|
||||
@@ -71,17 +68,17 @@ class resp3_handshaker {
|
||||
public:
|
||||
void set_config(config const& cfg) { cfg_ = cfg; }
|
||||
|
||||
template <class Connection, class Logger, class CompletionToken>
|
||||
auto async_hello(Connection& conn, Logger l, CompletionToken token)
|
||||
template <class Connection, class CompletionToken>
|
||||
auto async_hello(Connection& conn, CompletionToken token)
|
||||
{
|
||||
return asio::async_compose<CompletionToken, void(system::error_code)>(
|
||||
hello_op<resp3_handshaker, Connection, Logger>{this, &conn, l},
|
||||
hello_op<resp3_handshaker, Connection>{this, &conn},
|
||||
token,
|
||||
conn);
|
||||
}
|
||||
|
||||
private:
|
||||
template <class, class, class> friend struct hello_op;
|
||||
template <class, class> friend struct hello_op;
|
||||
|
||||
void add_hello()
|
||||
{
|
||||
|
||||
@@ -5,25 +5,42 @@
|
||||
*/
|
||||
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/impl/log_to_file.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
connection::connection(executor_type ex, asio::ssl::context ctx)
|
||||
: impl_{ex, std::move(ctx)}
|
||||
{ }
|
||||
logger detail::make_stderr_logger(logger::level lvl, std::string prefix)
|
||||
{
|
||||
return logger(lvl, [prefix = std::move(prefix)](logger::level, std::string_view msg) {
|
||||
log_to_file(stderr, msg, prefix.c_str());
|
||||
});
|
||||
}
|
||||
|
||||
connection::connection(asio::io_context& ioc, asio::ssl::context ctx)
|
||||
: impl_{ioc.get_executor(), std::move(ctx)}
|
||||
connection::connection(executor_type ex, asio::ssl::context ctx, logger lgr)
|
||||
: impl_{std::move(ex), std::move(ctx), std::move(lgr)}
|
||||
{ }
|
||||
|
||||
void connection::async_run_impl(
|
||||
config const& cfg,
|
||||
logger l,
|
||||
logger&& l,
|
||||
asio::any_completion_handler<void(boost::system::error_code)> token)
|
||||
{
|
||||
impl_.async_run(cfg, l, std::move(token));
|
||||
// Avoid calling the basic_connection::async_run overload taking a logger
|
||||
// because it generates deprecated messages when building this file
|
||||
impl_.set_stderr_logger(l.lvl, cfg);
|
||||
impl_.async_run(cfg, std::move(token));
|
||||
}
|
||||
|
||||
void connection::async_run_impl(
|
||||
config const& cfg,
|
||||
asio::any_completion_handler<void(boost::system::error_code)> token)
|
||||
{
|
||||
impl_.async_run(cfg, std::move(token));
|
||||
}
|
||||
|
||||
void connection::async_exec_impl(
|
||||
|
||||
183
include/boost/redis/impl/connection_logger.ipp
Normal file
183
include/boost/redis/impl/connection_logger.ipp
Normal file
@@ -0,0 +1,183 @@
|
||||
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/detail/connection_logger.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace boost::redis::detail {
|
||||
|
||||
inline void format_tcp_endpoint(const asio::ip::tcp::endpoint& ep, std::string& to)
|
||||
{
|
||||
// This formatting is inspired by Asio's endpoint operator<<
|
||||
const auto& addr = ep.address();
|
||||
if (addr.is_v6())
|
||||
to += '[';
|
||||
to += addr.to_string();
|
||||
if (addr.is_v6())
|
||||
to += ']';
|
||||
to += ':';
|
||||
to += std::to_string(ep.port());
|
||||
}
|
||||
|
||||
inline void format_error_code(system::error_code ec, std::string& to)
|
||||
{
|
||||
// Using error_code::what() includes any source code info
|
||||
// that the error may contain, making the messages too long.
|
||||
// This implementation was taken from error_code::what()
|
||||
to += ec.message();
|
||||
to += " [";
|
||||
to += ec.to_string();
|
||||
to += ']';
|
||||
}
|
||||
|
||||
void connection_logger::on_resolve(
|
||||
system::error_code const& ec,
|
||||
asio::ip::tcp::resolver::results_type const& res)
|
||||
{
|
||||
if (logger_.lvl < logger::level::info)
|
||||
return;
|
||||
|
||||
if (ec) {
|
||||
msg_ = "Error resolving the server hostname: ";
|
||||
format_error_code(ec, msg_);
|
||||
} else {
|
||||
msg_ = "Resolve results: ";
|
||||
auto iter = res.cbegin();
|
||||
auto end = res.cend();
|
||||
|
||||
if (iter != end) {
|
||||
format_tcp_endpoint(iter->endpoint(), msg_);
|
||||
++iter;
|
||||
for (; iter != end; ++iter) {
|
||||
msg_ += ", ";
|
||||
format_tcp_endpoint(iter->endpoint(), msg_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger_.fn(logger::level::info, msg_);
|
||||
}
|
||||
|
||||
void connection_logger::on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep)
|
||||
{
|
||||
if (logger_.lvl < logger::level::info)
|
||||
return;
|
||||
|
||||
if (ec) {
|
||||
msg_ = "Failed connecting to the server: ";
|
||||
format_error_code(ec, msg_);
|
||||
} else {
|
||||
msg_ = "Connected to ";
|
||||
format_tcp_endpoint(ep, msg_);
|
||||
}
|
||||
|
||||
logger_.fn(logger::level::info, msg_);
|
||||
}
|
||||
|
||||
void connection_logger::on_connect(system::error_code const& ec, std::string_view unix_socket_ep)
|
||||
{
|
||||
if (logger_.lvl < logger::level::info)
|
||||
return;
|
||||
|
||||
if (ec) {
|
||||
msg_ = "Failed connecting to the server: ";
|
||||
format_error_code(ec, msg_);
|
||||
} else {
|
||||
msg_ = "Connected to ";
|
||||
msg_ += unix_socket_ep;
|
||||
}
|
||||
|
||||
logger_.fn(logger::level::info, msg_);
|
||||
}
|
||||
|
||||
void connection_logger::on_ssl_handshake(system::error_code const& ec)
|
||||
{
|
||||
if (logger_.lvl < logger::level::info)
|
||||
return;
|
||||
|
||||
msg_ = "SSL handshake: ";
|
||||
format_error_code(ec, msg_);
|
||||
|
||||
logger_.fn(logger::level::info, msg_);
|
||||
}
|
||||
|
||||
void connection_logger::on_write(system::error_code const& ec, std::size_t n)
|
||||
{
|
||||
if (logger_.lvl < logger::level::info)
|
||||
return;
|
||||
|
||||
msg_ = "writer_op: ";
|
||||
if (ec) {
|
||||
format_error_code(ec, msg_);
|
||||
} else {
|
||||
msg_ += std::to_string(n);
|
||||
msg_ += " bytes written.";
|
||||
}
|
||||
|
||||
logger_.fn(logger::level::info, msg_);
|
||||
}
|
||||
|
||||
void connection_logger::on_read(system::error_code const& ec, std::size_t n)
|
||||
{
|
||||
if (logger_.lvl < logger::level::info)
|
||||
return;
|
||||
|
||||
msg_ = "reader_op: ";
|
||||
if (ec) {
|
||||
format_error_code(ec, msg_);
|
||||
} else {
|
||||
msg_ += std::to_string(n);
|
||||
msg_ += " bytes read.";
|
||||
}
|
||||
|
||||
logger_.fn(logger::level::info, msg_);
|
||||
}
|
||||
|
||||
void connection_logger::on_hello(system::error_code const& ec, generic_response const& resp)
|
||||
{
|
||||
if (logger_.lvl < logger::level::info)
|
||||
return;
|
||||
|
||||
msg_ = "hello_op: ";
|
||||
if (ec) {
|
||||
format_error_code(ec, msg_);
|
||||
if (resp.has_error()) {
|
||||
msg_ += " (";
|
||||
msg_ += resp.error().diagnostic;
|
||||
msg_ += ')';
|
||||
}
|
||||
} else {
|
||||
msg_ += "success";
|
||||
}
|
||||
|
||||
logger_.fn(logger::level::info, msg_);
|
||||
}
|
||||
|
||||
void connection_logger::log(logger::level lvl, std::string_view message)
|
||||
{
|
||||
if (logger_.lvl < lvl)
|
||||
return;
|
||||
logger_.fn(lvl, message);
|
||||
}
|
||||
|
||||
void connection_logger::log(logger::level lvl, std::string_view op, system::error_code const& ec)
|
||||
{
|
||||
if (logger_.lvl < lvl)
|
||||
return;
|
||||
|
||||
msg_ = op;
|
||||
msg_ += ": ";
|
||||
format_error_code(ec, msg_);
|
||||
|
||||
logger_.fn(lvl, msg_);
|
||||
}
|
||||
|
||||
} // namespace boost::redis::detail
|
||||
37
include/boost/redis/impl/log_to_file.hpp
Normal file
37
include/boost/redis/impl/log_to_file.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
#ifndef BOOST_REDIS_LOG_TO_STDERR_HPP
|
||||
#define BOOST_REDIS_LOG_TO_STDERR_HPP
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <string_view>
|
||||
|
||||
namespace boost::redis::detail {
|
||||
|
||||
// Shared by several ipp files
|
||||
inline void log_to_file(FILE* f, std::string_view msg, const char* prefix = "(Boost.Redis) ")
|
||||
{
|
||||
// If the message is empty, data() might return a null pointer
|
||||
const char* msg_ptr = msg.empty() ? "" : msg.data();
|
||||
|
||||
// Precision should be an int when passed to fprintf. Technically,
|
||||
// message could be larger than INT_MAX. Impose a sane limit on message sizes
|
||||
// to prevent memory problems
|
||||
auto precision = static_cast<int>((std::min)(msg.size(), static_cast<std::size_t>(0xffffu)));
|
||||
|
||||
// Log the message. None of our messages should contain NULL bytes, so this should be OK.
|
||||
// We choose fprintf over std::clog because it's safe in multi-threaded environments.
|
||||
std::fprintf(f, "%s%.*s\n", prefix, precision, msg_ptr);
|
||||
}
|
||||
|
||||
} // namespace boost::redis::detail
|
||||
|
||||
#endif
|
||||
@@ -1,175 +1,24 @@
|
||||
/* Copyright (c) 2018-2024 Marcelo Zimbres Silva (mzimbres@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
#include <boost/redis/impl/log_to_file.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <cstdio>
|
||||
#include <string_view>
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
void logger::write_prefix()
|
||||
{
|
||||
if (!std::empty(prefix_))
|
||||
std::clog << prefix_;
|
||||
}
|
||||
|
||||
void logger::on_resolve(
|
||||
system::error_code const& ec,
|
||||
asio::ip::tcp::resolver::results_type const& res)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "resolve results: ";
|
||||
|
||||
if (ec) {
|
||||
std::clog << ec.message() << std::endl;
|
||||
} else {
|
||||
auto begin = std::cbegin(res);
|
||||
auto end = std::cend(res);
|
||||
|
||||
if (begin == end)
|
||||
return;
|
||||
|
||||
std::clog << begin->endpoint();
|
||||
for (auto iter = std::next(begin); iter != end; ++iter)
|
||||
std::clog << ", " << iter->endpoint();
|
||||
}
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void logger::on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "connected to ";
|
||||
|
||||
if (ec)
|
||||
std::clog << ec.message() << std::endl;
|
||||
else
|
||||
std::clog << ep;
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
|
||||
void logger::on_connect(system::error_code const& ec, std::string_view unix_socket_ep)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "connected to ";
|
||||
|
||||
if (ec)
|
||||
std::clog << ec.message() << std::endl;
|
||||
else
|
||||
std::clog << unix_socket_ep;
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
#endif
|
||||
|
||||
void logger::on_ssl_handshake(system::error_code const& ec)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << "SSL handshake: " << ec.message() << std::endl;
|
||||
}
|
||||
|
||||
void logger::on_write(system::error_code const& ec, std::string_view payload)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
if (ec)
|
||||
std::clog << "writer_op: " << ec.message();
|
||||
else
|
||||
std::clog << "writer_op: " << std::size(payload) << " bytes written.";
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void logger::on_read(system::error_code const& ec, std::size_t n)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
if (ec)
|
||||
std::clog << "reader_op: " << ec.message();
|
||||
else
|
||||
std::clog << "reader_op: " << n << " bytes read.";
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void logger::on_hello(system::error_code const& ec, generic_response const& resp)
|
||||
{
|
||||
if (level_ < level::info)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
if (ec) {
|
||||
std::clog << "hello_op: " << ec.message();
|
||||
if (resp.has_error())
|
||||
std::clog << " (" << resp.error().diagnostic << ")";
|
||||
} else {
|
||||
std::clog << "hello_op: Success";
|
||||
}
|
||||
|
||||
std::clog << std::endl;
|
||||
}
|
||||
|
||||
void logger::trace(std::string_view message)
|
||||
{
|
||||
if (level_ < level::debug)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << message << std::endl;
|
||||
}
|
||||
|
||||
void logger::trace(std::string_view op, system::error_code const& ec)
|
||||
{
|
||||
if (level_ < level::debug)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << op << ": " << ec.message() << std::endl;
|
||||
}
|
||||
|
||||
void logger::log_error(std::string_view op, system::error_code const& ec)
|
||||
{
|
||||
if (level_ < level::err)
|
||||
return;
|
||||
|
||||
write_prefix();
|
||||
|
||||
std::clog << op << ": " << ec.message() << std::endl;
|
||||
}
|
||||
logger::logger(level l)
|
||||
: lvl{l}
|
||||
, fn{[](level, std::string_view msg) {
|
||||
detail::log_to_file(stderr, msg);
|
||||
}}
|
||||
{ }
|
||||
|
||||
} // namespace boost::redis
|
||||
|
||||
@@ -7,30 +7,17 @@
|
||||
#ifndef BOOST_REDIS_LOGGER_HPP
|
||||
#define BOOST_REDIS_LOGGER_HPP
|
||||
|
||||
#include <boost/redis/response.hpp>
|
||||
|
||||
#include <boost/asio/ip/tcp.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
namespace boost::system {
|
||||
class error_code;
|
||||
}
|
||||
|
||||
namespace boost::redis {
|
||||
|
||||
/** @brief Logger class
|
||||
/** @brief Defines logging configuration
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* The class can be passed to the connection objects to log to `std::clog`
|
||||
*
|
||||
* Notice that currently this class has no stable interface. Users
|
||||
* that don't want any logging can disable it by contructing a logger
|
||||
* with logger::level::emerg to the connection.
|
||||
* See the member descriptions for more info.
|
||||
*/
|
||||
class logger {
|
||||
public:
|
||||
struct logger {
|
||||
/** @brief Syslog-like log levels
|
||||
* @ingroup high-level-api
|
||||
*/
|
||||
@@ -61,83 +48,51 @@ public:
|
||||
info,
|
||||
|
||||
/// Debug
|
||||
debug
|
||||
debug,
|
||||
};
|
||||
|
||||
/** @brief Constructor
|
||||
* @ingroup high-level-api
|
||||
/** @brief Constructor from a level.
|
||||
*
|
||||
* @param l Log level.
|
||||
* Constructs a logger with the specified level
|
||||
* and a logging function that prints messages to stderr.
|
||||
*
|
||||
* @param l The value to set @ref lvl to.
|
||||
*
|
||||
* @par Exceptions
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
logger(level l = level::debug)
|
||||
: level_{l}
|
||||
logger(level l = level::info);
|
||||
|
||||
/** @brief Constructor from a level and a function.
|
||||
*
|
||||
* Constructs a logger by setting its members to the specified values.
|
||||
*
|
||||
* @param l The value to set @ref lvl to.
|
||||
* @param fn The value to set @ref fn to.
|
||||
*
|
||||
* @par Exceptions
|
||||
* No-throw guarantee.
|
||||
*/
|
||||
logger(level l, std::function<void(level, std::string_view)> fn)
|
||||
: lvl{l}
|
||||
, fn{std::move(fn)}
|
||||
{ }
|
||||
|
||||
/** @brief Called when the resolve operation completes.
|
||||
* @ingroup high-level-api
|
||||
/**
|
||||
* @brief Defines a severity filter for messages.
|
||||
*
|
||||
* @param ec Error returned by the resolve operation.
|
||||
* @param res Resolve results.
|
||||
* Only messages with a level >= to the one specified by the logger
|
||||
* will be logged.
|
||||
*/
|
||||
void on_resolve(system::error_code const& ec, asio::ip::tcp::resolver::results_type const& res);
|
||||
level lvl;
|
||||
|
||||
/** @brief Called when the connect operation completes.
|
||||
* @ingroup high-level-api
|
||||
/**
|
||||
* @brief Defines a severity filter for messages.
|
||||
*
|
||||
* @param ec Error returned by the connect operation.
|
||||
* @param ep Endpoint to which the connection connected.
|
||||
* Only messages with a level >= to the one specified by the logger
|
||||
* will be logged.
|
||||
*/
|
||||
void on_connect(system::error_code const& ec, asio::ip::tcp::endpoint const& ep);
|
||||
#ifdef BOOST_ASIO_HAS_LOCAL_SOCKETS
|
||||
void on_connect(system::error_code const& ec, std::string_view unix_socket_ep);
|
||||
#endif
|
||||
|
||||
/** @brief Called when the ssl handshake operation completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param ec Error returned by the handshake operation.
|
||||
*/
|
||||
void on_ssl_handshake(system::error_code const& ec);
|
||||
|
||||
/** @brief Called when the write operation completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param ec Error code returned by the write operation.
|
||||
* @param payload The payload written to the socket.
|
||||
*/
|
||||
void on_write(system::error_code const& ec, std::string_view payload);
|
||||
|
||||
/** @brief Called when the read operation completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param ec Error code returned by the read operation.
|
||||
* @param n Number of bytes read.
|
||||
*/
|
||||
void on_read(system::error_code const& ec, std::size_t n);
|
||||
|
||||
/** @brief Called when the `HELLO` request completes.
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param ec Error code returned by the async_exec operation.
|
||||
* @param resp Response sent by the Redis server.
|
||||
*/
|
||||
void on_hello(system::error_code const& ec, generic_response const& resp);
|
||||
|
||||
/** @brief Sets a prefix to every log message
|
||||
* @ingroup high-level-api
|
||||
*
|
||||
* @param prefix The prefix.
|
||||
*/
|
||||
void set_prefix(std::string_view prefix) { prefix_ = prefix; }
|
||||
|
||||
void trace(std::string_view message);
|
||||
void trace(std::string_view op, system::error_code const& ec);
|
||||
void log_error(std::string_view op, system::error_code const& ec);
|
||||
|
||||
private:
|
||||
void write_prefix();
|
||||
level level_;
|
||||
std::string prefix_;
|
||||
std::function<void(level, std::string_view)> fn;
|
||||
};
|
||||
|
||||
} // namespace boost::redis
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include <boost/redis/impl/connection.ipp>
|
||||
#include <boost/redis/impl/connection_logger.ipp>
|
||||
#include <boost/redis/impl/error.ipp>
|
||||
#include <boost/redis/impl/exec_fsm.ipp>
|
||||
#include <boost/redis/impl/ignore.ipp>
|
||||
|
||||
@@ -5,11 +5,10 @@ target_link_libraries(boost_redis_project_options INTERFACE boost_redis)
|
||||
if (MSVC)
|
||||
# C4459: name hides outer scope variable is issued by Asio
|
||||
target_compile_options(boost_redis_project_options INTERFACE /bigobj /W4 /WX /wd4459)
|
||||
target_compile_definitions(boost_redis_project_options INTERFACE _WIN32_WINNT=0x0601)
|
||||
target_compile_definitions(boost_redis_project_options INTERFACE _WIN32_WINNT=0x0601 _CRT_SECURE_NO_WARNINGS=1)
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_compile_options(boost_redis_project_options INTERFACE -Wall -Wextra -Werror)
|
||||
endif()
|
||||
target_compile_definitions(boost_redis_project_options INTERFACE BOOST_ALLOW_DEPRECATED=1) # we need to still test deprecated fns
|
||||
|
||||
add_library(boost_redis_src STATIC boost_redis.cpp)
|
||||
target_compile_features(boost_redis_src PRIVATE cxx_std_17)
|
||||
@@ -29,6 +28,7 @@ macro(make_test TEST_NAME)
|
||||
boost_redis_project_options
|
||||
Boost::unit_test_framework
|
||||
)
|
||||
target_compile_definitions(${EXE_NAME} PRIVATE BOOST_ALLOW_DEPRECATED=1) # we need to still test deprecated fns
|
||||
add_test(${EXE_NAME} ${EXE_NAME})
|
||||
endmacro()
|
||||
|
||||
@@ -38,6 +38,8 @@ make_test(test_request)
|
||||
make_test(test_low_level_sync_sans_io)
|
||||
make_test(test_any_adapter)
|
||||
make_test(test_exec_fsm)
|
||||
make_test(test_log_to_file)
|
||||
make_test(test_conn_logging)
|
||||
|
||||
# Tests that require a real Redis server
|
||||
make_test(test_conn_quit)
|
||||
|
||||
@@ -14,7 +14,8 @@ local requirements =
|
||||
<define>BOOST_ASIO_DISABLE_BOOST_BIND=1
|
||||
<define>BOOST_ASIO_DISABLE_BOOST_DATE_TIME=1
|
||||
<define>BOOST_ASIO_DISABLE_BOOST_REGEX=1
|
||||
<define>BOOST_ALLOW_DEPRECATED=1 # we need to test deprecated fns
|
||||
<define>BOOST_ALLOW_DEPRECATED=1 # we need to test deprecated fns
|
||||
<define>_CRT_SECURE_NO_WARNINGS=1 # suppress MSVC warnings
|
||||
<toolset>msvc:<cxxflags>"/bigobj"
|
||||
<target-os>windows:<define>_WIN32_WINNT=0x0601
|
||||
[ requires
|
||||
@@ -53,6 +54,8 @@ local tests =
|
||||
test_low_level_sync_sans_io
|
||||
test_any_adapter
|
||||
test_exec_fsm
|
||||
test_log_to_file
|
||||
test_conn_logging
|
||||
;
|
||||
|
||||
# Build and run the tests
|
||||
|
||||
@@ -25,10 +25,9 @@ void run(
|
||||
std::shared_ptr<boost::redis::connection> conn,
|
||||
boost::redis::config cfg,
|
||||
boost::system::error_code ec,
|
||||
boost::redis::operation op,
|
||||
boost::redis::logger::level l)
|
||||
boost::redis::operation op)
|
||||
{
|
||||
conn->async_run(cfg, {l}, run_callback{conn, op, ec});
|
||||
conn->async_run(cfg, run_callback{conn, op, ec});
|
||||
}
|
||||
|
||||
static std::string safe_getenv(const char* name, const char* default_value)
|
||||
|
||||
@@ -33,5 +33,4 @@ void run(
|
||||
std::shared_ptr<boost::redis::connection> conn,
|
||||
boost::redis::config cfg = make_test_config(),
|
||||
boost::system::error_code ec = boost::asio::error::operation_aborted,
|
||||
boost::redis::operation op = boost::redis::operation::receive,
|
||||
boost::redis::logger::level l = boost::redis::logger::level::debug);
|
||||
boost::redis::operation op = boost::redis::operation::receive);
|
||||
|
||||
159
test/test_conn_logging.cpp
Normal file
159
test/test_conn_logging.cpp
Normal file
@@ -0,0 +1,159 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
#include <boost/redis/config.hpp>
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/logger.hpp>
|
||||
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <boost/asio/ssl/context.hpp>
|
||||
#include <boost/core/lightweight_test.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include "common.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using boost::system::error_code;
|
||||
namespace net = boost::asio;
|
||||
using namespace boost::redis;
|
||||
|
||||
namespace {
|
||||
|
||||
// user tests
|
||||
// logging can be disabled
|
||||
// logging can be changed verbosity
|
||||
|
||||
template <class Conn>
|
||||
void run_with_invalid_config(net::io_context& ioc, Conn& conn)
|
||||
{
|
||||
config cfg;
|
||||
cfg.use_ssl = true;
|
||||
cfg.unix_socket = "/tmp/sock";
|
||||
conn.async_run(cfg, [](error_code ec) {
|
||||
BOOST_TEST_NE(ec, error_code());
|
||||
});
|
||||
ioc.run_for(test_timeout);
|
||||
}
|
||||
|
||||
template <class Conn>
|
||||
void test_connection_constructor_executor_1()
|
||||
{
|
||||
// Setup
|
||||
net::io_context ioc;
|
||||
std::vector<std::string> messages;
|
||||
logger lgr(logger::level::info, [&](logger::level, std::string_view msg) {
|
||||
messages.emplace_back(msg);
|
||||
});
|
||||
Conn conn{ioc.get_executor(), std::move(lgr)};
|
||||
|
||||
// Produce some logging
|
||||
run_with_invalid_config(ioc, conn);
|
||||
|
||||
// Some logging was produced
|
||||
BOOST_TEST_EQ(messages.size(), 1u);
|
||||
}
|
||||
|
||||
template <class Conn>
|
||||
void test_connection_constructor_context_1()
|
||||
{
|
||||
// Setup
|
||||
net::io_context ioc;
|
||||
std::vector<std::string> messages;
|
||||
logger lgr(logger::level::info, [&](logger::level, std::string_view msg) {
|
||||
messages.emplace_back(msg);
|
||||
});
|
||||
Conn conn{ioc, std::move(lgr)};
|
||||
|
||||
// Produce some logging
|
||||
run_with_invalid_config(ioc, conn);
|
||||
|
||||
// Some logging was produced
|
||||
BOOST_TEST_EQ(messages.size(), 1u);
|
||||
}
|
||||
|
||||
template <class Conn>
|
||||
void test_connection_constructor_executor_2()
|
||||
{
|
||||
// Setup
|
||||
net::io_context ioc;
|
||||
std::vector<std::string> messages;
|
||||
logger lgr(logger::level::info, [&](logger::level, std::string_view msg) {
|
||||
messages.emplace_back(msg);
|
||||
});
|
||||
Conn conn{
|
||||
ioc.get_executor(),
|
||||
net::ssl::context{net::ssl::context::tlsv12_client},
|
||||
std::move(lgr)};
|
||||
|
||||
// Produce some logging
|
||||
run_with_invalid_config(ioc, conn);
|
||||
|
||||
// Some logging was produced
|
||||
BOOST_TEST_EQ(messages.size(), 1u);
|
||||
}
|
||||
|
||||
template <class Conn>
|
||||
void test_connection_constructor_context_2()
|
||||
{
|
||||
// Setup
|
||||
net::io_context ioc;
|
||||
std::vector<std::string> messages;
|
||||
logger lgr(logger::level::info, [&](logger::level, std::string_view msg) {
|
||||
messages.emplace_back(msg);
|
||||
});
|
||||
Conn conn{ioc, net::ssl::context{net::ssl::context::tlsv12_client}, std::move(lgr)};
|
||||
|
||||
// Produce some logging
|
||||
run_with_invalid_config(ioc, conn);
|
||||
|
||||
// Some logging was produced
|
||||
BOOST_TEST_EQ(messages.size(), 1u);
|
||||
}
|
||||
|
||||
void test_disable_logging()
|
||||
{
|
||||
// Setup
|
||||
net::io_context ioc;
|
||||
std::vector<std::string> messages;
|
||||
logger lgr(logger::level::disabled, [&](logger::level, std::string_view msg) {
|
||||
messages.emplace_back(msg);
|
||||
});
|
||||
connection conn{ioc, std::move(lgr)};
|
||||
|
||||
// Produce some logging
|
||||
run_with_invalid_config(ioc, conn);
|
||||
|
||||
// Some logging was produced
|
||||
BOOST_TEST_EQ(messages.size(), 0u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
// basic_connection
|
||||
using basic_conn_t = basic_connection<net::io_context::executor_type>;
|
||||
test_connection_constructor_executor_1<basic_conn_t>();
|
||||
test_connection_constructor_executor_2<basic_conn_t>();
|
||||
test_connection_constructor_context_1<basic_conn_t>();
|
||||
test_connection_constructor_context_2<basic_conn_t>();
|
||||
|
||||
// connection
|
||||
test_connection_constructor_executor_1<connection>();
|
||||
test_connection_constructor_executor_2<connection>();
|
||||
test_connection_constructor_context_1<connection>();
|
||||
test_connection_constructor_context_2<connection>();
|
||||
|
||||
test_disable_logging();
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
123
test/test_log_to_file.cpp
Normal file
123
test/test_log_to_file.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// Copyright (c) 2025 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
|
||||
#include <boost/redis/impl/log_to_file.hpp>
|
||||
|
||||
#include <boost/core/lightweight_test.hpp>
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
using namespace boost::redis;
|
||||
|
||||
namespace {
|
||||
|
||||
// RAII helpers for working with C FILE*
|
||||
struct file_deleter {
|
||||
void operator()(FILE* f) const { std::fclose(f); }
|
||||
};
|
||||
using unique_file = std::unique_ptr<FILE, file_deleter>;
|
||||
|
||||
unique_file create_temporary()
|
||||
{
|
||||
unique_file f{std::tmpfile()};
|
||||
if (!BOOST_TEST_NE(f.get(), nullptr))
|
||||
exit(1);
|
||||
return f;
|
||||
}
|
||||
|
||||
std::string get_file_contents(FILE* f)
|
||||
{
|
||||
if (!BOOST_TEST_EQ(std::fseek(f, 0, SEEK_END), 0))
|
||||
exit(1);
|
||||
long fsize = std::ftell(f);
|
||||
if (!BOOST_TEST_GE(fsize, 0))
|
||||
exit(1);
|
||||
std::rewind(f);
|
||||
std::string res(fsize, 0);
|
||||
if (!BOOST_TEST_EQ(std::fread(res.data(), 1u, res.size(), f), fsize))
|
||||
exit(1);
|
||||
return res;
|
||||
}
|
||||
|
||||
void test_regular()
|
||||
{
|
||||
auto f = create_temporary();
|
||||
detail::log_to_file(f.get(), "something happened");
|
||||
BOOST_TEST_EQ(get_file_contents(f.get()), "(Boost.Redis) something happened\n");
|
||||
}
|
||||
|
||||
void test_empty_message()
|
||||
{
|
||||
auto f = create_temporary();
|
||||
detail::log_to_file(f.get(), {});
|
||||
BOOST_TEST_EQ(get_file_contents(f.get()), "(Boost.Redis) \n");
|
||||
}
|
||||
|
||||
void test_empty_prefix()
|
||||
{
|
||||
auto f = create_temporary();
|
||||
detail::log_to_file(f.get(), {}, "");
|
||||
BOOST_TEST_EQ(get_file_contents(f.get()), "\n");
|
||||
}
|
||||
|
||||
void test_message_not_null_terminated()
|
||||
{
|
||||
constexpr std::string_view str = "some_string";
|
||||
auto f = create_temporary();
|
||||
detail::log_to_file(f.get(), str.substr(0, 4));
|
||||
BOOST_TEST_EQ(get_file_contents(f.get()), "(Boost.Redis) some\n");
|
||||
}
|
||||
|
||||
// NULL bytes don't cause UB. None of our messages have
|
||||
// them, so this is an edge case
|
||||
void test_message_null_bytes()
|
||||
{
|
||||
char buff[] = {'a', 'b', 'c', 0, 'l', 0};
|
||||
auto f = create_temporary();
|
||||
detail::log_to_file(f.get(), std::string_view(buff, sizeof(buff)));
|
||||
BOOST_TEST_EQ(get_file_contents(f.get()), "(Boost.Redis) abc\n");
|
||||
}
|
||||
|
||||
// Internally, sizes are converted to int because of C APIs. Check that this
|
||||
// does not cause trouble. We impose a sanity limit of 0xffff bytes for all messages
|
||||
void test_message_very_long()
|
||||
{
|
||||
// Setup. Allocating a string of size INT_MAX causes trouble, so we pass a string_view
|
||||
// with that size, but with only the first 0xffff bytes being valid
|
||||
std::string msg(0xffffu + 1u, 'a');
|
||||
const auto msg_size = static_cast<std::size_t>((std::numeric_limits<int>::max)()) + 1u;
|
||||
auto f = create_temporary();
|
||||
|
||||
// Log
|
||||
detail::log_to_file(f.get(), std::string_view(msg.data(), msg_size));
|
||||
|
||||
// Check
|
||||
std::string expected = "(Boost.Redis) ";
|
||||
expected += std::string_view(msg.data(), 0xffffu);
|
||||
expected += '\n';
|
||||
BOOST_TEST_EQ(get_file_contents(f.get()), expected);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
{
|
||||
test_regular();
|
||||
test_empty_message();
|
||||
test_empty_prefix();
|
||||
test_message_not_null_terminated();
|
||||
test_message_null_bytes();
|
||||
test_message_very_long();
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
Reference in New Issue
Block a user