From 4cfa31fae4cac9294f34f636d37414b724fdbd14 Mon Sep 17 00:00:00 2001 From: Vyas Ramasubramani Date: Tue, 26 Nov 2024 12:23:40 -0800 Subject: [PATCH] Create logger wrapper around spdlog that can be easily reused in other libraries (#1722) This PR defines a new way to produce a logger wrapping spdlog. The logger's interface is declared in a template header file that can be processed by CMake to produce an interface that may be customized for placement into any project. The new implementation uses the PImpl idiom to isolate the spdlog (and transitively, fmt) dependency from the public API of the logger. The implementation is defined in an impl header. A corresponding source template file is provided that simply includes this header. All of these files are wrapped in some CMake logic for producing a custom target for a given project. rmm leverages this new logger by requesting the creation of a logger target and a corresponding implementation. This is a breaking change because consumers of rmm will need to link the new `rmm_logger_impl` target into their own libraries to get logging support. Once this gets merged, the plan is to move this implementation out of rmm into its own repository. At that point, the logger may also be used to completely replace logger implementations in cudf, raft, and cuml (as well as any other RAPIDS libraries that are aiming to provide their own logging implementation). Once everything in RAPIDS is migrated to using the new logger, we will update the way that it uses spdlog to completely hide all spdlog symbols, which solves a half dozen different problems for us when it comes to packaging (symbol collision issues, ABI compatibility, conda environment conflicts, bundling of headers into conda packages, etc). Resolves #1709 Authors: - Vyas Ramasubramani (https://github.com/vyasr) Approvers: - Robert Maynard (https://github.com/robertmaynard) - Mark Harris (https://github.com/harrism) - Jake Awe (https://github.com/AyodeAwe) URL: https://github.com/rapidsai/rmm/pull/1722 --- CMakeLists.txt | 6 +- README.md | 4 +- benchmarks/CMakeLists.txt | 11 +- benchmarks/replay/replay.cpp | 4 +- ci/check_symbols.sh | 23 +- cmake/thirdparty/get_spdlog.cmake | 3 +- include/rmm/detail/format.hpp | 48 -- include/rmm/detail/logging_assert.hpp | 2 +- include/rmm/logger.hpp | 117 ---- .../rmm/mr/device/arena_memory_resource.hpp | 26 +- include/rmm/mr/device/detail/arena.hpp | 28 +- .../detail/stream_ordered_memory_resource.hpp | 2 +- .../mr/device/logging_resource_adaptor.hpp | 64 +-- .../mr/device/tracking_resource_adaptor.hpp | 4 +- python/rmm/CMakeLists.txt | 6 +- python/rmm/docs/conf.py | 2 + python/rmm/rmm/__init__.py | 4 +- python/rmm/rmm/_cuda/CMakeLists.txt | 2 +- python/rmm/rmm/librmm/CMakeLists.txt | 2 +- python/rmm/rmm/librmm/_logger.pxd | 71 +-- python/rmm/rmm/librmm/_logger.pyx | 2 - python/rmm/rmm/pylibrmm/CMakeLists.txt | 2 +- python/rmm/rmm/pylibrmm/logger.pyx | 24 +- python/rmm/rmm/tests/test_rmm.py | 12 +- rapids_logger/CMakeLists.txt | 177 ++++++ rapids_logger/README.md | 22 + rapids_logger/logger.cpp.in | 17 + rapids_logger/logger.hpp.in | 504 ++++++++++++++++++ rapids_logger/logger_impl.hpp.in | 205 +++++++ tests/CMakeLists.txt | 19 +- tests/logger_tests.cpp | 4 +- tests/mr/device/tracking_mr_tests.cpp | 15 +- 32 files changed, 1068 insertions(+), 364 deletions(-) delete mode 100644 include/rmm/logger.hpp create mode 100644 rapids_logger/CMakeLists.txt create mode 100644 rapids_logger/README.md create mode 100644 rapids_logger/logger.cpp.in create mode 100644 rapids_logger/logger.hpp.in create mode 100644 rapids_logger/logger_impl.hpp.in diff --git a/CMakeLists.txt b/CMakeLists.txt index 07bd368ee..7a7ba3525 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,7 +73,9 @@ rapids_find_package( # add third party dependencies using CPM rapids_cpm_init() -include(cmake/thirdparty/get_spdlog.cmake) +add_subdirectory(rapids_logger) +rapids_make_logger(rmm EXPORT_SET rmm-exports) + include(cmake/thirdparty/get_cccl.cmake) include(cmake/thirdparty/get_nvtx.cmake) @@ -93,8 +95,8 @@ else() target_link_libraries(rmm INTERFACE CUDA::cudart) endif() +target_link_libraries(rmm INTERFACE rmm_logger) target_link_libraries(rmm INTERFACE CCCL::CCCL) -target_link_libraries(rmm INTERFACE spdlog::spdlog_header_only) target_link_libraries(rmm INTERFACE dl) target_link_libraries(rmm INTERFACE nvtx3::nvtx3-cpp) target_compile_features(rmm INTERFACE cxx_std_17 $) diff --git a/README.md b/README.md index 0a25cd348..d72d411f5 100644 --- a/README.md +++ b/README.md @@ -658,8 +658,8 @@ of more detailed logging. The default is `INFO`. Available levels are `TRACE`, ` The log relies on the [spdlog](https://github.com/gabime/spdlog.git) library. Note that to see logging below the `INFO` level, the application must also set the logging level at -run time. C++ applications must must call `rmm::logger().set_level()`, for example to enable all -levels of logging down to `TRACE`, call `rmm::logger().set_level(spdlog::level::trace)` (and compile +run time. C++ applications must must call `rmm::default_logger().set_level()`, for example to enable all +levels of logging down to `TRACE`, call `rmm::default_logger().set_level(spdlog::level::trace)` (and compile librmm with `-DRMM_LOGGING_LEVEL=TRACE`). Python applications must call `rmm.set_logging_level()`, for example to enable all levels of logging down to `TRACE`, call `rmm.set_logging_level("trace")` (and compile the RMM Python module with `-DRMM_LOGGING_LEVEL=TRACE`). diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 0487a2dfa..b3b60cfcb 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -34,9 +34,9 @@ function(ConfigureBench BENCH_NAME) RUNTIME_OUTPUT_DIRECTORY "$" CUDA_ARCHITECTURES "${CMAKE_CUDA_ARCHITECTURES}" INSTALL_RPATH "\$ORIGIN/../../../lib") - target_link_libraries(${BENCH_NAME} benchmark::benchmark pthread rmm) + target_link_libraries(${BENCH_NAME} PRIVATE benchmark::benchmark pthread rmm) target_compile_definitions(${BENCH_NAME} - PUBLIC "SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_${RMM_LOGGING_LEVEL}") + PUBLIC "RMM_LOG_ACTIVE_LEVEL=RMM_LOG_LEVEL_${RMM_LOGGING_LEVEL}") if(PER_THREAD_DEFAULT_STREAM) target_compile_definitions(${BENCH_NAME} PUBLIC CUDA_API_PER_THREAD_DEFAULT_STREAM) @@ -44,6 +44,9 @@ function(ConfigureBench BENCH_NAME) target_compile_options(${BENCH_NAME} PUBLIC $<$:-Wall -Werror -Wno-unknown-pragmas>) + + target_link_libraries(${BENCH_NAME} PRIVATE rmm_bench_logger) + if(DISABLE_DEPRECATION_WARNING) target_compile_options( ${BENCH_NAME} PUBLIC $<$:-Xcompiler=-Wno-deprecated-declarations>) @@ -58,6 +61,10 @@ function(ConfigureBench BENCH_NAME) EXCLUDE_FROM_ALL) endfunction(ConfigureBench) +# Create an object library for the logger so that we don't have to recompile it. +add_library(rmm_bench_logger OBJECT) +target_link_libraries(rmm_bench_logger PRIVATE rmm_logger_impl) + # random allocations benchmark ConfigureBench(RANDOM_ALLOCATIONS_BENCH random_allocations/random_allocations.cpp) diff --git a/benchmarks/replay/replay.cpp b/benchmarks/replay/replay.cpp index 7f45b7691..8edbf11f9 100644 --- a/benchmarks/replay/replay.cpp +++ b/benchmarks/replay/replay.cpp @@ -33,10 +33,10 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -403,7 +403,7 @@ int main(int argc, char** argv) auto const num_threads = per_thread_events.size(); // Uncomment to enable / change default log level - // rmm::detail::logger().set_level(spdlog::level::trace); + // rmm::logger().set_level(rmm::level_enum::trace); if (args.count("resource") > 0) { std::string mr_name = args["resource"].as(); diff --git a/ci/check_symbols.sh b/ci/check_symbols.sh index 1d73a082b..155e509da 100755 --- a/ci/check_symbols.sh +++ b/ci/check_symbols.sh @@ -47,32 +47,13 @@ for dso_file in ${dso_files}; do echo " * WEAK: $(grep --count -E ' WEAK ' < ${symbol_file})" echo " * LOCAL: $(grep --count -E ' LOCAL ' < ${symbol_file})" - # Explanation for '-v' uses here: - # - # * 'format_error' symbols are intentionally exported, that type of error - # can be thrown across library boundaries. See "Problems with C++ exceptions" - # at https://gcc.gnu.org/wiki/Visibility. echo "checking for 'fmt::' symbols..." - if grep -E 'fmt\:\:' < "${symbol_file}" \ - | grep -v 'format_error' - then + if grep -E 'fmt\:\:' < "${symbol_file}"; then raise-symbols-found-error 'fmt::' fi - # Explanation for '-v' uses here: - # - # * trivially-destructible objects sometimes get an entry in the symbol table - # for a specialization of `std::_Destroy_aux()` called to destroy them. - # There is one for `spdlog::details::log_msg_buffer like that: - # - # 'std::_Destroy_aux::__destroy' - # - # That should be safe to export. - # echo "checking for 'spdlog::' symbols..." - if grep -E 'spdlog\:\:' < "${symbol_file}" \ - | grep -v 'std\:\:_Destroy_aux' - then + if grep -E 'spdlog\:\:' < "${symbol_file}"; then raise-symbols-found-error 'spdlog::' fi echo "No symbol visibility issues found" diff --git a/cmake/thirdparty/get_spdlog.cmake b/cmake/thirdparty/get_spdlog.cmake index 7f80b3726..212f604c3 100644 --- a/cmake/thirdparty/get_spdlog.cmake +++ b/cmake/thirdparty/get_spdlog.cmake @@ -12,7 +12,7 @@ # the License. # ============================================================================= -# Use CPM to find or clone speedlog +# Use CPM to find or clone speedlog. function(find_and_configure_spdlog) include(${rapids-cmake-dir}/cpm/spdlog.cmake) @@ -20,7 +20,6 @@ function(find_and_configure_spdlog) FMT_OPTION "EXTERNAL_FMT_HO" INSTALL_EXPORT_SET rmm-exports BUILD_EXPORT_SET rmm-exports) - endfunction() find_and_configure_spdlog() diff --git a/include/rmm/detail/format.hpp b/include/rmm/detail/format.hpp index 21acac032..6cd1dd9d2 100644 --- a/include/rmm/detail/format.hpp +++ b/include/rmm/detail/format.hpp @@ -20,60 +20,12 @@ #include #include -#include -#include -#include #include -#include #include namespace RMM_NAMESPACE { namespace detail { -/** - * @brief Format a message string with printf-style formatting - * - * This function performs printf-style formatting to avoid the need for fmt - * or spdlog's own templated APIs (which would require exposing spdlog - * symbols publicly) and returns the formatted message as a `std::string`. - * - * @param format The format string - * @param args The format arguments - * @return The formatted message - * @throw rmm::logic_error if an error occurs during formatting - */ -template -std::string formatted_log(std::string const& format, Args&&... args) -{ - auto convert_to_c_string = [](auto&& arg) -> decltype(auto) { - using ArgType = std::decay_t; - if constexpr (std::is_same_v) { - return arg.c_str(); - } else { - return std::forward(arg); - } - }; - - // NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) - auto retsize = - std::snprintf(nullptr, 0, format.c_str(), convert_to_c_string(std::forward(args))...); - RMM_EXPECTS(retsize >= 0, "Error during formatting."); - if (retsize == 0) { return {}; } - auto size = static_cast(retsize) + 1; // for null terminator - // NOLINTNEXTLINE(modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays) - std::unique_ptr buf(new char[size]); - std::snprintf(buf.get(), size, format.c_str(), convert_to_c_string(std::forward(args))...); - // NOLINTEND(cppcoreguidelines-pro-type-vararg) - return {buf.get(), buf.get() + size - 1}; // drop '\0' -} - -// specialization for no arguments -template <> -inline std::string formatted_log(std::string const& format) -{ - return format; -} - // Stringify a size in bytes to a human-readable value inline std::string format_bytes(std::size_t value) { diff --git a/include/rmm/detail/logging_assert.hpp b/include/rmm/detail/logging_assert.hpp index 4d702ee2b..c3b12ffe3 100644 --- a/include/rmm/detail/logging_assert.hpp +++ b/include/rmm/detail/logging_assert.hpp @@ -31,7 +31,7 @@ */ #ifdef NDEBUG #define RMM_LOGGING_ASSERT(_expr) (void)0 -#elif SPDLOG_ACTIVE_LEVEL < SPDLOG_LEVEL_OFF +#elif RMM_LOG_ACTIVE_LEVEL < RMM_LOG_LEVEL_OFF #define RMM_LOGGING_ASSERT(_expr) \ do { \ bool const success = (_expr); \ diff --git a/include/rmm/logger.hpp b/include/rmm/logger.hpp deleted file mode 100644 index 2cfd921b1..000000000 --- a/include/rmm/logger.hpp +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2020-2024, NVIDIA CORPORATION. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -#include -#include - -#include - -namespace RMM_NAMESPACE { - -namespace detail { - -/** - * @brief Returns the default log filename for the RMM global logger. - * - * If the environment variable `RMM_DEBUG_LOG_FILE` is defined, its value is used as the path and - * name of the log file. Otherwise, the file `rmm_log.txt` in the current working directory is used. - * - * @return std::string The default log file name. - */ -inline std::string default_log_filename() -{ - auto* filename = std::getenv("RMM_DEBUG_LOG_FILE"); - return (filename == nullptr) ? std::string{"rmm_log.txt"} : std::string{filename}; -} - -/** - * @brief Simple wrapper around a spdlog::logger that performs RMM-specific initialization - */ -struct logger_wrapper { - spdlog::logger logger_; ///< The underlying logger - - logger_wrapper() - : logger_{"RMM", - std::make_shared( - default_log_filename(), true // truncate file - )} - { - logger_.set_pattern("[%6t][%H:%M:%S:%f][%-6l] %v"); - logger_.flush_on(spdlog::level::warn); -#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_INFO -#ifdef CUDA_API_PER_THREAD_DEFAULT_STREAM - logger_.info("----- RMM LOG BEGIN [PTDS ENABLED] -----"); -#else - logger_.info("----- RMM LOG BEGIN [PTDS DISABLED] -----"); -#endif - logger_.flush(); -#endif - } -}; - -inline spdlog::logger& logger() -{ - static detail::logger_wrapper wrapped{}; - return wrapped.logger_; -} -} // namespace detail - -/** - * @brief Returns the global RMM logger - * - * @ingroup logging - * - * This is a spdlog logger. The easiest way to log messages is to use the `RMM_LOG_*` macros. - * - * @return spdlog::logger& The logger. - */ -[[deprecated( - "Support for direct access to spdlog loggers in rmm is planned for " - "removal")]] RMM_EXPORT inline spdlog::logger& -logger() -{ - return detail::logger(); -} - -//! @cond Doxygen_Suppress -// -// The default is INFO, but it should be used sparingly, so that by default a log file is only -// output if there is important information, warnings, errors, and critical failures -// Log messages that require computation should only be used at level TRACE and DEBUG -#define RMM_LOG_TRACE(...) \ - SPDLOG_LOGGER_TRACE(&rmm::detail::logger(), rmm::detail::formatted_log(__VA_ARGS__)) -#define RMM_LOG_DEBUG(...) \ - SPDLOG_LOGGER_DEBUG(&rmm::detail::logger(), rmm::detail::formatted_log(__VA_ARGS__)) -#define RMM_LOG_INFO(...) \ - SPDLOG_LOGGER_INFO(&rmm::detail::logger(), rmm::detail::formatted_log(__VA_ARGS__)) -#define RMM_LOG_WARN(...) \ - SPDLOG_LOGGER_WARN(&rmm::detail::logger(), rmm::detail::formatted_log(__VA_ARGS__)) -#define RMM_LOG_ERROR(...) \ - SPDLOG_LOGGER_ERROR(&rmm::detail::logger(), rmm::detail::formatted_log(__VA_ARGS__)) -#define RMM_LOG_CRITICAL(...) \ - SPDLOG_LOGGER_CRITICAL(&rmm::detail::logger(), rmm::detail::formatted_log(__VA_ARGS__)) - -//! @endcond - -} // namespace RMM_NAMESPACE - -//! @endcond diff --git a/include/rmm/mr/device/arena_memory_resource.hpp b/include/rmm/mr/device/arena_memory_resource.hpp index d3a4bb09d..fe07aab04 100644 --- a/include/rmm/mr/device/arena_memory_resource.hpp +++ b/include/rmm/mr/device/arena_memory_resource.hpp @@ -27,8 +27,6 @@ #include -#include - #include #include #include @@ -98,12 +96,9 @@ class arena_memory_resource final : public device_memory_resource { : global_arena_{upstream_mr, arena_size}, dump_log_on_failure_{dump_log_on_failure} { if (dump_log_on_failure_) { - logger_ = - std::make_shared("arena_memory_dump", - std::make_shared( - "rmm_arena_memory_dump.log", true /*truncate file*/)); + logger_ = std::make_shared("arena_memory_dump", "rmm_arena_memory_dump.log"); // Set the level to `debug` for more detailed output. - logger_->set_level(spdlog::level::info); + logger_->set_level(level_enum::info); } } @@ -120,17 +115,9 @@ class arena_memory_resource final : public device_memory_resource { explicit arena_memory_resource(Upstream* upstream_mr, std::optional arena_size = std::nullopt, bool dump_log_on_failure = false) - : global_arena_{to_device_async_resource_ref_checked(upstream_mr), arena_size}, - dump_log_on_failure_{dump_log_on_failure} + : arena_memory_resource{ + to_device_async_resource_ref_checked(upstream_mr), arena_size, dump_log_on_failure} { - if (dump_log_on_failure_) { - logger_ = - std::make_shared("arena_memory_dump", - std::make_shared( - "rmm_arena_memory_dump.log", true /*truncate file*/)); - // Set the level to `debug` for more detailed output. - logger_->set_level(spdlog::level::info); - } } ~arena_memory_resource() override = default; @@ -336,8 +323,7 @@ class arena_memory_resource final : public device_memory_resource { void dump_memory_log(size_t bytes) { logger_->info("**************************************************"); - logger_->info(rmm::detail::formatted_log("Ran out of memory trying to allocate %s.", - rmm::detail::format_bytes(bytes))); + logger_->info("Ran out of memory trying to allocate %s.", rmm::detail::format_bytes(bytes)); logger_->info("**************************************************"); logger_->info("Global arena:"); global_arena_.dump_memory_log(logger_); @@ -366,7 +352,7 @@ class arena_memory_resource final : public device_memory_resource { /// If true, dump memory information to log on allocation failure. bool dump_log_on_failure_{}; /// The logger for memory dump. - std::shared_ptr logger_{}; + std::shared_ptr logger_{}; /// Mutex for read and write locks on arena maps. mutable std::shared_mutex map_mtx_; /// Mutex for shared and unique locks on the mr. diff --git a/include/rmm/mr/device/detail/arena.hpp b/include/rmm/mr/device/detail/arena.hpp index 419c4fcf4..20095d504 100644 --- a/include/rmm/mr/device/detail/arena.hpp +++ b/include/rmm/mr/device/detail/arena.hpp @@ -28,8 +28,6 @@ #include -#include - #include #include #include @@ -645,33 +643,29 @@ class global_arena final { /** * @brief Dump memory to log. * - * @param logger the spdlog logger to use + * @param logger the logger to use */ - RMM_HIDDEN void dump_memory_log(std::shared_ptr const& logger) const + void dump_memory_log(std::shared_ptr const& logger) const { std::lock_guard lock(mtx_); - logger->info(rmm::detail::formatted_log(" Arena size: %s", - rmm::detail::format_bytes(upstream_block_.size()))); - logger->info(rmm::detail::formatted_log(" # superblocks: %zu", superblocks_.size())); + logger->info(" Arena size: %s", rmm::detail::format_bytes(upstream_block_.size())); + logger->info(" # superblocks: %zu", superblocks_.size()); if (!superblocks_.empty()) { - logger->debug( - rmm::detail::formatted_log(" Total size of superblocks: %s", - rmm::detail::format_bytes(total_memory_size(superblocks_)))); + logger->debug(" Total size of superblocks: %s", + rmm::detail::format_bytes(total_memory_size(superblocks_))); auto const total_free = total_free_size(superblocks_); auto const max_free = max_free_size(superblocks_); auto const fragmentation = (1 - max_free / static_cast(total_free)) * 100; - logger->info(rmm::detail::formatted_log(" Total free memory: %s", - rmm::detail::format_bytes(total_free))); - logger->info(rmm::detail::formatted_log(" Largest block of free memory: %s", - rmm::detail::format_bytes(max_free))); - logger->info(rmm::detail::formatted_log(" Fragmentation: %0.2f", fragmentation)); + logger->info(" Total free memory: %s", rmm::detail::format_bytes(total_free)); + logger->info(" Largest block of free memory: %s", rmm::detail::format_bytes(max_free)); + logger->info(" Fragmentation: %0.2f", fragmentation); auto index = decltype(superblocks_.size()){0}; char* prev_end{}; for (auto const& sblk : superblocks_) { if (prev_end == nullptr) { prev_end = sblk.pointer(); } - logger->debug(rmm::detail::formatted_log( + logger->debug( " Superblock %zu: start=%p, end=%p, size=%s, empty=%s, # free blocks=%zu, max " "free=%s, " "gap=%s", @@ -682,7 +676,7 @@ class global_arena final { sblk.empty() ? "T" : "F", sblk.free_blocks(), rmm::detail::format_bytes(sblk.max_free_size()), - rmm::detail::format_bytes(static_cast(sblk.pointer() - prev_end)))); + rmm::detail::format_bytes(static_cast(sblk.pointer() - prev_end))); prev_end = sblk.end(); index++; } diff --git a/include/rmm/mr/device/detail/stream_ordered_memory_resource.hpp b/include/rmm/mr/device/detail/stream_ordered_memory_resource.hpp index f177504f2..0900d44b2 100644 --- a/include/rmm/mr/device/detail/stream_ordered_memory_resource.hpp +++ b/include/rmm/mr/device/detail/stream_ordered_memory_resource.hpp @@ -458,7 +458,7 @@ class stream_ordered_memory_resource : public crtp, public device_ void log_summary_trace() { -#if (SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_TRACE) +#if (RMM_LOG_ACTIVE_LEVEL <= RMM_LOG_LEVEL_TRACE) std::size_t num_blocks{0}; std::size_t max_block{0}; std::size_t free_mem{0}; diff --git a/include/rmm/mr/device/logging_resource_adaptor.hpp b/include/rmm/mr/device/logging_resource_adaptor.hpp index 578543852..3e6e5babc 100644 --- a/include/rmm/mr/device/logging_resource_adaptor.hpp +++ b/include/rmm/mr/device/logging_resource_adaptor.hpp @@ -19,14 +19,10 @@ #include #include #include +#include #include #include -#include -#include -#include -#include - #include #include #include @@ -35,6 +31,7 @@ namespace RMM_NAMESPACE { namespace mr { + /** * @addtogroup device_resource_adaptors * @{ @@ -78,9 +75,8 @@ class logging_resource_adaptor final : public device_memory_resource { logging_resource_adaptor(Upstream* upstream, std::string const& filename = get_default_filename(), bool auto_flush = false) - : logger_{make_logger(filename)}, upstream_{to_device_async_resource_ref_checked(upstream)} + : logging_resource_adaptor(to_device_async_resource_ref_checked(upstream), filename, auto_flush) { - init_logger(auto_flush); } /** @@ -98,9 +94,8 @@ class logging_resource_adaptor final : public device_memory_resource { * performance. */ logging_resource_adaptor(Upstream* upstream, std::ostream& stream, bool auto_flush = false) - : logger_{make_logger(stream)}, upstream_{to_device_async_resource_ref_checked(upstream)} + : logging_resource_adaptor(to_device_async_resource_ref_checked(upstream), stream, auto_flush) { - init_logger(auto_flush); } /** @@ -118,11 +113,10 @@ class logging_resource_adaptor final : public device_memory_resource { * performance. */ logging_resource_adaptor(Upstream* upstream, - spdlog::sinks_init_list sinks, + std::initializer_list sinks, bool auto_flush = false) - : logger_{make_logger(sinks)}, upstream_{to_device_async_resource_ref_checked(upstream)} + : logging_resource_adaptor{to_device_async_resource_ref_checked(upstream), sinks, auto_flush} { - init_logger(auto_flush); } /** @@ -148,9 +142,8 @@ class logging_resource_adaptor final : public device_memory_resource { logging_resource_adaptor(device_async_resource_ref upstream, std::string const& filename = get_default_filename(), bool auto_flush = false) - : logger_{make_logger(filename)}, upstream_{upstream} + : logging_resource_adaptor{make_logger(filename), upstream, auto_flush} { - init_logger(auto_flush); } /** @@ -168,9 +161,8 @@ class logging_resource_adaptor final : public device_memory_resource { logging_resource_adaptor(device_async_resource_ref upstream, std::ostream& stream, bool auto_flush = false) - : logger_{make_logger(stream)}, upstream_{upstream} + : logging_resource_adaptor{make_logger(stream), upstream, auto_flush} { - init_logger(auto_flush); } /** @@ -186,11 +178,10 @@ class logging_resource_adaptor final : public device_memory_resource { * performance. */ logging_resource_adaptor(device_async_resource_ref upstream, - spdlog::sinks_init_list sinks, + std::initializer_list sinks, bool auto_flush = false) - : logger_{make_logger(sinks)}, upstream_{upstream} + : logging_resource_adaptor{make_logger(sinks), upstream, auto_flush} { - init_logger(auto_flush); } logging_resource_adaptor() = delete; @@ -241,29 +232,24 @@ class logging_resource_adaptor final : public device_memory_resource { } private: - static auto make_logger(std::ostream& stream) - { - return std::make_shared( - "RMM", std::make_shared(stream)); - } + static auto make_logger(std::ostream& stream) { return std::make_shared("RMM", stream); } static auto make_logger(std::string const& filename) { - return std::make_shared( - "RMM", std::make_shared(filename, true /*truncate file*/)); + return std::make_shared("RMM", filename); } - static auto make_logger(spdlog::sinks_init_list sinks) + static auto make_logger(std::initializer_list sinks) { - return std::make_shared("RMM", sinks); + return std::make_shared("RMM", sinks); } - /** - * @brief Initialize the logger. - */ - void init_logger(bool auto_flush) + logging_resource_adaptor(std::shared_ptr logger, + device_async_resource_ref upstream, + bool auto_flush) + : logger_{logger}, upstream_{upstream} { - if (auto_flush) { logger_->flush_on(spdlog::level::info); } + if (auto_flush) { logger_->flush_on(level_enum::info); } logger_->set_pattern("%v"); logger_->info(header()); logger_->set_pattern("%t,%H:%M:%S.%f,%v"); @@ -298,12 +284,11 @@ class logging_resource_adaptor final : public device_memory_resource { { try { auto const ptr = get_upstream_resource().allocate_async(bytes, stream); - logger_->info(rmm::detail::formatted_log( - "allocate,%p,%zu,%s", ptr, bytes, rmm::detail::format_stream(stream))); + logger_->info("allocate,%p,%zu,%s", ptr, bytes, rmm::detail::format_stream(stream)); return ptr; } catch (...) { - logger_->info(rmm::detail::formatted_log( - "allocate failure,%p,%zu,%s", nullptr, bytes, rmm::detail::format_stream(stream))); + logger_->info( + "allocate failure,%p,%zu,%s", nullptr, bytes, rmm::detail::format_stream(stream)); throw; } } @@ -324,8 +309,7 @@ class logging_resource_adaptor final : public device_memory_resource { */ void do_deallocate(void* ptr, std::size_t bytes, cuda_stream_view stream) override { - logger_->info( - rmm::detail::formatted_log("free,%p,%zu,%s", ptr, bytes, rmm::detail::format_stream(stream))); + logger_->info("free,%p,%zu,%s", ptr, bytes, rmm::detail::format_stream(stream)); get_upstream_resource().deallocate_async(ptr, bytes, stream); } @@ -344,7 +328,7 @@ class logging_resource_adaptor final : public device_memory_resource { return get_upstream_resource() == cast->get_upstream_resource(); } - std::shared_ptr logger_; ///< spdlog logger object + std::shared_ptr logger_{}; device_async_resource_ref upstream_; ///< The upstream resource used for satisfying ///< allocation requests diff --git a/include/rmm/mr/device/tracking_resource_adaptor.hpp b/include/rmm/mr/device/tracking_resource_adaptor.hpp index 8131eef4d..09631960e 100644 --- a/include/rmm/mr/device/tracking_resource_adaptor.hpp +++ b/include/rmm/mr/device/tracking_resource_adaptor.hpp @@ -185,9 +185,9 @@ class tracking_resource_adaptor final : public device_memory_resource { */ void log_outstanding_allocations() const { -#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG +#if RMM_LOG_ACTIVE_LEVEL <= RMM_LOG_LEVEL_DEBUG RMM_LOG_DEBUG("Outstanding Allocations: %s", get_outstanding_allocations_str()); -#endif // SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG +#endif // RMM_LOG_ACTIVE_LEVEL <= RMM_LOG_LEVEL_DEBUG } private: diff --git a/python/rmm/CMakeLists.txt b/python/rmm/CMakeLists.txt index ac8495e14..a39ac8868 100644 --- a/python/rmm/CMakeLists.txt +++ b/python/rmm/CMakeLists.txt @@ -27,7 +27,11 @@ include(rapids-cython-core) rapids_cython_init() # pass through logging level to spdlog -add_compile_definitions("SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_${RMM_LOGGING_LEVEL}") +add_compile_definitions("RMM_LOG_ACTIVE_LEVEL=RMM_LOG_LEVEL_${RMM_LOGGING_LEVEL}") + +# Create an object library for the logger so that we don't have to recompile it. +add_library(cpp_logger OBJECT) +target_link_libraries(cpp_logger PRIVATE rmm::rmm_logger_impl) add_subdirectory(rmm/_cuda) add_subdirectory(rmm/librmm) diff --git a/python/rmm/docs/conf.py b/python/rmm/docs/conf.py index 2aad3a82c..99242daa5 100644 --- a/python/rmm/docs/conf.py +++ b/python/rmm/docs/conf.py @@ -238,6 +238,8 @@ def on_missing_reference(app, env, node, contnode): "thrust", "spdlog", "stream_ref", + # logger names (we may eventually want to link out for those) + "sink_ptr", # libcu++ names "cuda", "cuda::mr", diff --git a/python/rmm/rmm/__init__.py b/python/rmm/rmm/__init__.py index 832fec095..5c865eba8 100644 --- a/python/rmm/rmm/__init__.py +++ b/python/rmm/rmm/__init__.py @@ -22,7 +22,7 @@ flush_logger, get_flush_level, get_logging_level, - logging_level, + level_enum, set_flush_level, set_logging_level, should_log, @@ -45,7 +45,7 @@ "get_log_filenames", "get_logging_level", "is_initialized", - "logging_level", + "level_enum", "mr", "register_reinitialize_hook", "reinitialize", diff --git a/python/rmm/rmm/_cuda/CMakeLists.txt b/python/rmm/rmm/_cuda/CMakeLists.txt index 7fd27d110..7759432d3 100644 --- a/python/rmm/rmm/_cuda/CMakeLists.txt +++ b/python/rmm/rmm/_cuda/CMakeLists.txt @@ -13,7 +13,7 @@ # ============================================================================= set(cython_sources stream.pyx) -set(linked_libraries rmm::rmm) +set(linked_libraries rmm::rmm cpp_logger) rapids_cython_create_modules(SOURCE_FILES "${cython_sources}" LINKED_LIBRARIES "${linked_libraries}" CXX) diff --git a/python/rmm/rmm/librmm/CMakeLists.txt b/python/rmm/rmm/librmm/CMakeLists.txt index 5da2a1a01..dc807fdba 100644 --- a/python/rmm/rmm/librmm/CMakeLists.txt +++ b/python/rmm/rmm/librmm/CMakeLists.txt @@ -13,7 +13,7 @@ # ============================================================================= set(cython_sources _logger.pyx) -set(linked_libraries rmm::rmm) +set(linked_libraries rmm::rmm cpp_logger) # Build all of the Cython targets rapids_cython_create_modules(SOURCE_FILES "${cython_sources}" LINKED_LIBRARIES "${linked_libraries}" diff --git a/python/rmm/rmm/librmm/_logger.pxd b/python/rmm/rmm/librmm/_logger.pxd index fb2126b2f..bd0728bc1 100644 --- a/python/rmm/rmm/librmm/_logger.pxd +++ b/python/rmm/rmm/librmm/_logger.pxd @@ -13,54 +13,27 @@ # limitations under the License. from libcpp cimport bool - - -cdef extern from "spdlog/common.h" namespace "spdlog::level" nogil: - cpdef enum logging_level "spdlog::level::level_enum": - """ - The debug logging level for RMM. - - Debug logging prints messages to a log file. See - `Debug Logging `_ - for more information. - - Valid levels, in decreasing order of verbosity, are TRACE, DEBUG, - INFO, WARN, ERR, CRITICAL, and OFF. Default is INFO. - - Examples - -------- - >>> import rmm - >>> rmm.logging_level.DEBUG - - >>> rmm.logging_level.DEBUG.value - 1 - >>> rmm.logging_level.DEBUG.name - 'DEBUG' - - See Also - -------- - set_logging_level : Set the debug logging level - get_logging_level : Get the current debug logging level - """ - TRACE "spdlog::level::trace" - DEBUG "spdlog::level::debug" - INFO "spdlog::level::info" - WARN "spdlog::level::warn" - ERR "spdlog::level::err" - CRITICAL "spdlog::level::critical" - OFF "spdlog::level::off" - - -cdef extern from "spdlog/spdlog.h" namespace "spdlog" nogil: - cdef cppclass spdlog_logger "spdlog::logger": - spdlog_logger() except + - void set_level(logging_level level) - logging_level level() +from libcpp.string cimport string + + +cdef extern from "rmm/logger.hpp" namespace "rmm" nogil: + cpdef enum class level_enum: + trace + debug + info + warn + error + critical + off + n_levels + + cdef cppclass logger: + logger(string name, string filename) except + + void set_level(level_enum log_level) except + + level_enum level() except + void flush() except + - void flush_on(logging_level level) - logging_level flush_level() - bool should_log(logging_level msg_level) - + void flush_on(level_enum level) except + + level_enum flush_level() except + + bool should_log(level_enum msg_level) except + -cdef extern from "rmm/logger.hpp" namespace "rmm::detail" nogil: - cdef spdlog_logger& logger() except + + cdef logger& default_logger() except + diff --git a/python/rmm/rmm/librmm/_logger.pyx b/python/rmm/rmm/librmm/_logger.pyx index 4392cb106..57bbf5c62 100644 --- a/python/rmm/rmm/librmm/_logger.pyx +++ b/python/rmm/rmm/librmm/_logger.pyx @@ -11,5 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - -from rmm.librmm._logger cimport logging_level # no-cython-lint diff --git a/python/rmm/rmm/pylibrmm/CMakeLists.txt b/python/rmm/rmm/pylibrmm/CMakeLists.txt index 0e88f01bb..0012cb93d 100644 --- a/python/rmm/rmm/pylibrmm/CMakeLists.txt +++ b/python/rmm/rmm/pylibrmm/CMakeLists.txt @@ -13,7 +13,7 @@ # ============================================================================= set(cython_sources device_buffer.pyx logger.pyx memory_resource.pyx cuda_stream.pyx helper.pyx) -set(linked_libraries rmm::rmm) +set(linked_libraries rmm::rmm cpp_logger) # Build all of the Cython targets rapids_cython_create_modules(SOURCE_FILES "${cython_sources}" LINKED_LIBRARIES "${linked_libraries}" diff --git a/python/rmm/rmm/pylibrmm/logger.pyx b/python/rmm/rmm/pylibrmm/logger.pyx index 119e1c92f..9d5877fae 100644 --- a/python/rmm/rmm/pylibrmm/logger.pyx +++ b/python/rmm/rmm/pylibrmm/logger.pyx @@ -14,14 +14,14 @@ import warnings -from rmm.librmm._logger cimport logger +from rmm.librmm._logger cimport default_logger -from rmm.librmm._logger import logging_level +from rmm.librmm._logger import level_enum def _validate_level_type(level): - if not isinstance(level, logging_level): - raise TypeError("level must be an instance of the logging_level enum") + if not isinstance(level, level_enum): + raise TypeError("level must be an instance of the level_enum enum") def should_log(level): @@ -54,7 +54,7 @@ def should_log(level): If the logging level is not an instance of the ``logging_level`` enum. """ _validate_level_type(level) - return logger().should_log(level) + return default_logger().should_log(level) def set_logging_level(level): @@ -86,10 +86,10 @@ def set_logging_level(level): >>> rmm.set_logging_level(rmm.logging_level.WARN) # set logging level to warn """ _validate_level_type(level) - logger().set_level(level) + default_logger().set_level(level) if not should_log(level): - warnings.warn(f"RMM will not log logging_level.{level.name}. This " + warnings.warn(f"RMM will not log level_enum.{level.name}. This " "may be because the C++ library is compiled for a " "less-verbose logging level.") @@ -118,7 +118,7 @@ def get_logging_level(): >>> rmm.get_logging_level() # get current logging level """ - return logging_level(logger().level()) + return default_logger().level() def flush_logger(): @@ -140,7 +140,7 @@ def flush_logger(): >>> import rmm >>> rmm.flush_logger() # flush the logger """ - logger().flush() + default_logger().flush() def set_flush_level(level): @@ -174,10 +174,10 @@ def set_flush_level(level): >>> rmm.flush_on(rmm.logging_level.WARN) # set flush level to warn """ _validate_level_type(level) - logger().flush_on(level) + default_logger().flush_on(level) if not should_log(level): - warnings.warn(f"RMM will not log logging_level.{level.name}. This " + warnings.warn(f"RMM will not log level_enum.{level.name}. This " "may be because the C++ library is compiled for a " "less-verbose logging level.") @@ -208,4 +208,4 @@ def get_flush_level(): >>> rmm.flush_level() # get current flush level """ - return logging_level(logger().flush_level()) + return default_logger().flush_level() diff --git a/python/rmm/rmm/tests/test_rmm.py b/python/rmm/rmm/tests/test_rmm.py index 182434dc5..7d13b5cac 100644 --- a/python/rmm/rmm/tests/test_rmm.py +++ b/python/rmm/rmm/tests/test_rmm.py @@ -29,6 +29,7 @@ import rmm._cuda.stream from rmm.allocators.cupy import rmm_cupy_allocator from rmm.allocators.numba import RMMNumbaManager +from rmm.pylibrmm.logger import level_enum cuda.set_memory_manager(RMMNumbaManager) @@ -1033,22 +1034,23 @@ def test_rmm_device_buffer_copy(cuda_ary, make_copy): np.testing.assert_equal(expected, result) -@pytest.mark.parametrize("level", rmm.logging_level) +@pytest.mark.parametrize("level", level_enum) def test_valid_logging_level(level): + default_level = level_enum.info with warnings.catch_warnings(): warnings.filterwarnings( - "ignore", message="RMM will not log logging_level.TRACE." + "ignore", message="RMM will not log level_enum.trace." ) warnings.filterwarnings( - "ignore", message="RMM will not log logging_level.DEBUG." + "ignore", message="RMM will not log level_enum.debug." ) rmm.set_logging_level(level) assert rmm.get_logging_level() == level - rmm.set_logging_level(rmm.logging_level.INFO) # reset to default + rmm.set_logging_level(default_level) # reset to default rmm.set_flush_level(level) assert rmm.get_flush_level() == level - rmm.set_flush_level(rmm.logging_level.INFO) # reset to default + rmm.set_flush_level(default_level) # reset to default rmm.should_log(level) diff --git a/rapids_logger/CMakeLists.txt b/rapids_logger/CMakeLists.txt new file mode 100644 index 000000000..fd50276ca --- /dev/null +++ b/rapids_logger/CMakeLists.txt @@ -0,0 +1,177 @@ +# ============================================================================= +# Copyright (c) 2024, NVIDIA CORPORATION. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software distributed under the License +# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing permissions and limitations under +# the License. +# ============================================================================= + +# cmake-lint: disable=R0915 + +include_guard(GLOBAL) + +cmake_minimum_required(VERSION 3.26.4 FATAL_ERROR) + +include(../rapids_config.cmake) + +include(rapids-cmake) +include(rapids-cpm) + +project( + RAPIDS_LOGGER + VERSION 0.0.1 + LANGUAGES CXX) + +rapids_cmake_build_type(Release) + +rapids_cpm_init() + +include(../cmake/thirdparty/get_spdlog.cmake) + +#[=======================================================================[.rst: +rapids_make_logger +------------------ + +Generate a logger implementation customized for the specified namespace. + +.. code-block:: cmake + + rapids_make_logger( + [EXPORT_SET ] + [LOGGER_TARGET ] + [LOGGER_HEADER_DIR ] + [LOGGER_MACRO_PREFIX ] + ) + +This function produces an interface target named that, when linked to by other targets, provides them a header file that defines a logger interface for the specified namespace. The logger is generated from the provided template files and is configured to use the specified namespace and macro prefix. The generated logger is placed in the specified header directory. + +The logger implementation lives in a separate header file that is not included in the declaration header. The logger implementation is compiled into a separate target that must be linked to by any target that uses the logger. + + +``logger_namespace`` + The namespace for which to generate the logger implementation. + +``EXPORT_SET`` + The name of the export set to which the logger target should be added. If not specified, the logger target is not added to any export set. + +``LOGGER_TARGET`` + The name of the logger (and logger impl) target to create. If not specified, defaults to _logger. + +``LOGGER_HEADER_DIR`` + The directory in which to place the generated logger header file. If not specified, the logger header file is placed in include/. + +``LOGGER_MACRO_PREFIX`` + The prefix to use for the logger macros. If not specified, the macro prefix is the uppercase version of the logger namespace. + +Result Targets +^^^^^^^^^^^^^^^^ + is an interface target that provides the logger interface for the specified namespace. + + _impl is an interface target that provides the logger implementation for the specified namespace. This target must be linked to by any target that uses the logger. Targets linking to this target will have the logger implementation compiled into them. + +Examples +^^^^^^^^ + +Example on how to use :cmake:command:`rapids_make_logger`. + + +.. code-block:: cmake + + # Generate a logger for the namespace "rapids" and associate it with the + # export set "rapids-exports". + rapids_make_logger(rapids + EXPORT_SET rapids-exports + ) + + # Generate a logger for the namespace "rmm" that does not support logging. + rapids_make_logger(rapids) + + +#]=======================================================================] +function(rapids_make_logger logger_namespace) + list(APPEND CMAKE_MESSAGE_CONTEXT "rapids_make_logger") + + set(_rapids_options) + set(_rapids_one_value EXPORT_SET LOGGER_TARGET LOGGER_HEADER_DIR LOGGER_MACRO_PREFIX) + set(_rapids_multi_value) + cmake_parse_arguments(_RAPIDS "${_rapids_options}" "${_rapids_one_value}" + "${_rapids_multi_value}" ${ARGN}) + + # Most arguments are optional and can be inferred from the namespace by default. + set(_RAPIDS_LOGGER_NAMESPACE ${logger_namespace}) + if(NOT _RAPIDS_LOGGER_TARGET) + set(_RAPIDS_LOGGER_TARGET "${logger_namespace}_logger") + endif() + if(NOT _RAPIDS_LOGGER_HEADER_DIR) + set(_RAPIDS_LOGGER_HEADER_DIR "include/${logger_namespace}") + endif() + if(NOT _RAPIDS_LOGGER_MACRO_PREFIX) + string(TOUPPER ${logger_namespace} _RAPIDS_LOGGER_MACRO_PREFIX) + endif() + + # All paths are computed relative to the current source/binary dir of the file from which the + # function is invoked. As a result we cannot use relative paths here because CMake will root these + # paths incorrectly for configure_file/install. + set(BUILD_DIR ${CMAKE_CURRENT_BINARY_DIR}/${_RAPIDS_LOGGER_HEADER_DIR}) + # TODO: Verify that installation works correctly with prefix removed. + set(INSTALL_DIR ${_RAPIDS_LOGGER_HEADER_DIR}) + + set(LOGGER_OUTPUT_FILE ${BUILD_DIR}/logger.hpp) + configure_file(${CMAKE_CURRENT_FUNCTION_LIST_DIR}/logger.hpp.in ${LOGGER_OUTPUT_FILE}) + install(FILES ${LOGGER_OUTPUT_FILE} DESTINATION ${INSTALL_DIR}) + + set(LOGGER_IMPL_OUTPUT_FILE ${BUILD_DIR}/logger_impl/logger_impl.hpp) + configure_file(${CMAKE_CURRENT_FUNCTION_LIST_DIR}/logger_impl.hpp.in ${LOGGER_IMPL_OUTPUT_FILE}) + install(FILES ${LOGGER_IMPL_OUTPUT_FILE} DESTINATION ${INSTALL_DIR}/logger_impl) + + add_library(${_RAPIDS_LOGGER_TARGET} INTERFACE) + include(GNUInstallDirs) + # Note: The BUILD_INTERFACE setting assumes that LOGGER_HEADER_DIR is the subdirectory of + # CMAKE_INSTALL_INCLUDEDIR relative to which all includes are rooted in the C++ code files. I + # think that is a safe assumption though since if it were violated then the INSTALL_INTERFACE + # would not only be incorrect (if computed using LOGGER_HEADER_DIR), but it would also break + # consumers of the installed package who expect to be able to write `#include + # <${LOGGER_HEADER_DIR/include\//}/logger.hpp>` and have it work. + target_include_directories( + ${_RAPIDS_LOGGER_TARGET} + INTERFACE "$" + "$") + target_compile_features(${_RAPIDS_LOGGER_TARGET} INTERFACE cxx_std_17) + + # Create an interface target that will trigger compilation of the logger implementation in any + # target that is linked to it. + set(LOGGER_IMPL_SRC_OUTPUT_FILE ${BUILD_DIR}/logger_impl/logger.cpp) + configure_file(${CMAKE_CURRENT_FUNCTION_LIST_DIR}/logger.cpp.in ${LOGGER_IMPL_SRC_OUTPUT_FILE}) + install(FILES ${LOGGER_IMPL_SRC_OUTPUT_FILE} DESTINATION ${INSTALL_DIR}/logger_impl) + + # Note that we cannot specify the source files directly in add_library, see the CMake + # documentation explaining that these do not populate INTERFACE_SOURCES. + # https://cmake.org/cmake/help/latest/command/add_library.html#interface-with-sources + set(impl_target ${_RAPIDS_LOGGER_TARGET}_impl) + add_library(${impl_target} INTERFACE) + target_sources( + ${impl_target} + INTERFACE $ + $) + target_link_libraries(${impl_target} INTERFACE ${_RAPIDS_LOGGER_TARGET} + spdlog::spdlog_header_only) + set_target_properties(${impl_target} PROPERTIES POSITION_INDEPENDENT_CODE ON + INTERFACE_POSITION_INDEPENDENT_CODE ON) + + set(_install_export) + if(_RAPIDS_EXPORT_SET) + set(_install_export EXPORT ${_RAPIDS_EXPORT_SET}) + endif() + + install(TARGETS ${_RAPIDS_LOGGER_TARGET} ${_install_export}) + if(TARGET ${impl_target}) + install(TARGETS ${impl_target} ${_install_export}) + endif() + +endfunction() diff --git a/rapids_logger/README.md b/rapids_logger/README.md new file mode 100644 index 000000000..00e505fc7 --- /dev/null +++ b/rapids_logger/README.md @@ -0,0 +1,22 @@ +# About + +The `rapids-logger` project defines an easy way to produce a project-specific logger using the excellent [spdlog](https://github.com/gabime/spdlog) package. +The goal of this project is to ensure that projects wishing to provide their own logger may do so easily without needing to reimplement their own custom wrappers around spdlog. +A core goal of the project is to ensure that the custom logger implementation does not leak any spdlog symbols, allowing the safe coexistence of different projects in the same environment even if they use different versions of spdlog. +That goal is the primary reason to prefer using this project rather than directly exposing a specialized instance of a spdlog logger in your own project. + +`rapids-logger` is designed to be used via CMake. +Its CMake defines a function `rapids_make_logger` that can be used to produce a project-specific logger class in a provided namespace. +The resulting logger exposes spdlog-like functionality via the [PImpl idiom](https://en.cppreference.com/w/cpp/language/pimpl) to avoid exposing spdlog symbols publicly. +It uses CMake and template C++ files to generate a public header file to describe the user interface and an inline header that should be placed in a single TU by consumers to compile the implementation. +To simplify usage, each invocation of the function produces two CMake targets, one representing the public header and one representing a trivial source file including the inline header. +Projects using `rapids-logger` should make the first target part of their public link interface while the latter should be linked to privately so that it is compiled into the project's library without public exposure. + +To mirror spdlog, each generated logger also ships with a set of logging macros `_LOG_` that may be used to control logging at compile-time as well as runtime using a compile-time variable `_LOG_ACTIVE_LEVEL`. +For example, a project called "rapids" will be able to write code like this: +``` +RAPIDS_LOG_WARN("Some message to be shown when the warning level is enabled"); +``` +and control whether that warning is shown by compiling the code with `RAPIDS_LOG_ACTIVE_LEVEL=RAPIDS_LOG_LEVEL_WARN`. +Each project is endowed with its own definition of levels, so different projects in the same environment may be safely configured independently of each other and of spdlog. +Each project is also given a `default_logger` function that produces a global logger that may be used anywhere, but projects may also freely instantiate additional loggers as needed. diff --git a/rapids_logger/logger.cpp.in b/rapids_logger/logger.cpp.in new file mode 100644 index 000000000..36bb2ce02 --- /dev/null +++ b/rapids_logger/logger.cpp.in @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "logger_impl.hpp" diff --git a/rapids_logger/logger.hpp.in b/rapids_logger/logger.hpp.in new file mode 100644 index 000000000..cd2bb2c79 --- /dev/null +++ b/rapids_logger/logger.hpp.in @@ -0,0 +1,504 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include + +namespace __attribute__((visibility("default"))) @_RAPIDS_LOGGER_NAMESPACE@ { + +// These values must be kept in sync with spdlog! +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_TRACE 0 +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_DEBUG 1 +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_INFO 2 +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_WARN 3 +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_ERROR 4 +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_CRITICAL 5 +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_OFF 6 + +/** + * @brief The log levels supported by the logger. + * + * These levels correspond to the levels defined by spdlog. + */ +enum class level_enum : int32_t { + trace = @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_TRACE, + debug = @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_DEBUG, + info = @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_INFO, + warn = @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_WARN, + error = @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_ERROR, + critical = @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_CRITICAL, + off = @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_OFF, + n_levels +}; + +namespace detail { +// Forward declare the implementation classes. +class logger_impl; +class sink_impl; +} + +// Forward declare for the sink for the logger to use. +class sink; +using sink_ptr = std::shared_ptr; + +/** + * @class logger + * @brief A logger class that either uses the real implementation (via spdlog) or performs no-ops if + * not supported. + */ +class logger { + public: + logger() = delete; ///< Not default constructible + logger(logger const&) = delete; ///< Not copy constructible + logger& operator=(logger const&) = delete; ///< Not copy assignable + + logger(logger&& other); ///< @default_move_constructor + logger& operator=(logger&& other); ///< @default_move_assignment{logger} + + /** + * @brief A class to manage a vector of sinks. + * + * This class is used internally by the logger class to manage its sinks. It handles synchronization of the sinks with the sinks in the underlying spdlog logger such that all vector-like operations performed on this class are reflected in the underlying spdlog logger's set of sinks. + */ + class sink_vector { + public: + using Iterator = std::vector::iterator; ///< The iterator type + using ConstIterator = std::vector::const_iterator; ///< The const iterator type + + /** + * @brief Construct a new sink_vector object + * + * @param parent The logger whose sinks are being managed + * @param sinks The sinks to manage + */ + explicit sink_vector(logger& parent, std::vector sinks={}) : parent{parent}, sinks_{sinks} {} + + /** + * @brief Add a sink to the vector. + * + * @param sink The sink to add + */ + void push_back(sink_ptr const& sink); + + /** + * @brief Add a sink to the vector. + * + * @param sink The sink to add + */ + void push_back(sink_ptr&& sink); + + /** + * @brief Remove the last sink from the vector. + */ + void pop_back(); + + /** + * @brief Remove all sinks from the vector. + */ + void clear(); + + /** + * @brief Get an iterator to the beginning of the vector. + * + * @return Iterator The iterator + */ + Iterator begin() { return sinks_.begin(); } + + /** + * @brief Get an iterator to the end of the vector. + * + * @return Iterator The iterator + */ + Iterator end() { return sinks_.end(); } + + /** + * @brief Get a const iterator to the beginning of the vector. + * + * @return ConstIterator The const iterator + */ + ConstIterator begin() const { return sinks_.begin(); } + + /** + * @brief Get a const iterator to the end of the vector. + * + * @return ConstIterator The const iterator + */ + ConstIterator end() const { return sinks_.end(); } + + /** + * @brief Get a const iterator to the beginning of the vector. + * + * @return ConstIterator The const iterator + */ + ConstIterator cbegin() const { return sinks_.cbegin(); } + + /** + * @brief Get a const iterator to the end of the vector. + * + * @return ConstIterator The const iterator + */ + ConstIterator cend() const { return sinks_.cend(); } + private: + logger& parent; ///< The logger this vector belongs to + std::vector sinks_; ///< The sinks + }; + + // TODO: When we migrate to C++20 we can use std::format and format strings + // instead of the printf-style printing used here. + /** + * @brief Format and log a message at the specified level. + * + * This function performs printf-style formatting to avoid the need for fmt + * or spdlog's own templated APIs (which would require exposing spdlog + * symbols publicly) and then invokes the base implementation with the + * preformatted string. + * + * @param lvl The log level + * @param format The format string + * @param args The format arguments + */ + template + void log(level_enum lvl, std::string const& format, Args&&... args) { + auto convert_to_c_string = [](auto&& arg) -> decltype(auto) { + using ArgType = std::decay_t; + if constexpr (std::is_same_v) { + return arg.c_str(); + } else { + return std::forward(arg); + } + }; + + // NOLINTBEGIN(cppcoreguidelines-pro-type-vararg) + auto formatted_size = + std::snprintf(nullptr, 0, format.c_str(), convert_to_c_string(std::forward(args))...); + if (formatted_size < 0) { throw std::runtime_error("Error during formatting."); } + if (formatted_size == 0) { log(lvl, {}); } + auto size = static_cast(formatted_size) + 1; // for null terminator + // NOLINTNEXTLINE(modernize-avoid-c-arrays, cppcoreguidelines-avoid-c-arrays) + std::unique_ptr buf(new char[size]); + std::snprintf(buf.get(), size, format.c_str(), convert_to_c_string(std::forward(args))...); + // NOLINTEND(cppcoreguidelines-pro-type-vararg) + log(lvl, {buf.get(), buf.get() + size - 1}); // drop '\0' + }; + + /** + * @brief Log a message at the TRACE level. + * + * @param format The format string + * @param args The format arguments + */ + template + void trace(std::string const& format, Args&&... args) + { + log(level_enum::trace, format, std::forward(args)...); + } + + /** + * @brief Log a message at the DEBUG level. + * + * @param format The format string + * @param args The format arguments + */ + template + void debug(std::string const& format, Args&&... args) + { + log(level_enum::debug, format, std::forward(args)...); + } + + /** + * @brief Log a message at the INFO level. + * + * @param format The format string + * @param args The format arguments + */ + template + void info(std::string const& format, Args&&... args) + { + log(level_enum::info, format, std::forward(args)...); + } + + /** + * @brief Log a message at the WARN level. + * + * @param format The format string + * @param args The format arguments + */ + template + void warn(std::string const& format, Args&&... args) + { + log(level_enum::warn, format, std::forward(args)...); + } + + /** + * @brief Log a message at the ERROR level. + * + * @param format The format string + * @param args The format arguments + */ + template + void error(std::string const& format, Args&&... args) + { + log(level_enum::error, format, std::forward(args)...); + } + + /** + * @brief Log a message at the CRITICAL level. + * + * @param format The format string + * @param args The format arguments + */ + template + void critical(std::string const& format, Args&&... args) + { + log(level_enum::critical, format, std::forward(args)...); + } + + // Everything below here is conditionally compiled based on whether logging is supported. + /** + * @brief Construct a new logger object + * + * @param name The name of the logger + * @param filename The name of the log file + */ + logger(std::string name, std::string filename); + + /** + * @brief Construct a new logger object + * + * @param name The name of the logger + * @param stream The stream to log to + */ + logger(std::string name, std::ostream& stream); + + /** + * @brief Construct a new logger object + * + * @param name The name of the logger + * @param sinks The sinks to log to + * + * Note that we must use a vector because initializer_lists are not flexible + * enough to support programmatic construction in callers, and an + * iterator-based API would require templating and thus exposing spdlog + * types. + */ + logger(std::string name, std::vector sinks); + + /** + * @brief Destroy the logger object + */ + ~logger(); + + /** + * @brief Log a message at the specified level. + * + * This is the core logging routine that dispatches to spdlog. + * + * @param lvl The log level + * @param message The message to log + */ + void log(level_enum lvl, std::string const& message); + + /** + * @brief Get the sinks for the logger. + * + * @return The sinks + */ + const sink_vector& sinks() const; + + /** + * @brief Get the sinks for the logger. + * + * @return The sinks + */ + sink_vector& sinks(); + + /** + * @brief Get the current log level. + * + * @return The current log level + */ + level_enum level() const; + + /** + * @brief Set the log level. + * + * @param log_level The new log level + */ + void set_level(level_enum log_level); + + /** + * @brief Flush the logger. + */ + void flush(); + + /** + * @brief Flush all writes on the specified level or above. + */ + void flush_on(level_enum log_level); + + /** + * @brief Get the current flush level. + */ + level_enum flush_level() const; + + /** + * @brief Check if the logger should log a message at the specified level. + * + * @param msg_level The level of the message + * @return true if the message should be logged, false otherwise + */ + bool should_log(level_enum msg_level) const; + + /** + * @brief Set the pattern for the logger. + * + * @param pattern The pattern to use + */ + void set_pattern(std::string pattern); + + private: + std::unique_ptr impl; ///< The logger implementation + sink_vector sinks_; ///< The sinks for the logger +}; + +/** + * @brief A sink for the logger. + * + * These sinks are wrappers around the spdlog sinks that allow us to keep the + * spdlog types private and avoid exposing them in the public API. + */ +class sink { + public: + ~sink(); + protected: + explicit sink(std::unique_ptr impl); + std::unique_ptr impl; + // The sink vector needs to be able to pass the underlying sink to the spdlog logger. + friend class logger::sink_vector; +}; + +/** + * @brief A sink that writes to a file. + * + * See spdlog::sinks::basic_file_sink_mt for more information. + */ +class basic_file_sink_mt : public sink { + public: + basic_file_sink_mt(std::string const& filename, bool truncate = false); +}; + +/** + * @brief A sink that writes to an ostream. + * + * See spdlog::sinks::ostream_sink_mt for more information. + */ +class ostream_sink_mt : public sink { + public: + ostream_sink_mt(std::ostream& stream, bool force_flush = false); +}; + + +/** + * @brief Returns the default log filename for the global logger. + * + * If the environment variable `@_RAPIDS_LOGGER_NAMESPACE@_DEBUG_LOG_FILE` is defined, its value is used as the path and + * name of the log file. Otherwise, the file `@_RAPIDS_LOGGER_NAMESPACE@_log.txt` in the current working directory is used. + * + * @return std::string The default log file name. + */ +inline std::string default_log_filename() +{ + auto* filename = std::getenv("@_RAPIDS_LOGGER_MACRO_PREFIX@_DEBUG_LOG_FILE"); + // TODO: Do we prefer rmm's default (a file rmm_log.txt) or cudf's default (a + // stderr sink)? I think the latter is better. + return (filename == nullptr) ? std::string{"@_RAPIDS_LOGGER_NAMESPACE@_log.txt"} : std::string{filename}; +} + +/** + * @brief Get the default logger. + * + * @return logger& The default logger + */ +inline logger& default_logger() +{ + static logger logger_ = [] { + logger logger_ { + "@_RAPIDS_LOGGER_MACRO_PREFIX@", default_log_filename() + }; +#if @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_ACTIVE_LEVEL <= @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_INFO +#ifdef CUDA_API_PER_THREAD_DEFAULT_STREAM + logger_.info("----- @_RAPIDS_LOGGER_MACRO_PREFIX@ LOG BEGIN [PTDS ENABLED] -----"); +#else + logger_.info("----- @_RAPIDS_LOGGER_MACRO_PREFIX@ LOG BEGIN [PTDS DISABLED] -----"); +#endif +#endif + return logger_; + }(); + return logger_; +} + +// Macros for easier logging, similar to spdlog. +#if !defined(@_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_ACTIVE_LEVEL) +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_ACTIVE_LEVEL @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_INFO +#endif + +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOGGER_CALL(logger, level, ...) (logger).log(level, __VA_ARGS__) + +#if @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_ACTIVE_LEVEL <= @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_TRACE +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_TRACE(...) \ + @_RAPIDS_LOGGER_MACRO_PREFIX@_LOGGER_CALL(@_RAPIDS_LOGGER_NAMESPACE@::default_logger(), @_RAPIDS_LOGGER_NAMESPACE@::level_enum::trace, __VA_ARGS__) +#else +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_TRACE(...) (void)0 +#endif + +#if @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_ACTIVE_LEVEL <= @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_DEBUG +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_DEBUG(...) \ + @_RAPIDS_LOGGER_MACRO_PREFIX@_LOGGER_CALL(@_RAPIDS_LOGGER_NAMESPACE@::default_logger(), @_RAPIDS_LOGGER_NAMESPACE@::level_enum::debug, __VA_ARGS__) +#else +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_DEBUG(...) (void)0 +#endif + +#if @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_ACTIVE_LEVEL <= @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_INFO +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_INFO(...) @_RAPIDS_LOGGER_MACRO_PREFIX@_LOGGER_CALL(@_RAPIDS_LOGGER_NAMESPACE@::default_logger(), @_RAPIDS_LOGGER_NAMESPACE@::level_enum::info, __VA_ARGS__) +#else +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_INFO(...) (void)0 +#endif + +#if @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_ACTIVE_LEVEL <= @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_WARN +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_WARN(...) @_RAPIDS_LOGGER_MACRO_PREFIX@_LOGGER_CALL(@_RAPIDS_LOGGER_NAMESPACE@::default_logger(), @_RAPIDS_LOGGER_NAMESPACE@::level_enum::warn, __VA_ARGS__) +#else +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_WARN(...) (void)0 +#endif + +#if @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_ACTIVE_LEVEL <= @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_ERROR +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_ERROR(...) \ + @_RAPIDS_LOGGER_MACRO_PREFIX@_LOGGER_CALL(@_RAPIDS_LOGGER_NAMESPACE@::default_logger(), @_RAPIDS_LOGGER_NAMESPACE@::level_enum::error, __VA_ARGS__) +#else +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_ERROR(...) (void)0 +#endif + +#if @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_ACTIVE_LEVEL <= @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_LEVEL_CRITICAL +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_CRITICAL(...) \ + @_RAPIDS_LOGGER_MACRO_PREFIX@_LOGGER_CALL(@_RAPIDS_LOGGER_NAMESPACE@::default_logger(), @_RAPIDS_LOGGER_NAMESPACE@::level_enum::critical, __VA_ARGS__) +#else +#define @_RAPIDS_LOGGER_MACRO_PREFIX@_LOG_CRITICAL(...) (void)0 +#endif + +} // namespace @_RAPIDS_LOGGER_NAMESPACE@ diff --git a/rapids_logger/logger_impl.hpp.in b/rapids_logger/logger_impl.hpp.in new file mode 100644 index 000000000..717a00ac9 --- /dev/null +++ b/rapids_logger/logger_impl.hpp.in @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2024, NVIDIA CORPORATION. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "../logger.hpp" + +// Include C headers for which we need symbols to be made public and don't want +// the below symbol hiding pattern to apply. +#include +#include +#include + +// Start hiding before including spdlog headers. +#pragma GCC visibility push(hidden) + +#include +#include +#include + +#include +#include +#include + +namespace @_RAPIDS_LOGGER_NAMESPACE@ { + +namespace detail { +namespace { + +/** + * @brief Convert a string to a log level. + * + * This function is used to process env-var specifications of log levels. + * @param env_lvl_str The string to convert. + * @return The log level. + */ +level_enum string_to_level(std::string_view const env_lvl_str) +{ + if (env_lvl_str == "TRACE") return level_enum::trace; + if (env_lvl_str == "DEBUG") return level_enum::debug; + if (env_lvl_str == "INFO") return level_enum::info; + if (env_lvl_str == "WARN") return level_enum::warn; + if (env_lvl_str == "ERROR") return level_enum::error; + if (env_lvl_str == "CRITICAL") return level_enum::critical; + if (env_lvl_str == "OFF") return level_enum::off; + std::ostringstream os{}; + os << "Invalid logging level: " << env_lvl_str; + throw std::invalid_argument(os.str()); +} + +/** + * @brief Convert a log level to an spdlog log level. + * + * @param lvl The log level to convert. + * @return The spdlog log level. + */ +spdlog::level::level_enum to_spdlog_level(level_enum lvl) +{ + return static_cast(static_cast(lvl)); +} + +/** + * @brief Convert an spdlog log level to a log level. + * + * @param lvl The spdlog log level to convert. + * @return The log level. + */ +level_enum from_spdlog_level(spdlog::level::level_enum lvl) +{ + return static_cast(static_cast(lvl)); +} +} + +/** + * @brief The sink_impl class is a wrapper around an spdlog sink. + * + * This class is the impl part of the PImpl for the sink. + */ +class sink_impl { +public: + sink_impl(std::shared_ptr sink) : underlying{sink} {} +private: + std::shared_ptr underlying; + // The sink_vector needs to be able to pass the underlying sink to the spdlog logger. + friend class logger::sink_vector; +}; + +/** + * @brief The logger_impl class is a wrapper around an spdlog logger. + * + * This class is the impl part of the PImpl for the logger. + */ +class logger_impl { + public: + logger_impl(std::string name) : underlying{spdlog::logger{name}} { + underlying.set_pattern("[%6t][%H:%M:%S:%f][%-6l] %v"); + auto const env_logging_level = + std::getenv("@_RAPIDS_LOGGER_MACRO_PREFIX@_DEFAULT_LOGGING_LEVEL"); + if (env_logging_level != nullptr) { set_level(detail::string_to_level(env_logging_level)); } + auto const env_flush_level = std::getenv("@_RAPIDS_LOGGER_MACRO_PREFIX@_DEFAULT_FLUSH_LEVEL"); + if (env_flush_level != nullptr) { flush_on(detail::string_to_level(env_flush_level)); } + } + + void log(level_enum lvl, std::string const& message) { underlying.log(to_spdlog_level(lvl), message); } + void set_level(level_enum log_level) { underlying.set_level(to_spdlog_level(log_level)); } + void flush() { underlying.flush(); } + void flush_on(level_enum log_level) { underlying.flush_on(to_spdlog_level(log_level)); } + level_enum flush_level() const { return from_spdlog_level(underlying.flush_level()); } + bool should_log(level_enum lvl) const { return underlying.should_log(to_spdlog_level(lvl)); } + level_enum level() const { return from_spdlog_level(underlying.level()); } + void set_pattern(std::string pattern) { underlying.set_pattern(pattern); } + const std::vector &sinks() const { return underlying.sinks(); } + std::vector &sinks() { return underlying.sinks(); } + +private: + spdlog::logger underlying; ///< The spdlog logger +}; + +} // namespace detail + +// Sink vector functions +void logger::sink_vector::push_back(sink_ptr const& sink) { + sinks_.push_back(sink); + parent.impl->sinks().push_back(sink->impl->underlying); +} +void logger::sink_vector::push_back(sink_ptr&& sink) { + sinks_.push_back(sink); + parent.impl->sinks().push_back(sink->impl->underlying); +} +void logger::sink_vector::pop_back() { + sinks_.pop_back(); + parent.impl->sinks().pop_back(); +} +void logger::sink_vector::clear() { + sinks_.clear(); + parent.impl->sinks().clear(); +} + +// Sink methods +sink::sink(std::unique_ptr impl) : impl{std::move(impl)} {} + +sink::~sink() = default; + +basic_file_sink_mt::basic_file_sink_mt(std::string const& filename, bool truncate) + : sink{std::make_unique(std::make_shared(filename, truncate))} {} + +ostream_sink_mt::ostream_sink_mt(std::ostream& stream, bool force_flush) + : sink{std::make_unique(std::make_shared(stream, force_flush))} {} + +// Logger methods +logger::logger(std::string name, std::string filename) + : impl{std::make_unique(name)}, sinks_{*this} { + sinks_.push_back(std::make_shared(filename, true)); +} + +logger::logger(std::string name, std::ostream& stream) + : impl{std::make_unique(name)}, sinks_{*this} { + sinks_.push_back(std::make_shared(stream)); +} + +logger::logger(std::string name, std::vector sinks) + : impl{std::make_unique(name)}, sinks_{*this} { + for (auto const& s : sinks) { + sinks_.push_back(s); + } +} + +logger::~logger() = default; +logger::logger(logger&& other) = default; +logger& logger::operator=(logger&& other) { + impl = std::move(other.impl); + sinks_.clear(); + for (auto const& s : other.sinks_) { + sinks_.push_back(s); + } + return *this; +} + +void logger::log(level_enum lvl, std::string const& message) { impl->log(lvl, message); } +void logger::set_level(level_enum log_level) { impl->set_level(log_level); } +void logger::flush() { impl->flush(); } +void logger::flush_on(level_enum log_level) { impl->flush_on(log_level); } +level_enum logger::flush_level() const { return impl->flush_level(); } +bool logger::should_log(level_enum lvl) const { return impl->should_log(lvl); } +level_enum logger::level() const { return impl->level(); } +void logger::set_pattern(std::string pattern) { impl->set_pattern(pattern); } +const logger::sink_vector& logger::sinks() const { return sinks_; } +logger::sink_vector& logger::sinks() { return sinks_; } + +} // namespace @_RAPIDS_LOGGER_NAMESPACE@ +// This visibility pragma must be here so that both our logger types and those coming from includes are hidden. +#pragma GCC visibility pop diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 476028af0..b718691ca 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -27,8 +27,9 @@ rapids_cmake_support_conda_env(conda_env) function(ConfigureTestInternal TEST_NAME) add_executable(${TEST_NAME} ${ARGN}) target_include_directories(${TEST_NAME} PRIVATE "$") - target_link_libraries(${TEST_NAME} GTest::gmock GTest::gtest GTest::gmock_main GTest::gtest_main - pthread rmm $) + target_link_libraries( + ${TEST_NAME} PRIVATE GTest::gmock GTest::gtest GTest::gmock_main GTest::gtest_main pthread rmm + $) set_target_properties( ${TEST_NAME} PROPERTIES POSITION_INDEPENDENT_CODE ON @@ -40,9 +41,11 @@ function(ConfigureTestInternal TEST_NAME) CUDA_STANDARD 17 CUDA_STANDARD_REQUIRED ON) target_compile_definitions(${TEST_NAME} - PUBLIC "SPDLOG_ACTIVE_LEVEL=SPDLOG_LEVEL_${RMM_LOGGING_LEVEL}") + PUBLIC "RMM_LOG_ACTIVE_LEVEL=RMM_LOG_LEVEL_${RMM_LOGGING_LEVEL}") target_compile_options(${TEST_NAME} PUBLIC $<$:-Wall -Werror>) + target_link_libraries(${TEST_NAME} PRIVATE rmm_test_logger) + if(DISABLE_DEPRECATION_WARNING) target_compile_options( ${TEST_NAME} PUBLIC $<$:-Xcompiler=-Wno-deprecated-declarations>) @@ -127,6 +130,12 @@ function(ConfigureTest TEST_NAME) endfunction() +# Create an object library for the logger so that we don't have to recompile it. +add_library(rmm_test_logger OBJECT) +target_link_libraries(rmm_test_logger PRIVATE rmm_logger_impl) + +include(../cmake/thirdparty/get_spdlog.cmake) + # test sources # device mr_ref tests @@ -159,6 +168,8 @@ ConfigureTest(STATISTICS_TEST mr/device/statistics_mr_tests.cpp) # tracking adaptor tests ConfigureTest(TRACKING_TEST mr/device/tracking_mr_tests.cpp) +target_link_libraries(TRACKING_TEST PRIVATE spdlog::spdlog_header_only) +target_link_libraries(TRACKING_PTDS_TEST PRIVATE spdlog::spdlog_header_only) # out-of-memory callback adaptor tests ConfigureTest(FAILURE_CALLBACK_TEST mr/device/failure_callback_mr_tests.cpp) @@ -195,6 +206,8 @@ ConfigureTest(PREFETCH_TEST prefetch_tests.cpp) # logger tests ConfigureTest(LOGGER_TEST logger_tests.cpp) +target_link_libraries(LOGGER_TEST PRIVATE spdlog::spdlog_header_only) +target_link_libraries(LOGGER_PTDS_TEST PRIVATE spdlog::spdlog_header_only) # arena MR tests ConfigureTest(ARENA_MR_TEST mr/device/arena_mr_tests.cpp GPUS 1 PERCENT 100) diff --git a/tests/logger_tests.cpp b/tests/logger_tests.cpp index 8a5d37be2..619143294 100644 --- a/tests/logger_tests.cpp +++ b/tests/logger_tests.cpp @@ -152,8 +152,8 @@ TEST(Adaptor, MultiSinkConstructor) std::string filename2{temp_dir.generate_path("test_multi_2.txt")}; rmm::mr::cuda_memory_resource upstream; - auto file_sink1 = std::make_shared(filename1, true); - auto file_sink2 = std::make_shared(filename2, true); + auto file_sink1 = std::make_shared(filename1, true); + auto file_sink2 = std::make_shared(filename2, true); rmm::mr::logging_resource_adaptor log_mr{&upstream, {file_sink1, file_sink2}}; diff --git a/tests/mr/device/tracking_mr_tests.cpp b/tests/mr/device/tracking_mr_tests.cpp index 3fce55fb8..c40a9127d 100644 --- a/tests/mr/device/tracking_mr_tests.cpp +++ b/tests/mr/device/tracking_mr_tests.cpp @@ -23,7 +23,6 @@ #include #include -#include namespace rmm::test { namespace { @@ -203,9 +202,9 @@ TEST(TrackingTest, DeallocWrongBytes) TEST(TrackingTest, LogOutstandingAllocations) { std::ostringstream oss; - auto oss_sink = std::make_shared(oss); - rmm::detail::logger().sinks().push_back(oss_sink); - auto old_level = rmm::detail::logger().level(); + auto oss_sink = std::make_shared(oss); + auto old_level = rmm::default_logger().level(); + rmm::default_logger().sinks().push_back(oss_sink); tracking_adaptor mr{rmm::mr::get_current_device_resource_ref()}; std::vector allocations; @@ -213,10 +212,10 @@ TEST(TrackingTest, LogOutstandingAllocations) allocations.push_back(mr.allocate(ten_MiB)); } - rmm::detail::logger().set_level(spdlog::level::debug); + rmm::default_logger().set_level(rmm::level_enum::debug); EXPECT_NO_THROW(mr.log_outstanding_allocations()); -#if SPDLOG_ACTIVE_LEVEL <= SPDLOG_LEVEL_DEBUG +#if RMM_LOG_ACTIVE_LEVEL <= RMM_LOG_LEVEL_DEBUG EXPECT_NE(oss.str().find("Outstanding Allocations"), std::string::npos); #endif @@ -224,8 +223,8 @@ TEST(TrackingTest, LogOutstandingAllocations) mr.deallocate(allocation, ten_MiB); } - rmm::detail::logger().set_level(old_level); - rmm::detail::logger().sinks().pop_back(); + rmm::default_logger().set_level(old_level); + rmm::default_logger().sinks().pop_back(); } } // namespace