diff --git a/.github/ci/packages.apt b/.github/ci/packages.apt index 805de1f..ebf173c 100644 --- a/.github/ci/packages.apt +++ b/.github/ci/packages.apt @@ -1 +1,2 @@ libgz-cmake4-dev +libspdlog-dev diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fee2d6..74fee08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,11 +37,17 @@ if(NOT GZ_UTILS_VENDOR_CLI11) gz_find_package(CLI11 REQUIRED_BY cli PKGCONFIG_IGNORE) endif() +gz_find_package( + spdlog + PKGCONFIG spdlog + REQUIRED_BY log + PURPOSE "Provide logging") + #============================================================================ # Configure the build #============================================================================ gz_configure_build(QUIT_IF_BUILD_ERRORS - COMPONENTS cli) + COMPONENTS cli log) #============================================================================ # Create package information diff --git a/examples/log/CMakeLists.txt b/examples/log/CMakeLists.txt new file mode 100644 index 0000000..4baa2c6 --- /dev/null +++ b/examples/log/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.22.1 FATAL_ERROR) +project(gz-utils-logger-demo) + +# Find the Gazebo Libraries used directly by the example +find_package(gz-utils3 REQUIRED COMPONENTS log) +set(GZ_UTILS_VER ${gz-utils3_VERSION_MAJOR}) + +add_executable(${PROJECT_NAME} main.cc) +target_link_libraries( + ${PROJECT_NAME} + gz-utils${GZ_UTILS_VER}::log +) diff --git a/examples/log/main.cc b/examples/log/main.cc new file mode 100644 index 0000000..1ad207d --- /dev/null +++ b/examples/log/main.cc @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * 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 +#include + +////////////////////////////////////////////////// +int main(int argc, char** argv) +{ + gz::utils::log::Logger logger("my_logger"); + logger.RawLogger().set_level(spdlog::level::trace); + + std::filesystem::path logDir = std::filesystem::temp_directory_path(); + std::filesystem::path logFile = "my_log.txt"; + std::filesystem::path logPath = logDir / logFile; + + logger.SetLogDestination(logPath); + logger.RawLogger().trace("trace"); + logger.RawLogger().info("info"); + logger.RawLogger().warn("warn"); + logger.RawLogger().error("error"); + logger.RawLogger().critical("critical"); +} diff --git a/log/include/CMakeLists.txt b/log/include/CMakeLists.txt new file mode 100644 index 0000000..6645618 --- /dev/null +++ b/log/include/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(gz/utils) diff --git a/log/include/gz/utils/CMakeLists.txt b/log/include/gz/utils/CMakeLists.txt new file mode 100644 index 0000000..8733f2d --- /dev/null +++ b/log/include/gz/utils/CMakeLists.txt @@ -0,0 +1 @@ +gz_install_all_headers(COMPONENT log) diff --git a/log/include/gz/utils/log/Logger.hh b/log/include/gz/utils/log/Logger.hh new file mode 100644 index 0000000..3e3a491 --- /dev/null +++ b/log/include/gz/utils/log/Logger.hh @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * 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. + * +*/ +#ifndef GZ_UTILS_LOG_LOGGER_HH_ +#define GZ_UTILS_LOG_LOGGER_HH_ + +#include +#include +#include +#include +#include +#include + +namespace gz +{ +namespace utils +{ +namespace log +{ +inline namespace GZ_UTILS_VERSION_NAMESPACE { + +/// \brief Gazebo console and file logging class. +/// This will configure spdlog with a sane set of defaults for logging to the +/// console as well as a file. +class GZ_UTILS_LOG_VISIBLE Logger +{ + /// \brief Class constructor. + /// \param[in] _loggerName Logger name. + public: explicit Logger(const std::string &_loggerName); + + /// \brief Set the log destination filename. + /// \param[in] _filename Log file name. + public: void SetLogDestination(const std::string &_filename); + + /// \brief Get the log destination filename. + /// \return Log file name. + public: std::string LogDestination() const; + + /// \brief Access the underlying spdlog logger. + /// \return The spdlog logger. + public: [[nodiscard]] spdlog::logger &RawLogger() const; + + /// \brief Access the underlying spdlog logger, with ownership. + /// \return The spdlog logger. + public: [[nodiscard]] std::shared_ptr RawLoggerPtr() const; + + /// \brief Implementation Pointer. + GZ_UTILS_UNIQUE_IMPL_PTR(dataPtr) +}; +} // namespace GZ_UTILS_LOG_VERSION_NAMESPACE +} // namespace log +} // namespace utils +} // namespace gz + +#endif // GZ_UTILS_LOG_LOGGER_HH_ diff --git a/log/include/gz/utils/log/SplitSink.hh b/log/include/gz/utils/log/SplitSink.hh new file mode 100644 index 0000000..fc0af5e --- /dev/null +++ b/log/include/gz/utils/log/SplitSink.hh @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * 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. + * + */ + +#ifndef GZ_UTILS_LOG_SPLITSINK_HH_ +#define GZ_UTILS_LOG_SPLITSINK_HH_ + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace gz +{ +namespace utils +{ +namespace log +{ +inline namespace GZ_UTILS_VERSION_NAMESPACE { + +/// \brief Logging sink for spdlog that logs in Gazebo-conventions. +/// +/// This will route messages with severity (warn, err, critical) to stderr, +/// and all other levels (info, debug, trace) to stdout. +class GZ_UTILS_LOG_VISIBLE SplitConsoleSink : public spdlog::sinks::sink +{ + /// \brief Class constructor. + public: SplitConsoleSink(); + + /// \brief Class destructor. + public: ~SplitConsoleSink() override = default; + + /// \brief Log a message. + /// \param[in] _msg The message to log. + public: void log(const spdlog::details::log_msg &_msg) override; + + /// \brief Flush messages. + public: void flush() override; + + /// \brief Set the logging pattern. + /// \param[in] _pattern The logging pattern. + public: void set_pattern(const std::string &_pattern) override; + + /// \brief Set the new formatter. + /// \param[in] _sinkFormatter The formatter. + public: void set_formatter(std::unique_ptr _sinkFormatter) + override; + + /// \brief Set the color mode. + /// \param[in] _mode Color mode. + public: void set_color_mode(spdlog::color_mode _mode); + + /// \brief Implementation Pointer. + GZ_UTILS_UNIQUE_IMPL_PTR(dataPtr) +}; +} // namespace GZ_UTILS_LOG_VERSION_NAMESPACE +} // namespace log +} // namespace utils +} // namespace gz + +#endif // GZ_UTILS_LOG_SPLITSINK_HH__ diff --git a/log/src/CMakeLists.txt b/log/src/CMakeLists.txt new file mode 100644 index 0000000..28eadb8 --- /dev/null +++ b/log/src/CMakeLists.txt @@ -0,0 +1,15 @@ +gz_get_libsources_and_unittests(sources gtest_sources) + +gz_add_component(log + SOURCES ${sources} + INDEPENDENT_FROM_PROJECT_LIB + GET_TARGET_NAME gz_utils_log_target_name) + +target_link_libraries(${gz_utils_log_target_name} + PUBLIC + spdlog::spdlog) + +gz_build_tests(TYPE UNIT + SOURCES ${gtest_sources} + LIB_DEPS ${gz_utils_log_target_name} +) diff --git a/log/src/Logger.cc b/log/src/Logger.cc new file mode 100644 index 0000000..340693a --- /dev/null +++ b/log/src/Logger.cc @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * 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 +#include + +#include +#include +#include +#include +#include + +namespace gz::utils::log +{ +/// \brief Private data for the Logger class. +class Logger::Implementation +{ + /// \brief Constructor. + /// \param[in] _loggerName Logger name. + public: explicit Implementation(const std::string &_loggerName) + : consoleSink(std::make_shared()), + sinks(std::make_shared()), + logger(std::make_shared(_loggerName, sinks)) + { + } + + /// \brief The console sink with stdout and stderr. + std::shared_ptr consoleSink; + + /// \brief The file sink for logging into a file. + std::shared_ptr fileSink {nullptr}; + + /// \brief A sink distribution storing multiple sinks. + std::shared_ptr sinks {nullptr}; + + /// \brief The underlying spdlog logger. + std::shared_ptr logger {nullptr}; +}; + +///////////////////////////////////////////////// +Logger::Logger(const std::string &_loggerName) + : dataPtr(gz::utils::MakeUniqueImpl(_loggerName)) +{ + // Add the console sink by default. + this->dataPtr->sinks->add_sink(this->dataPtr->consoleSink); + + // Configure the logger. + this->dataPtr->logger->set_level(spdlog::level::err); + this->dataPtr->logger->flush_on(spdlog::level::err); + + spdlog::flush_every(std::chrono::seconds(5)); + spdlog::register_logger(this->dataPtr->logger); +} + +///////////////////////////////////////////////// +void Logger::SetLogDestination(const std::string &_filename) +{ + if (this->dataPtr->fileSink) + this->dataPtr->sinks->remove_sink(this->dataPtr->fileSink); + + if (!_filename.empty()) + { + this->dataPtr->fileSink = + std::make_shared(_filename, true); + this->dataPtr->sinks->add_sink(this->dataPtr->fileSink); + } +} + +///////////////////////////////////////////////// +std::string Logger::LogDestination() const +{ + std::string logPath = ""; + if (this->dataPtr->fileSink) + logPath = this->dataPtr->fileSink->filename(); + + return logPath; +} + +///////////////////////////////////////////////// +spdlog::logger &Logger::RawLogger() const +{ + return *this->dataPtr->logger; +} + +///////////////////////////////////////////////// +std::shared_ptr Logger::RawLoggerPtr() const +{ + return this->dataPtr->logger; +} + +} // namespace gz::utils::log diff --git a/log/src/SplitSink.cc b/log/src/SplitSink.cc new file mode 100644 index 0000000..c70c7bb --- /dev/null +++ b/log/src/SplitSink.cc @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * 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 +#include + +#include +#include +#include + +namespace gz::utils::log +{ +/// \brief Private data for the SplitConsoleSink class. +class SplitConsoleSink::Implementation +{ + /// \brief Constructor. + /// \param[in] _loggerName Logger name. + public: Implementation() + { + } + + /// \brief Standard output. + public: spdlog::sinks::stdout_color_sink_mt stdout_; + + /// \brief Standard error. + public: spdlog::sinks::stderr_color_sink_mt stderr_; +}; + +///////////////////////////////////////////////// +SplitConsoleSink::SplitConsoleSink() + : dataPtr(gz::utils::MakeUniqueImpl()) +{ +} + +///////////////////////////////////////////////// +void SplitConsoleSink::log(const spdlog::details::log_msg &_msg) +{ + if (_msg.level == spdlog::level::warn || + _msg.level == spdlog::level::err || + _msg.level == spdlog::level::critical) + { + this->dataPtr->stderr_.log(_msg); + } + else + this->dataPtr->stdout_.log(_msg); +} + +///////////////////////////////////////////////// +void SplitConsoleSink::flush() +{ + this->dataPtr->stdout_.flush(); + this->dataPtr->stderr_.flush(); +} + +///////////////////////////////////////////////// +void SplitConsoleSink::set_pattern(const std::string &_pattern) +{ + this->dataPtr->stdout_.set_pattern(_pattern); + this->dataPtr->stderr_.set_pattern(_pattern); +} + +///////////////////////////////////////////////// +void SplitConsoleSink::set_formatter( + std::unique_ptr _sinkFormatter) +{ + this->dataPtr->stdout_.set_formatter(_sinkFormatter->clone()); + this->dataPtr->stderr_.set_formatter(std::move(_sinkFormatter)); +} + +///////////////////////////////////////////////// +void SplitConsoleSink::set_color_mode(spdlog::color_mode _mode) +{ + this->dataPtr->stdout_.set_color_mode(_mode); + this->dataPtr->stderr_.set_color_mode(_mode); +} + +} // namespace gz::utils::log diff --git a/log/src/SplitSink_TEST.cc b/log/src/SplitSink_TEST.cc new file mode 100644 index 0000000..f591302 --- /dev/null +++ b/log/src/SplitSink_TEST.cc @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 Open Source Robotics Foundation + * + * 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 +#include +#include + +///////////////////////////////////////////////// +TEST(SplitConsoleSink, foo) +{ + auto splitSink = std::make_shared(); + + spdlog::logger logger("split_sink", {splitSink}); + logger.set_level(spdlog::level::trace); + + logger.trace("trace"); + logger.debug("debug"); + logger.info("info"); + logger.warn("warn"); + logger.error("error"); + logger.critical("critical"); +} diff --git a/package.xml b/package.xml index 5764bbf..52ae53d 100644 --- a/package.xml +++ b/package.xml @@ -13,6 +13,8 @@ gz-cmake4 + spdlog + cmake