From a85115bd3332c1f1b9da5d4fa944429f1ed8d7e4 Mon Sep 17 00:00:00 2001 From: eldritch horrors Date: Tue, 14 Oct 2025 15:31:15 +0200 Subject: [PATCH] libutil: add a log line splitter we'll need this in other places too soon. Change-Id: I8ddd57aefa4cbd1f8f7232f35e30a204ace77e58 --- lix/libstore/build/derivation-goal.cc | 42 ++++++++++----------------- lix/libutil/logging.cc | 24 +++++++++++++++ lix/libutil/logging.hh | 23 +++++++++++++++ 3 files changed, 63 insertions(+), 26 deletions(-) diff --git a/lix/libstore/build/derivation-goal.cc b/lix/libstore/build/derivation-goal.cc index 1b6c38f9b..94d5ae2cb 100644 --- a/lix/libstore/build/derivation-goal.cc +++ b/lix/libstore/build/derivation-goal.cc @@ -1229,27 +1229,20 @@ Goal::WorkResult DerivationGoal::tooMuchLogs() kj::Promise>> DerivationGoal::handleBuilderOutput(AsyncInputStream & in) noexcept try { - std::string currentLogLine; - size_t currentLogLinePos = 0; // to handle carriage return + LogLineSplitter splitter; - auto flushLine = [&] { - KJ_DEFER({ - currentLogLine = ""; - currentLogLinePos = 0; - }); - - if (const auto state = handleJSONLogMessage( - currentLogLine, *act, builderActivities, "the derivation builder" - )) + auto flushLine = [&](const std::string & line) { + if (const auto state = + handleJSONLogMessage(line, *act, builderActivities, "the derivation builder")) { return *state; } else { - logTail.push_back(currentLogLine); + logTail.push_back(line); if (logTail.size() > settings.logLines) { logTail.pop_front(); } - return act->result(resBuildLogLine, currentLogLine); + return act->result(resBuildLogLine, line); } }; @@ -1274,8 +1267,8 @@ try { lastChildActivity = AIO().provider.getTimer().now(); if (data.empty()) { - if (!currentLogLine.empty()) { - if (flushLine() == Logger::BufferState::NeedsFlush) { + if (auto left = splitter.finish(); !left.empty()) { + if (flushLine(left) == Logger::BufferState::NeedsFlush) { TRY_AWAIT(act->getLogger().flush()); } } @@ -1287,20 +1280,17 @@ try { co_return tooMuchLogs(); } - for (auto c : data) - if (c == '\r') - currentLogLinePos = 0; - else if (c == '\n') { - if (flushLine() == Logger::BufferState::NeedsFlush) { + if (logSink) { + (*logSink)(data); + } + + while (!data.empty()) { + if (auto line = splitter.feed(data)) { + if (flushLine(*line) == Logger::BufferState::NeedsFlush) { TRY_AWAIT(act->getLogger().flush()); } - } else { - if (currentLogLinePos >= currentLogLine.size()) - currentLogLine.resize(currentLogLinePos + 1); - currentLogLine[currentLogLinePos++] = c; } - - if (logSink) (*logSink)(data); + } } } catch (...) { co_return result::current_exception(); diff --git a/lix/libutil/logging.cc b/lix/libutil/logging.cc index 846b386bf..b2335713d 100644 --- a/lix/libutil/logging.cc +++ b/lix/libutil/logging.cc @@ -423,4 +423,28 @@ void logFatal(std::string const & s) syslog(LOG_CRIT, "%s", requireCString(s).asCStr()); } +std::optional LogLineSplitter::feed(std::string_view & input) +{ + for (auto [idx, c] : enumerate(input)) { + if (c == '\r') { + pos = 0; + } else if (c == '\n') { + input = input.substr(idx + 1); + return finish(); + } else { + if (pos >= line.size()) { + line.resize(pos + 1); + } + line[pos++] = c; + } + } + input = {}; + return std::nullopt; +} + +std::string LogLineSplitter::finish() +{ + pos = 0; + return std::move(line); +} } diff --git a/lix/libutil/logging.hh b/lix/libutil/logging.hh index 7fd9160c1..198b6a933 100644 --- a/lix/libutil/logging.hh +++ b/lix/libutil/logging.hh @@ -435,4 +435,27 @@ std::optional handleJSONLogMessage( std::map & activities, std::string_view source ); + +/** + * Split a log stream into lines, processing carriage returns (`\r`) as a terminal would. + */ +class LogLineSplitter +{ + std::string line; + size_t pos = 0; + +public: + /** + * Feeds some input to the splitter and returns the first full line or `nullopt` if + * there is no complete line in the buffer yet. If any input remains `input` is set + * to the unconsumed data and `feed` should be called again until `input` is empty. + * If this function returns `nullopt` it guarantees that `input` is fully consumed. + */ + std::optional feed(std::string_view & input); + + /** + * Clear the line buffer and return its current contents. + */ + std::string finish(); +}; }