Bläddra i källkod

enable telemetry for frontend package download subprocess (#3270)

Martin Xu 1 år sedan
förälder
incheckning
4e959694a1
3 ändrade filer med 62 tillägg och 9 borttagningar
  1. 6 0
      reflex/utils/prerequisites.py
  2. 51 4
      reflex/utils/processes.py
  3. 5 5
      reflex/utils/telemetry.py

+ 6 - 0
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,

+ 51 - 4
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,
             )
 

+ 5 - 5
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,
     }