Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add callback sink and default pattern access #1

Merged
merged 7 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Generate a logger implementation customized for the specified namespace.
[LOGGER_TARGET <logger-target-name>]
[LOGGER_HEADER_DIR <header-dir>]
[LOGGER_MACRO_PREFIX <macro-prefix>]
[CMAKE_ALIAS_NAMESPACE <alias-namespace>]
)

This function produces an interface target named <logger-target-name> 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.
Expand All @@ -70,6 +71,9 @@ The logger implementation lives in a separate header file that is not included i
``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.

``CMAKE_ALIAS_NAMESPACE``
The namespace to use for the CMake alias target. If not specified, the alias namespace is the same as the logger namespace.

Result Targets
^^^^^^^^^^^^^^^^
<logger-target-name> is an interface target that provides the logger interface for the specified namespace.
Expand Down Expand Up @@ -115,6 +119,9 @@ function(rapids_make_logger logger_namespace)
if(NOT _RAPIDS_LOGGER_MACRO_PREFIX)
string(TOUPPER ${logger_namespace} _RAPIDS_LOGGER_MACRO_PREFIX)
endif()
if(NOT _RAPIDS_CMAKE_NAMESPACE_ALIAS)
set(_RAPIDS_CMAKE_NAMESPACE_ALIAS ${logger_namespace})
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
Expand All @@ -132,6 +139,7 @@ function(rapids_make_logger logger_namespace)
install(FILES ${LOGGER_IMPL_OUTPUT_FILE} DESTINATION ${INSTALL_DIR}/logger_impl)

add_library(${_RAPIDS_LOGGER_TARGET} INTERFACE)
add_library(${_RAPIDS_CMAKE_NAMESPACE_ALIAS}::${_RAPIDS_LOGGER_TARGET} ALIAS ${_RAPIDS_LOGGER_TARGET})
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
Expand All @@ -156,6 +164,7 @@ function(rapids_make_logger logger_namespace)
# 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)
add_library(${_RAPIDS_CMAKE_NAMESPACE_ALIAS}::${impl_target} ALIAS ${impl_target})
target_sources(
${impl_target}
INTERFACE $<BUILD_INTERFACE:${LOGGER_IMPL_SRC_OUTPUT_FILE}>
Expand Down
36 changes: 36 additions & 0 deletions logger.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,17 @@ class null_sink_mt : public sink {
null_sink_mt();
};

typedef void (*log_callback_t)(int lvl, const char* msg);
typedef void (*flush_callback_t)();

/**
* @brief A sink that executes a callback whenever a message is logged.
*/
class callback_sink_mt : public sink {
public:
explicit callback_sink_mt(const log_callback_t &callback, const flush_callback_t &flush = nullptr);
};

/**
* @brief Returns the default log filename for the global logger.
*
Expand All @@ -440,6 +451,13 @@ inline std::string default_log_filename()
return (filename == nullptr) ? std::string{"@_RAPIDS_LOGGER_NAMESPACE@_log.txt"} : std::string{filename};
}

/**
* @brief Returns the default log pattern for the global logger.
*
* @return std::string The default log pattern.
*/
inline std::string default_pattern() { return "[%6t][%H:%M:%S:%f][%-6l] %v"; }

/**
* @brief Get the default logger.
*
Expand All @@ -463,6 +481,24 @@ inline logger& default_logger()
return logger_;
}

/**
* @brief An object used for scoped log level setting
*
* Instances will set the logging to the level indicated on construction and
* will revert to the previous set level on destruction.
*/
struct log_level_setter {
explicit log_level_setter(level_enum level)
{
prev_level_ = default_logger().level();
default_logger().set_level(level);
}
~log_level_setter() { default_logger().set_level(prev_level_); }

private:
level_enum prev_level_;
};

// 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
Expand Down
46 changes: 45 additions & 1 deletion logger_impl.hpp.in
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@
#include <spdlog/sinks/null_sink.h>
#include <spdlog/sinks/ostream_sink.h>
#include <spdlog/spdlog.h>
#include <spdlog/details/log_msg.h>
#include <spdlog/sinks/base_sink.h>
#pragma GCC diagnostic pop

#include <iostream>
#include <memory>
#include <mutex>
#include <sstream>
#include <string>

Expand Down Expand Up @@ -113,7 +117,7 @@ private:
class logger_impl {
public:
logger_impl(std::string name) : underlying{spdlog::logger{name}} {
underlying.set_pattern("[%6t][%H:%M:%S:%f][%-6l] %v");
underlying.set_pattern(default_pattern());
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)); }
Expand All @@ -136,6 +140,43 @@ private:
spdlog::logger underlying; ///< The spdlog logger
};

// Default flush function
void default_flush() { std::cout << std::flush; }

/**
* @brief A sink that calls a callback function with log messages.
*
* We do not currently use spdlog's callback sink because it does not support
* flushing. We could contribute that function to the upstream callback_sink_mt
* to simplify this code.
*/
template <class Mutex>
class callback_sink : public spdlog::sinks::base_sink<Mutex> {
public:
explicit callback_sink(log_callback_t callback,
flush_callback_t flush = nullptr)
: _callback{callback}, _flush{flush ? flush : default_flush} {}

protected:
void sink_it_(const spdlog::details::log_msg& msg) override
{
spdlog::memory_buf_t formatted;
spdlog::sinks::base_sink<Mutex>::formatter_->format(msg, formatted);
std::string msg_string = fmt::to_string(formatted);

if (_callback) {
_callback(static_cast<int>(msg.level), msg_string.c_str());
} else {
std::cout << msg_string;
}
}

void flush_() override { _flush();}

log_callback_t _callback;
void (*_flush)();
};

} // namespace detail

// Sink vector functions
Expand Down Expand Up @@ -170,6 +211,9 @@ ostream_sink_mt::ostream_sink_mt(std::ostream& stream, bool force_flush)
null_sink_mt::null_sink_mt()
: sink{std::make_unique<detail::sink_impl>(std::make_shared<spdlog::sinks::null_sink_mt>())} {}

callback_sink_mt::callback_sink_mt(const log_callback_t &callback, const flush_callback_t &flush)
: sink{std::make_unique<detail::sink_impl>(std::make_shared<detail::callback_sink<std::mutex>>(callback, flush))} {}

// Logger methods
logger::logger(std::string name, std::string filename)
: impl{std::make_unique<detail::logger_impl>(name)}, sinks_{*this} {
Expand Down