diff --git a/apps/api/src/api/tool_instances.py b/apps/api/src/api/tool_instances.py index 0c56ed0..ca57f57 100644 --- a/apps/api/src/api/tool_instances.py +++ b/apps/api/src/api/tool_instances.py @@ -345,6 +345,7 @@ async def start_instance( instance.status = "building" await session.commit() + logger.info("Starting instance %s (name=%s)", instance.id, instance.name) # Fetch tool configs for this tool type env_vars = {} @@ -359,6 +360,7 @@ async def start_instance( config_result = await session.execute(config_query) configs = config_result.scalars().all() + logger.info("Found %d tool configs for instance %s", len(configs), instance.id) for config in configs: if config.config_type == "env": @@ -372,18 +374,24 @@ async def start_instance( if env_vars: env_file_path = write_env_file(instance_dir, env_vars) + logger.info("Wrote env file for instance %s: %s", instance.id, env_file_path) if config_files: write_config_files(instance_dir, config_files) + logger.info("Wrote %d config files for instance %s", len(config_files), instance.id) # Execute docker compose up with env file + logger.info("Running docker compose up for instance %s (compose_path=%s)", instance.id, instance.compose_path) returncode, stdout, stderr = execute_compose_command( instance.compose_path, "up", env_file=env_file_path ) + logger.info("Docker compose up completed for instance %s: returncode=%d, stdout=%s, stderr=%s", + instance.id, returncode, stdout[:200] if stdout else "", stderr[:500] if stderr else "") if returncode != 0: instance.status = "error" await session.commit() + logger.error("Failed to start instance %s: %s", instance.id, stderr) raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"failed to start instance: {stderr}", @@ -393,21 +401,28 @@ async def start_instance( container_id = get_container_id(instance.name) if container_id: instance.container_id = container_id + logger.info("Container ID for instance %s: %s", instance.id, container_id) container_name = get_container_name(instance.name) if container_name: instance.container_name = container_name + logger.info("Container name for instance %s: %s", instance.id, container_name) instance.status = "running" instance.last_started_at = datetime.now() await session.commit() + logger.info("Instance %s is now running", instance.id) # Get tool type for default port tool_type = await session.get(ToolType, instance.tool_type_id) instance_port = tool_type.default_port if tool_type and tool_type.default_port else 8080 + logger.info("Tool type for instance %s: name=%s, default_port=%s", + instance.id, tool_type.name if tool_type else "unknown", instance_port) # Create Cloudflare tunnel for public access try: + logger.info("Creating Cloudflare tunnel for instance %s (name=%s, port=%d)", + instance.id, instance.name, instance_port) tunnel_info = await create_tunnel( instance_name=instance.name, instance_id=str(instance.id), @@ -418,12 +433,13 @@ async def start_instance( instance.url = tunnel_info["public_url"] await session.commit() logger.info( - "Created tunnel for instance %s: %s", + "Created tunnel for instance %s: tunnel_id=%s, url=%s", instance.id, + tunnel_info["tunnel_id"], tunnel_info["public_url"], ) except Exception as exc: - logger.warning( + logger.exception( "Failed to create tunnel for instance %s: %s. Falling back to proxy URL.", instance.id, exc, diff --git a/apps/api/src/services/cloudflare_tunnel.py b/apps/api/src/services/cloudflare_tunnel.py index 0810dec..389f033 100644 --- a/apps/api/src/services/cloudflare_tunnel.py +++ b/apps/api/src/services/cloudflare_tunnel.py @@ -45,11 +45,17 @@ async def create_tunnel( if not settings.cloudflare_api_token: raise ValueError("CLOUDFLARE_API_TOKEN not configured") + logger.info("Creating Cloudflare tunnel for instance_name=%s, instance_id=%s, port=%d", + instance_name, instance_id, instance_port) + logger.info("Cloudflare config: account_id=%s, zone_id=%s, base_domain=%s", + settings.cloudflare_account_id, settings.cloudflare_zone_id, settings.cloudflare_base_domain) + headers = _get_headers(settings) account_id = settings.cloudflare_account_id # Create tunnel async with httpx.AsyncClient() as client: + logger.info("Step 1: Creating tunnel via Cloudflare API...") response = await client.post( f"{CLOUDFLARE_API_BASE}/accounts/{account_id}/cfd_tunnel", headers=headers, @@ -58,6 +64,7 @@ async def create_tunnel( "config_src": "cloudflare", }, ) + logger.info("Tunnel creation response: status=%d, body=%s", response.status_code, response.text[:500]) response.raise_for_status() data = response.json() @@ -66,19 +73,24 @@ async def create_tunnel( tunnel = data["result"] tunnel_id = tunnel["id"] + logger.info("Step 1 complete: tunnel_id=%s", tunnel_id) # Get tunnel token + logger.info("Step 2: Getting tunnel token...") token_response = await client.get( f"{CLOUDFLARE_API_BASE}/accounts/{account_id}/cfd_tunnel/{tunnel_id}/token", headers=headers, ) + logger.info("Token response: status=%d", token_response.status_code) token_response.raise_for_status() token_data = token_response.json() tunnel_token = token_data["result"] + logger.info("Step 2 complete: got tunnel token") # Create DNS record for the tunnel subdomain = f"instance-{instance_id[:8]}" hostname = f"{subdomain}.{settings.cloudflare_base_domain}" + logger.info("Step 3: Creating DNS record for subdomain=%s, hostname=%s", subdomain, hostname) dns_response = await client.post( f"{CLOUDFLARE_API_BASE}/zones/{settings.cloudflare_zone_id}/dns_records", @@ -91,9 +103,12 @@ async def create_tunnel( "proxied": True, }, ) + logger.info("DNS response: status=%d, body=%s", dns_response.status_code, dns_response.text[:500]) dns_response.raise_for_status() + logger.info("Step 3 complete: DNS record created") # Update cloudflared config + logger.info("Step 4: Updating cloudflared config...") await update_cloudflared_config( tunnel_id=tunnel_id, tunnel_token=tunnel_token, @@ -102,7 +117,9 @@ async def create_tunnel( instance_port=instance_port, settings=settings, ) + logger.info("Step 4 complete: cloudflared config updated") + logger.info("Tunnel creation complete: tunnel_id=%s, public_url=https://%s", tunnel_id, hostname) return { "tunnel_id": tunnel_id, "public_url": f"https://{hostname}",