about summary refs log tree commit diff
path: root/R2R/r2r/telemetry
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /R2R/r2r/telemetry
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to 'R2R/r2r/telemetry')
-rwxr-xr-xR2R/r2r/telemetry/__init__.py0
-rwxr-xr-xR2R/r2r/telemetry/events.py59
-rwxr-xr-xR2R/r2r/telemetry/posthog.py58
-rwxr-xr-xR2R/r2r/telemetry/telemetry_decorator.py56
4 files changed, 173 insertions, 0 deletions
diff --git a/R2R/r2r/telemetry/__init__.py b/R2R/r2r/telemetry/__init__.py
new file mode 100755
index 00000000..e69de29b
--- /dev/null
+++ b/R2R/r2r/telemetry/__init__.py
diff --git a/R2R/r2r/telemetry/events.py b/R2R/r2r/telemetry/events.py
new file mode 100755
index 00000000..5bd7528b
--- /dev/null
+++ b/R2R/r2r/telemetry/events.py
@@ -0,0 +1,59 @@
+import uuid
+from typing import Any, Dict
+
+
+class BaseTelemetryEvent:
+    def __init__(self, event_type: str, properties: Dict[str, Any]):
+        self.event_type = event_type
+        self.properties = properties
+        self.event_id = str(uuid.uuid4())
+
+
+class DailyActiveUserEvent(BaseTelemetryEvent):
+    def __init__(self, user_id: str):
+        super().__init__("DailyActiveUser", {"user_id": user_id})
+
+
+class FeatureUsageEvent(BaseTelemetryEvent):
+    def __init__(self, user_id: str, feature: str):
+        super().__init__(
+            "FeatureUsage", {"user_id": user_id, "feature": feature}
+        )
+
+
+class ErrorEvent(BaseTelemetryEvent):
+    def __init__(self, user_id: str, endpoint: str, error_message: str):
+        super().__init__(
+            "Error",
+            {
+                "user_id": user_id,
+                "endpoint": endpoint,
+                "error_message": error_message,
+            },
+        )
+
+
+class RequestLatencyEvent(BaseTelemetryEvent):
+    def __init__(self, endpoint: str, latency: float):
+        super().__init__(
+            "RequestLatency", {"endpoint": endpoint, "latency": latency}
+        )
+
+
+class GeographicDistributionEvent(BaseTelemetryEvent):
+    def __init__(self, user_id: str, country: str):
+        super().__init__(
+            "GeographicDistribution", {"user_id": user_id, "country": country}
+        )
+
+
+class SessionDurationEvent(BaseTelemetryEvent):
+    def __init__(self, user_id: str, duration: float):
+        super().__init__(
+            "SessionDuration", {"user_id": user_id, "duration": duration}
+        )
+
+
+class UserPathEvent(BaseTelemetryEvent):
+    def __init__(self, user_id: str, path: str):
+        super().__init__("UserPath", {"user_id": user_id, "path": path})
diff --git a/R2R/r2r/telemetry/posthog.py b/R2R/r2r/telemetry/posthog.py
new file mode 100755
index 00000000..64e63895
--- /dev/null
+++ b/R2R/r2r/telemetry/posthog.py
@@ -0,0 +1,58 @@
+import logging
+import os
+
+import posthog
+
+from r2r.telemetry.events import BaseTelemetryEvent
+
+logger = logging.getLogger(__name__)
+
+
+class PosthogClient:
+    """
+    This is a write-only project API key, so it can only create new events. It can't
+    read events or any of your other data stored with PostHog, so it's safe to use in public apps.
+    """
+
+    def __init__(
+        self, api_key: str, enabled: bool = True, debug: bool = False
+    ):
+        self.enabled = enabled
+        self.debug = debug
+
+        if self.enabled:
+            logger.info(
+                "Initializing anonymized telemetry. To disable, set TELEMETRY_ENABLED=false in your environment."
+            )
+            posthog.project_api_key = api_key
+            posthog.disable_geoip = False
+        else:
+            posthog.disabled = True
+
+        if self.debug:
+            posthog.debug = True
+
+        logger.info(
+            f"Posthog telemetry {'enabled' if self.enabled else 'disabled'}, debug mode {'on' if self.debug else 'off'}"
+        )
+
+    def capture(self, event: BaseTelemetryEvent):
+        if self.enabled:
+            posthog.capture(event.event_id, event.event_type, event.properties)
+
+
+# Initialize the telemetry client with a flag to enable or disable telemetry
+telemetry_enabled = os.getenv("TELEMETRY_ENABLED", "true").lower() in (
+    "true",
+    "1",
+    "t",
+)
+debug_mode = os.getenv("DEBUG_MODE", "false").lower() in (
+    "true",
+    "1",
+    "t",
+)
+telemetry_client = PosthogClient(
+    api_key="phc_OPBbibOIErCGc4NDLQsOrMuYFTKDmRwXX6qxnTr6zpU",
+    enabled=telemetry_enabled,
+)
diff --git a/R2R/r2r/telemetry/telemetry_decorator.py b/R2R/r2r/telemetry/telemetry_decorator.py
new file mode 100755
index 00000000..2938a83e
--- /dev/null
+++ b/R2R/r2r/telemetry/telemetry_decorator.py
@@ -0,0 +1,56 @@
+import asyncio
+import logging
+from functools import wraps
+
+from r2r.telemetry.events import ErrorEvent, FeatureUsageEvent
+from r2r.telemetry.posthog import telemetry_client
+
+logger = logging.getLogger(__name__)
+
+
+def telemetry_event(event_name):
+    def decorator(func):
+        @wraps(func)
+        async def async_wrapper(*args, **kwargs):
+            user_id = kwargs.get("user_id", "unknown_user")
+            try:
+                result = await func(*args, **kwargs)
+                try:
+                    telemetry_client.capture(
+                        FeatureUsageEvent(user_id=user_id, feature=event_name)
+                    )
+                except Exception as e:
+                    logger.error(f"Error in telemetry event logging: {str(e)}")
+                return result
+            except Exception as e:
+                try:
+                    telemetry_client.capture(
+                        ErrorEvent(
+                            user_id=user_id,
+                            endpoint=event_name,
+                            error_message=str(e),
+                        )
+                    )
+                except Exception as e:
+                    logger.error(f"Error in telemetry event logging: {str(e)}")
+
+                raise
+
+        @wraps(func)
+        def sync_wrapper(*args, **kwargs):
+            loop = asyncio.get_event_loop()
+            if loop.is_running():
+                future = asyncio.run_coroutine_threadsafe(
+                    async_wrapper(*args, **kwargs), loop
+                )
+                return future.result()
+            else:
+                return loop.run_until_complete(async_wrapper(*args, **kwargs))
+
+        return (
+            async_wrapper
+            if asyncio.iscoroutinefunction(func)
+            else sync_wrapper
+        )
+
+    return decorator