Skip to content

Commit acb0bc2

Browse files
authored
only send reflex error in telemetry (#5082)
* only send reflex error in telemetry * maybe * what * maybe * what * what * tf i'm doing * bruh * tf is this * what * idk what i'm doing * unless none * we don't want this guy to change
1 parent c23031d commit acb0bc2

File tree

3 files changed

+104
-24
lines changed

3 files changed

+104
-24
lines changed

reflex/utils/decorator.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def once(f: Callable[[], T]) -> Callable[[], T]:
1818
unset = object()
1919
value: object | T = unset
2020

21+
@functools.wraps(f)
2122
def wrapper() -> T:
2223
nonlocal value
2324
value = f() if value is unset else value
@@ -26,6 +27,26 @@ def wrapper() -> T:
2627
return wrapper
2728

2829

30+
def once_unless_none(f: Callable[[], T | None]) -> Callable[[], T | None]:
31+
"""A decorator that calls the function once and caches the result unless it is None.
32+
33+
Args:
34+
f: The function to call.
35+
36+
Returns:
37+
A function that calls the function once and caches the result unless it is None.
38+
"""
39+
value: T | None = None
40+
41+
@functools.wraps(f)
42+
def wrapper() -> T | None:
43+
nonlocal value
44+
value = f() if value is None else value
45+
return value
46+
47+
return wrapper
48+
49+
2950
P = ParamSpec("P")
3051

3152

reflex/utils/telemetry.py

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99
import warnings
1010
from contextlib import suppress
1111
from datetime import datetime, timezone
12+
from typing import TypedDict
1213

1314
import httpx
1415
import psutil
1516

1617
from reflex import constants
1718
from reflex.config import environment
1819
from reflex.utils import console
20+
from reflex.utils.decorator import once_unless_none
21+
from reflex.utils.exceptions import ReflexError
1922
from reflex.utils.prerequisites import ensure_reflex_installation_id, get_project_hash
2023

2124
UTC = timezone.utc
@@ -94,15 +97,39 @@ def _raise_on_missing_project_hash() -> bool:
9497
return not environment.REFLEX_SKIP_COMPILE.get()
9598

9699

97-
def _prepare_event(event: str, **kwargs) -> dict:
98-
"""Prepare the event to be sent to the PostHog server.
100+
class _Properties(TypedDict):
101+
"""Properties type for telemetry."""
99102

100-
Args:
101-
event: The event name.
102-
kwargs: Additional data to send with the event.
103+
distinct_id: int
104+
distinct_app_id: int
105+
user_os: str
106+
user_os_detail: str
107+
reflex_version: str
108+
python_version: str
109+
cpu_count: int
110+
memory: int
111+
cpu_info: dict
112+
113+
114+
class _DefaultEvent(TypedDict):
115+
"""Default event type for telemetry."""
116+
117+
api_key: str
118+
properties: _Properties
119+
120+
121+
class _Event(_DefaultEvent):
122+
"""Event type for telemetry."""
123+
124+
event: str
125+
timestamp: str
126+
127+
128+
def _get_event_defaults() -> _DefaultEvent | None:
129+
"""Get the default event data.
103130
104131
Returns:
105-
The event data.
132+
The default event data.
106133
"""
107134
from reflex.utils.prerequisites import get_cpu_info
108135

@@ -113,19 +140,12 @@ def _prepare_event(event: str, **kwargs) -> dict:
113140
console.debug(
114141
f"Could not get installation_id or project_hash: {installation_id}, {project_hash}"
115142
)
116-
return {}
117-
118-
stamp = datetime.now(UTC).isoformat()
143+
return None
119144

120145
cpuinfo = get_cpu_info()
121146

122-
additional_keys = ["template", "context", "detail", "user_uuid"]
123-
additional_fields = {
124-
key: value for key in additional_keys if (value := kwargs.get(key)) is not None
125-
}
126147
return {
127148
"api_key": "phc_JoMo0fOyi0GQAooY3UyO9k0hebGkMyFJrrCw1Gt5SGb",
128-
"event": event,
129149
"properties": {
130150
"distinct_id": installation_id,
131151
"distinct_app_id": project_hash,
@@ -136,13 +156,55 @@ def _prepare_event(event: str, **kwargs) -> dict:
136156
"cpu_count": get_cpu_count(),
137157
"memory": get_memory(),
138158
"cpu_info": dataclasses.asdict(cpuinfo) if cpuinfo else {},
139-
**additional_fields,
140159
},
160+
}
161+
162+
163+
@once_unless_none
164+
def get_event_defaults() -> _DefaultEvent | None:
165+
"""Get the default event data.
166+
167+
Returns:
168+
The default event data.
169+
"""
170+
return _get_event_defaults()
171+
172+
173+
def _prepare_event(event: str, **kwargs) -> _Event | None:
174+
"""Prepare the event to be sent to the PostHog server.
175+
176+
Args:
177+
event: The event name.
178+
kwargs: Additional data to send with the event.
179+
180+
Returns:
181+
The event data.
182+
"""
183+
event_data = get_event_defaults()
184+
if not event_data:
185+
return None
186+
187+
additional_keys = ["template", "context", "detail", "user_uuid"]
188+
189+
properties = event_data["properties"]
190+
191+
for key in additional_keys:
192+
if key in properties or key not in kwargs:
193+
continue
194+
195+
properties[key] = kwargs[key]
196+
197+
stamp = datetime.now(UTC).isoformat()
198+
199+
return {
200+
"api_key": event_data["api_key"],
201+
"event": event,
202+
"properties": properties,
141203
"timestamp": stamp,
142204
}
143205

144206

145-
def _send_event(event_data: dict) -> bool:
207+
def _send_event(event_data: _Event) -> bool:
146208
try:
147209
httpx.post(POSTHOG_API_URL, json=event_data)
148210
except Exception:
@@ -151,7 +213,7 @@ def _send_event(event_data: dict) -> bool:
151213
return True
152214

153215

154-
def _send(event: str, telemetry_enabled: bool | None, **kwargs):
216+
def _send(event: str, telemetry_enabled: bool | None, **kwargs) -> bool:
155217
from reflex.config import get_config
156218

157219
# Get the telemetry_enabled from the config if it is not specified.
@@ -167,6 +229,7 @@ def _send(event: str, telemetry_enabled: bool | None, **kwargs):
167229
if not event_data:
168230
return False
169231
return _send_event(event_data)
232+
return False
170233

171234

172235
def send(event: str, telemetry_enabled: bool | None = None, **kwargs):
@@ -196,8 +259,6 @@ def send_error(error: Exception, context: str):
196259
Args:
197260
error: The error to send.
198261
context: The context of the error (e.g. "frontend" or "backend")
199-
200-
Returns:
201-
Whether the telemetry was sent successfully.
202262
"""
203-
return send("error", detail=type(error).__name__, context=context)
263+
if isinstance(error, ReflexError):
264+
send("error", detail=type(error).__name__, context=context)

tests/units/test_telemetry.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def test_send(mocker, event):
3636
httpx_post_mock = mocker.patch("httpx.post")
3737

3838
# Mock the read_text method of Path
39-
pathlib_path_read_text_mock = mocker.patch(
39+
mocker.patch(
4040
"pathlib.Path.read_text",
4141
return_value='{"project_hash": "78285505863498957834586115958872998605"}',
4242
)
@@ -45,5 +45,3 @@ def test_send(mocker, event):
4545

4646
telemetry._send(event, telemetry_enabled=True)
4747
httpx_post_mock.assert_called_once()
48-
49-
assert pathlib_path_read_text_mock.call_count == 2

0 commit comments

Comments
 (0)