feat: Add common telemetry fields (#7899)

* feat: add common telemetry fields and dynamic timestamp to telemetry payloads

* test: Add common telemetry fields to exception telemetry tests

- Introduced common telemetry fields including langflow_version, platform, and os to the telemetry service in multiple test cases.
- Updated assertions to verify the inclusion of these fields in the telemetry data sent during exception handling.

* test: Enhance exception telemetry tests with common telemetry fields

- Added common telemetry fields (langflow_version, platform, os) to the telemetry service in multiple test cases.
- Updated assertions to ensure these fields are included in the telemetry data during exception handling.

---------

Co-authored-by: Carlos Coelho <80289056+carlosrcoelho@users.noreply.github.com>
This commit is contained in:
Gabriel Luiz Freitas Almeida
2025-10-09 18:00:06 -03:00
committed by GitHub
parent d12bb50320
commit b840b71193
3 changed files with 72 additions and 14 deletions

View File

@ -50,6 +50,14 @@ class TelemetryService(Service):
self.log_package_version_task: asyncio.Task | None = None
self.client_type = self._get_client_type()
# Initialize static telemetry fields
version_info = get_version_info()
self.common_telemetry_fields = {
"langflow_version": version_info["version"],
"platform": "desktop" if self._get_langflow_desktop() else "python_package",
"os": platform.system().lower(),
}
async def telemetry_worker(self) -> None:
while self.running:
func, payload, path = await self.telemetry_queue.get()
@ -74,6 +82,14 @@ class TelemetryService(Service):
try:
payload_dict = payload.model_dump(by_alias=True, exclude_none=True, exclude_unset=True)
# Add common fields to all payloads except VersionPayload
if not isinstance(payload, VersionPayload):
payload_dict.update(self.common_telemetry_fields)
# Add timestamp dynamically
if "timestamp" not in payload_dict:
payload_dict["timestamp"] = datetime.now(timezone.utc).isoformat()
response = await self.client.get(url, params=payload_dict)
if response.status_code != httpx.codes.OK:
await logger.aerror(f"Failed to send telemetry data: {response.status_code} {response.text}")

View File

@ -26,6 +26,11 @@ class TestExceptionTelemetryIntegration:
telemetry_service.base_url = "https://mock-telemetry.example.com"
telemetry_service.do_not_track = False
telemetry_service.client_type = "oss"
telemetry_service.common_telemetry_fields = {
"langflow_version": "1.0.0",
"platform": "python_package",
"os": "darwin",
}
# Mock successful response
mock_response = MagicMock()
@ -78,6 +83,11 @@ class TestExceptionTelemetryIntegration:
telemetry_service.base_url = "https://mock-telemetry.example.com"
telemetry_service.do_not_track = False
telemetry_service.client_type = "oss"
telemetry_service.common_telemetry_fields = {
"langflow_version": "1.0.0",
"platform": "python_package",
"os": "darwin",
}
# Mock the async queue and HTTP client
telemetry_service.telemetry_queue = asyncio.Queue()
@ -296,21 +306,21 @@ class TestTelemetryPayloadValidation:
exception_type="ValueError",
exception_message="Invalid input parameter",
exception_context="handler",
stack_trace_hash="abc123def456",
stack_trace_hash="abc123def456", # pragma: allowlist secret
client_type="oss",
)
assert payload.exception_type == "ValueError"
assert payload.exception_message == "Invalid input parameter"
assert payload.exception_context == "handler"
assert payload.stack_trace_hash == "abc123def456"
assert payload.stack_trace_hash == "abc123def456" # pragma: allowlist secret
serialized = payload.model_dump(by_alias=True)
expected = {
"exceptionType": "ValueError",
"exceptionMessage": "Invalid input parameter",
"exceptionContext": "handler",
"stackTraceHash": "abc123def456",
"stackTraceHash": "abc123def456", # pragma: allowlist secret
"clientType": "oss",
}
assert serialized == expected

View File

