Skip to content

Commit f04d97f

Browse files
authored
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
1 parent 7304d99 commit f04d97f

34 files changed

+1056
-376
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,44 @@ imported in the global namespace by the user. In the
407407
[Examples](#examples) section the reader can find examples showing how
408408
to serialize using json and [protobuf](https://protobuf.dev/).
409409
410+
<a name="logging"></a>
411+
412+
### Logging
413+
414+
`connection::async_run` is a complex algorithm, with features like built-in reconnection.
415+
This can make configuration problems, like a misconfigured hostname, difficult to debug -
416+
Boost.Redis will keep retrying to connect to the same hostname over and over.
417+
For this reason, Boost.Redis incorporates a lightweight logging solution, and
418+
**will log some status messages to stderr by default**.
419+
420+
Logging can be customized by passing a `logger` object to the `connection`'s constructor.
421+
422+
For example, logging can be disabled by writing:
423+
424+
```cpp
425+
asio::io_context ioc;
426+
redis::connection conn {ioc, redis::logger{redis::logger::level::disabled}};
427+
```
428+
429+
Every message logged by the library is attached a
430+
[syslog-like severity](https://en.wikipedia.org/wiki/Syslog#Severity_level)
431+
tag (a `logger::level`).
432+
You can filter messages by severity by creating a `logger` with a specific level:
433+
434+
```cpp
435+
asio::io_context ioc;
436+
437+
// Logs to stderr messages with severity >= level::error.
438+
// This will hide all informational output.
439+
redis::connection conn {ioc, redis::logger{redis::logger::level::error}};
440+
```
441+
442+
`logger`'s constructor accepts a `std::function<void(logger::level, std::string_view)>`
443+
as second argument. If supplied, Boost.Redis will call this function when logging
444+
instead of printing to stderr. This can be used to integrate third-party logging
445+
libraries. See our [spdlog integration example](example/cpp17_spdlog.cpp) for sample code.
446+
447+
410448
<a name="examples"></a>
411449
## Examples
412450
@@ -424,6 +462,7 @@ The examples below show how to use the features discussed so far
424462
* cpp20_chat_room.cpp: A command line chat built on Redis pubsub.
425463
* cpp17_intro.cpp: Uses callbacks and requires C++17.
426464
* cpp17_intro_sync.cpp: Runs `async_run` in a separate thread and performs synchronous calls to `async_exec`.
465+
* cpp17_spdlog.cpp: Shows how to use third-party logging libraries like `spdlog` with Boost.Redis.
427466
428467
The main function used in some async examples has been factored out in
429468
the main.cpp file.

example/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,12 @@ endif()
5151
if (NOT MSVC)
5252
make_example(cpp20_chat_room 20)
5353
endif()
54+
55+
# We build and test the spdlog integration example only if the library is found
56+
find_package(spdlog)
57+
if (spdlog_FOUND)
58+
make_testable_example(cpp17_spdlog 17)
59+
target_link_libraries(cpp17_spdlog PRIVATE spdlog::spdlog)
60+
else()
61+
message(STATUS "Skipping the spdlog example because the spdlog package couldn't be found")
62+
endif()

example/cpp17_intro.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ auto main(int argc, char* argv[]) -> int
3434
asio::io_context ioc;
3535
connection conn{ioc};
3636

37-
conn.async_run(cfg, {}, asio::detached);
37+
conn.async_run(cfg, asio::detached);
3838

3939
conn.async_exec(req, resp, [&](auto ec, auto) {
4040
if (!ec)

example/cpp17_spdlog.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//
2+
// Copyright (c) 2025 Marcelo Zimbres Silva ([email protected]),
3+
// Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
4+
//
5+
// Distributed under the Boost Software License, Version 1.0. (See accompanying
6+
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7+
//
8+
9+
#include <boost/redis/connection.hpp>
10+
#include <boost/redis/logger.hpp>
11+
12+
#include <boost/asio/detached.hpp>
13+
#include <boost/system/error_code.hpp>
14+
15+
#include <cstddef>
16+
#include <iostream>
17+
#include <spdlog/spdlog.h>
18+
#include <string_view>
19+
20+
namespace asio = boost::asio;
21+
namespace redis = boost::redis;
22+
23+
// Maps a Boost.Redis log level to a spdlog log level
24+
static spdlog::level::level_enum to_spdlog_level(redis::logger::level lvl)
25+
{
26+
switch (lvl) {
27+
// spdlog doesn't include the emerg and alert syslog levels,
28+
// so we convert them to the highest supported level.
29+
// Similarly, notice is similar to info
30+
case redis::logger::level::emerg:
31+
case redis::logger::level::alert:
32+
case redis::logger::level::crit: return spdlog::level::critical;
33+
case redis::logger::level::err: return spdlog::level::err;
34+
case redis::logger::level::warning: return spdlog::level::warn;
35+
case redis::logger::level::notice:
36+
case redis::logger::level::info: return spdlog::level::info;
37+
case redis::logger::level::debug:
38+
default: return spdlog::level::debug;
39+
}
40+
}
41+
42+
// This function glues Boost.Redis logging and spdlog.
43+
// It should have the signature shown here. It will be invoked
44+
// by Boost.Redis whenever a message is to be logged.
45+
static void do_log(redis::logger::level level, std::string_view msg)
46+
{
47+
spdlog::log(to_spdlog_level(level), "(Boost.Redis) {}", msg);
48+
}
49+
50+
auto main(int argc, char* argv[]) -> int
51+
{
52+
if (argc != 3) {
53+
std::cerr << "Usage: " << argv[0] << " <server-host> <server-port>\n";
54+
exit(1);
55+
}
56+
57+
try {
58+
// Create an execution context, required to create any I/O objects
59+
asio::io_context ioc;
60+
61+
// Create a connection to connect to Redis, and pass it a custom logger.
62+
// Boost.Redis will call do_log whenever it needs to log a message.
63+
// Note that the function will only be called for messages with level >= info
64+
// (i.e. filtering is done by Boost.Redis).
65+
redis::connection conn{
66+
ioc,
67+
redis::logger{redis::logger::level::info, do_log}
68+
};
69+
70+
// Configuration to connect to the server
71+
redis::config cfg;
72+
cfg.addr.host = argv[1];
73+
cfg.addr.port = argv[2];
74+
75+
// Run the connection with the specified configuration.
76+
// This will establish the connection and keep it healthy
77+
conn.async_run(cfg, asio::detached);
78+
79+
// Execute a request
80+
redis::request req;
81+
req.push("PING", "Hello world");
82+
83+
redis::response<std::string> resp;
84+
85+
conn.async_exec(req, resp, [&](boost::system::error_code ec, std::size_t /* bytes_read*/) {
86+
if (ec) {
87+
spdlog::error("Request failed: {}", ec.what());
88+
exit(1);
89+
} else {
90+
spdlog::info("PING: {}", std::get<0>(resp).value());
91+
}
92+
conn.cancel();
93+
});
94+
95+
// Actually run our example. Nothing will happen until we call run()
96+
ioc.run();
97+
98+
} catch (std::exception const& e) {
99+
spdlog::error("Error: {}", e.what());
100+
return 1;
101+
}
102+
}

example/cpp20_chat_room.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ auto co_main(config cfg) -> awaitable<void>
8686

8787
co_spawn(ex, receiver(conn), detached);
8888
co_spawn(ex, publisher(stream, conn), detached);
89-
conn->async_run(cfg, {}, consign(detached, conn));
89+
conn->async_run(cfg, consign(detached, conn));
9090

9191
signal_set sig_set{ex, SIGINT, SIGTERM};
9292
co_await sig_set.async_wait();

example/cpp20_containers.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ auto transaction(std::shared_ptr<connection> conn) -> awaitable<void>
133133
awaitable<void> co_main(config cfg)
134134
{
135135
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
136-
conn->async_run(cfg, {}, consign(detached, conn));
136+
conn->async_run(cfg, consign(detached, conn));
137137

138138
co_await store(conn);
139139
co_await transaction(conn);

example/cpp20_echo_server.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ auto co_main(config cfg) -> asio::awaitable<void>
6060
auto ex = co_await asio::this_coro::executor;
6161
auto conn = std::make_shared<connection>(ex);
6262
asio::co_spawn(ex, listener(conn), asio::detached);
63-
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
63+
conn->async_run(cfg, asio::consign(asio::detached, conn));
6464

6565
signal_set sig_set(ex, SIGINT, SIGTERM);
6666
co_await sig_set.async_wait();

example/cpp20_intro.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ using boost::redis::connection;
2424
auto co_main(config cfg) -> asio::awaitable<void>
2525
{
2626
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
27-
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
27+
conn->async_run(cfg, asio::consign(asio::detached, conn));
2828

2929
// A request containing only a ping command.
3030
request req;

example/cpp20_intro_tls.cpp

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include <boost/asio/consign.hpp>
1010
#include <boost/asio/detached.hpp>
11+
#include <boost/asio/ssl/context.hpp>
1112
#include <boost/asio/use_awaitable.hpp>
1213

1314
#include <iostream>
@@ -35,17 +36,18 @@ auto co_main(config cfg) -> asio::awaitable<void>
3536
cfg.addr.host = "db.occase.de";
3637
cfg.addr.port = "6380";
3738

38-
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
39-
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
39+
asio::ssl::context ctx{asio::ssl::context::tlsv12_client};
40+
ctx.set_verify_mode(asio::ssl::verify_peer);
41+
ctx.set_verify_callback(verify_certificate);
42+
43+
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor, std::move(ctx));
44+
conn->async_run(cfg, asio::consign(asio::detached, conn));
4045

4146
request req;
4247
req.push("PING");
4348

4449
response<std::string> resp;
4550

46-
conn->next_layer().set_verify_mode(asio::ssl::verify_peer);
47-
conn->next_layer().set_verify_callback(verify_certificate);
48-
4951
co_await conn->async_exec(req, resp);
5052
conn->cancel();
5153

example/cpp20_json.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ auto co_main(config cfg) -> asio::awaitable<void>
5858
{
5959
auto ex = co_await asio::this_coro::executor;
6060
auto conn = std::make_shared<connection>(ex);
61-
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
61+
conn->async_run(cfg, asio::consign(asio::detached, conn));
6262

6363
// user object that will be stored in Redis in json format.
6464
user const u{"Joao", "58", "Brazil"};

example/cpp20_protobuf.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ asio::awaitable<void> co_main(config cfg)
6464
{
6565
auto ex = co_await asio::this_coro::executor;
6666
auto conn = std::make_shared<connection>(ex);
67-
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
67+
conn->async_run(cfg, asio::consign(asio::detached, conn));
6868

6969
person p;
7070
p.set_name("Louis");

example/cpp20_resolve_with_sentinel.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,9 @@ auto resolve_master_address(std::vector<address> const& addresses) -> asio::awai
4444
// TODO: async_run and async_exec should be lauched in
4545
// parallel here so we can wait for async_run completion
4646
// before eventually calling it again.
47-
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
47+
conn->async_run(cfg, asio::consign(asio::detached, conn));
4848
co_await conn->async_exec(req, resp, redir(ec));
4949
conn->cancel();
50-
conn->reset_stream();
5150
if (!ec && std::get<0>(resp))
5251
co_return address{
5352
std::get<0>(resp).value().value().at(0),

example/cpp20_streams.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ auto co_main(config cfg) -> net::awaitable<void>
8888

8989
// Disable health checks.
9090
cfg.health_check_interval = std::chrono::seconds::zero();
91-
conn->async_run(cfg, {}, net::consign(net::detached, conn));
91+
conn->async_run(cfg, net::consign(net::detached, conn));
9292

9393
signal_set sig_set(ex, SIGINT, SIGTERM);
9494
co_await sig_set.async_wait();

example/cpp20_subscriber.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ auto co_main(config cfg) -> asio::awaitable<void>
8787
auto ex = co_await asio::this_coro::executor;
8888
auto conn = std::make_shared<connection>(ex);
8989
asio::co_spawn(ex, receiver(conn), asio::detached);
90-
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
90+
conn->async_run(cfg, asio::consign(asio::detached, conn));
9191

9292
signal_set sig_set(ex, SIGINT, SIGTERM);
9393
co_await sig_set.async_wait();

example/cpp20_unix_sockets.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ auto co_main(config cfg) -> asio::awaitable<void>
3434
cfg.unix_socket = "/tmp/redis-socks/redis.sock";
3535

3636
auto conn = std::make_shared<connection>(co_await asio::this_coro::executor);
37-
conn->async_run(cfg, {}, asio::consign(asio::detached, conn));
37+
conn->async_run(cfg, asio::consign(asio::detached, conn));
3838

3939
request req;
4040
req.push("PING");

example/sync_connection.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class sync_connection {
3333
// Starts a thread that will can io_context::run on which the
3434
// connection will run.
3535
thread_ = std::thread{[this, cfg]() {
36-
conn_->async_run(cfg, {}, asio::detached);
36+
conn_->async_run(cfg, asio::detached);
3737
ioc_.run();
3838
}};
3939
}

include/boost/redis/config.hpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,13 @@ struct config {
6060
/// Message used by the health-checker in `boost::redis::connection::async_run`.
6161
std::string health_check_id = "Boost.Redis";
6262

63-
/// Logger prefix, see `boost::redis::logger`.
63+
/**
64+
* @brief (Deprecated) Sets the logger prefix, a string printed before log messages.
65+
*
66+
* Setting a prefix in this struct is deprecated. If you need to change how log messages
67+
* look like, please construct a logger object passing a formatting function, and use that
68+
* logger in connection's constructor. This member will be removed in subsequent releases.
69+
*/
6470
std::string log_prefix = "(Boost.Redis) ";
6571

6672
/// Time the resolve operation is allowed to last.

0 commit comments

Comments
 (0)