From 14eb78f3de451c45598d6cb9d7d6db81ea6ef179 Mon Sep 17 00:00:00 2001 From: Ethan Harris Date: Thu, 30 Jul 2026 17:40:54 +0000 Subject: [PATCH] Fix: Properly trim leading logs token --- python/lightning_sdk/api/logs_api.py | 11 ++++++++--- python/tests/api/test_logs_api.py | 15 ++++++++++++++- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/python/lightning_sdk/api/logs_api.py b/python/lightning_sdk/api/logs_api.py index cf206d00..29c59e5a 100644 --- a/python/lightning_sdk/api/logs_api.py +++ b/python/lightning_sdk/api/logs_api.py @@ -77,8 +77,7 @@ def format(self, *, timestamps: bool = False, prefix: Optional[str] = None) -> s if prefix: parts.append(f"[{prefix}]") - # strip `+ lightning\n` prefix from the message (sometimes returned from the backend) - parts.append(self.message.removeprefix("+ lightning\n")) + parts.append(self.message) return " ".join(parts) @@ -292,8 +291,14 @@ def get_page( kwargs["page_token"] = page_token response = self._client.jobs_service_get_logs(project_id=teamspace_id, **kwargs) + + raw_entries = list(getattr(response, "entries", None) or []) + # if it's the first page, trim leading "+ lightning" from the output + if not page_token and raw_entries and getattr(raw_entries[0], "message", "") == "+ lightning": + raw_entries = raw_entries[1:] + return LogsPage( - entries=[_entry_from_model(entry) for entry in (getattr(response, "entries", None) or [])], + entries=[_entry_from_model(entry) for entry in raw_entries], next_page_token=getattr(response, "next_page_token", None) or None, follow_url=getattr(response, "follow_url", None) or None, ) diff --git a/python/tests/api/test_logs_api.py b/python/tests/api/test_logs_api.py index a5cbe749..5569a624 100644 --- a/python/tests/api/test_logs_api.py +++ b/python/tests/api/test_logs_api.py @@ -56,7 +56,7 @@ def test_parse_log_entries_tolerates_missing_timestamp() -> None: def test_log_entry_format() -> None: - entry = LogEntry(message="+ lightning\nready", timestamp=datetime(2026, 7, 27, 9, 0, tzinfo=timezone.utc)) + entry = LogEntry(message="ready", timestamp=datetime(2026, 7, 27, 9, 0, tzinfo=timezone.utc)) assert entry.format() == "ready" assert entry.format(prefix="replica-0") == "[replica-0] ready" @@ -125,6 +125,19 @@ def test_stream_follows_page_tokens() -> None: assert client.jobs_service_get_logs.call_args_list[1].kwargs["page_token"] == "cursor-1" +def test_stream_filters_lightning_prefix() -> None: + api, client = _api( + V1GetLogsResponse(entries=[_entry("+ lightning"), _entry("one")], next_page_token="cursor-1"), + V1GetLogsResponse(entries=[_entry("two")], next_page_token=""), + ) + + entries = list(api.stream("project-id", deployment_id="dep-id")) + + assert [e.message for e in entries] == ["one", "two"] + assert client.jobs_service_get_logs.call_count == 2 + assert client.jobs_service_get_logs.call_args_list[1].kwargs["page_token"] == "cursor-1" + + def test_stream_tail_keeps_the_last_lines_across_pages() -> None: api, _ = _api( V1GetLogsResponse(entries=[_entry("a"), _entry("b")], next_page_token="cursor-1"),