Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 7 additions & 10 deletions src/dstack/_internal/server/services/logs/filelog.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from typing import List, Union
from uuid import UUID

from dstack._internal.core.errors import ServerClientError
from dstack._internal.core.models.logs import (
JobSubmissionLogs,
LogEvent,
Expand All @@ -14,7 +15,6 @@
from dstack._internal.server.schemas.runner import LogEvent as RunnerLogEvent
from dstack._internal.server.services.logs.base import (
LogStorage,
LogStorageError,
b64encode_raw_message,
unix_time_ms_to_datetime,
)
Expand All @@ -30,9 +30,6 @@ def __init__(self, root: Union[Path, str, None] = None) -> None:
self.root = Path(root)

def poll_logs(self, project: ProjectModel, request: PollLogsRequest) -> JobSubmissionLogs:
if request.descending:
raise LogStorageError("descending: true is not supported")

log_producer = LogProducer.RUNNER if request.diagnose else LogProducer.JOB
log_file_path = self._get_log_file_path(
project_name=project.name,
Expand All @@ -46,11 +43,11 @@ def poll_logs(self, project: ProjectModel, request: PollLogsRequest) -> JobSubmi
try:
start_line = int(request.next_token)
if start_line < 0:
raise LogStorageError(
raise ServerClientError(
f"Invalid next_token: {request.next_token}. Must be a non-negative integer."
)
except ValueError:
raise LogStorageError(
raise ServerClientError(
f"Invalid next_token: {request.next_token}. Must be a valid integer."
)

Expand All @@ -59,9 +56,12 @@ def poll_logs(self, project: ProjectModel, request: PollLogsRequest) -> JobSubmi
current_line = 0

try:
# FIXME: Do not read all the lines in memory
with open(log_file_path) as f:
lines = f.readlines()

except FileNotFoundError:
pass
else:
for i, line in enumerate(lines):
if current_line < start_line:
current_line += 1
Expand All @@ -83,9 +83,6 @@ def poll_logs(self, project: ProjectModel, request: PollLogsRequest) -> JobSubmi
next_token = str(current_line)
break

except IOError as e:
raise LogStorageError(f"Failed to read log file {log_file_path}: {e}")

return JobSubmissionLogs(logs=logs, next_token=next_token)

def write_logs(
Expand Down
42 changes: 6 additions & 36 deletions src/tests/_internal/server/services/test_logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pydantic import ValidationError
from sqlalchemy.ext.asyncio import AsyncSession

from dstack._internal.core.errors import ServerClientError
from dstack._internal.core.models.logs import LogEvent, LogEventSource
from dstack._internal.server.models import ProjectModel
from dstack._internal.server.schemas.logs import PollLogsRequest
Expand Down Expand Up @@ -206,49 +207,22 @@ async def test_poll_logs_invalid_next_token_raises_error(
limit=10,
diagnose=True,
)
with pytest.raises(
LogStorageError, match="Invalid next_token: invalid. Must be a valid integer."
):
with pytest.raises(ServerClientError):
log_storage.poll_logs(project, poll_request)

# Test with negative next_token
poll_request.next_token = "-1"
with pytest.raises(
LogStorageError, match="Invalid next_token: -1. Must be a non-negative integer."
):
with pytest.raises(ServerClientError):
log_storage.poll_logs(project, poll_request)

# Test with float next_token
poll_request.next_token = "1.5"
with pytest.raises(
LogStorageError, match="Invalid next_token: 1.5. Must be a valid integer."
):
with pytest.raises(ServerClientError):
log_storage.poll_logs(project, poll_request)

@pytest.mark.asyncio
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
async def test_poll_logs_descending_raises_error(
self, test_db, session: AsyncSession, tmp_path: Path
):
project = await create_project(session=session)
log_storage = FileLogStorage(tmp_path)

# Test that descending=True raises LogStorageError
poll_request = PollLogsRequest(
run_name="test_run",
job_submission_id=UUID("1b0e1b45-2f8c-4ab6-8010-a0d1a3e44e0e"),
limit=10,
diagnose=True,
# Note: This bypasses schema validation for testing the implementation
)
poll_request.descending = True # Set directly to bypass validation

with pytest.raises(LogStorageError, match="descending: true is not supported"):
log_storage.poll_logs(project, poll_request)

@pytest.mark.asyncio
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
async def test_poll_logs_file_not_found_raises_error(
async def test_poll_logs_file_not_found_raises_no_error(
self, test_db, session: AsyncSession, tmp_path: Path
):
project = await create_project(session=session)
Expand All @@ -261,11 +235,7 @@ async def test_poll_logs_file_not_found_raises_error(
limit=10,
diagnose=True,
)

with pytest.raises(
LogStorageError, match="Failed to read log file .* No such file or directory"
):
log_storage.poll_logs(project, poll_request)
log_storage.poll_logs(project, poll_request)

@pytest.mark.asyncio
@pytest.mark.parametrize("test_db", ["sqlite", "postgres"], indirect=True)
Expand Down
Loading