From 19a117889c2db6b817af9458c04c04f324162e75 Mon Sep 17 00:00:00 2001 From: Stefan Boberg Date: Mon, 9 Mar 2026 10:50:47 +0100 Subject: Eliminate spdlog dependency (#773) Removes the vendored spdlog library (~12,000 lines) and replaces it with a purpose-built logging system in zencore (~1,800 lines). The new implementation provides the same functionality with fewer abstractions, no shared_ptr overhead, and full control over the logging pipeline. ### What changed **New logging core in zencore/logging/:** - LogMessage, Formatter, Sink, Logger, Registry - core abstractions matching spdlog's model but simplified - AnsiColorStdoutSink - ANSI color console output (replaces spdlog stdout_color_sink) - MsvcSink - OutputDebugString on Windows (replaces spdlog msvc_sink) - AsyncSink - async logging via BlockingQueue worker thread (replaces spdlog async_logger) - NullSink, MessageOnlyFormatter - utility types - Thread-safe timestamp caching in formatters using RwLock **Moved to zenutil/logging/:** - FullFormatter - full log formatting with timestamp, logger name, level, source location, multiline alignment - JsonFormatter - structured JSON log output - RotatingFileSink - rotating file sink with atomic size tracking **API changes:** - Log levels are now an enum (LogLevel) instead of int, eliminating the zen::logging::level namespace - LoggerRef no longer wraps shared_ptr - it holds a raw pointer with the registry owning lifetime - Logger error handler is wired through Registry and propagated to all loggers on registration - Logger::Log() now populates ThreadId on every message **Cleanup:** - Deleted thirdparty/spdlog/ entirely (110+ files) - Deleted full_test_formatter (was ~80% duplicate of FullFormatter) - Renamed snake_case classes to PascalCase (full_formatter -> FullFormatter, json_formatter -> JsonFormatter, sentry_sink -> SentrySink) - Removed spdlog from xmake dependency graph ### Build / test impact - zencore no longer depends on spdlog - zenutil and zenvfs xmake.lua updated to drop spdlog dep - zentelemetry xmake.lua updated to drop spdlog dep - All existing tests pass, no test changes required beyond formatter class renames --- thirdparty/spdlog/tests/test_async.cpp | 200 --------------------------------- 1 file changed, 200 deletions(-) delete mode 100644 thirdparty/spdlog/tests/test_async.cpp (limited to 'thirdparty/spdlog/tests/test_async.cpp') diff --git a/thirdparty/spdlog/tests/test_async.cpp b/thirdparty/spdlog/tests/test_async.cpp deleted file mode 100644 index 76fdd7c6b..000000000 --- a/thirdparty/spdlog/tests/test_async.cpp +++ /dev/null @@ -1,200 +0,0 @@ -#include "includes.h" -#include "spdlog/async.h" -#include "spdlog/sinks/basic_file_sink.h" -#include "test_sink.h" - -#define TEST_FILENAME "test_logs/async_test.log" - -TEST_CASE("basic async test ", "[async]") { - auto test_sink = std::make_shared(); - size_t overrun_counter = 0; - size_t queue_size = 128; - size_t messages = 256; - { - auto tp = std::make_shared(queue_size, 1); - auto logger = std::make_shared("as", test_sink, tp, - spdlog::async_overflow_policy::block); - for (size_t i = 0; i < messages; i++) { - logger->info("Hello message #{}", i); - } - logger->flush(); - overrun_counter = tp->overrun_counter(); - } - REQUIRE(test_sink->msg_counter() == messages); - REQUIRE(test_sink->flush_counter() == 1); - REQUIRE(overrun_counter == 0); -} - -TEST_CASE("discard policy ", "[async]") { - auto test_sink = std::make_shared(); - test_sink->set_delay(std::chrono::milliseconds(1)); - size_t queue_size = 4; - size_t messages = 1024; - - auto tp = std::make_shared(queue_size, 1); - auto logger = std::make_shared( - "as", test_sink, tp, spdlog::async_overflow_policy::overrun_oldest); - for (size_t i = 0; i < messages; i++) { - logger->info("Hello message"); - } - REQUIRE(test_sink->msg_counter() < messages); - REQUIRE(tp->overrun_counter() > 0); -} - -TEST_CASE("discard policy discard_new ", "[async]") { - auto test_sink = std::make_shared(); - test_sink->set_delay(std::chrono::milliseconds(1)); - size_t queue_size = 4; - size_t messages = 1024; - - auto tp = std::make_shared(queue_size, 1); - auto logger = std::make_shared( - "as", test_sink, tp, spdlog::async_overflow_policy::discard_new); - for (size_t i = 0; i < messages; i++) { - logger->info("Hello message"); - } - REQUIRE(test_sink->msg_counter() < messages); - REQUIRE(tp->discard_counter() > 0); -} - -TEST_CASE("discard policy using factory ", "[async]") { - size_t queue_size = 4; - size_t messages = 1024; - spdlog::init_thread_pool(queue_size, 1); - - auto logger = spdlog::create_async_nb("as2"); - auto test_sink = std::static_pointer_cast(logger->sinks()[0]); - test_sink->set_delay(std::chrono::milliseconds(3)); - - for (size_t i = 0; i < messages; i++) { - logger->info("Hello message"); - } - - REQUIRE(test_sink->msg_counter() < messages); - spdlog::drop_all(); -} - -TEST_CASE("flush", "[async]") { - auto test_sink = std::make_shared(); - size_t queue_size = 256; - size_t messages = 256; - { - auto tp = std::make_shared(queue_size, 1); - auto logger = std::make_shared("as", test_sink, tp, - spdlog::async_overflow_policy::block); - for (size_t i = 0; i < messages; i++) { - logger->info("Hello message #{}", i); - } - - logger->flush(); - } - // std::this_thread::sleep_for(std::chrono::milliseconds(250)); - REQUIRE(test_sink->msg_counter() == messages); - REQUIRE(test_sink->flush_counter() == 1); -} - -TEST_CASE("async periodic flush", "[async]") { - auto logger = spdlog::create_async("as"); - auto test_sink = std::static_pointer_cast(logger->sinks()[0]); - - spdlog::flush_every(std::chrono::seconds(1)); - std::this_thread::sleep_for(std::chrono::milliseconds(1700)); - REQUIRE(test_sink->flush_counter() == 1); - spdlog::flush_every(std::chrono::seconds(0)); - spdlog::drop_all(); -} - -TEST_CASE("tp->wait_empty() ", "[async]") { - auto test_sink = std::make_shared(); - test_sink->set_delay(std::chrono::milliseconds(5)); - size_t messages = 100; - - auto tp = std::make_shared(messages, 2); - auto logger = std::make_shared("as", test_sink, tp, - spdlog::async_overflow_policy::block); - for (size_t i = 0; i < messages; i++) { - logger->info("Hello message #{}", i); - } - logger->flush(); - tp.reset(); - - REQUIRE(test_sink->msg_counter() == messages); - REQUIRE(test_sink->flush_counter() == 1); -} - -TEST_CASE("multi threads", "[async]") { - auto test_sink = std::make_shared(); - size_t queue_size = 128; - size_t messages = 256; - size_t n_threads = 10; - { - auto tp = std::make_shared(queue_size, 1); - auto logger = std::make_shared("as", test_sink, tp, - spdlog::async_overflow_policy::block); - - std::vector threads; - for (size_t i = 0; i < n_threads; i++) { - threads.emplace_back([logger, messages] { - for (size_t j = 0; j < messages; j++) { - logger->info("Hello message #{}", j); - } - }); - logger->flush(); - } - - for (auto &t : threads) { - t.join(); - } - } - - REQUIRE(test_sink->msg_counter() == messages * n_threads); - REQUIRE(test_sink->flush_counter() == n_threads); -} - -TEST_CASE("to_file", "[async]") { - prepare_logdir(); - size_t messages = 1024; - size_t tp_threads = 1; - spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME); - { - auto file_sink = std::make_shared(filename, true); - auto tp = std::make_shared(messages, tp_threads); - auto logger = - std::make_shared("as", std::move(file_sink), std::move(tp)); - - for (size_t j = 0; j < messages; j++) { - logger->info("Hello message #{}", j); - } - } - - require_message_count(TEST_FILENAME, messages); - auto contents = file_contents(TEST_FILENAME); - using spdlog::details::os::default_eol; - REQUIRE(ends_with(contents, spdlog::fmt_lib::format("Hello message #1023{}", default_eol))); -} - -TEST_CASE("to_file multi-workers", "[async]") { - prepare_logdir(); - size_t messages = 1024 * 10; - size_t tp_threads = 10; - spdlog::filename_t filename = SPDLOG_FILENAME_T(TEST_FILENAME); - { - auto file_sink = std::make_shared(filename, true); - auto tp = std::make_shared(messages, tp_threads); - auto logger = - std::make_shared("as", std::move(file_sink), std::move(tp)); - - for (size_t j = 0; j < messages; j++) { - logger->info("Hello message #{}", j); - } - } - require_message_count(TEST_FILENAME, messages); -} - -TEST_CASE("bad_tp", "[async]") { - auto test_sink = std::make_shared(); - std::shared_ptr const empty_tp; - auto logger = std::make_shared("as", test_sink, empty_tp); - logger->info("Please throw an exception"); - REQUIRE(test_sink->msg_counter() == 0); -} -- cgit v1.2.3