diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..f2d0613 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,12 @@ +common --enable_bzlmod +common --lockfile_mode=off + +# Add C++17 compiler flags. +build --cxxopt=-std=c++17 +build --host_cxxopt=-std=c++17 + +build --force_pic +build --strip=never +build --strict_system_includes +build --fission=dbg +build --features=per_object_debug_info diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000..643916c --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +7.3.1 diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml new file mode 100644 index 0000000..8dcf7f8 --- /dev/null +++ b/.github/workflows/bazel.yml @@ -0,0 +1,24 @@ +name: Bazel CI +on: + push: + branches: [gz-utils3, main] + pull_request: + branches: [gz-utils3, main] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + test: + uses: bazel-contrib/.github/.github/workflows/bazel.yaml@v6 + with: + folders: | + [ + ".", + ] + exclude: | + [ + {"bazelversion": "6.4.0"} + {"bazelversion": "5.4.0"}, + ] diff --git a/.github/workflows/ci.bazelrc b/.github/workflows/ci.bazelrc new file mode 100644 index 0000000..3b4aad2 --- /dev/null +++ b/.github/workflows/ci.bazelrc @@ -0,0 +1,15 @@ +# This file contains Bazel settings to apply on CI only. +# It is referenced with a --bazelrc option in the call to bazel in ci.yaml + +# Debug where options came from +build --announce_rc +# This directory is configured in GitHub actions to be persisted between runs. +# We do not enable the repository cache to cache downloaded external artifacts +# as these are generally faster to download again than to fetch them from the +# GitHub actions cache. +build --disk_cache=~/.cache/bazel +# Don't rely on test logs being easily accessible from the test runner, +# though it makes the log noisier. +test --test_output=errors +# Allows tests to run bazelisk-in-bazel, since this is the cache folder used +test --test_env=XDG_CACHE_HOME diff --git a/.gitignore b/.gitignore index 62b9898..39555d6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,9 @@ build_* # OS generated files .DS_Store *.swp + +# Bazel generated files +bazel-bin/ +bazel-out/ +bazel-testlogs/ +bazel-* diff --git a/BUILD.bazel b/BUILD.bazel index 435829a..395826f 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,18 +1,11 @@ -load( - "@gz//bazel/skylark:build_defs.bzl", - "GZ_FEATURES", - "GZ_ROOT", - "GZ_VISIBILITY", - "gz_configure_header", - "gz_export_header", - "gz_include_header", -) +load("@buildifier_prebuilt//:rules.bzl", "buildifier", "buildifier_test") +load("@rules_gazebo//gazebo:headers.bzl", "gz_configure_header", "gz_export_header") load("@rules_license//rules:license.bzl", "license") package( - default_applicable_licenses = [GZ_ROOT + "utils:license"], - default_visibility = GZ_VISIBILITY, - features = GZ_FEATURES, + default_applicable_licenses = [":license"], + default_visibility = ["__subpackages__"], + features = ["layering_check"], ) license( @@ -20,51 +13,87 @@ license( package_name = "gz-utils", ) -licenses(["notice"]) +exports_files([ + "LICENSE", + "MODULE.bazel", +]) -exports_files(["LICENSE"]) +gz_export_header( + name = "Export", + out = "include/gz/utils/Export.hh", + export_base = "GZ_UTILS", + lib_name = "gz-utils", +) gz_configure_header( - name = "config", + name = "Config", src = "include/gz/utils/config.hh.in", - cmakelists = ["CMakeLists.txt"], - package = "utils", + package_xml = "package.xml", ) -gz_export_header( - name = "include/gz/utils/Export.hh", - export_base = "GZ_UTILS", - lib_name = "gz-utils", - visibility = ["//visibility:private"], +cc_library( + name = "Environment", + srcs = ["src/Environment.cc"], + hdrs = ["include/gz/utils/Environment.hh"], + includes = ["include"], + deps = [ + ":Config", + ":Export", + ], ) -public_headers_no_gen = glob([ - "include/gz/utils/*.hh", - "include/gz/utils/detail/*.hh", - "include/gz/utils/detail/*.h", -]) - -gz_include_header( - name = "utilshh_genrule", - out = "include/gz/utils.hh", - hdrs = public_headers_no_gen + [ - "include/gz/utils/Export.hh", - "include/gz/utils/config.hh", +cc_library( + name = "ImplPtr", + hdrs = [ + "include/gz/utils/ImplPtr.hh", + "include/gz/utils/detail/DefaultOps.hh", + "include/gz/utils/detail/ImplPtr.hh", + ], + includes = ["include"], + visibility = ["//visibility:public"], + deps = [ + ":Export", + ":SuppressWarning", ], ) -public_headers = public_headers_no_gen + [ - "include/gz/utils/config.hh", - "include/gz/utils/Export.hh", - "include/gz/utils.hh", -] +cc_library( + name = "NeverDestroyed", + hdrs = ["include/gz/utils/NeverDestroyed.hh"], + includes = ["include"], + visibility = ["//visibility:public"], +) cc_library( - name = "utils", - srcs = ["src/Environment.cc"], - hdrs = public_headers, - copts = ["-fexceptions"], + name = "SuppressWarning", + hdrs = [ + "include/gz/utils/SuppressWarning.hh", + "include/gz/utils/detail/SuppressWarning.hh", + ], + includes = ["include"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "Subprocess", + hdrs = [ + "include/gz/utils/Subprocess.hh", + "include/gz/utils/detail/subprocess.h", + ], includes = ["include"], + visibility = ["//visibility:public"], +) + +cc_library( + name = "gz-utils", + visibility = ["//visibility:public"], + deps = [ + ":Environment", + ":ImplPtr", + ":NeverDestroyed", + ":Subprocess", + ":SuppressWarning", + ], ) # Tests @@ -77,7 +106,7 @@ cc_library( "test/integration/implptr/implptr_test_classes.hh", ], includes = ["test/integration/implptr"], - deps = [":utils"], + deps = [":ImplPtr"], ) cc_test( @@ -86,8 +115,7 @@ cc_test( srcs = ["test/integration/implptr/ImplPtr_TEST.cc"], deps = [ ":implptr_test_classes", - "@gtest", - "@gtest//:gtest_main", + "@googletest//:gtest_main", ], ) @@ -95,39 +123,43 @@ cc_test( name = "Environment_TEST", srcs = ["src/Environment_TEST.cc"], deps = [ - ":utils", - "@gtest", - "@gtest//:gtest_main", + ":Environment", + "@googletest//:gtest_main", ], ) -cc_test( - name = "NeverDestroyed_TEST", - srcs = ["src/NeverDestroyed_TEST.cc"], - copts = ["-fexceptions"], +cc_binary( + name = "subprocess_main", + srcs = ["test/integration/subprocess/subprocess_main.cc"], deps = [ - ":utils", - "@gtest", - "@gtest//:gtest_main", + ":Environment", + "//cli", ], ) -cc_binary( - name = "subprocess_main", - srcs = ["test/integration/subprocess/subprocess_main.cc"], - deps = [ - GZ_ROOT + "utils/cli", - ] -) - cc_test( name = "subprocess_TEST", srcs = ["test/integration/subprocess_TEST.cc"], + local_defines = ['SUBPROCESS_EXECUTABLE_PATH=\\"subprocess_main\\"'], deps = [ - ":utils", + ":gz-utils", ":subprocess_main", - "@gtest", - "@gtest//:gtest_main", + "@googletest//:gtest_main", ], - local_defines = ['SUBPROCESS_EXECUTABLE_PATH=\\"utils/subprocess_main\\"'], +) + +buildifier( + name = "buildifier.fix", + exclude_patterns = ["./.git/*"], + lint_mode = "fix", + mode = "fix", +) + +buildifier_test( + name = "buildifier.test", + exclude_patterns = ["./.git/*"], + lint_mode = "warn", + mode = "diff", + no_sandbox = True, + workspace = "//:MODULE.bazel", ) diff --git a/CMakeLists.txt b/CMakeLists.txt index a375316..74fee08 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,7 +17,7 @@ find_package(gz-cmake4 REQUIRED) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -gz_configure_project(VERSION_SUFFIX pre1) +gz_configure_project(VERSION_SUFFIX) #============================================================================ # Set project-specific options diff --git a/Changelog.md b/Changelog.md index 8c33e33..361212d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,33 @@ ## Gazebo Utils 3.x -## Gazebo Utils 3.0.0 (20XX-XX-XX) +## Gazebo Utils 3.0.0 (2024-09-25) + +1. **Baseline:** this includes all changes from 2.2.0 and earlier. + +1. Add EoL and use SetConsoleSinkLevel in example + * [Pull request #148](https://github.com/gazebosim/gz-utils/pull/148) + +1. Catch spdlog exception thrown when creating file + * [Pull request #145](https://github.com/gazebosim/gz-utils/pull/145) + +1. Include file name in log format + * [Pull request #144](https://github.com/gazebosim/gz-utils/pull/144) + +1. Update logger level and flush_on values + * [Pull request #142](https://github.com/gazebosim/gz-utils/pull/142) + +1. Use common formatter for console and file sinks + * [Pull request #141](https://github.com/gazebosim/gz-utils/pull/141) + +1. Logger updates + * [Pull request #139](https://github.com/gazebosim/gz-utils/pull/139) + +1. Update badges to point to gz-utils3 branch + * [Pull request #137](https://github.com/gazebosim/gz-utils/pull/137) + * [Pull request #138](https://github.com/gazebosim/gz-utils/pull/138) + +1. Update changelog and add prepare for prereleases + * [Pull request #136](https://github.com/gazebosim/gz-utils/pull/136) 1. Move spdlog::logger to gz-utils * [Pull request #134](https://github.com/gazebosim/gz-utils/pull/134) diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..6a8b2ce --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,12 @@ +"gz-utils" + +module( + name = "gz-utils", + repo_name = "org_gazebosim_gz-utils", +) + +bazel_dep(name = "buildifier_prebuilt", version = "7.3.1") +bazel_dep(name = "googletest", version = "1.14.0") +bazel_dep(name = "rules_gazebo", version = "0.0.2") +bazel_dep(name = "rules_license", version = "1.0.0") +bazel_dep(name = "spdlog", version = "1.14.1") diff --git a/README.md b/README.md index 7b3acb9..d16ee44 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,10 @@ Build | Status -- | -- -Test coverage | [![codecov](https://codecov.io/gh/gazebosim/gz-utils/branch/main/graph/badge.svg)](https://codecov.io/gh/gazebosim/gz-utils) +Test coverage | [![codecov](https://codecov.io/gh/gazebosim/gz-utils/branch/main/graph/badge.svg)](https://app.codecov.io/gh/gazebosim/gz-utils/tree/main) Ubuntu Noble | [![Build Status](https://build.osrfoundation.org/buildStatus/icon?job=gz_utils-ci-main-noble-amd64)](https://build.osrfoundation.org/job/gz_utils-ci-main-noble-amd64) Homebrew | [![Build Status](https://build.osrfoundation.org/buildStatus/icon?job=gz_utils-ci-main-homebrew-amd64)](https://build.osrfoundation.org/job/gz_utils-ci-main-homebrew-amd64) -Windows | [![Build Status](https://build.osrfoundation.org/job/gz_utils-main-win/badge/icon)](https://build.osrfoundation.org/job/ign_utils-ci-win/) +Windows | [![Build Status](https://build.osrfoundation.org/job/gz_utils-main-win/badge/icon)](https://build.osrfoundation.org/job/gz_utils-main-win/) Gazebo Utils, a component of [Gazebo](https://gazebosim.org), provides general purpose classes and functions designed for robotic applications. diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..e69de29 diff --git a/cli/BUILD.bazel b/cli/BUILD.bazel index b97bc2d..f1d18fa 100644 --- a/cli/BUILD.bazel +++ b/cli/BUILD.bazel @@ -1,12 +1,7 @@ -load( - "@gz//bazel/skylark:build_defs.bzl", - "GZ_ROOT", - "GZ_VISIBILITY", -) load("@rules_license//rules:license.bzl", "license") package( - default_applicable_licenses = [GZ_ROOT + "utils/cli:license"], + default_applicable_licenses = ["//:license"], ) license( @@ -14,23 +9,39 @@ license( package_name = "gz-utils-cli", ) -public_headers = [ - "include/gz/utils/cli/GzFormatter.hpp", -] + glob([ - "include/external-cli/gz/utils/cli/*.hpp", -]) +cc_library( + name = "cli11", + hdrs = glob([ + "include/vendored-cli/gz/utils/cli/*.hpp", + ]), + includes = ["include/vendored-cli"], +) + +cc_library( + name = "GzFormatter", + hdrs = [ + "include/gz/utils/cli/GzFormatter.hpp", + ], + includes = ["include"], + deps = [ + "//:Export", + ], +) cc_library( name = "cli", - hdrs = public_headers, - copts = ["-fexceptions"], - includes = [ - "include", - "include/external-cli", + visibility = ["//visibility:public"], + deps = [ + ":GzFormatter", + ":cli11", ], - visibility = GZ_VISIBILITY, +) + +cc_test( + name = "cli_TEST", + srcs = ["src/cli_TEST.cc"], deps = [ - GZ_ROOT + "utils:utils", - "@cli11" + ":cli", + "@googletest//:gtest_main", ], ) diff --git a/examples/log/main.cc b/examples/log/main.cc index 1ad207d..7aee2df 100644 --- a/examples/log/main.cc +++ b/examples/log/main.cc @@ -22,16 +22,18 @@ int main(int argc, char** argv) { gz::utils::log::Logger logger("my_logger"); - logger.RawLogger().set_level(spdlog::level::trace); + logger.SetConsoleSinkLevel(spdlog::level::info); 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"); + + SPDLOG_LOGGER_TRACE(logger.RawLoggerPtr(), "trace\n"); + SPDLOG_LOGGER_DEBUG(logger.RawLoggerPtr(), "debug\n"); + SPDLOG_LOGGER_INFO(logger.RawLoggerPtr(), "info\n"); + SPDLOG_LOGGER_WARN(logger.RawLoggerPtr(), "warn\n"); + SPDLOG_LOGGER_ERROR(logger.RawLoggerPtr(), "error\n"); + SPDLOG_LOGGER_CRITICAL(logger.RawLoggerPtr(), "critical\n"); } diff --git a/include/gz/utils/config.hh.in b/include/gz/utils/config.hh.in index 60984a8..4c05ce3 100644 --- a/include/gz/utils/config.hh.in +++ b/include/gz/utils/config.hh.in @@ -21,15 +21,15 @@ #define GZ_UTILS_CONFIG_HH_ /* Version number */ -#define GZ_UTILS_MAJOR_VERSION ${PROJECT_VERSION_MAJOR} -#define GZ_UTILS_MINOR_VERSION ${PROJECT_VERSION_MINOR} -#define GZ_UTILS_PATCH_VERSION ${PROJECT_VERSION_PATCH} +#define GZ_UTILS_MAJOR_VERSION @PROJECT_VERSION_MAJOR@ +#define GZ_UTILS_MINOR_VERSION @PROJECT_VERSION_MINOR@ +#define GZ_UTILS_PATCH_VERSION @PROJECT_VERSION_PATCH@ -#define GZ_UTILS_VERSION "${PROJECT_VERSION}" -#define GZ_UTILS_VERSION_FULL "${PROJECT_VERSION_FULL}" +#define GZ_UTILS_VERSION "@PROJECT_VERSION@" +#define GZ_UTILS_VERSION_FULL "@PROJECT_VERSION_FULL@" -#define GZ_UTILS_VERSION_NAMESPACE v${PROJECT_VERSION_MAJOR} +#define GZ_UTILS_VERSION_NAMESPACE v@PROJECT_VERSION_MAJOR@ -#define GZ_UTILS_VERSION_HEADER "Gazebo Utils, version ${PROJECT_VERSION_FULL}\nCopyright (C) 2020 Open Source Robotics Foundation.\nReleased under the Apache 2.0 License.\n\n" +#define GZ_UTILS_VERSION_HEADER "Gazebo Utils, version @PROJECT_VERSION_FULL@\nCopyright (C) 2020 Open Source Robotics Foundation.\nReleased under the Apache 2.0 License.\n\n" #endif diff --git a/log/BUILD.bazel b/log/BUILD.bazel new file mode 100644 index 0000000..6e314c7 --- /dev/null +++ b/log/BUILD.bazel @@ -0,0 +1,65 @@ +load("@rules_gazebo//gazebo:headers.bzl", "gz_export_header") +load("@rules_license//rules:license.bzl", "license") + +package( + default_applicable_licenses = ["//:license"], +) + +license( + name = "license", + package_name = "gz-utils-log", +) + +gz_export_header( + name = "Export", + out = "include/gz/utils/log/Export.hh", + export_base = "GZ_UTILS_LOG", + lib_name = "gz-utils-log", +) + +cc_library( + name = "SplitSink", + srcs = [ + "src/SplitSink.cc", + ], + hdrs = [ + "include/gz/utils/log/SplitSink.hh", + ], + includes = ["include"], + deps = [ + ":Export", + "//:Config", + "//:ImplPtr", + "@spdlog", + ], +) + +cc_library( + name = "Logger", + srcs = [ + "src/Logger.cc", + ], + hdrs = [ + "include/gz/utils/log/Logger.hh", + ], + includes = ["include"], + deps = [":SplitSink"], +) + +cc_library( + name = "log", + visibility = ["//visibility:public"], + deps = [ + ":Logger", + ":SplitSink", + ], +) + +cc_test( + name = "SplitSink_TEST", + srcs = ["src/SplitSink_TEST.cc"], + deps = [ + ":log", + "@googletest//:gtest_main", + ], +) diff --git a/log/include/gz/utils/log/Logger.hh b/log/include/gz/utils/log/Logger.hh index 3e3a491..d9d94f7 100644 --- a/log/include/gz/utils/log/Logger.hh +++ b/log/include/gz/utils/log/Logger.hh @@ -17,6 +17,10 @@ #ifndef GZ_UTILS_LOG_LOGGER_HH_ #define GZ_UTILS_LOG_LOGGER_HH_ +#if !defined(SPDLOG_ACTIVE_LEVEL) + #define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_TRACE +#endif + #include #include #include @@ -57,6 +61,10 @@ class GZ_UTILS_LOG_VISIBLE Logger /// \return The spdlog logger. public: [[nodiscard]] std::shared_ptr RawLoggerPtr() const; + /// \brief Set the severity level of the Console sink + /// \param [in] _level Severity level + public: void SetConsoleSinkLevel(spdlog::level::level_enum _level); + /// \brief Implementation Pointer. GZ_UTILS_UNIQUE_IMPL_PTR(dataPtr) }; diff --git a/log/src/Logger.cc b/log/src/Logger.cc index 340693a..7346f99 100644 --- a/log/src/Logger.cc +++ b/log/src/Logger.cc @@ -14,6 +14,8 @@ * limitations under the License. * */ +#include +#include #include #include @@ -25,6 +27,12 @@ namespace gz::utils::log { +namespace { + /// \brief Default log format + /// Example output +constexpr std::string_view kDefaultLogFormat{ + "%^(%Y-%m-%d %T.%e) [%l] [%s:%#] %v%$"}; +} /// \brief Private data for the Logger class. class Logger::Implementation { @@ -33,6 +41,9 @@ class Logger::Implementation public: explicit Implementation(const std::string &_loggerName) : consoleSink(std::make_shared()), sinks(std::make_shared()), + formatter(std::make_unique( + kDefaultLogFormat.data(), + spdlog::pattern_time_type::local, std::string(""))), logger(std::make_shared(_loggerName, sinks)) { } @@ -46,6 +57,9 @@ class Logger::Implementation /// \brief A sink distribution storing multiple sinks. std::shared_ptr sinks {nullptr}; + /// \brief Common formatter for both all sinks + std::unique_ptr formatter; + /// \brief The underlying spdlog logger. std::shared_ptr logger {nullptr}; }; @@ -58,11 +72,11 @@ Logger::Logger(const std::string &_loggerName) 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); + this->dataPtr->logger->set_level(spdlog::level::trace); + this->dataPtr->consoleSink->set_level(spdlog::level::err); + this->dataPtr->logger->flush_on(spdlog::level::debug); - spdlog::flush_every(std::chrono::seconds(5)); - spdlog::register_logger(this->dataPtr->logger); + this->dataPtr->logger->set_formatter(this->dataPtr->formatter->clone()); } ///////////////////////////////////////////////// @@ -73,9 +87,18 @@ void Logger::SetLogDestination(const std::string &_filename) if (!_filename.empty()) { - this->dataPtr->fileSink = - std::make_shared(_filename, true); - this->dataPtr->sinks->add_sink(this->dataPtr->fileSink); + try + { + this->dataPtr->fileSink = + std::make_shared(_filename, true); + this->dataPtr->fileSink->set_formatter(this->dataPtr->formatter->clone()); + this->dataPtr->fileSink->set_level(spdlog::level::trace); + this->dataPtr->sinks->add_sink(this->dataPtr->fileSink); + } + catch (const std::exception &_e) + { + std::cerr << "Error creating log file: " << _e.what() << std::endl; + } } } @@ -101,4 +124,13 @@ std::shared_ptr Logger::RawLoggerPtr() const return this->dataPtr->logger; } +///////////////////////////////////////////////// +void Logger::SetConsoleSinkLevel(spdlog::level::level_enum _level) +{ + if (this->dataPtr->consoleSink) + { + this->dataPtr->consoleSink->set_level(_level); + } +} + } // namespace gz::utils::log diff --git a/log/src/Logger_TEST.cc b/log/src/Logger_TEST.cc new file mode 100644 index 0000000..8e12061 --- /dev/null +++ b/log/src/Logger_TEST.cc @@ -0,0 +1,160 @@ +/* + * 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 + +///////////////////////////////////////////////// +std::string getLogContent(const std::string &_filename) +{ + // Open the log file, and read back the string + std::ifstream ifs(_filename.c_str(), std::ios::in); + std::string loggedString; + + while (!ifs.eof()) + { + std::string line; + std::getline(ifs, line); + loggedString += line; + } + + return loggedString; +} + +///////////////////////////////////////////////// +TEST(Logger, defaults) +{ + gz::utils::log::Logger logger("my_logger"); + + // Logger defaults. + EXPECT_TRUE(logger.RawLogger().should_log(spdlog::level::trace)); + EXPECT_EQ(spdlog::level::debug, logger.RawLogger().flush_level()); + EXPECT_EQ(spdlog::level::debug, logger.RawLoggerPtr()->flush_level()); + + // Sink defaults. + EXPECT_EQ(1u, logger.RawLogger().sinks().size()); + auto sink = logger.RawLogger().sinks().front(); + ASSERT_NE(nullptr, sink); +} + +///////////////////////////////////////////////// +TEST(Logger, basicLogging) +{ + gz::utils::log::Logger logger("my_logger"); + + testing::internal::CaptureStdout(); + testing::internal::CaptureStderr(); + + SPDLOG_LOGGER_TRACE(logger.RawLoggerPtr(), "trace\n"); + SPDLOG_LOGGER_DEBUG(logger.RawLoggerPtr(), "debug\n"); + SPDLOG_LOGGER_INFO(logger.RawLoggerPtr(), "info\n"); + SPDLOG_LOGGER_WARN(logger.RawLoggerPtr(), "warn\n"); + SPDLOG_LOGGER_ERROR(logger.RawLoggerPtr(), "error\n"); + SPDLOG_LOGGER_CRITICAL(logger.RawLoggerPtr(), "critical\n"); + + std::string stdOut = testing::internal::GetCapturedStdout(); + std::string stdErr = testing::internal::GetCapturedStderr(); + + for (auto word : {"trace", "debug", "info"}) + EXPECT_FALSE(stdOut.find(word) != std::string::npos); + + for (auto word : {"warn"}) + EXPECT_FALSE(stdErr.find(word) != std::string::npos); + + for (auto word : {"error", "critical"}) + EXPECT_TRUE(stdErr.find(word) != std::string::npos); +} + +///////////////////////////////////////////////// +TEST(Logger, consoleSinkLevel) +{ + gz::utils::log::Logger logger("my_logger"); + logger.SetConsoleSinkLevel(spdlog::level::critical); + + testing::internal::CaptureStdout(); + testing::internal::CaptureStderr(); + + SPDLOG_LOGGER_TRACE(logger.RawLoggerPtr(), "trace\n"); + SPDLOG_LOGGER_DEBUG(logger.RawLoggerPtr(), "debug\n"); + SPDLOG_LOGGER_INFO(logger.RawLoggerPtr(), "info\n"); + SPDLOG_LOGGER_WARN(logger.RawLoggerPtr(), "warn\n"); + SPDLOG_LOGGER_ERROR(logger.RawLoggerPtr(), "error\n"); + SPDLOG_LOGGER_CRITICAL(logger.RawLoggerPtr(), "critical\n"); + + std::string stdOut = testing::internal::GetCapturedStdout(); + std::string stdErr = testing::internal::GetCapturedStderr(); + + for (auto word : {"trace", "debug", "info"}) + EXPECT_FALSE(stdOut.find(word) != std::string::npos); + + for (auto word : {"warn", "error"}) + EXPECT_FALSE(stdErr.find(word) != std::string::npos); + + for (auto word : {"critical"}) + EXPECT_TRUE(stdErr.find(word) != std::string::npos); +} + +///////////////////////////////////////////////// +TEST(Logger, fileLocation) +{ + gz::utils::log::Logger logger("my_logger"); + + testing::internal::CaptureStdout(); + testing::internal::CaptureStderr(); + + SPDLOG_LOGGER_ERROR(logger.RawLoggerPtr(), "error\n"); + SPDLOG_LOGGER_CRITICAL(logger.RawLoggerPtr(), "critical\n"); + + std::string stdOut = testing::internal::GetCapturedStdout(); + std::string stdErr = testing::internal::GetCapturedStderr(); + + for (auto word : {"error", "critical"}) + { + EXPECT_TRUE(stdErr.find(word) != std::string::npos); + EXPECT_TRUE(stdErr.find("Logger_TEST.cc:") != std::string::npos); + } +} + +///////////////////////////////////////////////// +TEST(Logger, fileLogging) +{ + gz::utils::log::Logger logger("my_logger"); + + std::filesystem::path logDir = std::filesystem::temp_directory_path(); + std::filesystem::path logFile = "my_log.txt"; + std::filesystem::path logPath = logDir / logFile; + + EXPECT_TRUE(logger.LogDestination().empty()); + logger.SetLogDestination(logPath.string()); + EXPECT_EQ(logPath.string(), logger.LogDestination()); + + logger.RawLogger().trace("trace\n"); + logger.RawLogger().debug("debug\n"); + logger.RawLogger().info("info\n"); + logger.RawLogger().warn("warn\n"); + logger.RawLogger().error("error\n"); + logger.RawLogger().critical("critical\n"); + + // Expect to find the string in the log file + std::string logContent = getLogContent(logPath.string()); + for (auto word : {"trace", "debug", "info", "warn", "error", "critical"}) + EXPECT_TRUE(logContent.find(word) != std::string::npos); +}