diff --git a/supervisor/api/host.py b/supervisor/api/host.py index df54f77d8c5..50bfa9190ce 100644 --- a/supervisor/api/host.py +++ b/supervisor/api/host.py @@ -222,10 +222,14 @@ async def advanced_logs_handler( "supported for now." ) - if request.headers[ACCEPT] == CONTENT_TYPE_X_LOG: + if "verbose" in request.query or request.headers[ACCEPT] == CONTENT_TYPE_X_LOG: log_formatter = LogFormatter.VERBOSE - if RANGE in request.headers: + if "lines" in request.query: + lines = request.query.get("lines", DEFAULT_RANGE) + # entries=cursor[[:num_skip]:num_entries] + range_header = f"entries=:-{lines}:" + elif RANGE in request.headers: range_header = request.headers.get(RANGE) else: range_header = f"entries=:-{DEFAULT_RANGE}:" diff --git a/tests/api/test_host.py b/tests/api/test_host.py index 41815f22a37..9c5d2901677 100644 --- a/tests/api/test_host.py +++ b/tests/api/test_host.py @@ -238,6 +238,51 @@ async def test_advanced_logs( ) +async def test_advaced_logs_query_parameters( + api_client: TestClient, + coresys: CoreSys, + journald_logs: MagicMock, + journal_logs_reader: MagicMock, +): + """Test advanced logging API entries controlled by query parameters.""" + # Check lines query parameter + await api_client.get("/host/logs?lines=53") + journald_logs.assert_called_once_with( + params={"SYSLOG_IDENTIFIER": coresys.host.logs.default_identifiers}, + range_header="entries=:-53:", + accept=LogFormat.JOURNAL, + ) + + journald_logs.reset_mock() + + # Check verbose logs formatter via query parameter + await api_client.get("/host/logs?verbose") + journald_logs.assert_called_once_with( + params={"SYSLOG_IDENTIFIER": coresys.host.logs.default_identifiers}, + range_header=DEFAULT_RANGE, + accept=LogFormat.JOURNAL, + ) + journal_logs_reader.assert_called_with(ANY, LogFormatter.VERBOSE) + + journal_logs_reader.reset_mock() + journald_logs.reset_mock() + + # Query parameters should take precedence over headers + await api_client.get( + "/host/logs?lines=53&verbose", + headers={ + "Range": "entries=:-19:10", + "Accept": "text/plain", + }, + ) + journald_logs.assert_called_once_with( + params={"SYSLOG_IDENTIFIER": coresys.host.logs.default_identifiers}, + range_header="entries=:-53:", + accept=LogFormat.JOURNAL, + ) + journal_logs_reader.assert_called_with(ANY, LogFormatter.VERBOSE) + + async def test_advanced_logs_boot_id_offset( api_client: TestClient, coresys: CoreSys, journald_logs: MagicMock ):