From 3669348edbdffc61bbf69c906001e8cbf68772be Mon Sep 17 00:00:00 2001 From: dgibson Date: Fri, 1 May 2026 20:10:01 +0000 Subject: [PATCH 1/6] Add support for passing nested writers to LogSerializer; Make zstd-compressed KV-IR outputs the default in log-converter. --- components/core/src/clp_s/CMakeLists.txt | 5 +++ .../log_converter/CommandLineArguments.cpp | 7 ++++ .../log_converter/CommandLineArguments.hpp | 5 +++ .../src/clp_s/log_converter/LogConverter.cpp | 7 +++- .../src/clp_s/log_converter/LogConverter.hpp | 10 +++-- .../src/clp_s/log_converter/LogSerializer.cpp | 37 +++++++++++++----- .../src/clp_s/log_converter/LogSerializer.hpp | 39 +++++++++++++------ .../src/clp_s/log_converter/log_converter.cpp | 3 +- 8 files changed, 86 insertions(+), 27 deletions(-) diff --git a/components/core/src/clp_s/CMakeLists.txt b/components/core/src/clp_s/CMakeLists.txt index f6a3ee1b90..e5141dae0a 100644 --- a/components/core/src/clp_s/CMakeLists.txt +++ b/components/core/src/clp_s/CMakeLists.txt @@ -67,6 +67,8 @@ set( ../clp/FileDescriptor.hpp ../clp/FileReader.cpp ../clp/FileReader.hpp + ../clp/FileWriter.cpp + ../clp/FileReader.hpp ../clp/GrepCore.cpp ../clp/GrepCore.hpp ../clp/SchemaSearcher.cpp @@ -96,7 +98,10 @@ set( ../clp/streaming_archive/ArchiveMetadata.hpp ../clp/streaming_archive/Constants.hpp ../clp/streaming_compression/Constants.hpp + ../clp/streaming_compression/Compressor.hpp ../clp/streaming_compression/Decompressor.hpp + ../clp/streaming_compression/zstd/Compressor.cpp + ../clp/streaming_compression/zstd/Compressor.hpp ../clp/streaming_compression/zstd/Decompressor.cpp ../clp/streaming_compression/zstd/Decompressor.hpp ../clp/StringReader.cpp diff --git a/components/core/src/clp_s/log_converter/CommandLineArguments.cpp b/components/core/src/clp_s/log_converter/CommandLineArguments.cpp index e449acc97d..d3709c935b 100644 --- a/components/core/src/clp_s/log_converter/CommandLineArguments.cpp +++ b/components/core/src/clp_s/log_converter/CommandLineArguments.cpp @@ -116,6 +116,7 @@ auto CommandLineArguments::parse_arguments(int argc, char const** argv) po::options_description conversion_options("Conversion options"); std::string input_path_list_file_path; std::string auth{cNoAuth}; + bool no_compress_converted_files{}; // clang-format off conversion_options.add_options()( "inputs-from,f", @@ -143,6 +144,10 @@ auto CommandLineArguments::parse_arguments(int argc, char const** argv) ->value_name("LOG_EVENT_SIZE") ->default_value(m_max_log_event_size), "Maximum allowed size (B) for a single log event before conversion fails." + )( + "no-compress-converted-files", + po::bool_switch(&no_compress_converted_files), + "Disable compression on the converted KV-IR files." ); // clang-format on @@ -198,6 +203,8 @@ auto CommandLineArguments::parse_arguments(int argc, char const** argv) if (m_max_log_event_size <= 0) { throw std::invalid_argument("Max event size must be greater than zero."); } + + m_compress_converted_files = false == no_compress_converted_files; } catch (std::exception& e) { SPDLOG_ERROR("{}", e.what()); print_basic_usage(); diff --git a/components/core/src/clp_s/log_converter/CommandLineArguments.hpp b/components/core/src/clp_s/log_converter/CommandLineArguments.hpp index 8060716a3b..90278f2477 100644 --- a/components/core/src/clp_s/log_converter/CommandLineArguments.hpp +++ b/components/core/src/clp_s/log_converter/CommandLineArguments.hpp @@ -37,6 +37,10 @@ class CommandLineArguments { [[nodiscard]] auto get_max_log_event_size() const -> size_t { return m_max_log_event_size; } + [[nodiscard]] auto get_compress_converted_files() const -> bool { + return m_compress_converted_files; + } + private: // Methods void print_basic_usage() const; @@ -47,6 +51,7 @@ class CommandLineArguments { NetworkAuthOption m_network_auth{}; std::string m_output_dir{"./"}; size_t m_max_log_event_size{512ULL * 1024ULL * 1024ULL}; // 512 MiB + bool m_compress_converted_files{true}; }; } // namespace clp_s::log_converter diff --git a/components/core/src/clp_s/log_converter/LogConverter.cpp b/components/core/src/clp_s/log_converter/LogConverter.cpp index 299a178187..f51b5685e5 100644 --- a/components/core/src/clp_s/log_converter/LogConverter.cpp +++ b/components/core/src/clp_s/log_converter/LogConverter.cpp @@ -43,7 +43,8 @@ constexpr std::string_view cDelimiters{R"(delimiters: \t\r\n[(:)"}; auto LogConverter::convert_file( clp_s::Path const& path, clp::ReaderInterface* reader, - std::string_view output_dir + std::string_view output_dir, + bool compress_converted_file ) -> ystdlib::error_handling::Result { log_surgeon::Schema schema; schema.add_delimiters(cDelimiters); @@ -55,7 +56,9 @@ auto LogConverter::convert_file( m_parser_offset = 0ULL; m_num_bytes_buffered = 0ULL; - auto serializer{YSTDLIB_ERROR_HANDLING_TRYX(LogSerializer::create(output_dir, path.path))}; + auto serializer{YSTDLIB_ERROR_HANDLING_TRYX( + LogSerializer::create(output_dir, path.path, compress_converted_file) + )}; bool reached_end_of_stream{false}; while (false == reached_end_of_stream) { diff --git a/components/core/src/clp_s/log_converter/LogConverter.hpp b/components/core/src/clp_s/log_converter/LogConverter.hpp index 0492cda64c..eee9b3540c 100644 --- a/components/core/src/clp_s/log_converter/LogConverter.hpp +++ b/components/core/src/clp_s/log_converter/LogConverter.hpp @@ -27,15 +27,19 @@ class LogConverter { * @param path The input path for the unstructured text file. * @param reader A reader positioned at the start of the input stream. * @param output_dir The output directory for generated KV-IR files. + * @param compress_converted_file Whether the converted file should be compressed. * @return A void result on success, or an error code indicating the failure: * - std::errc::no_message if `log_surgeon::BufferParser::parse_next_event` returns an error. * - Forwards `LogSerializer::create()`'s return values. * - Forwards `refill_buffer()`'s return values. * - Forwards `LogSerializer::add_message()`'s return values. */ - [[nodiscard]] auto - convert_file(clp_s::Path const& path, clp::ReaderInterface* reader, std::string_view output_dir) - -> ystdlib::error_handling::Result; + [[nodiscard]] auto convert_file( + clp_s::Path const& path, + clp::ReaderInterface* reader, + std::string_view output_dir, + bool compress_converted_file + ) -> ystdlib::error_handling::Result; private: // Constants diff --git a/components/core/src/clp_s/log_converter/LogSerializer.cpp b/components/core/src/clp_s/log_converter/LogSerializer.cpp index 66ee49feb0..a616b0c006 100644 --- a/components/core/src/clp_s/log_converter/LogSerializer.cpp +++ b/components/core/src/clp_s/log_converter/LogSerializer.cpp @@ -3,11 +3,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include @@ -16,17 +18,23 @@ #include #include -#include "../../clp/ffi/ir_stream/Serializer.hpp" -#include "../../clp/ir/types.hpp" -#include "../FileWriter.hpp" +#include +#include +#include +#include +#include namespace clp_s::log_converter { namespace { constexpr msgpack::object_map cEmptyMap{.size = 0U, .ptr = nullptr}; +constexpr std::string_view cDefaultIRFileExtension{".clp"}; } // namespace -auto LogSerializer::create(std::string_view output_dir, std::string_view original_file_path) - -> ystdlib::error_handling::Result { +auto LogSerializer::create( + std::string_view output_dir, + std::string_view original_file_path, + bool use_zstd +) -> ystdlib::error_handling::Result { nlohmann::json metadata; metadata.emplace(cOriginalFileMetadataKey, original_file_path); auto serializer{YSTDLIB_ERROR_HANDLING_TRYX( @@ -36,16 +44,27 @@ auto LogSerializer::create(std::string_view output_dir, std::string_view origina )}; boost::uuids::random_generator uuid_generator; - std::string const file_name{boost::uuids::to_string(uuid_generator()) + ".clp"}; + auto file_extension{use_zstd ? clp::ir::cIrFileExtension : cDefaultIRFileExtension}; + std::string const file_name{ + boost::uuids::to_string(uuid_generator()) + std::string{file_extension} + }; auto const converted_path{std::filesystem::path{output_dir} / file_name}; - clp_s::FileWriter writer; + std::vector> nested_writers; try { - writer.open(converted_path, clp_s::FileWriter::OpenMode::CreateForWriting); + auto writer{std::make_unique()}; + writer->open(converted_path, clp::FileWriter::OpenMode::CREATE_FOR_WRITING); + nested_writers.emplace_back(std::move(writer)); } catch (std::exception const&) { return std::errc::no_such_file_or_directory; } - return LogSerializer{std::move(serializer), std::move(writer)}; + if (use_zstd) { + auto compressor{std::make_unique()}; + compressor->open(*nested_writers.back().get()); + nested_writers.emplace_back(std::move(compressor)); + } + + return LogSerializer{std::move(serializer), std::move(nested_writers)}; } auto LogSerializer::add_message(std::string_view timestamp, std::string_view message) diff --git a/components/core/src/clp_s/log_converter/LogSerializer.hpp b/components/core/src/clp_s/log_converter/LogSerializer.hpp index 17212beb33..c8a6c4787f 100644 --- a/components/core/src/clp_s/log_converter/LogSerializer.hpp +++ b/components/core/src/clp_s/log_converter/LogSerializer.hpp @@ -2,16 +2,20 @@ #define CLP_S_LOG_CONVERTER_LOGSERIALIZER_HPP #include +#include #include #include +#include #include -#include "../../clp/ffi/ir_stream/protocol_constants.hpp" -#include "../../clp/ffi/ir_stream/Serializer.hpp" -#include "../../clp/ir/types.hpp" -#include "../../clp/type_utils.hpp" -#include "../FileWriter.hpp" +#include +#include +#include +#include +#include +#include +#include namespace clp_s::log_converter { /** @@ -24,13 +28,14 @@ class LogSerializer { * Creates an instance of `LogSerializer`. * @param output_dir The destination directory for generated KV-IR. * @param original_file_path The original path for the file being converted to KV-IR. + * @param use_zstd Whether the output KV-IR should be zstd-compressed. * @return A result containing a `LogSerializer` on success, or an error code indicating the * failure: * - std::errc::no_such_file_or_directory if a `clp_s::FileWriter` fails to open an output file. * - Forwards `clp::ffi::ir_stream::Serializer<>::create()`'s return values. */ [[nodiscard]] static auto - create(std::string_view output_dir, std::string_view original_file_path) + create(std::string_view output_dir, std::string_view original_file_path, bool use_zstd) -> ystdlib::error_handling::Result; // Constructors @@ -74,8 +79,18 @@ class LogSerializer { */ void close() { flush_buffer(); - m_writer.write_numeric_value(clp::ffi::ir_stream::cProtocol::Eof); - m_writer.close(); + m_nested_writers.back()->write_numeric_value(clp::ffi::ir_stream::cProtocol::Eof); + for (auto it{m_nested_writers.rbegin()}; it != m_nested_writers.rend(); ++it) { + if (auto compressor{dynamic_cast(it->get())}; + nullptr != compressor) + { + compressor->close(); + } else if (auto file_writer{dynamic_cast(it->get())}; + nullptr != file_writer) + { + file_writer->close(); + } + } } private: @@ -88,10 +103,10 @@ class LogSerializer { // Constructors explicit LogSerializer( clp::ffi::ir_stream::Serializer&& serializer, - clp_s::FileWriter&& writer + std::vector>&& nested_writers ) : m_serializer{std::move(serializer)}, - m_writer{std::move(writer)} {} + m_nested_writers{std::move(nested_writers)} {} // Methods /** @@ -99,7 +114,7 @@ class LogSerializer { */ void flush_buffer() { auto const buffer{m_serializer.get_ir_buf_view()}; - m_writer.write( + m_nested_writers.back()->write( clp::size_checked_pointer_cast(buffer.data()), buffer.size_bytes() ); @@ -107,7 +122,7 @@ class LogSerializer { } clp::ffi::ir_stream::Serializer m_serializer; - clp_s::FileWriter m_writer; + std::vector> m_nested_writers; }; } // namespace clp_s::log_converter diff --git a/components/core/src/clp_s/log_converter/log_converter.cpp b/components/core/src/clp_s/log_converter/log_converter.cpp index 760df43459..94f82eaa32 100644 --- a/components/core/src/clp_s/log_converter/log_converter.cpp +++ b/components/core/src/clp_s/log_converter/log_converter.cpp @@ -69,7 +69,8 @@ auto convert_files(CommandLineArguments const& command_line_arguments) -> bool { auto const convert_result{log_converter.convert_file( path, nested_readers.back().get(), - command_line_arguments.get_output_dir() + command_line_arguments.get_output_dir(), + command_line_arguments.get_compress_converted_files() )}; if (convert_result.has_error()) { auto const& error{convert_result.error()}; From b73d0fc512d2e22079d8de54acf2c6e276078b82 Mon Sep 17 00:00:00 2001 From: gibber9809 Date: Wed, 6 May 2026 20:45:24 +0000 Subject: [PATCH 2/6] Address rabbit comments. --- components/core/src/clp_s/CMakeLists.txt | 2 +- .../core/src/clp_s/log_converter/LogSerializer.cpp | 9 +++++++-- .../core/src/clp_s/log_converter/LogSerializer.hpp | 3 ++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/components/core/src/clp_s/CMakeLists.txt b/components/core/src/clp_s/CMakeLists.txt index 985d6447f1..b278b1f5a6 100644 --- a/components/core/src/clp_s/CMakeLists.txt +++ b/components/core/src/clp_s/CMakeLists.txt @@ -68,7 +68,7 @@ set( ../clp/FileReader.cpp ../clp/FileReader.hpp ../clp/FileWriter.cpp - ../clp/FileReader.hpp + ../clp/FileWriter.hpp ../clp/GrepCore.cpp ../clp/GrepCore.hpp ../clp/SchemaSearcher.cpp diff --git a/components/core/src/clp_s/log_converter/LogSerializer.cpp b/components/core/src/clp_s/log_converter/LogSerializer.cpp index a616b0c006..cd97ca32a9 100644 --- a/components/core/src/clp_s/log_converter/LogSerializer.cpp +++ b/components/core/src/clp_s/log_converter/LogSerializer.cpp @@ -58,12 +58,17 @@ auto LogSerializer::create( return std::errc::no_such_file_or_directory; } - if (use_zstd) { + if (false == use_zstd) { + return LogSerializer{std::move(serializer), std::move(nested_writers)}; + } + + try { auto compressor{std::make_unique()}; compressor->open(*nested_writers.back().get()); nested_writers.emplace_back(std::move(compressor)); + } catch (std::exception const&) { + return std::errc::protocol_error; } - return LogSerializer{std::move(serializer), std::move(nested_writers)}; } diff --git a/components/core/src/clp_s/log_converter/LogSerializer.hpp b/components/core/src/clp_s/log_converter/LogSerializer.hpp index c8a6c4787f..dd3c9f5fa3 100644 --- a/components/core/src/clp_s/log_converter/LogSerializer.hpp +++ b/components/core/src/clp_s/log_converter/LogSerializer.hpp @@ -31,7 +31,8 @@ class LogSerializer { * @param use_zstd Whether the output KV-IR should be zstd-compressed. * @return A result containing a `LogSerializer` on success, or an error code indicating the * failure: - * - std::errc::no_such_file_or_directory if a `clp_s::FileWriter` fails to open an output file. + * - std::errc::no_such_file_or_directory if a `clp::FileWriter` fails to open an output file. + * - std::errc::protocol_error if a `clp::zstd::Compressor` fails to open a compression stream. * - Forwards `clp::ffi::ir_stream::Serializer<>::create()`'s return values. */ [[nodiscard]] static auto From 256286551681289043bab12c09f74619060bdde7 Mon Sep 17 00:00:00 2001 From: gibber9809 Date: Thu, 14 May 2026 20:52:15 +0000 Subject: [PATCH 3/6] Rename cDefaultIrFileExtension to cUncompressedFileExtension. --- components/core/src/clp_s/log_converter/LogSerializer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/core/src/clp_s/log_converter/LogSerializer.cpp b/components/core/src/clp_s/log_converter/LogSerializer.cpp index cd97ca32a9..4ea9678b21 100644 --- a/components/core/src/clp_s/log_converter/LogSerializer.cpp +++ b/components/core/src/clp_s/log_converter/LogSerializer.cpp @@ -27,7 +27,7 @@ namespace clp_s::log_converter { namespace { constexpr msgpack::object_map cEmptyMap{.size = 0U, .ptr = nullptr}; -constexpr std::string_view cDefaultIRFileExtension{".clp"}; +constexpr std::string_view cUncompressedFileExtension{".clp"}; } // namespace auto LogSerializer::create( @@ -44,7 +44,7 @@ auto LogSerializer::create( )}; boost::uuids::random_generator uuid_generator; - auto file_extension{use_zstd ? clp::ir::cIrFileExtension : cDefaultIRFileExtension}; + auto file_extension{use_zstd ? clp::ir::cIrFileExtension : cUncompressedFileExtension}; std::string const file_name{ boost::uuids::to_string(uuid_generator()) + std::string{file_extension} }; From dc6ab261bbcbe7340f8b9c2328c5a74415cd9436 Mon Sep 17 00:00:00 2001 From: gibber9809 Date: Thu, 14 May 2026 20:56:26 +0000 Subject: [PATCH 4/6] Add docstring explaining semantics of nested_writers. --- components/core/src/clp_s/log_converter/LogSerializer.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/components/core/src/clp_s/log_converter/LogSerializer.hpp b/components/core/src/clp_s/log_converter/LogSerializer.hpp index dd3c9f5fa3..53a1fcfd7e 100644 --- a/components/core/src/clp_s/log_converter/LogSerializer.hpp +++ b/components/core/src/clp_s/log_converter/LogSerializer.hpp @@ -123,6 +123,8 @@ class LogSerializer { } clp::ffi::ir_stream::Serializer m_serializer; + // Nested writers are ordered from closest to furthest from output sink. Typically this will + // look like `FileWriter` <- `Compressor`. std::vector> m_nested_writers; }; } // namespace clp_s::log_converter From 4b772fa546844c091776fd27654b4d2af69ec129 Mon Sep 17 00:00:00 2001 From: Devin Gibson Date: Thu, 21 May 2026 15:39:06 -0400 Subject: [PATCH 5/6] Apply suggestions from code review Co-authored-by: kirkrodrigues <2454684+kirkrodrigues@users.noreply.github.com> --- .../core/src/clp_s/log_converter/CommandLineArguments.cpp | 2 +- components/core/src/clp_s/log_converter/LogSerializer.hpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/components/core/src/clp_s/log_converter/CommandLineArguments.cpp b/components/core/src/clp_s/log_converter/CommandLineArguments.cpp index d3709c935b..1e27c6ed80 100644 --- a/components/core/src/clp_s/log_converter/CommandLineArguments.cpp +++ b/components/core/src/clp_s/log_converter/CommandLineArguments.cpp @@ -116,7 +116,7 @@ auto CommandLineArguments::parse_arguments(int argc, char const** argv) po::options_description conversion_options("Conversion options"); std::string input_path_list_file_path; std::string auth{cNoAuth}; - bool no_compress_converted_files{}; + bool no_compress_converted_files{false == m_compress_converted_files}; // clang-format off conversion_options.add_options()( "inputs-from,f", diff --git a/components/core/src/clp_s/log_converter/LogSerializer.hpp b/components/core/src/clp_s/log_converter/LogSerializer.hpp index 53a1fcfd7e..a43dddfb6f 100644 --- a/components/core/src/clp_s/log_converter/LogSerializer.hpp +++ b/components/core/src/clp_s/log_converter/LogSerializer.hpp @@ -123,8 +123,9 @@ class LogSerializer { } clp::ffi::ir_stream::Serializer m_serializer; - // Nested writers are ordered from closest to furthest from output sink. Typically this will + // Nested writers are ordered from closest to furthest from output sink. Typically, this will // look like `FileWriter` <- `Compressor`. + // NOTE: This class depends on there being at least one writer in `m_nested_writers` at all times. std::vector> m_nested_writers; }; } // namespace clp_s::log_converter From 991416eecd833d508e9f9f8dc07eeba307260b56 Mon Sep 17 00:00:00 2001 From: gibber9809 Date: Thu, 21 May 2026 19:50:34 +0000 Subject: [PATCH 6/6] Rename use_zstd to compress_with_zstd. --- .../core/src/clp_s/log_converter/LogSerializer.cpp | 8 +++++--- .../core/src/clp_s/log_converter/LogSerializer.hpp | 13 ++++++++----- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/components/core/src/clp_s/log_converter/LogSerializer.cpp b/components/core/src/clp_s/log_converter/LogSerializer.cpp index 4ea9678b21..e913fc4fac 100644 --- a/components/core/src/clp_s/log_converter/LogSerializer.cpp +++ b/components/core/src/clp_s/log_converter/LogSerializer.cpp @@ -33,7 +33,7 @@ constexpr std::string_view cUncompressedFileExtension{".clp"}; auto LogSerializer::create( std::string_view output_dir, std::string_view original_file_path, - bool use_zstd + bool compress_with_zstd ) -> ystdlib::error_handling::Result { nlohmann::json metadata; metadata.emplace(cOriginalFileMetadataKey, original_file_path); @@ -44,7 +44,9 @@ auto LogSerializer::create( )}; boost::uuids::random_generator uuid_generator; - auto file_extension{use_zstd ? clp::ir::cIrFileExtension : cUncompressedFileExtension}; + auto file_extension{ + compress_with_zstd ? clp::ir::cIrFileExtension : cUncompressedFileExtension + }; std::string const file_name{ boost::uuids::to_string(uuid_generator()) + std::string{file_extension} }; @@ -58,7 +60,7 @@ auto LogSerializer::create( return std::errc::no_such_file_or_directory; } - if (false == use_zstd) { + if (false == compress_with_zstd) { return LogSerializer{std::move(serializer), std::move(nested_writers)}; } diff --git a/components/core/src/clp_s/log_converter/LogSerializer.hpp b/components/core/src/clp_s/log_converter/LogSerializer.hpp index a43dddfb6f..82fa13fb86 100644 --- a/components/core/src/clp_s/log_converter/LogSerializer.hpp +++ b/components/core/src/clp_s/log_converter/LogSerializer.hpp @@ -28,16 +28,18 @@ class LogSerializer { * Creates an instance of `LogSerializer`. * @param output_dir The destination directory for generated KV-IR. * @param original_file_path The original path for the file being converted to KV-IR. - * @param use_zstd Whether the output KV-IR should be zstd-compressed. + * @param compress_with_zstd Whether the output KV-IR should be zstd-compressed. * @return A result containing a `LogSerializer` on success, or an error code indicating the * failure: * - std::errc::no_such_file_or_directory if a `clp::FileWriter` fails to open an output file. * - std::errc::protocol_error if a `clp::zstd::Compressor` fails to open a compression stream. * - Forwards `clp::ffi::ir_stream::Serializer<>::create()`'s return values. */ - [[nodiscard]] static auto - create(std::string_view output_dir, std::string_view original_file_path, bool use_zstd) - -> ystdlib::error_handling::Result; + [[nodiscard]] static auto create( + std::string_view output_dir, + std::string_view original_file_path, + bool compress_with_zstd + ) -> ystdlib::error_handling::Result; // Constructors // Delete copy constructor and assignment operator @@ -125,7 +127,8 @@ class LogSerializer { clp::ffi::ir_stream::Serializer m_serializer; // Nested writers are ordered from closest to furthest from output sink. Typically, this will // look like `FileWriter` <- `Compressor`. - // NOTE: This class depends on there being at least one writer in `m_nested_writers` at all times. + // NOTE: This class depends on there being at least one writer in `m_nested_writers` at all + // times. std::vector> m_nested_writers; }; } // namespace clp_s::log_converter