From 3c221de2fc8fe9db94a9090be5e2d24df11a0642 Mon Sep 17 00:00:00 2001 From: Michael Carroll Date: Wed, 28 Aug 2024 18:50:55 -0500 Subject: [PATCH] Experimenting with spdlog (#615) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * spdlog logging integration. Signed-off-by: Michael Carroll Signed-off-by: Carlos Agüero Signed-off-by: Addisu Z. Taddese (cherry picked from commit 6cb5a53ec82a64108f99a50c77494e0a06d221ac) --- .github/ci/packages.apt | 2 + CMakeLists.txt | 2 +- Migration.md | 13 + examples/console.cc | 45 +++- include/gz/common/Console.hh | 291 ++++++++--------------- package.xml | 1 + src/CMakeLists.txt | 5 +- src/Console.cc | 365 +++++++---------------------- src/Console_TEST.cc | 28 ++- testing/src/AutoLogFixture_TEST.cc | 1 + 10 files changed, 252 insertions(+), 501 deletions(-) diff --git a/.github/ci/packages.apt b/.github/ci/packages.apt index cc24e4912..e4b057bbf 100644 --- a/.github/ci/packages.apt +++ b/.github/ci/packages.apt @@ -8,6 +8,8 @@ libgdal-dev libgz-cmake4-dev libgz-math8-dev libgz-utils3-dev +libgz-utils3-log-dev +libspdlog-dev libswscale-dev libtinyxml2-dev pkg-config diff --git a/CMakeLists.txt b/CMakeLists.txt index 3804066b7..7288c8840 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}) #-------------------------------------- diff --git a/Migration.md b/Migration.md index c93cf3095..df92bb7db 100644 --- a/Migration.md +++ b/Migration.md @@ -9,6 +9,19 @@ release will remove the deprecated code. ### Modifications +1. Logging has been heavily modified as we're relying on + [spdlog](https://github.com/gabime/spdlog). See + [example1](https://github.com/gazebosim/gz-utils/blob/gz-utils3/examples/log/main.cc) + and [example2]((https://github.com/gazebosim/gz-common/blob/gz-common6/examples/console.cc)) + to learn how to access the internal spdlog logger and the global options. + + The `gzdbg`, `gzmsg`, `gzlog`, `gzwarn` and `gzerr` macros should work the + same as previous versions but you now have two extra macros: `gzcrit` and + `gztrace` for logging critical errors and traces respectively. + + `Console::SetVerbosity` only accepts values between 0 (critical errors only) + and 5 (all log messages). Other values don't have any effect. + 1. Removed the `graphics` component's dependency on the GTS (GNU Triangulated Surface) library which was used for doing triangulation and CSG Boolean operation by the `MeshManager`. The Delaunay triangulation diff --git a/examples/console.cc b/examples/console.cc index 0faf26593..f86e2e0a6 100644 --- a/examples/console.cc +++ b/examples/console.cc @@ -14,22 +14,43 @@ * 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" << std::endl; - gzmsg << "This is an informational message" << std::endl; - gzwarn << "This is a warning" << std::endl; - gzerr << "This is an error" << std::endl; + // Default verbosity is 3 (critical, error, warn and info messages show). + gztrace << "This is a trace message"; + gzdbg << "This is a debug message"; + gzmsg << "This is an informational message"; + gzwarn << "This is a warning"; + gzerr << "This is an error"; + gzcrit << "This is a critical error"; - // Change verbosity to level 4, all messages show - gz::common::Console::SetVerbosity(4); - gzdbg << "This is a debug message" << std::endl; - gzmsg << "This is an informational message" << std::endl; - gzwarn << "This is a warning" << std::endl; - gzerr << "This is an error" << std::endl; + // Change verbosity to level 5, all messages show. + gz::common::Console::SetVerbosity(5); + gz::common::Console::SetPrefix("My prefix. "); + gztrace << "This is a trace message"; + gzdbg << "This is a debug message"; + gzmsg << "This is an informational message"; + gzwarn << "This is a warning"; + gzerr << "This is an error"; + gzcrit << "This is a critical error"; + + 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..a188de200 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 Output a warning message, if the verbose level is >= 2 - #define gzwarn (gz::common::Console::warn(__FILE__, __LINE__)) + /// \brief Destructor. + public: ~LogMessage(); - /// \brief Output a message, if the verbose level is >= 3 - #define gzmsg (gz::common::Console::msg()) + /// \brief Get access to the underlying stream. + /// \return The underlying stream. + public: std::ostream &stream(); - /// \brief Output a debug message, if the verbose level is >= 4 - #define gzdbg (gz::common::Console::dbg(__FILE__, __LINE__)) + /// \brief Log level. + private: spdlog::level::level_enum severity; - /// \brief Output a message to a log file, regardless of verbosity level - #define gzlog (gz::common::Console::log()) + /// \brief Source file location information. + private: spdlog::source_loc sourceLocation; + + /// \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::err).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 @@ -53,194 +97,55 @@ namespace gz /// \param[in] _dir Name of directory in which to store the log file. Note /// that if _dir is not an absolute path, then _dir will /// be relative to your home directory. - /// \param[in] _file Name of log file for ignlog messages. + /// \param[in] _file Name of log file for gzlog 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; - - /// \brief Level of verbosity - public: int verbosity; + public: static std::string Directory(); - /// \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,20 +174,8 @@ 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 True if initialized. + public: static bool initialized; /// \brief The level of verbosity, the default level is 1. private: static int verbosity; @@ -290,6 +183,10 @@ namespace gz GZ_UTILS_WARN_IGNORE__DLL_INTERFACE_MISSING /// \brief A custom prefix. See SetPrefix(). private: static std::string customPrefix; + + /// \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/package.xml b/package.xml index e840d76f1..ad6da2f38 100644 --- a/package.xml +++ b/package.xml @@ -18,6 +18,7 @@ gz-utils3 libfreeimage-dev libgdal-dev + spdlog tinyxml2 uuid diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 773f29f4c..a0dc9a8f2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -18,7 +18,8 @@ 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 ) # Handle non-Windows configuration settings @@ -42,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..ca231d996 100644 --- a/src/Console.cc +++ b/src/Console.cc @@ -14,223 +14,71 @@ * limitations under the License. * */ -#include -#include - -#include -#include -#include +#include #ifdef _WIN32 #include #endif -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() -{ -} - -///////////////////////////////////////////////// -Logger &Logger::operator()() -{ - Console::log() << "(" << common::systemTimeIso() << ") "; - (*this) << Console::Prefix() << this->prefix; - - return (*this); -} - -///////////////////////////////////////////////// -Logger &Logger::operator()(const std::string &_file, int _line) -{ - int index = _file.find_last_of("/") + 1; +#include +#include +#include +#include +#include - Console::log() << "(" << common::systemTimeIso() << ") "; - std::stringstream prefixString; - prefixString << Console::Prefix() << this->prefix - << "[" << _file.substr(index , _file.size() - index) << ":" - << _line << "] "; - (*this) << prefixString.str(); +#include +#include +#include +#include +#include - return (*this); -} +using namespace gz; +using namespace common; ///////////////////////////////////////////////// -Logger::Buffer::Buffer(LogType _type, const int _color, const int _verbosity) - : type(_type), color(_color), verbosity(_verbosity) +LogMessage::LogMessage(const char *_file, int _line, + spdlog::level::level_enum _logLevel) + : severity(_logLevel), + sourceLocation(_file, _line, "") { + // Use default initialization if needed. + if (!Console::initialized) + Console::Init(".gz", "auto_default.log"); } ///////////////////////////////////////////////// -Logger::Buffer::~Buffer() +LogMessage::~LogMessage() { - this->pubsync(); + gz::common::Console::Root().RawLogger().log( + this->sourceLocation, this->severity, + gz::common::Console::Prefix() + this->ss.str()); } ///////////////////////////////////////////////// -std::streamsize Logger::Buffer::xsputn(const char *_char, - std::streamsize _count) +std::ostream &LogMessage::stream() { - std::lock_guard lk(this->syncMutex); - return std::stringbuf::xsputn(_char, _count); + return this->ss; } -///////////////////////////////////////////////// -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; -} +bool Console::initialized = false; +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 +96,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 +105,79 @@ 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; - - // Update the log directory name. - if (isDirectory(logPath)) - this->logDirectory = logPath; - else - this->logDirectory = common::parentPath(logPath); + Console::Root().SetLogDestination(logPath.c_str()); + Console::initialized = true; - this->initialized = true; - - /// \todo(anyone) Reimplement this. - // Output the version of the project. - // (*buf->stream) << PROJECT_VERSION_HEADER << std::endl; + return true; } ///////////////////////////////////////////////// -void FileLogger::Close() +void Console::Close() { - auto* buf = dynamic_cast(this->rdbuf()); - if (buf && buf->stream && buf->stream->is_open()) - { - buf->stream->close(); - delete buf->stream; - buf->stream = nullptr; - } + // Detach the current file sink. + Console::Root().SetLogDestination(std::string()); } ///////////////////////////////////////////////// -FileLogger &FileLogger::operator()() +std::string Console::Directory() { - if (!this->initialized) - this->Init(".gz", "auto_default.log"); - - (*this) << "(" << common::systemTimeIso() << ") "; - return (*this); + std::filesystem::path path = gz::common::Console::Root().LogDestination(); + return path.parent_path().string(); } -///////////////////////////////////////////////// -FileLogger &FileLogger::operator()(const std::string &_file, int _line) -{ - 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::string FileLogger::LogDirectory() const +////////////////////////////////////////////////// +void Console::SetVerbosity(const int _level) { - return this->logDirectory; -} + if (_level < 0) + { + Console::Root().RawLogger().log(spdlog::level::err, + "Negative verbosity level. Ignoring it"); + return; + } -///////////////////////////////////////////////// -FileLogger::Buffer::Buffer(const std::string &_filename) - : stream(NULL) -{ - if (!_filename.empty()) + switch (_level) { - this->stream = new std::ofstream(_filename.c_str(), std::ios::out); + case 0: + gz::common::Console::Root().SetConsoleSinkLevel(spdlog::level::critical); + break; + case 1: + gz::common::Console::Root().SetConsoleSinkLevel(spdlog::level::err); + break; + case 2: + gz::common::Console::Root().SetConsoleSinkLevel(spdlog::level::warn); + break; + case 3: + gz::common::Console::Root().SetConsoleSinkLevel(spdlog::level::info); + break; + case 4: + gz::common::Console::Root().SetConsoleSinkLevel(spdlog::level::debug); + break; + case 5: + gz::common::Console::Root().SetConsoleSinkLevel(spdlog::level::trace); + break; + default: + gz::common::Console::Root().SetConsoleSinkLevel(spdlog::level::trace); } + + verbosity = std::min(5, _level); } -///////////////////////////////////////////////// -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/Console_TEST.cc b/src/Console_TEST.cc index 8e8c9543a..97d65c324 100644 --- a/src/Console_TEST.cc +++ b/src/Console_TEST.cc @@ -48,6 +48,7 @@ class Console_TEST : public ::testing::Test { private: std::unique_ptr temp; }; +///////////////////////////////////////////////// std::string GetLogContent(const std::string &_filename) { // Get the absolute path @@ -89,7 +90,10 @@ TEST_F(Console_TEST, NoInitAndLog) std::string path; EXPECT_TRUE(common::env(GZ_HOMEDIR, path)); path = common::joinPaths(path, logPath); - EXPECT_TRUE(common::removeAll(path)); + + // This is causing an issue on Windows as the resource is busy, + // probably locked by spdlog. + common::removeAll(path); } ///////////////////////////////////////////////// @@ -119,7 +123,10 @@ TEST_F(Console_TEST, InitAndLog) // Cleanup gzLogClose(); - EXPECT_TRUE(common::removeAll(basePath)); + + // This is causing an issue on Windows as the resource is busy, + // probably locked by spdlog. + common::removeAll(basePath); } ////////////////////////////////////////////////// @@ -470,13 +477,16 @@ TEST_F(Console_TEST, ColorErr) /// \brief Test Console::Verbosity TEST_F(Console_TEST, Verbosity) { - EXPECT_EQ(common::Console::Verbosity(), 1); + EXPECT_EQ(1, common::Console::Verbosity()); common::Console::SetVerbosity(2); - EXPECT_EQ(common::Console::Verbosity(), 2); + EXPECT_EQ(2, common::Console::Verbosity()); common::Console::SetVerbosity(-1); - EXPECT_EQ(common::Console::Verbosity(), -1); + EXPECT_EQ(2, common::Console::Verbosity()); + + common::Console::SetVerbosity(1000); + EXPECT_EQ(5, common::Console::Verbosity()); } ///////////////////////////////////////////////// @@ -509,10 +519,10 @@ TEST_F(Console_TEST, Prefix) std::string logContent = GetLogContent(logPath); // Check - EXPECT_TRUE(logContent.find("**test** [Err]") != std::string::npos); - EXPECT_TRUE(logContent.find("**test** [Wrn]") != std::string::npos); - EXPECT_TRUE(logContent.find("**test** [Msg]") != std::string::npos); - EXPECT_TRUE(logContent.find("**test** [Dbg]") != std::string::npos); + EXPECT_TRUE(logContent.find("**test** error") != std::string::npos); + EXPECT_TRUE(logContent.find("**test** warning") != std::string::npos); + EXPECT_TRUE(logContent.find("**test** message") != std::string::npos); + EXPECT_TRUE(logContent.find("**test** debug") != std::string::npos); // Reset common::Console::SetPrefix(""); diff --git a/testing/src/AutoLogFixture_TEST.cc b/testing/src/AutoLogFixture_TEST.cc index 1fc8cb49e..aa598711f 100644 --- a/testing/src/AutoLogFixture_TEST.cc +++ b/testing/src/AutoLogFixture_TEST.cc @@ -30,5 +30,6 @@ TEST_F(AutoLogFixture, AutoLogFixture) Console::SetVerbosity(0); gzdbg << "This is a debug" << std::endl; + gz::common::Console::Root().RawLogger().flush(); EXPECT_FALSE(this->LogContent().empty()); }