Catch more errors in frontend/backend (#3346)

This commit is contained in:
Nikhil Rao 2024-05-20 16:55:41 -07:00 committed by GitHub
parent 656914edef
commit ec72448b8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 47 additions and 24 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import asyncio
import multiprocessing import multiprocessing
import platform import platform
@ -157,17 +158,7 @@ def _send_event(event_data: dict) -> bool:
return False return False
def send(event: str, telemetry_enabled: bool | None = None, **kwargs) -> bool: def _send(event, telemetry_enabled, **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.
Returns:
Whether the telemetry was sent successfully.
"""
from reflex.config import get_config from reflex.config import get_config
# Get the telemetry_enabled from the config if it is not specified. # 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: if not event_data:
return False return False
return _send_event(event_data) 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)

View File

@ -29,7 +29,7 @@ def test_telemetry():
def test_disable(): def test_disable():
"""Test that disabling telemetry works.""" """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"]) @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") 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() httpx.post.assert_called_once()
if telemetry.get_os() == "Windows": if telemetry.get_os() == "Windows":
open.assert_called_with(".web\\reflex.json", "r") open.assert_called_with(".web\\reflex.json", "r")