//------------------------------------------------------------------------------ /* This file is part of clio: https://github.com/XRPLF/clio Copyright (c) 2022, the clio developers. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ //============================================================================== #pragma once #include "util/SourceLocation.hpp" #include #include #include #include #include #include #include #include #include // We forward declare spdlog::logger and spdlog::sinks::sink // to avoid including the spdlog headers in this header file. namespace spdlog { class logger; // NOLINT(readability-identifier-naming) namespace sinks { class sink; // NOLINT(readability-identifier-naming) } // namespace sinks } // namespace spdlog struct BenchmarkLoggingInitializer; class LoggerFixture; struct LogServiceInitTests; namespace util { namespace impl { class OnAssert; } // namespace impl namespace config { class ClioConfigDefinition; } // namespace config /** * @brief Skips evaluation of expensive argument lists if the given logger is disabled for the required severity level. * * Note: Currently this introduces potential shadowing (unlikely). */ #ifndef COVERAGE_ENABLED #define LOG(x) \ if (auto clio_pump__ = x; not clio_pump__) { \ } else \ clio_pump__ #else #define LOG(x) x #endif /** * @brief Custom severity levels for @ref util::Logger. */ enum class Severity { TRC, DBG, NFO, WRN, ERR, FTL, }; /** * @brief A simple thread-safe logger for the channel specified * in the constructor. * * This is cheap to copy and move. Designed to be used as a member variable or * otherwise. See @ref LogService::init() for setup of the logging core and * severity levels for each channel. */ class Logger final { std::shared_ptr logger_; friend class LogService; // to expose the Pump interface friend struct ::BenchmarkLoggingInitializer; /** * @brief Helper that pumps data into a log record via `operator<<`. */ class Pump final { std::shared_ptr logger_; Severity const severity_; SourceLocationType const sourceLocation_; std::ostringstream stream_; bool const enabled_; public: ~Pump(); Pump(std::shared_ptr logger, Severity sev, SourceLocationType const& loc); Pump(Pump&&) = delete; Pump(Pump const&) = delete; Pump& operator=(Pump const&) = delete; Pump& operator=(Pump&&) = delete; /** * @brief Perfectly forwards any incoming data into the underlying stream if data should be logged. * * @tparam T Type of data to pump * @param data The data to pump * @return Reference to itself for chaining */ template [[maybe_unused]] Pump& operator<<(T&& data) { if (enabled_) stream_ << std::forward(data); return *this; } /** * @return true if logger is enabled; false otherwise */ operator bool() const { return enabled_; } }; public: static constexpr std::array kCHANNELS = { "General", "WebServer", "Backend", "RPC", "ETL", "Subscriptions", "Performance", "Migration", }; /** * @brief Construct a new Logger object that produces loglines for the * specified channel. * * See @ref LogService::init() for general setup and configuration of * severity levels per channel. * * @param channel The channel this logger will report into. */ Logger(std::string channel); Logger(Logger const&) = default; ~Logger() = default; Logger(Logger&&) = default; Logger& operator=(Logger const&) = default; Logger& operator=(Logger&&) = default; /** * @brief Interface for logging at Severity::TRC severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] Pump trace(SourceLocationType const& loc = CURRENT_SRC_LOCATION) const; /** * @brief Interface for logging at Severity::DBG severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] Pump debug(SourceLocationType const& loc = CURRENT_SRC_LOCATION) const; /** * @brief Interface for logging at Severity::NFO severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] Pump info(SourceLocationType const& loc = CURRENT_SRC_LOCATION) const; /** * @brief Interface for logging at Severity::WRN severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] Pump warn(SourceLocationType const& loc = CURRENT_SRC_LOCATION) const; /** * @brief Interface for logging at Severity::ERR severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] Pump error(SourceLocationType const& loc = CURRENT_SRC_LOCATION) const; /** * @brief Interface for logging at Severity::FTL severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] Pump fatal(SourceLocationType const& loc = CURRENT_SRC_LOCATION) const; private: Logger(std::shared_ptr logger); }; /** * @brief Base state management class for the logging service. * * This class manages the global state and core functionality for the logging system, * including initialization, sink management, and logger registration. */ class LogServiceState { protected: friend struct ::LogServiceInitTests; friend class ::LoggerFixture; friend class Logger; friend class ::util::impl::OnAssert; /** * @brief Initialize the logging core with specified parameters. * * @param isAsync Whether logging should be asynchronous * @param defaultSeverity The default severity level for new loggers * @param sinks Vector of spdlog sinks to use for output */ static void init(bool isAsync, Severity defaultSeverity, std::vector> const& sinks); /** * @brief Whether the LogService is initialized or not * * @return true if the LogService is initialized */ [[nodiscard]] static bool initialized(); /** * @brief Reset the logging service to uninitialized state. */ static void reset(); /** * @brief Replace the current sinks with a new set of sinks. * * @param sinks Vector of new spdlog sinks to replace the current ones */ static void replaceSinks(std::vector> const& sinks); /** * @brief Register a new logger for the specified channel. * * Creates and registers a new spdlog logger instance for the given channel * with the specified or default severity level. * * @param channel The name of the logging channel * @param severity Optional severity level override; uses default if not specified * @return Shared pointer to the registered spdlog logger */ static std::shared_ptr registerLogger(std::string const& channel, std::optional severity = std::nullopt); protected: static bool isAsync_; // NOLINT(readability-identifier-naming) static Severity defaultSeverity_; // NOLINT(readability-identifier-naming) static std::vector> sinks_; // NOLINT(readability-identifier-naming) static bool initialized_; // NOLINT(readability-identifier-naming) }; /** * @brief A global logging service. * * Used to initialize and setup the logging core as well as a globally available * entrypoint for logging into the `General` channel as well as raising alerts. */ class LogService : public LogServiceState { public: LogService() = delete; /** * @brief Global log core initialization from a @ref config::ClioConfigDefinition * * @param config The configuration to use * @return Void on success, error message on failure */ [[nodiscard]] static std::expected init(config::ClioConfigDefinition const& config); /** * @brief Shutdown spdlog to guarantee output is not lost */ static void shutdown(); /** * @brief Globally accessible General logger at Severity::TRC severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] static Logger::Pump trace(SourceLocationType const& loc = CURRENT_SRC_LOCATION); /** * @brief Globally accessible General logger at Severity::DBG severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] static Logger::Pump debug(SourceLocationType const& loc = CURRENT_SRC_LOCATION); /** * @brief Globally accessible General logger at Severity::NFO severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] static Logger::Pump info(SourceLocationType const& loc = CURRENT_SRC_LOCATION); /** * @brief Globally accessible General logger at Severity::WRN severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] static Logger::Pump warn(SourceLocationType const& loc = CURRENT_SRC_LOCATION); /** * @brief Globally accessible General logger at Severity::ERR severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] static Logger::Pump error(SourceLocationType const& loc = CURRENT_SRC_LOCATION); /** * @brief Globally accessible General logger at Severity::FTL severity * * @param loc The source location of the log message * @return The pump to use for logging */ [[nodiscard]] static Logger::Pump fatal(SourceLocationType const& loc = CURRENT_SRC_LOCATION); private: /** * @brief Parses the sinks from a @ref config::ClioConfigDefinition * * @param config The configuration to parse sinks from * @return A vector of sinks on success, error message on failure */ [[nodiscard]] static std::expected>, std::string> getSinks(config::ClioConfigDefinition const& config); struct FileLoggingParams { std::string logDir; uint32_t rotationSizeMB; uint32_t dirMaxFiles; }; friend struct ::BenchmarkLoggingInitializer; [[nodiscard]] static std::shared_ptr createFileSink(FileLoggingParams const& params, std::string const& format); }; }; // namespace util