Skip to content

Commit c01bddf

Browse files
committed
Add demo logging feature and enhance structured logging system
- Introduced a new demo_logging.py file to showcase the logging capabilities of the TaskLogger, including navigation, interaction, and data extraction phases. - Enhanced the structured logging system with color-coded log levels, task states, and visual separators for better readability. - Implemented error handling improvements with smart retry logic and structured error logging. - Added support for detailed performance metrics and progress tracking in task execution. - Updated README.md to reflect the new logging features and improvements in the logging system. - Introduced new utility functions for structured logging and error handling in the src/utils directory. - Added comprehensive tests for the new logging features and error handling mechanisms.
1 parent 8c0118a commit c01bddf

15 files changed

+2962
-24
lines changed

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,40 @@ playwright install
257257

258258
- [x] **2025/01/10:** Thanks to @casistack. Now we have Docker Setup option and also Support keep browser open between tasks.[Video tutorial demo](https://github.com/browser-use/web-ui/issues/1#issuecomment-2582511750).
259259
- [x] **2025/01/06:** Thanks to @richard-devbot. A New and Well-Designed WebUI is released. [Video tutorial demo](https://github.com/warmshao/browser-use-webui/issues/1#issuecomment-2573393113).
260+
261+
## Fork Information
262+
This is a fork of the original browser-use project with additional features and improvements.
263+
264+
## Changelog
265+
266+
### January 2025 - Logging System Overhaul
267+
- **Enhanced Logging System**
268+
- Added structured task logging with context and state tracking
269+
- Implemented color-coded log levels and task states
270+
- Added visual separators between task phases
271+
- Introduced emoji indicators for different action types (🌐 navigation, 🖱️ interaction, 📑 extraction)
272+
- Added status symbols for task states (→ running, ✓ complete, × failed)
273+
274+
- **Error Handling Improvements**
275+
- Implemented smart retry logic with exponential backoff
276+
- Added structured error logging with context
277+
- Introduced visual error separators
278+
- Added retry history and statistics tracking
279+
280+
- **Progress Tracking**
281+
- Added percentage-based progress tracking
282+
- Implemented step duration tracking
283+
- Added detailed browser state information
284+
- Introduced performance metrics breakdown
285+
286+
- **Log Management**
287+
- Added semantic step descriptions
288+
- Implemented message filtering and deduplication
289+
- Added support for both JSON and human-readable output
290+
- Introduced custom color schemes and formatting options
291+
292+
### Coming Soon
293+
- Log buffering for high-frequency events
294+
- Recovery suggestions for common errors
295+
- Real-time monitoring dashboard
296+
- Interactive log viewer interface

demo_logging.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import asyncio
2+
from src.utils.task_logging import (
3+
TaskLogger, TaskStatus, ActionType, RetryConfig,
4+
ColorScheme, SeparatorStyle
5+
)
6+
7+
async def demo_logging():
8+
# Initialize logger with custom styles
9+
logger = TaskLogger(
10+
"demo_task",
11+
"Demonstrate all logging features",
12+
color_scheme=ColorScheme(),
13+
separator_style=SeparatorStyle(
14+
task="★" * 40,
15+
phase="•" * 30,
16+
error="!" * 35
17+
)
18+
)
19+
20+
# Start navigation phase
21+
logger.start_phase("Navigation Phase")
22+
logger.update_step(
23+
"Navigate to example.com",
24+
TaskStatus.RUNNING,
25+
action_type=ActionType.NAVIGATION,
26+
context={"url": "https://example.com"}
27+
)
28+
29+
# Update browser state
30+
logger.update_browser_state(
31+
url="https://example.com",
32+
page_ready=True,
33+
dynamic_content_loaded=True,
34+
visible_elements=15,
35+
page_title="Example Domain"
36+
)
37+
38+
# Complete navigation
39+
logger.update_step(
40+
"Page loaded successfully",
41+
TaskStatus.COMPLETE,
42+
action_type=ActionType.NAVIGATION,
43+
progress=0.25,
44+
results={"status": 200, "load_time": 0.5}
45+
)
46+
47+
# Start interaction phase
48+
logger.start_phase("Interaction Phase")
49+
logger.update_step(
50+
"Click search button",
51+
TaskStatus.RUNNING,
52+
action_type=ActionType.INTERACTION,
53+
context={"element": "search_button"}
54+
)
55+
56+
# Simulate error and retry
57+
async def failing_operation():
58+
raise ValueError("Search button not found")
59+
60+
try:
61+
await logger.execute_with_retry(
62+
failing_operation,
63+
"click_search",
64+
RetryConfig(max_retries=2, base_delay=0.1)
65+
)
66+
except ValueError:
67+
pass
68+
69+
# Start extraction phase
70+
logger.start_phase("Data Extraction Phase")
71+
logger.update_step(
72+
"Extract search results",
73+
TaskStatus.RUNNING,
74+
action_type=ActionType.EXTRACTION,
75+
progress=0.75
76+
)
77+
78+
# Complete extraction
79+
logger.update_step(
80+
"Data extracted successfully",
81+
TaskStatus.COMPLETE,
82+
action_type=ActionType.EXTRACTION,
83+
progress=1.0,
84+
results={"items_found": 10}
85+
)
86+
87+
# Display log history
88+
print("\nLog History:")
89+
print("=" * 80)
90+
for entry in logger.get_log_history():
91+
print(entry)
92+
print("=" * 80)
93+
94+
# Log final state
95+
print("\nFinal State:")
96+
logger.log_state()
97+
98+
if __name__ == "__main__":
99+
asyncio.run(demo_logging())

src/agent/custom_agent.py

Lines changed: 102 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
import logging
99
import pdb
1010
import traceback
11-
from typing import Optional, Type
11+
from typing import Optional, Type, Any, Dict
1212
from PIL import Image, ImageDraw, ImageFont
1313
import os
1414
import base64
1515
import io
16+
import datetime
1617

1718
from browser_use.agent.prompts import SystemPrompt
1819
from browser_use.agent.service import Agent
@@ -37,11 +38,13 @@
3738
BaseMessage,
3839
)
3940
from src.utils.agent_state import AgentState
41+
from src.utils.logging import BatchedEventLogger
4042

4143
from .custom_massage_manager import CustomMassageManager
4244
from .custom_views import CustomAgentOutput, CustomAgentStepInfo
4345

4446
logger = logging.getLogger(__name__)
47+
batched_logger = BatchedEventLogger(logger)
4548

4649

4750
class CustomAgent(Agent):
@@ -117,23 +120,41 @@ def _setup_action_models(self) -> None:
117120
self.AgentOutput = CustomAgentOutput.type_with_custom_actions(self.ActionModel)
118121

119122
def _log_response(self, response: CustomAgentOutput) -> None:
120-
"""Log the model's response"""
121-
if "Success" in response.current_state.prev_action_evaluation:
122-
emoji = "✅"
123-
elif "Failed" in response.current_state.prev_action_evaluation:
124-
emoji = "❌"
125-
else:
126-
emoji = "🤷"
127-
128-
logger.info(f"{emoji} Eval: {response.current_state.prev_action_evaluation}")
129-
logger.info(f"🧠 New Memory: {response.current_state.important_contents}")
130-
logger.info(f"⏳ Task Progress: {response.current_state.completed_contents}")
131-
logger.info(f"🤔 Thought: {response.current_state.thought}")
132-
logger.info(f"🎯 Summary: {response.current_state.summary}")
123+
"""Log the model's response in a structured format"""
124+
evaluation_status = "success" if "Success" in response.current_state.prev_action_evaluation else "failed"
125+
126+
log_data = {
127+
"timestamp": datetime.datetime.now().isoformat(),
128+
"action": "model_response",
129+
"status": evaluation_status,
130+
"state": {
131+
"evaluation": response.current_state.prev_action_evaluation,
132+
"memory": response.current_state.important_contents,
133+
"progress": response.current_state.completed_contents,
134+
"thought": response.current_state.thought,
135+
"summary": response.current_state.summary
136+
}
137+
}
138+
139+
logger.info(
140+
f"Model Response: {evaluation_status}",
141+
extra={
142+
"event_type": "model_response",
143+
"event_data": log_data
144+
}
145+
)
146+
147+
# Batch action logging
133148
for i, action in enumerate(response.action):
134-
logger.info(
135-
f"🛠️ Action {i + 1}/{len(response.action)}: {action.model_dump_json(exclude_unset=True)}"
149+
batched_logger.add_event(
150+
"action",
151+
{
152+
"action_number": i + 1,
153+
"total_actions": len(response.action),
154+
"action_data": json.loads(action.model_dump_json(exclude_unset=True))
155+
}
136156
)
157+
batched_logger.flush()
137158

138159
def update_step_info(
139160
self, model_output: CustomAgentOutput, step_info: CustomAgentStepInfo = None
@@ -193,7 +214,19 @@ async def get_next_action(self, input_messages: list[BaseMessage]) -> AgentOutpu
193214
@time_execution_async("--step")
194215
async def step(self, step_info: Optional[CustomAgentStepInfo] = None) -> None:
195216
"""Execute one step of the task"""
196-
logger.info(f"\n📍 Step {self.n_steps}")
217+
step_data = {
218+
"step_number": self.n_steps,
219+
"timestamp": datetime.datetime.now().isoformat()
220+
}
221+
222+
logger.info(
223+
f"Starting step {self.n_steps}",
224+
extra={
225+
"event_type": "step_start",
226+
"event_data": step_data
227+
}
228+
)
229+
197230
state = None
198231
model_output = None
199232
result: list[ActionResult] = []
@@ -204,9 +237,18 @@ async def step(self, step_info: Optional[CustomAgentStepInfo] = None) -> None:
204237
input_messages = self.message_manager.get_messages()
205238
model_output = await self.get_next_action(input_messages)
206239
self.update_step_info(model_output, step_info)
207-
logger.info(f"🧠 All Memory: {step_info.memory}")
240+
241+
if step_info:
242+
logger.debug(
243+
"Step memory updated",
244+
extra={
245+
"event_type": "memory_update",
246+
"event_data": {"memory": step_info.memory}
247+
}
248+
)
249+
208250
self._save_conversation(input_messages, model_output)
209-
self.message_manager._remove_last_state_message() # we dont want the whole state in the chat history
251+
self.message_manager._remove_last_state_message()
210252
self.message_manager.add_model_output(model_output)
211253

212254
result: list[ActionResult] = await self.controller.multi_act(
@@ -215,17 +257,37 @@ async def step(self, step_info: Optional[CustomAgentStepInfo] = None) -> None:
215257
self._last_result = result
216258

217259
if len(result) > 0 and result[-1].is_done:
218-
logger.info(f"📄 Result: {result[-1].extracted_content}")
260+
logger.info(
261+
"Task completed",
262+
extra={
263+
"event_type": "task_complete",
264+
"event_data": {
265+
"result": result[-1].extracted_content
266+
}
267+
}
268+
)
219269

220270
self.consecutive_failures = 0
221271

222272
except Exception as e:
223273
result = self._handle_step_error(e)
224274
self._last_result = result
275+
logger.error(
276+
f"Step error: {str(e)}",
277+
extra={
278+
"event_type": "step_error",
279+
"event_data": {
280+
"error": str(e),
281+
"traceback": traceback.format_exc()
282+
}
283+
},
284+
exc_info=True
285+
)
225286

226287
finally:
227288
if not result:
228289
return
290+
229291
for r in result:
230292
if r.error:
231293
self.telemetry.capture(
@@ -234,8 +296,28 @@ async def step(self, step_info: Optional[CustomAgentStepInfo] = None) -> None:
234296
error=r.error,
235297
)
236298
)
299+
logger.error(
300+
f"Action error: {r.error}",
301+
extra={
302+
"event_type": "action_error",
303+
"event_data": {
304+
"error": r.error
305+
}
306+
}
307+
)
308+
237309
if state:
238310
self._make_history_item(model_output, state, result)
311+
312+
step_data["status"] = "completed"
313+
logger.info(
314+
f"Step {self.n_steps} completed",
315+
extra={
316+
"event_type": "step_complete",
317+
"event_data": step_data
318+
}
319+
)
320+
239321
def create_history_gif(
240322
self,
241323
output_path: str = 'agent_history.gif',

0 commit comments

Comments
 (0)