diff --git a/astrbot/core/computer/booters/boxlite.py b/astrbot/core/computer/booters/boxlite.py index 55518bfd4..01a29fa8c 100644 --- a/astrbot/core/computer/booters/boxlite.py +++ b/astrbot/core/computer/booters/boxlite.py @@ -71,7 +71,7 @@ class MockShipyardSandboxClient: async with session.post(url, data=data) as response: if response.status == 200: logger.info( - "[Computer] File uploaded to Boxlite sandbox: %s", + "[Computer] file_upload booter=boxlite remote_path=%s", remote_path, ) return { @@ -81,6 +81,11 @@ class MockShipyardSandboxClient: } else: error_text = await response.text() + logger.warning( + "[Computer] file_upload_failed booter=boxlite error=http_status status=%s remote_path=%s", + response.status, + remote_path, + ) return { "success": False, "error": f"Server returned {response.status}: {error_text}", @@ -88,30 +93,39 @@ class MockShipyardSandboxClient: } except aiohttp.ClientError as e: - logger.error(f"Failed to upload file: {e}") + logger.error("[Computer] file_upload_failed booter=boxlite error=%s", e) return { "success": False, "error": f"Connection error: {str(e)}", "message": "File upload failed", } except asyncio.TimeoutError: + logger.warning( + "[Computer] file_upload_failed booter=boxlite error=timeout remote_path=%s", + remote_path, + ) return { "success": False, "error": "File upload timeout", "message": "File upload failed", } except FileNotFoundError: - logger.error(f"File not found: {path}") + logger.error( + "[Computer] file_upload_failed booter=boxlite error=file_not_found path=%s", + path, + ) return { "success": False, "error": f"File not found: {path}", "message": "File upload failed", } - except Exception as e: - logger.error(f"Unexpected error uploading file: {e}") + except Exception as exc: + logger.exception( + "[Computer] file_upload_failed booter=boxlite error=unexpected" + ) return { "success": False, - "error": f"Internal error: {str(e)}", + "error": f"Internal error: {str(exc)}", "message": "File upload failed", } @@ -120,24 +134,42 @@ class MockShipyardSandboxClient: loop = 60 while loop > 0: try: - logger.info( - f"Checking health for sandbox {ship_id} on {self.sb_url}..." + logger.debug( + "[Computer] health_check booter=boxlite ship_id=%s session=%s endpoint=%s attempt=%s healthy=pending", + ship_id, + session_id, + self.sb_url, + 61 - loop, ) url = f"{self.sb_url}/health" async with aiohttp.ClientSession() as session: async with session.get(url) as response: if response.status == 200: - logger.info(f"Sandbox {ship_id} is healthy") - return + logger.debug( + "[Computer] health_check booter=boxlite ship_id=%s session=%s endpoint=%s healthy=true", + ship_id, + session_id, + self.sb_url, + ) + return + await asyncio.sleep(1) + loop -= 1 except Exception: await asyncio.sleep(1) loop -= 1 + logger.warning( + "[Computer] health_check_timeout booter=boxlite ship_id=%s session=%s endpoint=%s", + ship_id, + session_id, + self.sb_url, + ) class BoxliteBooter(ComputerBooter): async def boot(self, session_id: str) -> None: logger.info( - f"Booting(Boxlite) for session: {session_id}, this may take a while..." + "[Computer] booter_boot booter=boxlite session=%s status=starting", + session_id, ) random_port = random.randint(20000, 30000) self.box = boxlite.SimpleBox( @@ -152,7 +184,11 @@ class BoxliteBooter(ComputerBooter): ], ) await self.box.start() - logger.info(f"Boxlite booter started for session: {session_id}") + logger.info( + "[Computer] booter_boot booter=boxlite session=%s status=ready ship_id=%s", + session_id, + self.box.id, + ) self.mocked = MockShipyardSandboxClient( sb_url=f"http://127.0.0.1:{random_port}" ) @@ -175,9 +211,15 @@ class BoxliteBooter(ComputerBooter): await self.mocked.wait_healthy(self.box.id, session_id) async def shutdown(self) -> None: - logger.info(f"Shutting down Boxlite booter for ship: {self.box.id}") + logger.info( + "[Computer] booter_shutdown booter=boxlite ship_id=%s status=starting", + self.box.id, + ) self.box.shutdown() - logger.info(f"Boxlite booter for ship: {self.box.id} stopped") + logger.info( + "[Computer] booter_shutdown booter=boxlite ship_id=%s status=done", + self.box.id, + ) @property def fs(self) -> FileSystemComponent: diff --git a/astrbot/core/computer/booters/shipyard.py b/astrbot/core/computer/booters/shipyard.py index 75de9c438..12a4ce965 100644 --- a/astrbot/core/computer/booters/shipyard.py +++ b/astrbot/core/computer/booters/shipyard.py @@ -56,11 +56,15 @@ class ShipyardBooter(ComputerBooter): max_session_num=self._session_num, session_id=session_id, ) - logger.info(f"Got sandbox ship: {ship.id} for session: {session_id}") + logger.info( + "[Computer] sandbox_created booter=shipyard ship_id=%s session=%s", + ship.id, + session_id, + ) self._ship = ship async def shutdown(self) -> None: - logger.info("[Computer] Shipyard booter shutdown.") + logger.info("[Computer] booter_shutdown booter=shipyard status=done") @property def fs(self) -> FileSystemComponent: @@ -77,14 +81,17 @@ class ShipyardBooter(ComputerBooter): async def upload_file(self, path: str, file_name: str) -> dict: """Upload file to sandbox""" result = await self._ship.upload_file(path, file_name) - logger.info("[Computer] File uploaded to Shipyard sandbox: %s", file_name) + logger.info( + "[Computer] file_upload booter=shipyard remote_path=%s", + file_name, + ) return result async def download_file(self, remote_path: str, local_path: str): """Download file from sandbox.""" result = await self._ship.download_file(remote_path, local_path) logger.info( - "[Computer] File downloaded from Shipyard sandbox: %s -> %s", + "[Computer] file_download booter=shipyard remote_path=%s local_path=%s", remote_path, local_path, ) @@ -96,18 +103,21 @@ class ShipyardBooter(ComputerBooter): ship_id = self._ship.id data = await self._sandbox_client.get_ship(ship_id) if not data: - logger.info( - "[Computer] Shipyard sandbox health check: id=%s, healthy=False (no data)", + logger.debug( + "[Computer] health_check booter=shipyard ship_id=%s healthy=false reason=no_data", ship_id, ) return False health = bool(data.get("status", 0) == 1) - logger.info( - "[Computer] Shipyard sandbox health check: id=%s, healthy=%s", + logger.debug( + "[Computer] health_check booter=shipyard ship_id=%s healthy=%s", ship_id, health, ) return health - except Exception as e: - logger.error(f"Error checking Shipyard sandbox availability: {e}") + except Exception: + logger.exception( + "[Computer] health_check_failed booter=shipyard ship_id=%s", + getattr(getattr(self, "_ship", None), "id", "unknown"), + ) return False diff --git a/astrbot/core/computer/booters/shipyard_neo.py b/astrbot/core/computer/booters/shipyard_neo.py index 5048eed39..431d21391 100644 --- a/astrbot/core/computer/booters/shipyard_neo.py +++ b/astrbot/core/computer/booters/shipyard_neo.py @@ -319,14 +319,17 @@ class ShipyardNeoBooter(ComputerBooter): if self._bay_manager is not None: await self._bay_manager.close_client() - logger.info("[Computer] Neo auto-start mode: launching Bay container") + logger.info("[Computer] bay_autostart status=starting") self._bay_manager = BayContainerManager() self._endpoint_url = await self._bay_manager.ensure_running() await self._bay_manager.wait_healthy() # Read auto-provisioned credentials if not self._access_token: self._access_token = await self._bay_manager.read_credentials() - logger.info("[Computer] Bay auto-started at %s", self._endpoint_url) + logger.info( + "[Computer] bay_autostart status=ready endpoint=%s", + self._endpoint_url, + ) if not self._endpoint_url or not self._access_token: if self._bay_manager is not None: @@ -366,7 +369,7 @@ class ShipyardNeoBooter(ComputerBooter): ) logger.info( - "Got Shipyard Neo sandbox: %s (profile=%s, capabilities=%s, auto=%s)", + "[Computer] sandbox_created booter=shipyard_neo sandbox_id=%s profile=%s capabilities=%s auto=%s", self._sandbox.id, resolved_profile, list(caps), @@ -388,7 +391,10 @@ class ShipyardNeoBooter(ComputerBooter): """ # User explicitly set a profile → honour it if self._profile and self._profile != self.DEFAULT_PROFILE: - logger.info("[Computer] Using user-specified profile: %s", self._profile) + logger.info( + "[Computer] profile_selected mode=user profile=%s", + self._profile, + ) return self._profile # Query Bay for available profiles @@ -401,7 +407,7 @@ class ShipyardNeoBooter(ComputerBooter): raise # auth errors must not be silenced except Exception as exc: logger.warning( - "[Computer] Failed to query Bay profiles, falling back to %s: %s", + "[Computer] profile_selection_fallback reason=query_failed fallback=%s error=%s", self.DEFAULT_PROFILE, exc, ) @@ -421,7 +427,7 @@ class ShipyardNeoBooter(ComputerBooter): if chosen != self.DEFAULT_PROFILE: caps = getattr(best, "capabilities", []) logger.info( - "[Computer] Auto-selected profile %s (capabilities=%s)", + "[Computer] profile_selected mode=auto profile=%s capabilities=%s", chosen, caps, ) @@ -432,12 +438,16 @@ class ShipyardNeoBooter(ComputerBooter): if self._client is not None: sandbox_id = getattr(self._sandbox, "id", "unknown") logger.info( - "[Computer] Shutting down Shipyard Neo sandbox: id=%s", sandbox_id + "[Computer] booter_shutdown booter=shipyard_neo sandbox_id=%s status=starting", + sandbox_id, ) await self._client.__aexit__(None, None, None) self._client = None self._sandbox = None - logger.info("[Computer] Shipyard Neo sandbox shut down: id=%s", sandbox_id) + logger.info( + "[Computer] booter_shutdown booter=shipyard_neo sandbox_id=%s status=done", + sandbox_id, + ) # NOTE: We intentionally do NOT stop the Bay container here. # It stays running for reuse by future sessions. The user can @@ -476,7 +486,10 @@ class ShipyardNeoBooter(ComputerBooter): content = f.read() remote_path = file_name.lstrip("/") await self._sandbox.filesystem.upload(remote_path, content) - logger.info("[Computer] File uploaded to Neo sandbox: %s", remote_path) + logger.info( + "[Computer] file_upload booter=shipyard_neo remote_path=%s", + remote_path, + ) return { "success": True, "message": "File uploaded successfully", @@ -493,7 +506,7 @@ class ShipyardNeoBooter(ComputerBooter): with open(local_path, "wb") as f: f.write(cast(bytes, content)) logger.info( - "[Computer] File downloaded from Neo sandbox: %s -> %s", + "[Computer] file_download booter=shipyard_neo remote_path=%s local_path=%s", remote_path, local_path, ) @@ -505,15 +518,18 @@ class ShipyardNeoBooter(ComputerBooter): await self._sandbox.refresh() status = getattr(self._sandbox.status, "value", str(self._sandbox.status)) healthy = status not in {"failed", "expired"} - logger.info( - "[Computer] Neo sandbox health check: id=%s, status=%s, healthy=%s", + logger.debug( + "[Computer] health_check booter=shipyard_neo sandbox_id=%s status=%s healthy=%s", getattr(self._sandbox, "id", "unknown"), status, healthy, ) return healthy - except Exception as e: - logger.error(f"Error checking Shipyard Neo sandbox availability: {e}") + except Exception: + logger.exception( + "[Computer] health_check_failed booter=shipyard_neo sandbox_id=%s", + getattr(self._sandbox, "id", "unknown"), + ) return False # ── Tool / prompt self-description ────────────────────────────