|
55 | 55 |
|
56 | 56 | #define SLICK_LOGGER_VERSION_MAJOR 1 |
57 | 57 | #define SLICK_LOGGER_VERSION_MINOR 0 |
58 | | -#define SLICK_LOGGER_VERSION_PATCH 7 |
59 | | -#define SLICK_LOGGER_VERSION "1.0.7" |
| 58 | +#define SLICK_LOGGER_VERSION_PATCH 8 |
| 59 | +#define SLICK_LOGGER_VERSION "1.0.8" |
60 | 60 |
|
61 | 61 | #ifndef SLICK_LOGGER_MAX_ARGS |
62 | 62 | #define SLICK_LOGGER_MAX_ARGS 20 |
@@ -620,12 +620,43 @@ class Logger { |
620 | 620 | */ |
621 | 621 | void shutdown(bool clear_sinks = true); |
622 | 622 |
|
| 623 | + /** |
| 624 | + * @brief Block until all log entries queued before this call have been |
| 625 | + * written to all sinks. The logger keeps running — unlike shutdown(), |
| 626 | + * logging can continue normally after flush() returns. |
| 627 | + * |
| 628 | + * Primary use case: call flush() before unloading a shared library whose |
| 629 | + * string literals are referenced by queued log entries. Because LOG_* with |
| 630 | + * string literals stores a raw pointer into the caller's code segment, |
| 631 | + * unloading the library while entries are still queued would leave dangling |
| 632 | + * pointers that the writer thread would later dereference. |
| 633 | + * |
| 634 | + * Does nothing if the logger has not been initialized. |
| 635 | + */ |
| 636 | + void flush(); |
| 637 | + |
623 | 638 | /** |
624 | 639 | * @brief Reset the logger to uninitialized state |
625 | 640 | * Note: This is primarily for testing purposes. Use with caution. |
626 | 641 | */ |
627 | 642 | void reset(); |
628 | 643 |
|
| 644 | + /** |
| 645 | + * @brief Redirect Logger::instance() to an external Logger. |
| 646 | + * |
| 647 | + * Because slick-logger is header-only, every shared library (.dll/.so) and |
| 648 | + * the host executable each compile in their own copy of Logger::instance(). |
| 649 | + * Call this from a shared library's init function to make all LOG_* macros |
| 650 | + * in that library route to the host's logger instead of the library-local one. |
| 651 | + * Pass nullptr to restore the library-local singleton. |
| 652 | + * |
| 653 | + * Thread-safety: uses atomic store/load with acquire-release ordering, so |
| 654 | + * all initialization of the target logger is visible before any LOG_* call |
| 655 | + * after set_instance() returns. |
| 656 | + */ |
| 657 | + static void set_instance(Logger* logger) noexcept; |
| 658 | + static void clear_instance_override() noexcept; |
| 659 | + |
629 | 660 | private: |
630 | 661 | Logger() = default; |
631 | 662 | ~Logger(); |
@@ -658,6 +689,14 @@ class Logger { |
658 | 689 | uint64_t read_index_{0}; |
659 | 690 | std::atomic<LogLevel> log_level_{LogLevel::L_TRACE}; |
660 | 691 | std::unordered_map<std::string_view, int> sinkname_index_map_; |
| 692 | + |
| 693 | + // The logger instance local to this shared library / executable. |
| 694 | + // override_instance_ points here by default. |
| 695 | + static Logger instance_; |
| 696 | + // Points to the active Logger for this shared library / executable. |
| 697 | + // Defaults to &instance_. Call set_instance() to redirect to an external |
| 698 | + // logger (e.g. the host application's logger when loaded as a plugin). |
| 699 | + static std::atomic<Logger*> override_instance_; |
661 | 700 | }; |
662 | 701 |
|
663 | 702 |
|
@@ -1253,8 +1292,18 @@ inline std::string DailyFileSink::get_date_string() const { |
1253 | 1292 | } |
1254 | 1293 |
|
1255 | 1294 | inline Logger& Logger::instance() { |
1256 | | - static Logger instance; |
1257 | | - return instance; |
| 1295 | + return *override_instance_.load(std::memory_order_acquire); |
| 1296 | +} |
| 1297 | + |
| 1298 | +inline Logger Logger::instance_; |
| 1299 | +inline std::atomic<Logger*> Logger::override_instance_{&Logger::instance_}; |
| 1300 | + |
| 1301 | +inline void Logger::set_instance(Logger* logger) noexcept { |
| 1302 | + override_instance_.store(logger, std::memory_order_release); |
| 1303 | +} |
| 1304 | + |
| 1305 | +inline void Logger::clear_instance_override() noexcept { |
| 1306 | + override_instance_.store(&instance_, std::memory_order_release); |
1258 | 1307 | } |
1259 | 1308 |
|
1260 | 1309 | inline void Logger::init(const std::filesystem::path& log_file, size_t log_queue_size, size_t string_buffer_size) { |
@@ -1680,6 +1729,19 @@ inline Logger::~Logger() { |
1680 | 1729 | shutdown(); |
1681 | 1730 | } |
1682 | 1731 |
|
| 1732 | +inline void Logger::flush() { |
| 1733 | + if (!running_.load(std::memory_order_relaxed) || !log_queue_) { |
| 1734 | + return; |
| 1735 | + } |
| 1736 | + // Snapshot the write cursor: any entry reserved before this point has |
| 1737 | + // already been assigned an index below drain_target. |
| 1738 | + const uint64_t drain_target = log_queue_->initial_reading_index(); |
| 1739 | + // Wait until the writer thread's read cursor reaches drain_target. |
| 1740 | + while (running_.load(std::memory_order_relaxed) && read_index_ < drain_target) { |
| 1741 | + std::this_thread::sleep_for(std::chrono::milliseconds(1)); |
| 1742 | + } |
| 1743 | +} |
| 1744 | + |
1683 | 1745 | inline void Logger::reset() { |
1684 | 1746 | shutdown(); |
1685 | 1747 | // Reset all state for fresh initialization |
|
0 commit comments