From e598a9215e242d440f6c783c2a1bce5c0319b34e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Ag=C3=BCero?= Date: Fri, 16 Aug 2024 19:08:04 +0200 Subject: [PATCH] Refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Carlos Agüero --- CMakeLists.txt | 2 +- examples/console.cc | 35 +- include/gz/common/Console.hh | 294 +++++---------- include/gz/common/ConsoleNew.hh | 230 ------------ src/CMakeLists.txt | 4 +- src/Console.cc | 351 ++++-------------- src/ConsoleNew.cc | 252 ------------- src/ConsoleNew_TEST.cc | 236 ------------ src/Console_TEST.cc | 1 + test/integration/console.cc | 1 + .../common/testing/detail/AutoLogFixture.hh | 1 + 11 files changed, 200 insertions(+), 1207 deletions(-) delete mode 100644 include/gz/common/ConsoleNew.hh delete mode 100644 src/ConsoleNew.cc delete mode 100644 src/ConsoleNew_TEST.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 848f66c8d..d71e394d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,7 +44,7 @@ set(GZ_MATH_VER ${gz-math8_VERSION_MAJOR}) #-------------------------------------- # Find gz-utils -gz_find_package(gz-utils3 REQUIRED) +gz_find_package(gz-utils3 REQUIRED COMPONENTS log) set(GZ_UTILS_VER ${gz-utils3_VERSION_MAJOR}) gz_find_package(spdlog REQUIRED) diff --git a/examples/console.cc b/examples/console.cc index 4ef8a1528..56e72b090 100644 --- a/examples/console.cc +++ b/examples/console.cc @@ -14,30 +14,41 @@ * limitations under the License. * */ -#include +#include +#include int main(int argc, char **argv) { // Default verbosity is 1, only error messages show - gzdbg << "This is a debug message"; - gzmsg << "This is an informational message"; + gzdbg << "This is a debug message"; + gzmsg << "This is an informational message"; gzwarn << "This is a warning"; - gzerr << "This is an error"; + gzerr << "This is an error"; gzcrit << "This is a critical error"; // Change verbosity to level 4, all messages show - gz::common::ConsoleGlobal::SetVerbosity(4); - gz::common::ConsoleGlobal::SetPrefix("My prefix. "); - gzdbg << "This is a debug message"; - gzmsg << "This is an informational message"; + // gz::common::ConsoleGlobal::SetVerbosity(4); + // gz::common::ConsoleGlobal::SetPrefix("My prefix. "); + gzdbg << "This is a debug message"; + gzmsg << "This is an informational message"; gzwarn << "This is a warning"; - gzerr << "This is an error"; + gzerr << "This is an error"; gzcrit << "This is a critical error"; - gz::common::ConsoleNew c("gz_tmp"); - c.SetLogDestination("/tmp/my_test_log.txt"); - auto logger = c.Logger(); + std::filesystem::path logDir = std::filesystem::temp_directory_path(); + std::filesystem::path logFile = "my_log.txt"; + + gz::common::Console c("gz_tmp"); + c.SetLogDestination(logDir / "tmp2" / logFile); + auto logger = c.RawLogger(); logger.log(spdlog::level::err, "Hello"); + gz::common::Console::Init(logDir / "tmp3", logFile); + gzerr << "Error 1"; + gzerr << "Error 2"; + gzerr << "Directory: " << gz::common::Console::Directory(); + gz::common::Console::Close(); + gzerr << "Error 3"; + return 0; } diff --git a/include/gz/common/Console.hh b/include/gz/common/Console.hh index 229880c26..afd1b409a 100644 --- a/include/gz/common/Console.hh +++ b/include/gz/common/Console.hh @@ -17,35 +17,79 @@ #ifndef GZ_COMMON_CONSOLE_HH_ #define GZ_COMMON_CONSOLE_HH_ -#include +#include +#include + #include -#include -#include +#include +#include #include #include #include -#include #include +#include +#include namespace gz { namespace common { - /// \brief Output an error message, if the verbose level is >= 1 - #define gzerr (gz::common::Console::err(__FILE__, __LINE__)) + /// \brief Helper class for providing gzlog macros. + class GZ_COMMON_VISIBLE LogMessage + { + /// \brief Constructor. + /// \param[in] _file Filename. + /// \param[in] _line Line number. + /// \param[in] _logLevel Log level. + public: LogMessage(const char *_file, + int _line, + spdlog::level::level_enum _logLevel); + + /// \brief Destructor. + public: ~LogMessage(); - /// \brief Output a warning message, if the verbose level is >= 2 - #define gzwarn (gz::common::Console::warn(__FILE__, __LINE__)) + /// \brief Get access to the underlying stream. + /// \return The underlying stream. + public: std::ostream &stream(); - /// \brief Output a message, if the verbose level is >= 3 - #define gzmsg (gz::common::Console::msg()) + /// \brief Log level. + private: spdlog::level::level_enum severity; - /// \brief Output a debug message, if the verbose level is >= 4 - #define gzdbg (gz::common::Console::dbg(__FILE__, __LINE__)) + /// \brief Source file location information. + private: spdlog::source_loc sourceLocation; - /// \brief Output a message to a log file, regardless of verbosity level - #define gzlog (gz::common::Console::log()) + /// \brief Underlying stream. + private: std::ostringstream ss; + }; + + /// \brief Output a critical message. + #define gzcrit (gz::common::LogMessage( \ + __FILE__, __LINE__, spdlog::level::critical).stream()) + + /// \brief Output an error message. + #define gzerr gz::common::LogMessage( \ + __FILE__, __LINE__, spdlog::level::err).stream() + + /// \brief Output a warning message. + #define gzwarn gz::common::LogMessage( \ + __FILE__, __LINE__, spdlog::level::warn).stream() + + /// \brief Output a message to a log file. + #define gzlog gz::common::LogMessage( \ + __FILE__, __LINE__, spdlog::level::info).stream() + + /// \brief Output a message. + #define gzmsg gz::common::LogMessage( \ + __FILE__, __LINE__, spdlog::level::info).stream() + + /// \brief Output a debug message. + #define gzdbg gz::common::LogMessage( \ + __FILE__, __LINE__, spdlog::level::debug).stream() + + /// \brief Output a trace message. + #define gztrace gz::common::LogMessage( \ + __FILE__, __LINE__, spdlog::level::trace).stream() /// \brief Initialize log file with filename given by _dir/_file. /// If called twice, it will close the file currently in use and open a new @@ -55,192 +99,53 @@ namespace gz /// be relative to your home directory. /// \param[in] _file Name of log file for ignlog messages. #define gzLogInit(_dir, _file)\ - gz::common::Console::log.Init(_dir, _file) - + gz::common::Console::Init(_dir, _file) + /// \brief Close the file used for logging. #define gzLogClose()\ - gz::common::Console::log.Close() + gz::common::Console::Close() /// \brief Get the full path of the directory where the log files are stored /// \return Full path of the directory #define gzLogDirectory()\ - (gz::common::Console::log.LogDirectory()) + (gz::common::Console::Directory()) - /// \class FileLogger FileLogger.hh common/common.hh - /// \brief A logger that outputs messages to a file. - class GZ_COMMON_VISIBLE FileLogger : public std::ostream + /// \class Console Console.hh common/common.hh + /// \brief Container for loggers, and global logging options + /// (such as verbose vs. quiet output). + class GZ_COMMON_VISIBLE Console : public gz::utils::log::Logger { - /// \brief Constructor. - /// \param[in] _filename Filename to write into. If empty, - /// FileLogger::Init must be called separately. - public: explicit FileLogger(const std::string &_filename = ""); + /// \brief Class constructor. + /// \param[in] _loggerName Logger name. + public: explicit Console(const std::string &_loggerName); - /// \brief Destructor. - public: virtual ~FileLogger(); + /// \brief Access the global gz console logger. + /// \return The gz consoler logger. + public: static Console &Root(); - /// \brief Initialize the file logger. + /// \brief Initialize the global logger. /// \param[in] _directory Name of directory that holds the log file. /// \param[in] _filename Name of the log file to write output into. - public: void Init(const std::string &_directory, - const std::string &_filename); - - /// \brief Close the open file handles. - public: void Close(); - - /// \brief Output a filename and line number, then return a reference - /// to the logger. - /// \return Reference to this logger. - public: virtual FileLogger &operator()(); - - /// \brief Output a filename and line number, then return a reference - /// to the logger. - /// \param[in] _file Filename to output. - /// \param[in] _line Line number in the _file. - /// \return Reference to this logger. - public: virtual FileLogger &operator()( - const std::string &_file, int _line); - + /// \return True when the initialization succeed or false otherwise. + public: static bool Init(const std::string &_directory, + const std::string &_filename); + + /// \brief Detach fhe file sink from the global logger. After this call, + /// console logging will keep working but no file logging. + public: static void Close(); + /// \brief Get the full path of the directory where all the log files /// are stored. /// \return Full path of the directory. - public: std::string LogDirectory() const; - - /// \brief String buffer for the file logger. - protected: class Buffer : public std::stringbuf - { - /// \brief Constructor. - /// \param[in] _filename Filename to write into. - public: explicit Buffer(const std::string &_filename); - - /// \brief Destructor. - public: virtual ~Buffer(); - - /// \brief Writes _count characters to the string buffer - /// \param[in] _char Input rharacter array. - /// \param[in] _count Number of characters in array. - /// \return The number of characters successfully written. - public: std::streamsize xsputn( - const char *_char, std::streamsize _count) override; - - /// \brief Sync the stream (output the string buffer - /// contents). - /// \return Return 0 on success. - public: int sync() override; - - /// \brief Stream to output information into. - public: std::ofstream *stream; - - /// \brief Mutex to synchronize writes to the string buffer - /// and the output stream. - public: std::mutex syncMutex; - }; - - GZ_UTILS_WARN_IGNORE__DLL_INTERFACE_MISSING - /// \brief Stores the full path of the directory where all the log files - /// are stored. - private: std::string logDirectory; - GZ_UTILS_WARN_RESUME__DLL_INTERFACE_MISSING - - /// \brief True if initialized. - private: bool initialized; - }; - - /// \class Logger Logger.hh common/common.hh - /// \brief Terminal logger. - class GZ_COMMON_VISIBLE Logger : public std::ostream - { - /// \enum LogType. - /// \brief Output destination type. - public: enum LogType - { - /// \brief Output to stdout. - STDOUT, - /// \brief Output to stderr. - STDERR - }; - - /// \brief Constructor. - /// \param[in] _prefix String to use as prefix when logging to file. - /// \param[in] _color Color of the output stream. - /// \param[in] _type Output destination type (STDOUT, or STDERR) - /// \param[in] _verbosity Verbosity level. - public: Logger(const std::string &_prefix, const int _color, - const LogType _type, const int _verbosity); - - /// \brief Destructor. - public: virtual ~Logger(); - - /// \brief Access operator. - /// \return Reference to this logger. - public: virtual Logger &operator()(); - - /// \brief Output a filename and line number, then return a reference - /// to the logger. - /// \param[in] _file Filename to output. - /// \param[in] _line Line number in the _file. - /// \return Reference to this logger. - public: virtual Logger &operator()( - const std::string &_file, int _line); - - /// \brief String buffer for the base logger. - protected: class Buffer : public std::stringbuf - { - /// \brief Constructor. - /// \param[in] _type Output destination type - /// (STDOUT, or STDERR) - /// \param[in] _color Color of the output stream. - /// \param[in] _verbosity Verbosity level. - public: Buffer(LogType _type, const int _color, - const int _verbosity); - - /// \brief Destructor. - public: virtual ~Buffer(); - - /// \brief Writes _count characters to the string buffer - /// \param[in] _char Input rharacter array. - /// \param[in] _count Number of characters in array. - /// \return The number of characters successfully written. - public: std::streamsize xsputn( - const char *_char, std::streamsize _count) override; - - /// \brief Sync the stream (output the string buffer - /// contents). - /// \return Return 0 on success. - public: int sync() override; - - /// \brief Destination type for the messages. - public: LogType type; - - /// \brief ANSI color code using Select Graphic Rendition - /// parameters (SGR). See - /// http://en.wikipedia.org/wiki/ANSI_escape_code#Colors - public: int color; + public: static std::string Directory(); - /// \brief Level of verbosity - public: int verbosity; - - /// \brief Mutex to synchronize writes to the string buffer - /// and the output stream. - public: std::mutex syncMutex; - }; - - GZ_UTILS_WARN_IGNORE__DLL_INTERFACE_MISSING - /// \brief Prefix to use when logging to file. - private: std::string prefix; - GZ_UTILS_WARN_RESUME__DLL_INTERFACE_MISSING - }; - - /// \class Console Console.hh common/common.hh - /// \brief Container for loggers, and global logging options - /// (such as verbose vs. quiet output). - class GZ_COMMON_VISIBLE Console - { /// \brief Set verbosity, where - /// <= 0: No output, - /// 1: Error messages, - /// 2: Error and warning messages, - /// 3: Error, warning, and info messages, - /// >= 4: Error, warning, info, and debug messages. + /// 0: Critical messages, + /// 1: Critical, error messages, + /// 2: Critical, error and warning messages, + /// 3: Critical, error, warning, and info messages, + /// 4: Critical, error, warning, info, and debug messages. + /// 5: Critical, error, warning, info, debug, and trace messages. /// \param[in] _level The new verbose level. public: static void SetVerbosity(const int _level); @@ -269,28 +174,17 @@ namespace gz /// \sa void SetPrefix(const std::string &_customPrefix) public: static std::string Prefix(); - /// \brief Global instance of the message logger. - public: static Logger msg; - - /// \brief Global instance of the error logger. - public: static Logger err; - - /// \brief Global instance of the debug logger. - public: static Logger dbg; - - /// \brief Global instance of the warning logger. - public: static Logger warn; - - /// \brief Global instance of the file logger. - public: static FileLogger log; - /// \brief The level of verbosity, the default level is 1. private: static int verbosity; - GZ_UTILS_WARN_IGNORE__DLL_INTERFACE_MISSING + //GZ_UTILS_WARN_IGNORE__DLL_INTERFACE_MISSING /// \brief A custom prefix. See SetPrefix(). private: static std::string customPrefix; - GZ_UTILS_WARN_RESUME__DLL_INTERFACE_MISSING + + /// \brief Stores the full path of the directory where all the log files + /// are stored. + private: std::string logDirectory; + //GZ_UTILS_WARN_RESUME__DLL_INTERFACE_MISSING }; } } diff --git a/include/gz/common/ConsoleNew.hh b/include/gz/common/ConsoleNew.hh deleted file mode 100644 index 8c903233d..000000000 --- a/include/gz/common/ConsoleNew.hh +++ /dev/null @@ -1,230 +0,0 @@ -/* - * 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_COMMON_CONSOLENEW_HH_ -#define GZ_COMMON_CONSOLENEW_HH_ - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -namespace gz::common -{ - /// \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_COMMON_VISIBLE ConsoleNew - { - /// \brief Class constructor. - /// \param[in] _loggerName Logger name. - public: explicit ConsoleNew(const std::string &_loggerName); - - /// \brief Set the console output color mode. - ///\param[in] _mode Color mode. - public: void SetColorMode(spdlog::color_mode _mode); - - /// \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 &Logger() const; - - /// \brief Access the underlying spdlog logger, with ownership. - /// \return The spdlog logger. - public: [[nodiscard]] std::shared_ptr LoggerPtr() const; - - /// \brief Access the global gz console logger. - /// \return The gz consoler logger. - public: static ConsoleNew &Root(); - - /// \brief Implementation Pointer. - GZ_UTILS_UNIQUE_IMPL_PTR(dataPtr) - }; - - /// Helper class for providing gzlog macros. - class GZ_COMMON_VISIBLE LogMessage - { - /// \brief Constructor. - /// \param[in] _file Filename. - /// \param[in] _line Line number. - /// \param[in] _logLevel Log level. - public: LogMessage(const char *_file, - int _line, - spdlog::level::level_enum _logLevel); - - /// \brief Destructor. - public: ~LogMessage(); - - /// \brief Get access to the underlying stream. - /// \return The underlying stream. - public: std::ostream &stream(); - - /// \brief Log level. - private: spdlog::level::level_enum severity; - - /// \brief Source file location information. - private: spdlog::source_loc sourceLocation; - - /// \brief Underlying stream. - private: std::ostringstream ss; - }; - - /// \brief Helper class for providing global options. - class GZ_COMMON_VISIBLE ConsoleGlobal - { - /// \brief Set verbosity, where - /// <= 0: No output, - /// 1: Error messages, - /// 2: Error and warning messages, - /// 3: Error, warning, and info messages, - /// >= 4: Error, warning, info, and debug messages. - /// \param[in] _level The new verbose level. - public: static void SetVerbosity(const int _level); - - /// \brief Get the verbose level. - /// \return The level of verbosity. - /// \sa SetVerbosity(const int _level) - public: static int Verbosity(); - - /// \brief Add a custom prefix in front of the default prefixes. - /// - /// By default, the custom prefix is an empty string, so the messages - /// start as: - /// - /// [Err], [Wrn], [Msg], [Dbg] - /// - /// If you set the prefix to "-my-", for example, they become: - /// - /// -my-[Err], -my-[Wrn], -my-[Msg], -my-[Dbg] - /// - /// \param[in] _customPrefix Prefix string. - /// \sa std::string Prefix() const - public: static void SetPrefix(const std::string &_customPrefix); - - /// \brief Get custom prefix. This is empty by default. - /// \return The custom prefix. - /// \sa void SetPrefix(const std::string &_customPrefix) - public: static std::string Prefix(); - - /// \brief ToDo. - public: friend class LogMessage; - - /// \brief The level of verbosity, the default level is 1. - private: static int verbosity; - - //GZ_UTILS_WARN_IGNORE__DLL_INTERFACE_MISSING - /// \brief A custom prefix. See SetPrefix(). - private: static std::string customPrefix; - //GZ_UTILS_WARN_RESUME__DLL_INTERFACE_MISSING - }; -} // namespace gz::common - -/// \brief Output a critical message. -#define gzcrit (gz::common::LogMessage( \ - __FILE__, __LINE__, spdlog::level::critical).stream()) - -/// \brief Output an error message. -#define gzerr gz::common::LogMessage( \ - __FILE__, __LINE__, spdlog::level::err).stream() - -/// \brief Output a warning message. -#define gzwarn gz::common::LogMessage( \ - __FILE__, __LINE__, spdlog::level::warn).stream() - -/// \brief Output a message to a log file. -#define gzlog gz::common::LogMessage( \ - __FILE__, __LINE__, spdlog::level::info).stream() - -/// \brief Output a message. -#define gzmsg gz::common::LogMessage( \ - __FILE__, __LINE__, spdlog::level::info).stream() - -/// \brief Output a debug message. -#define gzdbg gz::common::LogMessage( \ - __FILE__, __LINE__, spdlog::level::debug).stream() - -/// \brief Output a trace message. -#define gztrace gz::common::LogMessage( \ - __FILE__, __LINE__, spdlog::level::trace).stream() - -/// \brief Initialize the Gazebo log. -/// \param[in] _directory Directory to log. -/// \param[in] _filename Filename to log. -void gzLogInit(const std::string &_directory, const std::string &_filename) -{ - auto &root = gz::common::ConsoleNew::Root(); - - std::string logPath; - if (!_directory.empty()) - { - logPath = _directory; - } else if (!gz::common::env(GZ_HOMEDIR, logPath)) - { - root.Logger().error( - "Missing HOME environment variable. No log file will be generated."); - return; - } - - if (!gz::common::createDirectories(logPath)) - { - root.Logger().error("Failed to create output log directory {}", - logPath.c_str()); - return; - } - - logPath = gz::common::joinPaths(logPath, _filename); - root.Logger().info("Setting log file output destination to {}", - logPath.c_str()); - gz::common::ConsoleNew::Root().SetLogDestination(logPath); - spdlog::set_default_logger(root.LoggerPtr()); -} - -/// \brief Close the file used for logging. -void gzLogClose() -{ - auto filePath = gz::common::ConsoleNew::Root().LogDestination(); - std::ifstream ifs(filePath); - if (!ifs.is_open()) - std::filesystem::remove(filePath); -} - -/// \brief Get the full path of the directory where the log files are stored -/// \return Full path of the directory. -std::string gzLogDirectory() -{ - std::filesystem::path path = gz::common::ConsoleNew::Root().LogDestination(); - return path.parent_path(); -} - -#endif // GZ_COMMON_CONSOLENEW_HH_ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 79d566149..a0dc9a8f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,7 +18,7 @@ target_link_libraries(${PROJECT_LIBRARY_TARGET_NAME} PRIVATE ${CXX_FILESYSTEM_LIBRARIES} PUBLIC - gz-utils${GZ_UTILS_VER}::gz-utils${GZ_UTILS_VER} + gz-utils${GZ_UTILS_VER}::log spdlog::spdlog ) @@ -43,7 +43,7 @@ gz_build_tests( TYPE UNIT SOURCES ${gtest_sources} LIB_DEPS - gz-utils${GZ_UTILS_VER}::gz-utils${GZ_UTILS_VER} + gz-utils${GZ_UTILS_VER}::log gz-common${GZ_COMMON_VER}-testing INCLUDE_DIRS # Used to make internal source file headers visible to the unit tests diff --git a/src/Console.cc b/src/Console.cc index 238e37e62..4be9f1787 100644 --- a/src/Console.cc +++ b/src/Console.cc @@ -14,6 +14,8 @@ * limitations under the License. * */ +#include +#include #include #include @@ -21,6 +23,10 @@ #include #include +#include +#include +#include + #ifdef _WIN32 #include #endif @@ -28,209 +34,46 @@ using namespace gz; using namespace common; - -FileLogger common::Console::log(""); - -// On UNIX, these are ANSI-based color codes. On Windows, these are colors from -// docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences . -// They happen to overlap, but there might be differences if more colors are -// added. -const int red = 31; -const int yellow = 33; -const int green = 32; -const int blue = 36; - -Logger Console::err("[Err] ", red, Logger::STDERR, 1); -Logger Console::warn("[Wrn] ", yellow, Logger::STDERR, 2); -Logger Console::msg("[Msg] ", green, Logger::STDOUT, 3); -Logger Console::dbg("[Dbg] ", blue, Logger::STDOUT, 4); - -int Console::verbosity = 1; -std::string Console::customPrefix = ""; // NOLINT(*) - -////////////////////////////////////////////////// -void Console::SetVerbosity(const int _level) -{ - verbosity = _level; -} - -////////////////////////////////////////////////// -int Console::Verbosity() -{ - return verbosity; -} - -////////////////////////////////////////////////// -void Console::SetPrefix(const std::string &_prefix) -{ - customPrefix = _prefix; -} - -////////////////////////////////////////////////// -std::string Console::Prefix() -{ - return customPrefix; -} - -///////////////////////////////////////////////// -Logger::Logger(const std::string &_prefix, const int _color, - const LogType _type, const int _verbosity) -: std::ostream(new Buffer(_type, _color, _verbosity)), prefix(_prefix) -{ - this->setf(std::ios_base::unitbuf); -} - ///////////////////////////////////////////////// -Logger::~Logger() +LogMessage::LogMessage(const char *_file, int _line, + spdlog::level::level_enum _logLevel) + : severity(_logLevel), + sourceLocation(_file, _line, "") { } ///////////////////////////////////////////////// -Logger &Logger::operator()() +LogMessage::~LogMessage() { - Console::log() << "(" << common::systemTimeIso() << ") "; - (*this) << Console::Prefix() << this->prefix; - - return (*this); + gz::common::Console::Root().RawLogger().log( + this->sourceLocation, this->severity, + gz::common::Console::Prefix() + this->ss.str()); } ///////////////////////////////////////////////// -Logger &Logger::operator()(const std::string &_file, int _line) +std::ostream &LogMessage::stream() { - int index = _file.find_last_of("/") + 1; - - Console::log() << "(" << common::systemTimeIso() << ") "; - std::stringstream prefixString; - prefixString << Console::Prefix() << this->prefix - << "[" << _file.substr(index , _file.size() - index) << ":" - << _line << "] "; - (*this) << prefixString.str(); - - return (*this); + return this->ss; } -///////////////////////////////////////////////// -Logger::Buffer::Buffer(LogType _type, const int _color, const int _verbosity) - : type(_type), color(_color), verbosity(_verbosity) -{ -} - -///////////////////////////////////////////////// -Logger::Buffer::~Buffer() -{ - this->pubsync(); -} - -///////////////////////////////////////////////// -std::streamsize Logger::Buffer::xsputn(const char *_char, - std::streamsize _count) -{ - std::lock_guard lk(this->syncMutex); - return std::stringbuf::xsputn(_char, _count); -} - -///////////////////////////////////////////////// -int Logger::Buffer::sync() -{ - std::string outstr; - { - std::lock_guard lk(this->syncMutex); - outstr = this->str(); - } - - // Log messages to disk - { - std::lock_guard lk(this->syncMutex); - Console::log << outstr; - Console::log.flush(); - } - - // Output to terminal - if (Console::Verbosity() >= this->verbosity && !outstr.empty()) - { -#ifndef _WIN32 - bool lastNewLine = outstr.back() == '\n'; - FILE *outstream = this->type == Logger::STDOUT ? stdout : stderr; - - if (lastNewLine) - outstr.pop_back(); - - std::stringstream ss; - ss << "\033[1;" << this->color << "m" << outstr << "\033[0m"; - if (lastNewLine) - ss << std::endl; - - { - std::lock_guard lk(this->syncMutex); - fprintf(outstream, "%s", ss.str().c_str()); - } -#else - HANDLE hConsole = CreateFileW( - L"CONOUT$", GENERIC_WRITE|GENERIC_READ, 0, nullptr, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, nullptr); - - DWORD dwMode = 0; - bool vtProcessing = false; - if (GetConsoleMode(hConsole, &dwMode)) - { - if ((dwMode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) > 0) - { - vtProcessing = true; - } - else - { - dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if (SetConsoleMode(hConsole, dwMode)) - vtProcessing = true; - } - } - - std::ostream &outStream = - this->type == Logger::STDOUT ? std::cout : std::cerr; - - { - std::lock_guard lk(this->syncMutex); - if (vtProcessing) - outStream << "\x1b[" << this->color << "m" << outstr << "\x1b[m"; - else - outStream << outstr; - } -#endif - } - - { - std::lock_guard lk(this->syncMutex); - this->str(""); - } - return 0; -} +int Console::verbosity = 1; +std::string Console::customPrefix = ""; // NOLINT(*) ///////////////////////////////////////////////// -FileLogger::FileLogger(const std::string &_filename) - : std::ostream(new Buffer(_filename)), - logDirectory("") +Console::Console(const std::string &_loggerName) + : gz::utils::log::Logger(_loggerName) { - this->initialized = false; - this->setf(std::ios_base::unitbuf); } ///////////////////////////////////////////////// -FileLogger::~FileLogger() +Console &Console::Root() { - if (this->initialized && this->rdbuf()) - { - auto *buf = dynamic_cast(this->rdbuf()); - if (buf->stream) - { - delete buf->stream; - buf->stream = nullptr; - } - } + static gz::utils::NeverDestroyed root{"gz"}; + return root.Access(); } ///////////////////////////////////////////////// -void FileLogger::Init(const std::string &_directory, - const std::string &_filename) +bool Console::Init(const std::string &_directory, const std::string &_filename) { std::string logPath; @@ -248,7 +91,7 @@ void FileLogger::Init(const std::string &_directory, // trying to get the log initialized std::cerr << "Missing HOME environment variable." << "No log file will be generated." << std::endl; - return; + return false; } logPath = joinPaths(logPath, _directory); } @@ -257,122 +100,82 @@ void FileLogger::Init(const std::string &_directory, logPath = _directory; } - auto* buf = dynamic_cast(this->rdbuf()); - - // Create the directory if it doesn't exist. - createDirectories(logPath); - logPath = joinPaths(logPath, _filename); - // Check if the Init method has been already called, and if so - // remove current buffer. - if (buf->stream) - { - delete buf->stream; - buf->stream = nullptr; - } - - buf->stream = new std::ofstream(logPath.c_str(), std::ios::out); - if (!buf->stream->is_open()) - std::cerr << "Error opening log file: " << logPath << std::endl; + Console::Root().SetLogDestination(logPath.c_str()); + Console::Root().RawLogger().log(spdlog::level::info, + "Setting log file output destination to {}", logPath.c_str()); - // Update the log directory name. - if (isDirectory(logPath)) - this->logDirectory = logPath; - else - this->logDirectory = common::parentPath(logPath); - - this->initialized = true; - - /// \todo(anyone) Reimplement this. - // Output the version of the project. - // (*buf->stream) << PROJECT_VERSION_HEADER << std::endl; -} - -///////////////////////////////////////////////// -void FileLogger::Close() -{ - auto* buf = dynamic_cast(this->rdbuf()); - if (buf && buf->stream && buf->stream->is_open()) - { - buf->stream->close(); - delete buf->stream; - buf->stream = nullptr; - } + return true; } ///////////////////////////////////////////////// -FileLogger &FileLogger::operator()() +void Console::Close() { - if (!this->initialized) - this->Init(".gz", "auto_default.log"); - - (*this) << "(" << common::systemTimeIso() << ") "; - return (*this); + // Detach the current file sink. + Console::Root().SetLogDestination(std::string()); } ///////////////////////////////////////////////// -FileLogger &FileLogger::operator()(const std::string &_file, int _line) +std::string Console::Directory() { - if (!this->initialized) - this->Init(".gz", "auto_default.log"); - - int index = _file.find_last_of("/") + 1; - (*this) << "(" << common::systemTimeIso() << ") [" - << _file.substr(index , _file.size() - index) << ":" << _line << "]"; - - return (*this); + std::filesystem::path path = gz::common::Console::Root().LogDestination(); + return path.parent_path(); } -///////////////////////////////////////////////// -std::string FileLogger::LogDirectory() const +////////////////////////////////////////////////// +void Console::SetVerbosity(const int _level) { - return this->logDirectory; -} + if (_level < 0) + { + Console::Root().RawLogger().log(spdlog::level::info, + "Negative verbosity level. Ignoring it"); + return; + } -///////////////////////////////////////////////// -FileLogger::Buffer::Buffer(const std::string &_filename) - : stream(NULL) -{ - if (!_filename.empty()) + auto globalLogger = gz::common::Console::Root().RawLogger(); + verbosity = _level; + switch (_level) { - this->stream = new std::ofstream(_filename.c_str(), std::ios::out); + case 0: + globalLogger.set_level(spdlog::level::critical); + break; + case 1: + globalLogger.set_level(spdlog::level::err); + break; + case 2: + globalLogger.set_level(spdlog::level::warn); + break; + case 3: + globalLogger.set_level(spdlog::level::info); + break; + case 4: + globalLogger.set_level(spdlog::level::debug); + break; + case 5: + globalLogger.set_level(spdlog::level::trace); + break; + default: + Console::Root().RawLogger().log(spdlog::level::info, + "Unknown verbosity level. Values should be between 0 and 5. Ignoring it"); + break; } } -///////////////////////////////////////////////// -FileLogger::Buffer::~Buffer() +////////////////////////////////////////////////// +int Console::Verbosity() { - if (this->stream) - static_cast(this->stream)->close(); + return verbosity; } -///////////////////////////////////////////////// -std::streamsize FileLogger::Buffer::xsputn(const char *_char, - std::streamsize _count) +////////////////////////////////////////////////// +void Console::SetPrefix(const std::string &_prefix) { - std::lock_guard lk(this->syncMutex); - return std::stringbuf::xsputn(_char, _count); + customPrefix = _prefix; } -///////////////////////////////////////////////// -int FileLogger::Buffer::sync() +////////////////////////////////////////////////// +std::string Console::Prefix() { - if (!this->stream) - return -1; - - { - std::lock_guard lk(this->syncMutex); - *this->stream << this->str(); - } - - { - std::lock_guard lk(this->syncMutex); - this->stream->flush(); - } - { - std::lock_guard lk(this->syncMutex); - this->str(""); - } - return !(*this->stream); + return customPrefix; } diff --git a/src/ConsoleNew.cc b/src/ConsoleNew.cc deleted file mode 100644 index df59f3b23..000000000 --- a/src/ConsoleNew.cc +++ /dev/null @@ -1,252 +0,0 @@ -/* - * 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 - -#include -#include -#include -#include - -namespace { -/// \brief Custom log sink that routes to stdout/stderr in Gazebo conventions. -class GzSplitSink : public spdlog::sinks::sink -{ - /// \brief Class destructor. - public: ~GzSplitSink() override = default; - - /// \brief Log a message. - /// \param[in] _msg The message to log. - public: void log(const spdlog::details::log_msg &_msg) override - { - if (_msg.level == spdlog::level::warn || - _msg.level == spdlog::level::err || - _msg.level == spdlog::level::critical) - { - this->stderr.log(_msg); - } - else - this->stdout.log(_msg); - } - - /// \brief Flush messages. - public: void flush() override - { - this->stdout.flush(); - this->stderr.flush(); - } - - /// \brief Set the logging pattern. - /// \param[in] _pattern The logging pattern. - public: void set_pattern(const std::string &_pattern) override - { - this->stdout.set_pattern(_pattern); - this->stderr.set_pattern(_pattern); - } - - /// \brief Set the new formatter. - /// \param[in] _sinkFormatter The formatter. - public: void set_formatter(std::unique_ptr _sinkFormatter) - override - { - this->stdout.set_formatter(_sinkFormatter->clone()); - this->stderr.set_formatter(std::move(_sinkFormatter)); - } - - /// \brief Set the color mode. - /// \param[in] _mode Color mode. - public: void set_color_mode(spdlog::color_mode _mode) - { - this->stdout.set_color_mode(_mode); - this->stderr.set_color_mode(_mode); - } - - /// \brief Standard output. - private: spdlog::sinks::stdout_color_sink_mt stdout; - - /// \brief Standard error. - private: spdlog::sinks::stderr_color_sink_mt stderr; -}; -} // namespace - -namespace gz::common -{ -int ConsoleGlobal::verbosity = 1; -std::string ConsoleGlobal::customPrefix = ""; // NOLINT(*) - -///////////////////////////////////////////////// -LogMessage::LogMessage(const char *_file, int _line, - spdlog::level::level_enum _logLevel) - : severity(_logLevel), sourceLocation(_file, _line, "") -{ -} - -///////////////////////////////////////////////// -LogMessage::~LogMessage() -{ - gz::common::ConsoleNew::Root().Logger().log( - this->sourceLocation, this->severity, - gz::common::ConsoleGlobal::Prefix() + this->ss.str()); -} - -///////////////////////////////////////////////// -std::ostream &LogMessage::stream() -{ - return this->ss; -} - -/// \brief Private data for the ConsoleNew class. -class ConsoleNew::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 . - std::shared_ptr consoleSink; - - /// \brief . - std::shared_ptr fileSink {nullptr}; - - /// \brief . - std::shared_ptr sinks {nullptr}; - - /// \brief . - std::shared_ptr logger {nullptr}; -}; - -///////////////////////////////////////////////// -ConsoleNew::ConsoleNew(const std::string &_loggerName) - : dataPtr(gz::utils::MakeUniqueImpl(_loggerName)) -{ - // Add the console sink as a destination. - 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 ConsoleNew::SetColorMode(spdlog::color_mode _mode) -{ - this->dataPtr->consoleSink->set_color_mode(_mode); -} - -///////////////////////////////////////////////// -void ConsoleNew::SetLogDestination(const std::string &_filename) -{ - if (this->dataPtr->fileSink) - this->dataPtr->sinks->remove_sink(this->dataPtr->fileSink); - - this->dataPtr->fileSink = - std::make_shared(_filename, true); - this->dataPtr->sinks->add_sink(this->dataPtr->fileSink); -} - -///////////////////////////////////////////////// -std::string ConsoleNew::LogDestination() const -{ - std::string logPath = ""; - if (this->dataPtr->fileSink) - { - logPath = this->dataPtr->fileSink->filename(); - } - - return logPath; -} - -///////////////////////////////////////////////// -spdlog::logger &ConsoleNew::Logger() const -{ - return *this->dataPtr->logger; -} - -///////////////////////////////////////////////// -std::shared_ptr ConsoleNew::LoggerPtr() const -{ - return this->dataPtr->logger; -} - -///////////////////////////////////////////////// -ConsoleNew &ConsoleNew::Root() -{ - static gz::utils::NeverDestroyed root{"gz"}; - return root.Access(); -} - -///////////////////////////////////////////////// -void ConsoleGlobal::SetVerbosity(const int _level) -{ - verbosity = _level; - switch (_level) - { - case 0: - gz::common::ConsoleNew::Root().Logger().set_level(spdlog::level::critical); - break; - case 1: - gz::common::ConsoleNew::Root().Logger().set_level(spdlog::level::err); - break; - case 2: - gz::common::ConsoleNew::Root().Logger().set_level(spdlog::level::warn); - break; - case 3: - gz::common::ConsoleNew::Root().Logger().set_level(spdlog::level::info); - break; - case 4: - gz::common::ConsoleNew::Root().Logger().set_level(spdlog::level::debug); - break; - case 5: - gz::common::ConsoleNew::Root().Logger().set_level(spdlog::level::trace); - break; - default: - break; - } -} - -////////////////////////////////////////////////// -int ConsoleGlobal::Verbosity() -{ - return verbosity; -} - -////////////////////////////////////////////////// -void ConsoleGlobal::SetPrefix(const std::string &_prefix) -{ - customPrefix = _prefix; -} - -////////////////////////////////////////////////// -std::string ConsoleGlobal::Prefix() -{ - return customPrefix; -} -} // namespace gz::common diff --git a/src/ConsoleNew_TEST.cc b/src/ConsoleNew_TEST.cc deleted file mode 100644 index f357b6d5e..000000000 --- a/src/ConsoleNew_TEST.cc +++ /dev/null @@ -1,236 +0,0 @@ -/* - * 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 - -/// \brief Test ConsoleNew. -class ConsoleNew_TEST : public ::testing::Test -{ - // Documentation inherited - protected: void SetUp() override - { - this->temp = std::make_unique( - "test", "gz_common", false); - ASSERT_TRUE(this->temp->Valid()); - EXPECT_TRUE(gz::common::setenv(GZ_HOMEDIR, this->temp->Path())); - } - - /// \brief Clear out all the directories we produced during this test. - public: void TearDown() override - { - gzLogClose(); - EXPECT_TRUE(gz::common::unsetenv(GZ_HOMEDIR)); - } - - /// \brief Temporary directory to run test in. - private: std::unique_ptr temp; -}; - -/// \brief Read the log content. -/// \param[in] _filename Log filename. -/// \return Log content. -std::string GetLogContent(const std::string &_filename) -{ - // Get the absolute path - std::string path; - EXPECT_TRUE(gz::common::env(GZ_HOMEDIR, path)); - path = gz::common::joinPaths(path, _filename); - EXPECT_TRUE(gz::common::exists(path)); - - // Open the log file, and read back the string - std::ifstream ifs(path.c_str(), std::ios::in); - std::string loggedString; - - while (!ifs.eof()) - { - std::string line; - std::getline(ifs, line); - loggedString += line; - } - - return loggedString; -} - -///////////////////////////////////////////////// -TEST_F(ConsoleNew_TEST, NonRootLogger) -{ - // Create a unique directory path - auto path = gz::common::uuid(); - - // Initialize logging - gzLogInit("", "test.log"); - - gz::common::ConsoleNew testConsole("test"); - auto testLogger = testConsole.Logger(); - - testing::internal::CaptureStdout(); - testing::internal::CaptureStderr(); - - testLogger.log(spdlog::level::trace, "This is a trace message"); - testLogger.log(spdlog::level::debug , "This is a debug message"); - testLogger.log(spdlog::level::info, "This is an info message"); - testLogger.log(spdlog::level::warn, "This is a warn message"); - testLogger.log(spdlog::level::err, "This is an error message"); - testLogger.log(spdlog::level::critical, "This is a critical error message"); - - std::string stdOut = testing::internal::GetCapturedStdout(); - std::string stdErr = testing::internal::GetCapturedStderr(); - - for (auto word : {"info"}) - EXPECT_TRUE(stdOut.find(word) != std::string::npos); - - for (auto word : {"warn", "error", "critical"}) - EXPECT_TRUE(stdErr.find(word) != std::string::npos); -} - -///////////////////////////////////////////////// -TEST_F(ConsoleNew_TEST, RootLogger) -{ - auto gzLogger = gz::common::ConsoleNew::Root().Logger(); - - testing::internal::CaptureStdout(); - testing::internal::CaptureStderr(); - - gzLogger.log(spdlog::level::trace, "This is a trace message"); - gzLogger.log(spdlog::level::debug , "This is a debug message"); - gzLogger.log(spdlog::level::info, "This is an info message"); - gzLogger.log(spdlog::level::warn, "This is a warn message"); - gzLogger.log(spdlog::level::err, "This is an error message"); - gzLogger.log(spdlog::level::critical, "This is a critical error message"); - - std::string stdOut = testing::internal::GetCapturedStdout(); - std::string stdErr = testing::internal::GetCapturedStderr(); - - for (auto word : {"info"}) - EXPECT_TRUE(stdOut.find(word) != std::string::npos); - - for (auto word : {"warn", "error", "critical"}) - EXPECT_TRUE(stdErr.find(word) != std::string::npos); -} - -///////////////////////////////////////////////// -TEST_F(ConsoleNew_TEST, RootLoggerColor) -{ - gz::common::ConsoleNew::Root().SetColorMode(spdlog::color_mode::always); - auto logger = spdlog::get("gz"); - ASSERT_TRUE(logger); - - testing::internal::CaptureStdout(); - testing::internal::CaptureStderr(); - - logger->log(spdlog::level::trace, "This is a trace message"); - logger->log(spdlog::level::debug , "This is a debug message"); - logger->log(spdlog::level::info, "This is an info message"); - logger->log(spdlog::level::warn, "This is a warn message"); - logger->log(spdlog::level::err, "This is an error message"); - logger->log(spdlog::level::critical, "This is a critical error message"); - - std::string stdOut = testing::internal::GetCapturedStdout(); - std::string stdErr = testing::internal::GetCapturedStderr(); - - for (auto word : {"info"}) - EXPECT_TRUE(stdOut.find(word) != std::string::npos); - - for (auto word : {"warn", "error", "critical"}) - EXPECT_TRUE(stdErr.find(word) != std::string::npos); -} - -///////////////////////////////////////////////// -TEST_F(ConsoleNew_TEST, RootLoggerNoColor) -{ - gz::common::ConsoleNew::Root().SetColorMode(spdlog::color_mode::never); - auto logger = spdlog::get("gz"); - ASSERT_TRUE(logger); - - testing::internal::CaptureStdout(); - testing::internal::CaptureStderr(); - - logger->trace("This is a trace message"); - logger->debug("This is a debug message"); - logger->info("This is an info message"); - logger->warn("This is a warning message"); - logger->error("This is an error message"); - logger->critical("This is a critical message"); - - std::string stdOut = testing::internal::GetCapturedStdout(); - std::string stdErr = testing::internal::GetCapturedStderr(); - - for (auto word : {"info"}) - EXPECT_TRUE(stdOut.find(word) != std::string::npos); - - for (auto word : {"warn", "error", "critical"}) - EXPECT_TRUE(stdErr.find(word) != std::string::npos); -} - -///////////////////////////////////////////////// -TEST_F(ConsoleNew_TEST, RootLoggerMacros) -{ - testing::internal::CaptureStdout(); - testing::internal::CaptureStderr(); - - gztrace << "This is a trace message"; - gzdbg << "This is a debug message"; - gzmsg << "This is an info message"; - gzwarn << "This is a warning message"; - gzerr << "This is an error message"; - gzcrit << "This is a critical message"; - - std::string stdOut = testing::internal::GetCapturedStdout(); - std::string stdErr = testing::internal::GetCapturedStderr(); - - for (auto word : {"info"}) - EXPECT_TRUE(stdOut.find(word) != std::string::npos); - - for (auto word : {"warn", "error", "critical"}) - EXPECT_TRUE(stdErr.find(word) != std::string::npos); -} - -///////////////////////////////////////////////// -TEST_F(ConsoleNew_TEST, LogToFile) -{ - gzLogInit("", "test.log"); - - gztrace << "This is a trace message"; - gzdbg << "This is a debug message"; - gzmsg << "This is an info message"; - gzwarn << "This is a warning message"; - gzerr << "This is an error message"; - gzcrit << "This is a critical message"; - - // gzLogInit installs a global handler. - spdlog::trace("This is a trace message"); - spdlog::debug("This is a debug message"); - spdlog::info("This is an info message"); - spdlog::warn("This is a warning message"); - spdlog::error("This is an error message"); - spdlog::critical("This is a critical message"); - - std::string logContent = GetLogContent("test.log"); - for (auto word : {"info", "warn", "error", "critical"}) - EXPECT_TRUE(logContent.find(word) != std::string::npos); - - auto dir = gzLogDirectory(); -} diff --git a/src/Console_TEST.cc b/src/Console_TEST.cc index 8e8c9543a..b0a963a77 100644 --- a/src/Console_TEST.cc +++ b/src/Console_TEST.cc @@ -17,6 +17,7 @@ #include #include +#include #include "gz/common/Console.hh" #include "gz/common/Filesystem.hh" diff --git a/test/integration/console.cc b/test/integration/console.cc index fb1f0d500..564f76da6 100644 --- a/test/integration/console.cc +++ b/test/integration/console.cc @@ -16,6 +16,7 @@ */ #include +#include #include #include diff --git a/testing/include/gz/common/testing/detail/AutoLogFixture.hh b/testing/include/gz/common/testing/detail/AutoLogFixture.hh index 658d3ba83..86839cd47 100644 --- a/testing/include/gz/common/testing/detail/AutoLogFixture.hh +++ b/testing/include/gz/common/testing/detail/AutoLogFixture.hh @@ -17,6 +17,7 @@ #ifndef GZ_COMMON_TESTING_DETAIL_AUTOLOGFIXTURE_HH_ #define GZ_COMMON_TESTING_DETAIL_AUTOLOGFIXTURE_HH_ +#include #include #include