Skip to content

Commit

Permalink
Add back build log command to CLI (#2053)
Browse files Browse the repository at this point in the history
  • Loading branch information
martinxu9 authored Oct 27, 2023
1 parent a2e185c commit 23255d4
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 48 deletions.
41 changes: 33 additions & 8 deletions reflex/reflex.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ def deploy(
enabled_regions = pre_deploy_response.enabled_regions

except Exception as ex:
console.error(f"Unable to prepare deployment due to: {ex}")
console.error(f"Unable to prepare deployment")
raise typer.Exit(1) from ex

# The app prefix should not change during the time of preparation
Expand Down Expand Up @@ -572,7 +572,6 @@ def deploy(
key = key_candidate

# Then CP needs to know the user's location, which requires user permission
console.debug(f"{enabled_regions=}")
while True:
region_input = console.ask(
"Region to deploy to. Enter to use default.",
Expand Down Expand Up @@ -669,10 +668,15 @@ def deploy(

console.print("Waiting for server to report progress ...")
# Display the key events such as build, deploy, etc
asyncio.get_event_loop().run_until_complete(
server_report_deploy_success = asyncio.get_event_loop().run_until_complete(
hosting.display_deploy_milestones(key, from_iso_timestamp=deploy_requested_at)
)

if not server_report_deploy_success:
console.error("Hosting server reports failure.")
console.error(
f"Check the server logs using `reflex deployments build-logs {key}`"
)
raise typer.Exit(1)
console.print("Waiting for the new deployment to come up")
backend_up = frontend_up = False

Expand Down Expand Up @@ -741,7 +745,7 @@ def list_deployments(
try:
deployments = hosting.list_deployments()
except Exception as ex:
console.error(f"Unable to list deployments due to: {ex}")
console.error(f"Unable to list deployments")
raise typer.Exit(1) from ex

if as_json:
Expand All @@ -768,7 +772,7 @@ def delete_deployment(
try:
hosting.delete_deployment(key)
except Exception as ex:
console.error(f"Unable to delete deployment due to: {ex}")
console.error(f"Unable to delete deployment")
raise typer.Exit(1) from ex
console.print(f"Successfully deleted [ {key} ].")

Expand Down Expand Up @@ -805,7 +809,7 @@ def get_deployment_status(
table = list(frontend_status.values())
console.print(tabulate([table], headers=headers))
except Exception as ex:
console.error(f"Unable to get deployment status due to: {ex}")
console.error(f"Unable to get deployment status")
raise typer.Exit(1) from ex


Expand All @@ -822,7 +826,28 @@ def get_deployment_logs(
try:
asyncio.get_event_loop().run_until_complete(hosting.get_logs(key))
except Exception as ex:
console.error(f"Unable to get deployment logs due to: {ex}")
console.error(f"Unable to get deployment logs")
raise typer.Exit(1) from ex


@deployments_cli.command(name="build-logs")
def get_deployment_build_logs(
key: str = typer.Argument(..., help="The name of the deployment."),
loglevel: constants.LogLevel = typer.Option(
config.loglevel, help="The log level to use."
),
):
"""Get the logs for a deployment."""
console.set_log_level(loglevel)

console.print("Note: there is a few seconds delay for logs to be available.")
try:
# TODO: we need to find a way not to fetch logs
# that match the deployed app name but not previously of a different owner
# This should not happen often
asyncio.run(hosting.get_logs(key, log_type=hosting.LogType.BUILD_LOG))
except Exception as ex:
console.error(f"Unable to get deployment logs")
raise typer.Exit(1) from ex


Expand Down
87 changes: 47 additions & 40 deletions reflex/utils/hosting.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
# Expected server response time to new deployment request. In seconds.
DEPLOYMENT_PICKUP_DELAY = 30
# End of deployment workflow message. Used to determine if it is the last message from server.
END_OF_DEPLOYMENT_MESSAGES = ["deploy success", "deploy failed"]
END_OF_DEPLOYMENT_MESSAGES = ["deploy success"]
# How many iterations to try and print the deployment event messages from server during deployment.
DEPLOYMENT_EVENT_MESSAGES_RETRIES = 90
# Timeout limit for http requests
Expand Down Expand Up @@ -93,7 +93,7 @@ def validate_token(token: str):
response.raise_for_status()
except httpx.RequestError as re:
console.debug(f"Request to auth server failed due to {re}")
raise Exception("request error") from re
raise Exception(str(re)) from re
except httpx.HTTPError as ex:
console.debug(f"Unable to validate the token due to: {ex}")
raise Exception("server error") from ex
Expand Down Expand Up @@ -307,22 +307,22 @@ def prepare_deploy(
enabled_regions=response_json.get("enabled_regions"),
)
except httpx.RequestError as re:
console.debug(f"Unable to prepare launch due to {re}.")
raise Exception("request error") from re
console.error(f"Unable to prepare launch due to {re}.")
raise Exception(str(re)) from re
except httpx.HTTPError as he:
console.debug(f"Unable to prepare deploy due to {he}.")
console.error(f"Unable to prepare deploy due to {he}.")
raise Exception(f"{he}") from he
except json.JSONDecodeError as jde:
console.debug(f"Server did not respond with valid json: {jde}")
console.error(f"Server did not respond with valid json: {jde}")
raise Exception("internal errors") from jde
except (KeyError, ValidationError) as kve:
console.debug(f"The server response format is unexpected {kve}")
console.error(f"The server response format is unexpected {kve}")
raise Exception("internal errors") from kve
except ValueError as ve:
# This is a recognized client error, currently indicates forbidden
raise Exception(f"{ve}") from ve
except Exception as ex:
console.debug(f"Unexpected error: {ex}.")
console.error(f"Unexpected error: {ex}.")
raise Exception("internal errors") from ex


Expand Down Expand Up @@ -460,26 +460,26 @@ def deploy(
backend_url=response_json["backend_url"],
)
except OSError as oe:
console.debug(f"Client side error related to file operation: {oe}")
console.error(f"Client side error related to file operation: {oe}")
raise
except httpx.RequestError as re:
console.debug(f"Unable to deploy due to request error: {re}")
console.error(f"Unable to deploy due to request error: {re}")
raise Exception("request error") from re
except httpx.HTTPError as he:
console.debug(f"Unable to deploy due to {he}.")
raise Exception("internal errors") from he
console.error(f"Unable to deploy due to {he}.")
raise Exception(str) from he
except json.JSONDecodeError as jde:
console.debug(f"Server did not respond with valid json: {jde}")
console.error(f"Server did not respond with valid json: {jde}")
raise Exception("internal errors") from jde
except (KeyError, ValidationError) as kve:
console.debug(f"Post params or server response format unexpected: {kve}")
console.error(f"Post params or server response format unexpected: {kve}")
raise Exception("internal errors") from kve
except AssertionError as ve:
console.debug(f"Unable to deploy due to request error: {ve}")
console.error(f"Unable to deploy due to request error: {ve}")
# re-raise the error back to the user as client side error
raise
except Exception as ex:
console.debug(f"Unable to deploy due to internal errors: {ex}.")
console.error(f"Unable to deploy due to internal errors: {ex}.")
raise Exception("internal errors") from ex


Expand Down Expand Up @@ -552,13 +552,13 @@ def list_deployments(
for deployment in response.json()
]
except httpx.RequestError as re:
console.debug(f"Unable to list deployments due to request error: {re}")
console.error(f"Unable to list deployments due to request error: {re}")
raise Exception("request timeout") from re
except httpx.HTTPError as he:
console.debug(f"Unable to list deployments due to {he}.")
console.error(f"Unable to list deployments due to {he}.")
raise Exception("internal errors") from he
except (ValidationError, KeyError, json.JSONDecodeError) as vkje:
console.debug(f"Server response format unexpected: {vkje}")
console.error(f"Server response format unexpected: {vkje}")
raise Exception("internal errors") from vkje
except Exception as ex:
console.error(f"Unexpected error: {ex}.")
Expand All @@ -584,15 +584,15 @@ def fetch_token(request_id: str) -> tuple[str, str]:
access_token = (resp_json := resp.json()).get("access_token", "")
invitation_code = resp_json.get("code", "")
except httpx.RequestError as re:
console.debug(f"Unable to fetch token due to request error: {re}")
console.error(f"Unable to fetch token due to request error: {re}")
except httpx.HTTPError as he:
console.debug(f"Unable to fetch token due to {he}")
console.error(f"Unable to fetch token due to {he}")
except json.JSONDecodeError as jde:
console.debug(f"Server did not respond with valid json: {jde}")
console.error(f"Server did not respond with valid json: {jde}")
except KeyError as ke:
console.debug(f"Server response format unexpected: {ke}")
console.error(f"Server response format unexpected: {ke}")
except Exception:
console.debug("Unexpected errors: {ex}")
console.error("Unexpected errors: {ex}")

return access_token, invitation_code

Expand All @@ -608,7 +608,7 @@ def poll_backend(backend_url: str) -> bool:
"""
try:
console.debug(f"Polling backend at {backend_url}")
resp = httpx.get(f"{backend_url}/ping", timeout=HTTP_REQUEST_TIMEOUT)
resp = httpx.get(f"{backend_url}/ping", timeout=1)
resp.raise_for_status()
return True
except httpx.HTTPError:
Expand All @@ -626,7 +626,7 @@ def poll_frontend(frontend_url: str) -> bool:
"""
try:
console.debug(f"Polling frontend at {frontend_url}")
resp = httpx.get(f"{frontend_url}", timeout=HTTP_REQUEST_TIMEOUT)
resp = httpx.get(f"{frontend_url}", timeout=1)
resp.raise_for_status()
return True
except httpx.HTTPError:
Expand Down Expand Up @@ -664,13 +664,13 @@ def delete_deployment(key: str):
response.raise_for_status()

except httpx.TimeoutException as te:
console.debug("Unable to delete deployment due to request timeout.")
console.error("Unable to delete deployment due to request timeout.")
raise Exception("request timeout") from te
except httpx.HTTPError as he:
console.debug(f"Unable to delete deployment due to {he}.")
console.error(f"Unable to delete deployment due to {he}.")
raise Exception("internal errors") from he
except Exception as ex:
console.debug(f"Unexpected errors {ex}.")
console.error(f"Unexpected errors {ex}.")
raise Exception("internal errors") from ex


Expand Down Expand Up @@ -755,7 +755,7 @@ def get_deployment_status(key: str) -> DeploymentStatusResponse:
),
)
except Exception as ex:
console.debug(f"Unable to get deployment status due to {ex}.")
console.error(f"Unable to get deployment status due to {ex}.")
raise Exception("internal errors") from ex


Expand All @@ -772,7 +772,7 @@ def convert_to_local_time(iso_timestamp: str) -> str:
local_dt = datetime.fromisoformat(iso_timestamp).astimezone()
return local_dt.strftime("%Y-%m-%d %H:%M:%S.%f %Z")
except Exception as ex:
console.debug(f"Unable to convert iso timestamp {iso_timestamp} due to {ex}.")
console.error(f"Unable to convert iso timestamp {iso_timestamp} due to {ex}.")
return iso_timestamp


Expand Down Expand Up @@ -1041,7 +1041,7 @@ def log_out_on_browser():
)


async def display_deploy_milestones(key: str, from_iso_timestamp: datetime):
async def display_deploy_milestones(key: str, from_iso_timestamp: datetime) -> bool:
"""Display the deploy milestone messages reported back from the hosting server.
Args:
Expand All @@ -1051,6 +1051,9 @@ async def display_deploy_milestones(key: str, from_iso_timestamp: datetime):
Raises:
ValueError: If a non-empty key is not provided.
Exception: If the user is not authenticated.
Returns:
False if server reports back failure, True otherwise.
"""
if not key:
raise ValueError("Non-empty key is required for querying deploy status.")
Expand All @@ -1076,18 +1079,22 @@ async def display_deploy_milestones(key: str, from_iso_timestamp: datetime):
]
)
)
if any(
msg in row_json["message"].lower()
for msg in END_OF_DEPLOYMENT_MESSAGES
):
server_message = row_json["message"].lower()
if "fail" in server_message:
console.debug(
"Received failure message, stop event message streaming"
)
return False
if any(msg in server_message for msg in END_OF_DEPLOYMENT_MESSAGES):
console.debug(
"Received end of deployment message, stop event message streaming"
)
return
return True
else:
console.debug("Server responded, no new events yet, this is normal")
except Exception as ex:
console.debug(f"Unable to get more deployment events due to {ex}.")
return False


def wait_for_server_to_pick_up_request():
Expand Down Expand Up @@ -1141,16 +1148,16 @@ def get_regions() -> list[dict]:
response.raise_for_status()
response_json = response.json()
if response_json is None or not isinstance(response_json, list):
console.debug("Expect server to return a list ")
console.error("Expect server to return a list ")
return []
if (
response_json
and response_json[0] is not None
and not isinstance(response_json[0], dict)
):
console.debug("Expect return values are dict's")
console.error("Expect return values are dict's")
return []
return response_json
except Exception as ex:
console.debug(f"Unable to get regions due to {ex}.")
console.error(f"Unable to get regions due to {ex}.")
return []

0 comments on commit 23255d4

Please sign in to comment.