From 4e959694a1d01de4070ada2eb92edbbdff311e83 Mon Sep 17 00:00:00 2001 From: Martin Xu <15661672+martinxu9@users.noreply.github.com> Date: Fri, 10 May 2024 10:17:10 -0700 Subject: [PATCH] enable telemetry for frontend package download subprocess (#3270) --- reflex/utils/prerequisites.py | 6 ++++ reflex/utils/processes.py | 55 ++++++++++++++++++++++++++++++++--- reflex/utils/telemetry.py | 10 +++---- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index cb83de76c..3d13e2bc4 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -848,6 +848,8 @@ def install_frontend_packages(packages: set[str], config: Config): processes.run_process_with_fallback( [get_install_package_manager(), "install"], # type: ignore fallback=fallback_command, + analytics_enabled=True, + error_filter_fn=lambda output: "404" in output, show_status_message="Installing base frontend packages", cwd=constants.Dirs.WEB, shell=constants.IS_WINDOWS, @@ -863,6 +865,8 @@ def install_frontend_packages(packages: set[str], config: Config): *((config.tailwind or {}).get("plugins", [])), ], fallback=fallback_command, + analytics_enabled=True, + error_filter_fn=lambda output: "404" in output, show_status_message="Installing tailwind", cwd=constants.Dirs.WEB, shell=constants.IS_WINDOWS, @@ -873,6 +877,8 @@ def install_frontend_packages(packages: set[str], config: Config): processes.run_process_with_fallback( [get_install_package_manager(), "add", *packages], fallback=fallback_command, + analytics_enabled=True, + error_filter_fn=lambda output: "404" in output, show_status_message="Installing frontend packages from config and components", cwd=constants.Dirs.WEB, shell=constants.IS_WINDOWS, diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index f63e1c709..c42b8b277 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -211,6 +211,8 @@ def stream_logs( process: subprocess.Popen, progress=None, suppress_errors: bool = False, + analytics_enabled: bool = False, + error_filter_fn: Callable[[str], bool] | None = None, ): """Stream the logs for a process. @@ -219,6 +221,8 @@ def stream_logs( process: The process. progress: The ongoing progress bar if one is being used. suppress_errors: If True, do not exit if errors are encountered (for fallback). + analytics_enabled: Whether analytics are enabled for this command. + error_filter_fn: A function that takes a line of output and returns True if the line should be considered an error, False otherwise. If None, all lines are considered errors. Yields: The lines of the process output. @@ -226,6 +230,8 @@ def stream_logs( Raises: Exit: If the process failed. """ + from reflex.utils import telemetry + # Store the tail of the logs. logs = collections.deque(maxlen=512) with process: @@ -246,6 +252,16 @@ def stream_logs( console.error(f"{message} failed with exit code {process.returncode}") for line in logs: console.error(line, end="") + if analytics_enabled: + error_lines = [ + line.strip() + for line in logs + if error_filter_fn is None or error_filter_fn(line) + ] + max_error_lines = 20 + telemetry.send( + "error", context=message, detail=error_lines[:max_error_lines] + ) console.error("Run with [bold]--loglevel debug [/bold] for the full log.") raise typer.Exit(1) @@ -261,16 +277,30 @@ def show_logs(message: str, process: subprocess.Popen): pass -def show_status(message: str, process: subprocess.Popen, suppress_errors: bool = False): +def show_status( + message: str, + process: subprocess.Popen, + suppress_errors: bool = False, + analytics_enabled: bool = False, + error_filter_fn: Callable[[str], bool] | None = None, +): """Show the status of a process. Args: message: The initial message to display. process: The process. suppress_errors: If True, do not exit if errors are encountered (for fallback). + analytics_enabled: Whether analytics are enabled for this command. + error_filter_fn: A function that takes a line of output and returns True if the line should be considered an error, False otherwise. If None, all lines are considered errors. """ with console.status(message) as status: - for line in stream_logs(message, process, suppress_errors=suppress_errors): + for line in stream_logs( + message, + process, + suppress_errors=suppress_errors, + analytics_enabled=analytics_enabled, + error_filter_fn=error_filter_fn, + ): status.update(f"{message} {line}") @@ -319,19 +349,34 @@ def get_command_with_loglevel(command: list[str]) -> list[str]: return command -def run_process_with_fallback(args, *, show_status_message, fallback=None, **kwargs): +def run_process_with_fallback( + args, + *, + show_status_message, + fallback=None, + analytics_enabled: bool = False, + error_filter_fn: Callable[[str], bool] | None = None, + **kwargs, +): """Run subprocess and retry using fallback command if initial command fails. Args: args: A string, or a sequence of program arguments. show_status_message: The status message to be displayed in the console. fallback: The fallback command to run. + analytics_enabled: Whether analytics are enabled for this command. + error_filter_fn: A function that takes a line of output and returns True if the line should be considered an error, False otherwise. If None, all lines are considered errors. kwargs: Kwargs to pass to new_process function. """ process = new_process(get_command_with_loglevel(args), **kwargs) if fallback is None: # No fallback given, or this _is_ the fallback command. - show_status(show_status_message, process) + show_status( + show_status_message, + process, + analytics_enabled=analytics_enabled, + error_filter_fn=error_filter_fn, + ) else: # Suppress errors for initial command, because we will try to fallback show_status(show_status_message, process, suppress_errors=True) @@ -345,6 +390,8 @@ def run_process_with_fallback(args, *, show_status_message, fallback=None, **kwa fallback_args, show_status_message=show_status_message, fallback=None, + analytics_enabled=analytics_enabled, + error_filter_fn=error_filter_fn, **kwargs, ) diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index e38c4fe79..f7398f01a 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -126,6 +126,10 @@ def _prepare_event(event: str, **kwargs) -> dict: cpuinfo = get_cpu_info() + additional_keys = ["template", "context", "detail"] + additional_fields = { + key: value for key in additional_keys if (value := kwargs.get(key)) is not None + } return { "api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb", "event": event, @@ -139,11 +143,7 @@ def _prepare_event(event: str, **kwargs) -> dict: "cpu_count": get_cpu_count(), "memory": get_memory(), "cpu_info": dict(cpuinfo) if cpuinfo else {}, - **( - {"template": template} - if (template := kwargs.get("template")) is not None - else {} - ), + **additional_fields, }, "timestamp": stamp, }