Przeglądaj źródła

Catch more errors in frontend/backend (#3346)

Nikhil Rao 1 rok temu
rodzic
commit
ec72448b8b

+ 3 - 4
reflex/app.py

@@ -1068,13 +1068,12 @@ async def process(
         client_ip: The client_ip.
 
     Raises:
-        ReflexError: If a reflex specific error occurs during processing the event.
+        Exception: If a reflex specific error occurs during processing the event.
 
     Yields:
         The state updates after processing the event.
     """
     from reflex.utils import telemetry
-    from reflex.utils.exceptions import ReflexError
 
     try:
         # Add request data to the state.
@@ -1118,8 +1117,8 @@ async def process(
 
                     # Yield the update.
                     yield update
-    except ReflexError as ex:
-        telemetry.send("error", context="backend", detail=str(ex))
+    except Exception as ex:
+        telemetry.send_error(ex, context="backend")
         raise
 
 

+ 3 - 0
reflex/app_module_for_backend.py

@@ -4,12 +4,14 @@ Only the app attribute is explicitly exposed.
 from concurrent.futures import ThreadPoolExecutor
 
 from reflex import constants
+from reflex.utils import telemetry
 from reflex.utils.exec import is_prod_mode
 from reflex.utils.prerequisites import get_app
 
 if "app" != constants.CompileVars.APP:
     raise AssertionError("unexpected variable name for 'app'")
 
+telemetry.send("compile")
 app_module = get_app(reload=False)
 app = getattr(app_module, constants.CompileVars.APP)
 # For py3.8 and py3.9 compatibility when redis is used, we MUST add any decorator pages
@@ -29,5 +31,6 @@ del app_module
 del compile_future
 del get_app
 del is_prod_mode
+del telemetry
 del constants
 del ThreadPoolExecutor

+ 1 - 3
reflex/state.py

@@ -1476,7 +1476,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
             StateUpdate object
         """
         from reflex.utils import telemetry
-        from reflex.utils.exceptions import ReflexError
 
         # Get the function to process the event.
         fn = functools.partial(handler.fn, state)
@@ -1516,8 +1515,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
         except Exception as ex:
             error = traceback.format_exc()
             print(error)
-            if isinstance(ex, ReflexError):
-                telemetry.send("error", context="backend", detail=str(ex))
+            telemetry.send_error(ex, context="backend")
             yield state._as_state_update(
                 handler,
                 window_alert("An error occurred. See logs for details."),

+ 3 - 4
reflex/utils/prerequisites.py

@@ -233,9 +233,8 @@ def get_app(reload: bool = False) -> ModuleType:
 
     Raises:
         RuntimeError: If the app name is not set in the config.
-        exceptions.ReflexError: Reflex specific errors.
     """
-    from reflex.utils import exceptions, telemetry
+    from reflex.utils import telemetry
 
     try:
         os.environ[constants.RELOAD_CONFIG] = str(reload)
@@ -259,8 +258,8 @@ def get_app(reload: bool = False) -> ModuleType:
             importlib.reload(app)
 
         return app
-    except exceptions.ReflexError as ex:
-        telemetry.send("error", context="frontend", detail=str(ex))
+    except Exception as ex:
+        telemetry.send_error(ex, context="frontend")
         raise
 
 

+ 35 - 11
reflex/utils/telemetry.py

@@ -2,6 +2,7 @@
 
 from __future__ import annotations
 
+import asyncio
 import multiprocessing
 import platform
 
@@ -157,17 +158,7 @@ def _send_event(event_data: dict) -> bool:
         return False
 
 
-def send(event: str, telemetry_enabled: bool | None = None, **kwargs) -> bool:
-    """Send anonymous telemetry for Reflex.
-
-    Args:
-        event: The event name.
-        telemetry_enabled: Whether to send the telemetry (If None, get from config).
-        kwargs: Additional data to send with the event.
-
-    Returns:
-        Whether the telemetry was sent successfully.
-    """
+def _send(event, telemetry_enabled, **kwargs):
     from reflex.config import get_config
 
     # Get the telemetry_enabled from the config if it is not specified.
@@ -182,3 +173,36 @@ def send(event: str, telemetry_enabled: bool | None = None, **kwargs) -> bool:
     if not event_data:
         return False
     return _send_event(event_data)
+
+
+def send(event: str, telemetry_enabled: bool | None = None, **kwargs):
+    """Send anonymous telemetry for Reflex.
+
+    Args:
+        event: The event name.
+        telemetry_enabled: Whether to send the telemetry (If None, get from config).
+        kwargs: Additional data to send with the event.
+    """
+
+    async def async_send(event, telemetry_enabled, **kwargs):
+        return _send(event, telemetry_enabled, **kwargs)
+
+    try:
+        # Within an event loop context, send the event asynchronously.
+        asyncio.create_task(async_send(event, telemetry_enabled, **kwargs))
+    except RuntimeError:
+        # If there is no event loop, send the event synchronously.
+        _send(event, telemetry_enabled, **kwargs)
+
+
+def send_error(error: Exception, context: str):
+    """Send an error event.
+
+    Args:
+        error: The error to send.
+        context: The context of the error (e.g. "frontend" or "backend")
+
+    Returns:
+        Whether the telemetry was sent successfully.
+    """
+    return send("error", detail=type(error).__name__, context=context)

+ 2 - 2
tests/test_telemetry.py

@@ -29,7 +29,7 @@ def test_telemetry():
 
 def test_disable():
     """Test that disabling telemetry works."""
-    assert not telemetry.send("test", telemetry_enabled=False)
+    assert not telemetry._send("test", telemetry_enabled=False)
 
 
 @pytest.mark.parametrize("event", ["init", "reinit", "run-dev", "run-prod", "export"])
@@ -43,7 +43,7 @@ def test_send(mocker, event):
     )
     mocker.patch("platform.platform", return_value="Mocked Platform")
 
-    telemetry.send(event, telemetry_enabled=True)
+    telemetry._send(event, telemetry_enabled=True)
     httpx.post.assert_called_once()
     if telemetry.get_os() == "Windows":
         open.assert_called_with(".web\\reflex.json", "r")