Skip to content
Draft
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
8 changes: 4 additions & 4 deletions blastai/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ async def run(
# Get cost from agent's token cost service
await self._get_cost_from_agent(self.agent)
except Exception as e:
logger.error(f"Error running agent: {e}")
logger.error(f"Error running agent: {e}", exc_info=True)
raise
return self._history

Expand Down Expand Up @@ -288,13 +288,13 @@ async def run(
# Get cost from agent's token cost service
await self._get_cost_from_agent(self.agent)
except Exception as e:
logger.error(f"Error rerunning history: {e}")
logger.error(f"Error rerunning history: {e}", exc_info=True)
raise
return self._history

except Exception as e:
# logger.error(f"Task {self.task_id} failed: {str(e)}")
raise RuntimeError(f"Failed to execute task: {str(e)}")
logger.error(f"Task {self.task_id} failed: {str(e)}", exc_info=True)
raise RuntimeError(f"Failed to execute task: {str(e)}") from e
finally:
self._running = False

Expand Down
4 changes: 2 additions & 2 deletions blastai/resource_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ async def create_executor(
return executor

except Exception as e:
logger.error(f"Failed to create VNC executor: {e}")
logger.error(f"Failed to create VNC executor: {e}", exc_info=True)
# Clean up VNC session if it exists
if vnc_session:
try:
Expand Down Expand Up @@ -169,7 +169,7 @@ async def create_executor(
)

except Exception as e:
logger.error(f"Failed to create executor: {e}")
logger.error(f"Failed to create executor: {e}", exc_info=True)
# Clean up resources on failure (non-VNC path only)
if constraints.require_patchright and not constraints.require_human_in_loop:
try:
Expand Down
4 changes: 2 additions & 2 deletions blastai/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,8 +327,8 @@ async def get_task_result(self, task_id: str) -> Optional[AgentHistoryList]:
await self.complete_task(task_id, success=False)
return None

except Exception:
# logger.error(f"Task {task_id} failed: {e}")
except Exception as e:
logger.error(f"Task {task_id} failed: {e}", exc_info=True)
# Mark task as complete but failed
await self.complete_task(task_id, success=False)
raise
Expand Down
10 changes: 5 additions & 5 deletions blastai/server_api_chat_completions.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,10 @@ async def handle_chat_completions(request: ChatCompletionRequest, engine: Engine
result = await engine.run(tasks, cache_control=cache_controls, mode="stream")
return StreamingResponse(format_chat_stream(result, request.model), media_type="text/event-stream")
except asyncio.TimeoutError:
logger.error("Task failed in stream_task_events: timeout")
logger.error("Task failed in stream_task_events: timeout", exc_info=True)
raise HTTPException(status_code=504, detail="Request timed out")
except Exception as e:
logger.error(f"Task failed in stream_task_events: {e}")
logger.error(f"Task failed in stream_task_events: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
else:
try:
Expand All @@ -207,13 +207,13 @@ async def handle_chat_completions(request: ChatCompletionRequest, engine: Engine
}
)
except asyncio.TimeoutError:
logger.error("Task failed in get_task_result: timeout")
logger.error("Task failed in get_task_result: timeout", exc_info=True)
raise HTTPException(status_code=504, detail="Request timed out")
except Exception as e:
logger.error(f"Task failed in get_task_result: {e}")
logger.error(f"Task failed in get_task_result: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
except Exception as e:
logger.error(f"Task failed: {e}")
logger.error(f"Task failed: {e}", exc_info=True)
if isinstance(e, HTTPException):
raise e
raise HTTPException(status_code=500, detail=str(e))
16 changes: 8 additions & 8 deletions blastai/server_api_realtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,12 @@ def from_task_result(cls, result: AgentHistoryListResponse) -> "RealtimeMessage"
else:
data["final_result"] = "Task completed"
except Exception as e:
logger.error(f"Error extracting final result: {e}")
logger.error(f"Error extracting final result: {e}", exc_info=True)
data["final_result"] = "Task completed with an error"

return cls(type=MessageType.TASK_RESULT, data=data)
except Exception as e:
logger.error(f"Error creating task result message: {e}")
logger.error(f"Error creating task result message: {e}", exc_info=True)
# Return a fallback error message
return cls(type=MessageType.ERROR, data={"error": f"Failed to process task result: {str(e)}"})

Expand Down Expand Up @@ -234,7 +234,7 @@ async def forward_engine_events(self):
self.current_task_id = None
break
except Exception as e:
logger.error(f"Error sending task result: {e}")
logger.error(f"Error sending task result: {e}", exc_info=True)
await self.websocket.send_json(
RealtimeMessage.error(f"Error processing task result: {str(e)}").model_dump()
)
Expand All @@ -246,7 +246,7 @@ async def forward_engine_events(self):
await self.websocket.send_json(RealtimeMessage.from_human_request(event).model_dump())

except Exception as e:
logger.error(f"Error forwarding engine events: {e}")
logger.error(f"Error forwarding engine events: {e}", exc_info=True)
await self.websocket.send_json(RealtimeMessage.error(str(e)).model_dump())

async def cleanup(self):
Expand Down Expand Up @@ -435,7 +435,7 @@ async def handle_realtime_connection(websocket: WebSocket, engine: Engine, conne
# Start forwarding in background
asyncio.create_task(connection.forward_engine_events())
except Exception as e:
logger.error(f"Error executing task: {str(e)}")
logger.error(f"Error executing task: {str(e)}", exc_info=True)
raise

elif message.type == MessageType.STOP:
Expand Down Expand Up @@ -474,7 +474,7 @@ async def handle_realtime_connection(websocket: WebSocket, engine: Engine, conne
raise

except Exception as e:
logger.error(f"Error processing stop request: {e}")
logger.error(f"Error processing stop request: {e}", exc_info=True)
try:
await websocket.send_json(
RealtimeMessage.error(f"Error stopping task: {str(e)}").model_dump()
Expand Down Expand Up @@ -528,7 +528,7 @@ async def handle_realtime_connection(websocket: WebSocket, engine: Engine, conne
await websocket.send_json(RealtimeMessage.error(str(e)).model_dump())
except Exception as e:
# Handle other errors
logger.error(f"Error handling message: {e}")
logger.error(f"Error handling message: {e}", exc_info=True)
await websocket.send_json(RealtimeMessage.error(str(e)).model_dump())

except WebSocketDisconnect:
Expand Down Expand Up @@ -571,7 +571,7 @@ async def delayed_cleanup():
del connections[connection_id]

except Exception as e:
logger.error(f"Error in WebSocket connection: {e}")
logger.error(f"Error in WebSocket connection: {e}", exc_info=True)
try:
await websocket.send_json(RealtimeMessage.error(str(e)).model_dump())
except:
Expand Down
96 changes: 96 additions & 0 deletions tests/test_error_logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""Tests for error logging improvements."""

import logging
import pytest
from unittest.mock import patch, MagicMock
from blastai.scheduler import Scheduler
from blastai.executor import Executor
from blastai.config import Constraints, Settings
from blastai.planner import Planner
from blastai.cache import CacheManager


@pytest.mark.asyncio
async def test_scheduler_exception_logging():
"""Test that scheduler logs exceptions with stack traces."""
# Create a mock logger
with patch('blastai.scheduler.logger') as mock_logger:
# Create scheduler with minimal setup
constraints = Constraints()
planner = Planner(constraints)
cache_manager = CacheManager(instance_hash="test", persist=False, constraints=constraints)
scheduler = Scheduler(constraints=constraints, cache_manager=cache_manager, planner=planner)
cache_manager.load(scheduler)

# Schedule a task
task_id = scheduler.schedule_task("test task", cache_control="")
task = scheduler.tasks[task_id]

# Create a mock executor that raises an exception
import asyncio

async def failing_run(*args, **kwargs):
raise RuntimeError("Test LLM error")

mock_executor = MagicMock()
mock_executor.run = failing_run
task.executor = mock_executor

# Create a mock task
task.executor_run_task = asyncio.create_task(mock_executor.run())

# Try to get result - should raise and log with exc_info
with pytest.raises(RuntimeError):
await scheduler.get_task_result(task_id)

# Verify that logger.error was called with exc_info=True
mock_logger.error.assert_called()
# Check that exc_info=True was passed
call_args = mock_logger.error.call_args
assert call_args is not None
assert call_args.kwargs.get('exc_info') is True, "exc_info=True should be passed to logger.error"


@pytest.mark.asyncio
async def test_executor_exception_logging():
"""Test that executor logs exceptions with stack traces."""
with patch('blastai.executor.logger') as mock_logger:
# Create minimal executor setup
from browser_use.browser import BrowserSession
from browser_use import Controller

# Mock the browser session and LLM
mock_browser = MagicMock(spec=BrowserSession)
mock_llm = MagicMock()
mock_controller = MagicMock(spec=Controller)

constraints = Constraints()
settings = Settings()

executor = Executor(
browser_session=mock_browser,
controller=mock_controller,
llm=mock_llm,
constraints=constraints,
task_id="test",
settings=settings
)

# Create an async function that raises an exception
async def failing_run(*args, **kwargs):
raise RuntimeError("Test LLM connection error")

# Mock agent.run() to raise an exception
executor.agent = MagicMock()
executor.agent.run = failing_run

# Try to run - should raise and log with exc_info
with pytest.raises(RuntimeError):
await executor.run("test task")

# Verify that logger.error was called with exc_info=True
mock_logger.error.assert_called()
# Check that at least one call has exc_info=True
exc_info_calls = [call for call in mock_logger.error.call_args_list
if call.kwargs.get('exc_info') is True]
assert len(exc_info_calls) > 0, "At least one logger.error call should have exc_info=True"