diff --git a/.github/workflows/bazel.yml b/.github/workflows/bazel.yml new file mode 100644 index 000000000..5e41ca36c --- /dev/null +++ b/.github/workflows/bazel.yml @@ -0,0 +1,82 @@ +name: Bazel and C++20 modules + +on: + push: + branches: [master] + pull_request: + branches: [master] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + bazel: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + bazel: [8.7.0, 9.2.0] + env: + USE_BAZEL_VERSION: ${{ matrix.bazel }} + # rules_cc looks for clang-scan-deps beside the selected compiler. + CC: /usr/lib/llvm-18/bin/clang + CXX: /usr/lib/llvm-18/bin/clang++ + steps: + - uses: actions/checkout@v5 + - uses: bazel-contrib/setup-bazel@0.19.0 + - name: Install Clang module tools + if: matrix.bazel == '9.2.0' + run: | + sudo apt-get update + sudo apt-get install -y clang-18 clang-tools-18 + "$CC" --version + "${CC%/*}/clang-scan-deps" --version + - name: Load all targets without enabling modules + run: bazel query //... --lockfile_mode=off + - name: Test the C++17 header targets + run: >- + bazel test //test/bazel:header_logging_test //test/bazel:fmtquill_test + --cxxopt=-std=c++17 --lockfile_mode=off --test_output=errors + - name: Check that modules require an explicit opt-in + run: | + if bazel build //:quill_module --lockfile_mode=off > module-disabled.log 2>&1; then + cat module-disabled.log + exit 1 + fi + cat module-disabled.log + if [[ "$USE_BAZEL_VERSION" == 9.* ]]; then + grep -q 'requires --experimental_cpp_modules' module-disabled.log + else + grep -q 'requires Bazel 9 or newer' module-disabled.log + fi + - name: Test optimized C++20 module imports + if: matrix.bazel == '9.2.0' + run: >- + bazel test //test/bazel:module_logging_test --compilation_mode=opt + --cxxopt=-std=c++20 --cxxopt=-Werror --experimental_cpp_modules + --lockfile_mode=off --test_output=errors + + cmake-module: + runs-on: ubuntu-24.04 + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + no_exceptions: ['OFF', 'ON'] + steps: + - uses: actions/checkout@v5 + - name: Configure the module examples + run: >- + cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release + -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_CXX_STANDARD=20 -DQUILL_BUILD_MODULE=ON + -DQUILL_BUILD_TESTS=ON -DQUILL_BUILD_EXAMPLES=ON + -DQUILL_NO_EXCEPTIONS=${{ matrix.no_exceptions }} + - name: Build the module examples + run: >- + cmake --build build --target quill_docs_module + quill_example_console_logging_module --parallel 2 + - name: Test logging and automatic shutdown + run: ctest --test-dir build -R '^quill_module_(logging|shutdown)_test$' --output-on-failure diff --git a/.gitignore b/.gitignore index 935e990c7..0c593e478 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ /docs/Doxyfile.rtd /docs/build/ /docs/_build*/ +/bazel-* +/MODULE.bazel.lock diff --git a/BUILD.bazel b/BUILD.bazel index f8d9e1df6..0ee7780a9 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,5 @@ load("@rules_cc//cc:cc_library.bzl", "cc_library") +load(":quill_module.bzl", "quill_module") package(default_visibility = ["//visibility:public"]) @@ -28,3 +29,21 @@ cc_library( hdrs = glob(["include/quill/bundled/fmt/*.h"]), includes = ["include"], ) + +quill_module( + name = "quill_module", + copts = select({ + "@rules_cc//cc/compiler:gcc": ["-Wno-gnu-zero-variadic-macro-arguments"], + "@rules_cc//cc/compiler:clang": ["-Wno-gnu-zero-variadic-macro-arguments"], + "//conditions:default": [], + }), + # rules_cc 0.2.22 uses the same module metadata path for PIC and non-PIC outputs. + features = [ + "cpp_modules", + "prefer_pic_for_opt_binaries", + ], + module_interfaces = ["src/quill.cc"], + # Prevent users without --experimental_cpp_modules support from accidentally building this + tags = ["manual"], + deps = [":quill"], +) diff --git a/CHANGELOG.md b/CHANGELOG.md index c46b03e53..6081e3416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,6 +105,10 @@ ## v13.1.0 +- Added the experimental `quill_module` target for Bazel 9+ consumers using C++20 and + `--experimental_cpp_modules`, while preserving ordinary header-only builds on older Bazel versions. +- Fixed C++20 module imports using separate logger state across translation units, which could + prevent logger lookup and crash automatic backend shutdown. - CMake, Meson, and Bazel consumers can use a separate `fmtquill` dependency for the bundled formatter without inheriting Quill's logging compile options, definitions, or link dependencies. - Fixed `FileSink::open_file()` retaining the old filename when a subclass reopens a different path, diff --git a/CMakeLists.txt b/CMakeLists.txt index b571461c2..8f16abec1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -403,6 +403,13 @@ if (QUILL_BUILD_MODULE) target_link_libraries(quill_module PUBLIC ${TARGET_NAME}) target_compile_features(quill_module PUBLIC cxx_std_20) set_target_properties(quill_module PROPERTIES CXX_SCAN_FOR_MODULES ON) + + if (QUILL_NO_EXCEPTIONS) + # The module and its importers must agree on exception and RTTI support. + target_compile_options(quill_module PUBLIC + $<$,$,$>:-fno-exceptions;-fno-rtti> + $<$:/GR-;/EHs-c-;/D_HAS_EXCEPTIONS=0>) + endif () endif () if (QUILL_BUILD_EXAMPLES) diff --git a/MODULE.bazel b/MODULE.bazel index b1a04cbbe..5987823bc 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,3 +6,4 @@ module( bazel_dep(name = "platforms", version = "1.1.0") bazel_dep(name = "rules_cc", version = "0.2.22") +bazel_dep(name = "bazel_features", version = "1.50.0") diff --git a/docs/installing.rst b/docs/installing.rst index 8a16c086a..49ae18cfa 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -125,6 +125,53 @@ Include ``quill/bundled/fmt/format.h`` and use ``fmtquill::format`` or specializ ``fmtquill::formatter``. These header-only dependencies use Quill's bundled, patched fmt in the ``fmtquill`` namespace; its version follows the Quill release. +Using the C++20 Module +---------------------- + +Quill provides an experimental C++20 named module interface (``src/quill.cc``). +It is opt-in; normal header-only use continues to require only C++17. + +With CMake 3.28 or newer and a compiler and generator supporting C++20 modules, set +``CMAKE_CXX_STANDARD=20`` and ``QUILL_BUILD_MODULE=ON`` when adding Quill through +``add_subdirectory`` or ``FetchContent``. Link against ``quill::quill_module``: + +.. code:: cmake + + target_link_libraries(my_project PRIVATE quill::quill_module) + +The module target is currently available from the source build, not from an installed +``find_package(quill)`` package. + +With Bazel 9 or newer, the declared ``rules_cc`` 0.2.22 dependency, and a toolchain supporting +C++20 modules, add ``@quill//:quill_module`` to the target's ``deps``. Enable C++20 for the +whole build and pass ``--experimental_cpp_modules`` (for Clang, use +``--cxxopt=-std=c++20 --experimental_cpp_modules``): + +.. code:: python + + cc_binary( + name = "my_project", + srcs = ["main.cpp"], + features = ["cpp_modules", "prefer_pic_for_opt_binaries"], + deps = ["@quill//:quill_module"], + ) + +The selected Clang toolchain must also provide a matching ``clang-scan-deps`` beside the +compiler executable. On Ubuntu 24.04, install ``clang-18`` and ``clang-tools-18``, and set +``CC=/usr/lib/llvm-18/bin/clang`` and ``CXX=/usr/lib/llvm-18/bin/clang++``. + +The module target is excluded from wildcard builds. Older Bazel versions can still use +``@quill//:quill`` and ``@quill//:fmtquill`` without enabling modules. + +``prefer_pic_for_opt_binaries`` keeps module producers and consumers in PIC mode, avoiding +conflicting module metadata outputs in optimized builds with ``rules_cc`` 0.2.22. + +Import Quill and define ``QUILL_USE_MODULE`` before including ``quill/LogMacros.h``. +Modules do not export macros, so this definition is required for the logging macros: + +.. literalinclude:: snippets/quill_docs_module.cpp + :language: cpp + Next Steps ---------- diff --git a/docs/snippets/CMakeLists.txt b/docs/snippets/CMakeLists.txt index 3c022256c..52fbb9f63 100644 --- a/docs/snippets/CMakeLists.txt +++ b/docs/snippets/CMakeLists.txt @@ -32,5 +32,19 @@ foreach (example_target ${EXAMPLE_TARGETS}) target_link_libraries(${example_target} quill) endforeach () +if (TARGET quill_module) + add_executable(quill_docs_module quill_docs_module.cpp) + set_common_compile_options(quill_docs_module) + target_link_libraries(quill_docs_module PRIVATE quill::quill_module) + set_target_properties(quill_docs_module PROPERTIES CXX_SCAN_FOR_MODULES ON) + list(APPEND EXAMPLE_TARGETS quill_docs_module) + + if (QUILL_BUILD_TESTS) + add_test(NAME quill_module_logging_test COMMAND quill_docs_module) + set_tests_properties(quill_module_logging_test PROPERTIES + PASS_REGULAR_EXPRESSION "Logging through the Quill module: 42") + endif () +endif () + install(TARGETS ${EXAMPLE_TARGETS} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/docs/snippets/quill_docs_module.cpp b/docs/snippets/quill_docs_module.cpp new file mode 100644 index 000000000..7fde43f59 --- /dev/null +++ b/docs/snippets/quill_docs_module.cpp @@ -0,0 +1,22 @@ +// GCC/libstdc++ needs these declarations before the module import. +#include +#include +#include + +import quill; + +#define QUILL_USE_MODULE +#include "quill/LogMacros.h" + +int main() +{ + quill::Backend::start(); + + auto console_sink = quill::Frontend::create_or_get_sink("module_docs_sink"); + quill::Logger* logger = + quill::Frontend::create_or_get_logger("module_docs_logger", std::move(console_sink)); + + LOG_INFO(logger, "Logging through the Quill module: {}", 42); + logger->flush_log(); + quill::Backend::stop(); +} diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index c3181723f..dbc109edd 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -87,6 +87,12 @@ if (TARGET quill_module) target_link_libraries(quill_example_console_logging_module PRIVATE quill_module) list(APPEND EXAMPLE_TARGETS quill_example_console_logging_module) + + if (QUILL_BUILD_TESTS) + add_test(NAME quill_module_shutdown_test COMMAND quill_example_console_logging_module) + set_tests_properties(quill_module_shutdown_test PROPERTIES + PASS_REGULAR_EXPRESSION "StringRef logging via module import") + endif () endif () install(TARGETS ${EXAMPLE_TARGETS} diff --git a/include/quill/backend/BackendManager.h b/include/quill/backend/BackendManager.h index f85aee8f5..27d7f156d 100644 --- a/include/quill/backend/BackendManager.h +++ b/include/quill/backend/BackendManager.h @@ -44,7 +44,7 @@ class BackendManager { public: /***/ - QUILL_EXPORT static BackendManager& instance() noexcept + QUILL_EXPORT static inline BackendManager& instance() noexcept { static BackendManager instance; return instance; diff --git a/include/quill/backend/RdtscClock.h b/include/quill/backend/RdtscClock.h index 8ec700d27..9a1075a82 100644 --- a/include/quill/backend/RdtscClock.h +++ b/include/quill/backend/RdtscClock.h @@ -42,7 +42,7 @@ class RdtscClock class RdtscTicks { public: - QUILL_NODISCARD QUILL_EXPORT static RdtscTicks& instance() + QUILL_NODISCARD QUILL_EXPORT static inline RdtscTicks& instance() { static RdtscTicks inst; return inst; diff --git a/include/quill/backend/SignalHandler.h b/include/quill/backend/SignalHandler.h index e45915663..ffb88aa8c 100644 --- a/include/quill/backend/SignalHandler.h +++ b/include/quill/backend/SignalHandler.h @@ -200,7 +200,7 @@ class SignalHandlerContext SignalHandlerContext& operator=(SignalHandlerContext const&) = delete; /***/ - QUILL_EXPORT static SignalHandlerContext& instance() noexcept + QUILL_EXPORT static inline SignalHandlerContext& instance() noexcept { static SignalHandlerContext instance; return instance; diff --git a/include/quill/core/LoggerBase.h b/include/quill/core/LoggerBase.h index 789aca9fe..b5faae1d8 100644 --- a/include/quill/core/LoggerBase.h +++ b/include/quill/core/LoggerBase.h @@ -205,7 +205,7 @@ class LoggerBase } // The exported accessor keeps its thread-local state shared with DLL consumers. - QUILL_EXPORT static bool& _backend_thread_flag() noexcept + QUILL_EXPORT static inline bool& _backend_thread_flag() noexcept { static QUILL_THREAD_LOCAL bool is_backend_thread{false}; return is_backend_thread; diff --git a/include/quill/core/LoggerManager.h b/include/quill/core/LoggerManager.h index 6bccfd311..339903efc 100644 --- a/include/quill/core/LoggerManager.h +++ b/include/quill/core/LoggerManager.h @@ -43,7 +43,8 @@ class LoggerManager LoggerManager& operator=(LoggerManager const&) = delete; /***/ - QUILL_EXPORT static LoggerManager& instance() noexcept + // Named modules do not give in-class definitions implicit inline linkage. + QUILL_EXPORT static inline LoggerManager& instance() noexcept { static LoggerManager instance; return instance; diff --git a/include/quill/core/MetricManager.h b/include/quill/core/MetricManager.h index 6ae94435c..b96514686 100644 --- a/include/quill/core/MetricManager.h +++ b/include/quill/core/MetricManager.h @@ -27,7 +27,7 @@ class MetricManager MetricManager& operator=(MetricManager const&) = delete; /***/ - QUILL_EXPORT static MetricManager& instance() noexcept + QUILL_EXPORT static inline MetricManager& instance() noexcept { static MetricManager instance; return instance; diff --git a/include/quill/core/SinkManager.h b/include/quill/core/SinkManager.h index 039b88f74..9a3104922 100644 --- a/include/quill/core/SinkManager.h +++ b/include/quill/core/SinkManager.h @@ -54,7 +54,7 @@ class SinkManager SinkManager& operator=(SinkManager const&) = delete; /***/ - QUILL_EXPORT static SinkManager& instance() noexcept + QUILL_EXPORT static inline SinkManager& instance() noexcept { static SinkManager instance; return instance; diff --git a/include/quill/core/ThreadContextManager.h b/include/quill/core/ThreadContextManager.h index d28c757d8..942bf56cb 100644 --- a/include/quill/core/ThreadContextManager.h +++ b/include/quill/core/ThreadContextManager.h @@ -225,7 +225,7 @@ class ThreadContextManager { public: /***/ - QUILL_EXPORT static ThreadContextManager& instance() noexcept + QUILL_EXPORT static inline ThreadContextManager& instance() noexcept { static ThreadContextManager instance; return instance; diff --git a/quill_module.bzl b/quill_module.bzl new file mode 100644 index 000000000..a71ca6b65 --- /dev/null +++ b/quill_module.bzl @@ -0,0 +1,25 @@ +"""Keep the experimental module optional on older Bazel versions.""" + +load("@bazel_features//:features.bzl", "bazel_features") +load("@rules_cc//cc:cc_library.bzl", "cc_library") + +def _unsupported_module_impl(ctx): + fail("{} requires Bazel 9 or newer for C++20 modules; use //:quill for header-only logging.".format(ctx.label)) + +_unsupported_module = rule(implementation = _unsupported_module_impl) + +def quill_module(name, **kwargs): + """Declare the module without passing unsupported attributes to older Bazel. + + Args: + name: Name of the module target. + **kwargs: Attributes forwarded to cc_library when modules are supported. + """ + + # Bazel 7 rejects module_interfaces; Bazel 8 accepts it but does not compile it. + # Modules require the Starlark C++ implementation used by rules_cc on Bazel 9+. + if not bazel_features.cc.cc_common_is_in_rules_cc: + _unsupported_module(name = name, tags = kwargs.get("tags", [])) + return + + cc_library(name = name, **kwargs) diff --git a/src/quill.cc b/src/quill.cc index 25fe29da4..4610cb795 100644 --- a/src/quill.cc +++ b/src/quill.cc @@ -16,6 +16,7 @@ module; #include #include #include +#include #include #include #include @@ -102,6 +103,14 @@ module; #if QUILL_HAS_INCLUDE() #include #endif +#if defined(__unix__) || defined(__APPLE__) || defined(__linux__) + #include + #include + #include + #if QUILL_HAS_INCLUDE() + #include + #endif +#endif export module quill; @@ -111,6 +120,15 @@ export module quill; #define FMTQUILL_BEGIN_EXPORT export { #define FMTQUILL_END_EXPORT } +// libstdc++ C forwarding headers may be re-entered after the global module fragment. +// Their declarations are guarded; suppress Clang's include-only diagnostic here. +#if defined(__clang__) && defined(__GLIBCXX__) + #if __has_warning("-Winclude-angled-in-module-purview") + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Winclude-angled-in-module-purview" + #endif +#endif + #include "quill/bundled/fmt/ostream.h" #include "quill/bundled/fmt/ranges.h" @@ -157,3 +175,9 @@ export module quill; #include "quill/std/Variant.h" #include "quill/std/Vector.h" #include "quill/std/WideString.h" + +#if defined(__clang__) && defined(__GLIBCXX__) + #if __has_warning("-Winclude-angled-in-module-purview") + #pragma clang diagnostic pop + #endif +#endif diff --git a/test/bazel/BUILD.bazel b/test/bazel/BUILD.bazel new file mode 100644 index 000000000..c12b6d8d8 --- /dev/null +++ b/test/bazel/BUILD.bazel @@ -0,0 +1,31 @@ +load("@rules_cc//cc:cc_test.bzl", "cc_test") + +cc_test( + name = "header_logging_test", + size = "small", + srcs = ["logging_test.cpp"], + deps = ["//:quill"], +) + +cc_test( + name = "fmtquill_test", + size = "small", + srcs = ["fmtquill_test.cpp"], + deps = ["//:fmtquill"], +) + +cc_test( + name = "module_logging_test", + size = "small", + srcs = [ + "logging_test.cpp", + "module_other.cpp", + ], + features = [ + "cpp_modules", + "prefer_pic_for_opt_binaries", + ], + local_defines = ["QUILL_TEST_MODULE"], + tags = ["manual"], + deps = ["//:quill_module"], +) diff --git a/test/bazel/fmtquill_test.cpp b/test/bazel/fmtquill_test.cpp new file mode 100644 index 000000000..ad8ffbfc8 --- /dev/null +++ b/test/bazel/fmtquill_test.cpp @@ -0,0 +1,6 @@ +#include "quill/bundled/fmt/format.h" + +int main() +{ + return fmtquill::format("Bundled formatter: {}", 42) == "Bundled formatter: 42" ? 0 : 1; +} diff --git a/test/bazel/logging_test.cpp b/test/bazel/logging_test.cpp new file mode 100644 index 000000000..0727c3198 --- /dev/null +++ b/test/bazel/logging_test.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(QUILL_TEST_MODULE) +import quill; + #define QUILL_USE_MODULE +bool log_from_another_module_translation_unit(); +#else + #include "quill/Backend.h" + #include "quill/Frontend.h" + #include "quill/LogFunctions.h" + #include "quill/sinks/ConsoleSink.h" + #include "quill/sinks/FileSink.h" + #include "quill/sinks/JsonSink.h" + #include "quill/sinks/RotatingFileSink.h" + #include "quill/std/Array.h" + #include "quill/std/Chrono.h" + #include "quill/std/Map.h" + #include "quill/std/Optional.h" + #include "quill/std/Variant.h" + #include "quill/std/Vector.h" +#endif + +#include "quill/LogMacros.h" + +int main() +{ +#if defined(QUILL_TEST_MODULE) + std::string const prefix{"bazel_module_logging_test"}; +#else + std::string const prefix{"bazel_header_logging_test"}; +#endif + + std::string const filename{prefix + ".log"}; + std::string const rotating_filename{prefix + "_rotating.log"}; + std::string const json_filename{prefix + ".json"}; + + quill::Backend::start(); + + quill::FileSinkConfig sink_config; + sink_config.set_open_mode('w'); + auto file_sink = quill::Frontend::create_or_get_sink(filename, sink_config); + + quill::RotatingFileSinkConfig rotating_config; + rotating_config.set_open_mode('w'); + rotating_config.set_rotation_max_file_size(1024 * 1024); + auto rotating_sink = + quill::Frontend::create_or_get_sink(rotating_filename, rotating_config); + + auto console_sink = quill::Frontend::create_or_get_sink(prefix + "_console"); + console_sink->set_log_level_filter(quill::LogLevel::Error); + + quill::Logger* logger = quill::Frontend::create_or_get_logger( + prefix + "_logger", + std::vector>{std::move(file_sink), std::move(rotating_sink), + std::move(console_sink)}, + quill::PatternFormatterOptions{"%(message)"}); + logger->set_log_level(quill::LogLevel::Info); + + LOG_INFO(logger, "Bazel logging test: {}", 42); + LOG_INFO(logger, "Basic values: {} {:.2f} {} {}", -7, 3.5, true, "text"); + LOG_DEBUG(logger, "Filtered debug message"); + quill::info(logger, "Macro-free logging: {}", 44); + + std::vector const values{1, 2, 3}; + std::array const array{{4, 5}}; + std::map const mapping{{"key", 7}}; + std::optional const optional{9}; + std::variant const variant{11}; + + LOG_INFO(logger, "Vector: {}", values); + LOG_INFO(logger, "Array: {}", array); + LOG_INFO(logger, "Map: {}", mapping); + LOG_INFO(logger, "Optional: {}", optional); + LOG_INFO(logger, "Variant: {}", variant); + LOG_INFO(logger, "Duration: {}", std::chrono::milliseconds{12}); + + logger->init_backtrace(2, quill::LogLevel::Error); + LOG_BACKTRACE(logger, "Buffered context: {}", 1); + LOG_ERROR(logger, "Error flushes backtrace"); + + std::thread producer{[logger]() { LOG_WARNING(logger, "Logging from another thread: {}", 45); }}; + producer.join(); + + auto json_sink = quill::Frontend::create_or_get_sink(json_filename, sink_config); + quill::Logger* json_logger = quill::Frontend::create_or_get_logger( + prefix + "_json_logger", std::move(json_sink), quill::PatternFormatterOptions{""}); + LOG_INFO(json_logger, "Structured {answer}", 42); + + bool passed{true}; +#if defined(QUILL_TEST_MODULE) + passed = log_from_another_module_translation_unit(); +#endif + + logger->flush_log(); + json_logger->flush_log(); + quill::Frontend::remove_logger_blocking(logger); + quill::Frontend::remove_logger_blocking(json_logger); + quill::Backend::stop(); + + for (std::string const& log_filename : {filename, rotating_filename}) + { + std::ifstream log_file{log_filename}; + std::string const output{std::istreambuf_iterator{log_file}, std::istreambuf_iterator{}}; + + for (char const* expected : + {"Bazel logging test: 42", "Basic values: -7 3.50 true text", "Macro-free logging: 44", + "Vector: [1, 2, 3]", "Array: [4, 5]", "Map: {\"key\": 7}", "Optional: optional(9)", + "Variant: variant(11)", "Duration: 12ms", "Buffered context: 1", + "Error flushes backtrace", "Logging from another thread: 45"}) + { + if (output.find(expected) == std::string::npos) + { + std::fprintf(stderr, "%s is missing: %s\n", log_filename.c_str(), expected); + passed = false; + } + } + + passed = passed && output.find("Filtered debug message") == std::string::npos; +#if defined(QUILL_TEST_MODULE) + passed = passed && output.find("Module logging from another translation unit: 43") != std::string::npos; +#endif + + log_file.close(); + std::remove(log_filename.c_str()); + } + + std::ifstream json_file{json_filename}; + std::string const json{std::istreambuf_iterator{json_file}, std::istreambuf_iterator{}}; + if (json.find("\"answer\":\"42\"") == std::string::npos) + { + std::fprintf(stderr, "Missing structured JSON field: %s\n", json.c_str()); + passed = false; + } + json_file.close(); + std::remove(json_filename.c_str()); + + return passed ? 0 : 1; +} diff --git a/test/bazel/module_other.cpp b/test/bazel/module_other.cpp new file mode 100644 index 000000000..9fabdc94c --- /dev/null +++ b/test/bazel/module_other.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include + +import quill; + +#define QUILL_USE_MODULE +#include "quill/LogMacros.h" + +bool log_from_another_module_translation_unit() +{ + quill::Logger* logger = quill::Frontend::get_logger("bazel_module_logging_test_logger"); + if (!logger) + { + std::fprintf(stderr, "Module importers did not share the logger registry\n"); + return false; + } + + LOG_INFO(logger, "Module logging from another translation unit: {}", 43); + return true; +}