@ -18,7 +18,7 @@ class TestExceptionTelemetry:
exception_type="ValueError",
exception_message="Test error message",
exception_context="handler",
stack_trace_hash="abc123def456",
stack_trace_hash="abc123def456", # pragma: allowlist secret
)
# Test serialization with aliases
@ -28,7 +28,7 @@ class TestExceptionTelemetry:
"exceptionType": "ValueError",
"exceptionMessage": "Test error message",
"exceptionContext": "handler",
"stackTraceHash": "abc123def456",
"stackTraceHash": "abc123def456", # pragma: allowlist secret
}
assert data == expected_fields
@ -80,6 +80,11 @@ class TestExceptionTelemetry:
telemetry_service.base_url = "https://mock-telemetry.example.com"
telemetry_service.do_not_track = False
telemetry_service.client_type = "oss"
telemetry_service.common_telemetry_fields = {
"langflow_version": "1.0.0",
"platform": "python_package",
"os": "linux",
}
# Mock HTTP client
mock_response = MagicMock()
@ -105,15 +110,17 @@ class TestExceptionTelemetry:
# Check URL
assert call_args[0][0] == "https://mock-telemetry.example.com/exception"
# Check query parameters
expected_params = {
"exceptionType": "ValueError",
"exceptionMessage": "Test error",
"exceptionContext": "handler",
"stackTraceHash": "abc123",
"clientType": "oss",
}
assert call_args[1]["params"] == expected_params
# Check query parameters (should include common telemetry fields)
params = call_args[1]["params"]
assert params["exceptionType"] == "ValueError"
assert params["exceptionMessage"] == "Test error"
assert params["exceptionContext"] == "handler"
assert params["stackTraceHash"] == "abc123"
assert params["clientType"] == "oss"
assert params["langflow_version"] == "1.0.0"
assert params["platform"] == "python_package"
assert params["os"] == "linux"
assert "timestamp" in params
@pytest.mark.asyncio
async def test_send_telemetry_data_respects_do_not_track(self):
@ -123,6 +130,11 @@ class TestExceptionTelemetry:
telemetry_service.base_url = "https://mock-telemetry.example.com"
telemetry_service.do_not_track = True
telemetry_service.client_type = "oss"
telemetry_service.common_telemetry_fields = {
"langflow_version": "1.0.0",
"platform": "python_package",
"os": "linux",
}
# Mock HTTP client
mock_client = AsyncMock()
@ -173,6 +185,11 @@ class TestExceptionTelemetry:
telemetry_service.base_url = "https://mock-telemetry.example.com"
telemetry_service.do_not_track = False
telemetry_service.client_type = "oss"
telemetry_service.common_telemetry_fields = {
"langflow_version": "1.0.0",
"platform": "python_package",
"os": "linux",
}
# Create payload with very long message
long_message = "A" * 2000 # Very long message
@ -203,6 +220,11 @@ class TestExceptionTelemetry:
telemetry_service.base_url = "https://mock-telemetry.example.com"
telemetry_service.do_not_track = False
telemetry_service.client_type = "oss"
telemetry_service.common_telemetry_fields = {
"langflow_version": "1.0.0",
"platform": "python_package",
"os": "linux",
}
# Create payload with special characters
special_message = "Error with special chars: &?=#@!$%^&*()"
@ -235,6 +257,11 @@ class TestExceptionTelemetry:
telemetry_service.base_url = "https://mock-telemetry.example.com"
telemetry_service.do_not_track = False
telemetry_service.client_type = "oss"
telemetry_service.common_telemetry_fields = {
"langflow_version": "1.0.0",
"platform": "python_package",
"os": "linux",
}
# Create payload with potentially sensitive data
sensitive_message = "Password: secret123, API Key: sk-abc123, Token: xyz789"
@ -267,6 +294,11 @@ class TestExceptionTelemetry:
telemetry_service.base_url = "https://mock-telemetry.example.com"
telemetry_service.do_not_track = False
telemetry_service.client_type = "oss"
telemetry_service.common_telemetry_fields = {
"langflow_version": "1.0.0",
"platform": "python_package",
"os": "linux",
}
# Create payload with unicode characters
unicode_message = "Error with unicode: 世界, 🚀, émojis"