2
0
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:
Anarthal (Rubén Pérez)
2025-06-23 12:07:21 +02:00
committed by GitHub
parent 7304d99bf6
commit f04d97ffa5
34 changed files with 1061 additions and 381 deletions

View File

@@ -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)

View File

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

View File

@@ -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)

View File

@@ -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
View 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
View 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();
}