feat: Add TelemetryService and TelemetryServiceFactory
This commit adds the `TelemetryService` class and `TelemetryServiceFactory` class to the `langflow.services.telemetry` module. The `TelemetryService` is responsible for handling telemetry-related functionality, while the `TelemetryServiceFactory` is used to create instances of the `TelemetryService` class. This addition enables the application to collect and analyze telemetry data.
This commit is contained in:
parent
597d1334b8
commit
609b99e9f3
5 changed files with 180 additions and 0 deletions
|
|
@ -18,6 +18,7 @@ if TYPE_CHECKING:
|
||||||
from langflow.services.storage.service import StorageService
|
from langflow.services.storage.service import StorageService
|
||||||
from langflow.services.store.service import StoreService
|
from langflow.services.store.service import StoreService
|
||||||
from langflow.services.task.service import TaskService
|
from langflow.services.task.service import TaskService
|
||||||
|
from langflow.services.telemetry.service import TelemetryService
|
||||||
from langflow.services.tracing.service import TracingService
|
from langflow.services.tracing.service import TracingService
|
||||||
from langflow.services.variable.service import VariableService
|
from langflow.services.variable.service import VariableService
|
||||||
|
|
||||||
|
|
@ -42,6 +43,18 @@ def get_service(service_type: ServiceType, default=None):
|
||||||
return service_manager.get(service_type, default) # type: ignore
|
return service_manager.get(service_type, default) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
|
def get_telemetry_service() -> "TelemetryService":
|
||||||
|
"""
|
||||||
|
Retrieves the TelemetryService instance from the service manager.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
TelemetryService: The TelemetryService instance.
|
||||||
|
"""
|
||||||
|
from langflow.services.telemetry.factory import TelemetryServiceFactory
|
||||||
|
|
||||||
|
return get_service(ServiceType.TELEMETRY_SERVICE, TelemetryServiceFactory()) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def get_tracing_service() -> "TracingService":
|
def get_tracing_service() -> "TracingService":
|
||||||
"""
|
"""
|
||||||
Retrieves the TracingService instance from the service manager.
|
Retrieves the TracingService instance from the service manager.
|
||||||
|
|
|
||||||
|
|
@ -22,3 +22,4 @@ class ServiceType(str, Enum):
|
||||||
# SOCKETIO_SERVICE = "socket_service"
|
# SOCKETIO_SERVICE = "socket_service"
|
||||||
STATE_SERVICE = "state_service"
|
STATE_SERVICE = "state_service"
|
||||||
TRACING_SERVICE = "tracing_service"
|
TRACING_SERVICE = "tracing_service"
|
||||||
|
TELEMETRY_SERVICE = "telemetry_service"
|
||||||
|
|
|
||||||
0
src/backend/base/langflow/services/telemetry/__init__.py
Normal file
0
src/backend/base/langflow/services/telemetry/__init__.py
Normal file
15
src/backend/base/langflow/services/telemetry/factory.py
Normal file
15
src/backend/base/langflow/services/telemetry/factory.py
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from langflow.services.factory import ServiceFactory
|
||||||
|
from langflow.services.telemetry.service import TelemetryService
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from langflow.services.settings.service import SettingsService
|
||||||
|
|
||||||
|
|
||||||
|
class TelemetryServiceFactory(ServiceFactory):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(TelemetryService)
|
||||||
|
|
||||||
|
def create(self, settings_service: "SettingsService"):
|
||||||
|
return TelemetryService(settings_service)
|
||||||
151
src/backend/base/langflow/services/telemetry/service.py
Normal file
151
src/backend/base/langflow/services/telemetry/service.py
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
import asyncio
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from loguru import logger
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from langflow.services.base import Service
|
||||||
|
from langflow.utils.version import get_version_info
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from langflow.services.settings.service import SettingsService
|
||||||
|
|
||||||
|
|
||||||
|
class RunPayload(BaseModel):
|
||||||
|
isEndpointName: str
|
||||||
|
isWebhook: bool
|
||||||
|
seconds: int
|
||||||
|
success: bool
|
||||||
|
errorMessage: str
|
||||||
|
|
||||||
|
|
||||||
|
class ShutdownPayload(BaseModel):
|
||||||
|
timeRunning: int
|
||||||
|
startingFlowsCount: int
|
||||||
|
finalFlowsCount: int
|
||||||
|
startingCompCount: int
|
||||||
|
finalCompCount: int
|
||||||
|
|
||||||
|
|
||||||
|
class VersionPayload(BaseModel):
|
||||||
|
version: str
|
||||||
|
platform: str
|
||||||
|
python: str
|
||||||
|
cacheType: str
|
||||||
|
backendOnly: bool
|
||||||
|
|
||||||
|
|
||||||
|
class PlaygroundPayload(BaseModel):
|
||||||
|
seconds: int
|
||||||
|
componentCount: int
|
||||||
|
success: bool
|
||||||
|
|
||||||
|
|
||||||
|
class ComponentPayload(BaseModel):
|
||||||
|
name: str
|
||||||
|
seconds: int
|
||||||
|
success: bool
|
||||||
|
errorMessage: str
|
||||||
|
|
||||||
|
|
||||||
|
class TelemetryService(Service):
|
||||||
|
name = "telemetry_service"
|
||||||
|
|
||||||
|
def __init__(self, settings_service: "SettingsService"):
|
||||||
|
super().__init__()
|
||||||
|
self.settings_service = settings_service
|
||||||
|
self.base_url = settings_service.settings.telemetry_base_url
|
||||||
|
self.telemetry_queue = asyncio.Queue()
|
||||||
|
self.client = httpx.AsyncClient(timeout=None)
|
||||||
|
self.running = False
|
||||||
|
self.package = get_version_info()["package"]
|
||||||
|
|
||||||
|
# Check for do-not-track settings
|
||||||
|
self.do_not_track = (
|
||||||
|
os.getenv("DO_NOT_TRACK", "False").lower() == "true" or settings_service.settings.do_not_track
|
||||||
|
)
|
||||||
|
|
||||||
|
async def telemetry_worker(self):
|
||||||
|
while self.running:
|
||||||
|
func, payload, path = await self.telemetry_queue.get()
|
||||||
|
try:
|
||||||
|
await func(payload, path)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error sending telemetry data: {e}")
|
||||||
|
finally:
|
||||||
|
self.telemetry_queue.task_done()
|
||||||
|
|
||||||
|
async def send_telemetry_data(self, payload: BaseModel, path: str | None = None):
|
||||||
|
if self.do_not_track:
|
||||||
|
logger.debug("Telemetry tracking is disabled.")
|
||||||
|
return
|
||||||
|
|
||||||
|
url = f"{self.base_url}/{self.package}"
|
||||||
|
if path:
|
||||||
|
url = f"{url}/{path}"
|
||||||
|
try:
|
||||||
|
response = await self.client.get(url, params=payload.model_dump())
|
||||||
|
if response.status_code != 200:
|
||||||
|
logger.error(f"Failed to send telemetry data: {response.status_code} {response.text}")
|
||||||
|
else:
|
||||||
|
logger.debug("Telemetry data sent successfully.")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to send telemetry data due to: {e}")
|
||||||
|
|
||||||
|
async def log_package_run(self, payload: RunPayload):
|
||||||
|
await self.telemetry_queue.put((self.send_telemetry_data, payload, "run"))
|
||||||
|
|
||||||
|
async def log_package_shutdown(self, payload: ShutdownPayload):
|
||||||
|
await self.telemetry_queue.put((self.send_telemetry_data, payload, "shutdown"))
|
||||||
|
|
||||||
|
async def log_package_version(self):
|
||||||
|
python_version = ".".join(platform.python_version().split(".")[:2])
|
||||||
|
version_info = get_version_info()
|
||||||
|
payload = VersionPayload(
|
||||||
|
version=version_info["version"],
|
||||||
|
platform=platform.platform(),
|
||||||
|
python=python_version,
|
||||||
|
cacheType=self.settings_service.settings.cache_type,
|
||||||
|
backendOnly=self.settings_service.settings.backend_only,
|
||||||
|
)
|
||||||
|
await self.telemetry_queue.put((self.send_telemetry_data, payload, None))
|
||||||
|
|
||||||
|
async def log_package_playground(self, payload: PlaygroundPayload):
|
||||||
|
await self.telemetry_queue.put((self.send_telemetry_data, payload, "playground"))
|
||||||
|
|
||||||
|
async def log_package_component(self, payload: ComponentPayload):
|
||||||
|
await self.telemetry_queue.put((self.send_telemetry_data, payload, "component"))
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
if self.running or self.do_not_track:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.running = True
|
||||||
|
self.worker_task = asyncio.create_task(self.telemetry_worker())
|
||||||
|
await self.worker_task
|
||||||
|
await self.log_package_version()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error starting telemetry service: {e}")
|
||||||
|
|
||||||
|
async def flush(self):
|
||||||
|
if self.do_not_track:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
await self.telemetry_queue.join()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error flushing logs: {e}")
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
if self.do_not_track:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
self.running = False
|
||||||
|
await self.flush()
|
||||||
|
self.worker_task.cancel()
|
||||||
|
if self.worker_task:
|
||||||
|
await self.worker_task
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error stopping tracing service: {e}")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue