diff --git a/src/crewai/telemetry/telemetry.py b/src/crewai/telemetry/telemetry.py index ffd78d28e..44b0c3a0d 100644 --- a/src/crewai/telemetry/telemetry.py +++ b/src/crewai/telemetry/telemetry.py @@ -76,10 +76,14 @@ class Telemetry: return cls._instance def __init__(self) -> None: + if hasattr(self, '_initialized'): + return + self.ready: bool = False self.trace_set: bool = False if self._is_telemetry_disabled(): + self._initialized: bool = True return try: @@ -105,6 +109,8 @@ class Telemetry: ): raise # Re-raise the exception to not interfere with system signals self.ready = False + finally: + self._initialized = True def _is_telemetry_disabled(self) -> bool: """Check if telemetry should be disabled based on environment variables.""" @@ -124,7 +130,7 @@ class Telemetry: self.trace_set = False def _safe_telemetry_operation(self, operation): - if not self.ready: + if not self.ready or self._is_telemetry_disabled(): return try: operation() diff --git a/tests/telemetry/test_telemetry.py b/tests/telemetry/test_telemetry.py index 51c5a79f1..b4943912e 100644 --- a/tests/telemetry/test_telemetry.py +++ b/tests/telemetry/test_telemetry.py @@ -22,6 +22,7 @@ from opentelemetry import trace ) def test_telemetry_environment_variables(env_var, value, expected_ready): """Test telemetry state with different environment variable configurations.""" + Telemetry._instance = None with patch.dict(os.environ, {env_var: value}): with patch("crewai.telemetry.telemetry.TracerProvider"): telemetry = Telemetry() @@ -30,6 +31,7 @@ def test_telemetry_environment_variables(env_var, value, expected_ready): def test_telemetry_enabled_by_default(): """Test that telemetry is enabled by default.""" + Telemetry._instance = None with patch.dict(os.environ, {}, clear=True): with patch("crewai.telemetry.telemetry.TracerProvider"): telemetry = Telemetry() diff --git a/tests/telemetry/test_telemetry_disable.py b/tests/telemetry/test_telemetry_disable.py index 16c02acaa..8d42e256e 100644 --- a/tests/telemetry/test_telemetry_disable.py +++ b/tests/telemetry/test_telemetry_disable.py @@ -1,5 +1,5 @@ import os -from unittest.mock import patch +from unittest.mock import patch, MagicMock import pytest @@ -16,6 +16,7 @@ from crewai.telemetry import Telemetry ]) def test_telemetry_environment_variables(env_var, value, expected_ready): """Test telemetry state with different environment variable configurations.""" + Telemetry._instance = None with patch.dict(os.environ, {env_var: value}): with patch("crewai.telemetry.telemetry.TracerProvider"): telemetry = Telemetry() @@ -24,7 +25,70 @@ def test_telemetry_environment_variables(env_var, value, expected_ready): def test_telemetry_enabled_by_default(): """Test that telemetry is enabled by default.""" + Telemetry._instance = None with patch.dict(os.environ, {}, clear=True): with patch("crewai.telemetry.telemetry.TracerProvider"): telemetry = Telemetry() assert telemetry.ready is True + + +def test_telemetry_disable_after_singleton_creation(): + """Test that telemetry operations are disabled when env var is set after singleton creation.""" + Telemetry._instance = None + + with patch.dict(os.environ, {}, clear=True): + with patch("crewai.telemetry.telemetry.TracerProvider"): + telemetry = Telemetry() + assert telemetry.ready is True + + mock_operation = MagicMock() + telemetry._safe_telemetry_operation(mock_operation) + mock_operation.assert_called_once() + + mock_operation.reset_mock() + + os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true' + + telemetry._safe_telemetry_operation(mock_operation) + mock_operation.assert_not_called() + + +def test_telemetry_disable_with_multiple_instances(): + """Test that multiple telemetry instances respect dynamically changed env vars.""" + Telemetry._instance = None + + with patch.dict(os.environ, {}, clear=True): + with patch("crewai.telemetry.telemetry.TracerProvider"): + telemetry1 = Telemetry() + assert telemetry1.ready is True + + os.environ['CREWAI_DISABLE_TELEMETRY'] = 'true' + + telemetry2 = Telemetry() + assert telemetry2 is telemetry1 + assert telemetry2.ready is True + + mock_operation = MagicMock() + telemetry2._safe_telemetry_operation(mock_operation) + mock_operation.assert_not_called() + + +def test_telemetry_otel_sdk_disabled_after_creation(): + """Test that OTEL_SDK_DISABLED also works when set after singleton creation.""" + Telemetry._instance = None + + with patch.dict(os.environ, {}, clear=True): + with patch("crewai.telemetry.telemetry.TracerProvider"): + telemetry = Telemetry() + assert telemetry.ready is True + + mock_operation = MagicMock() + telemetry._safe_telemetry_operation(mock_operation) + mock_operation.assert_called_once() + + mock_operation.reset_mock() + + os.environ['OTEL_SDK_DISABLED'] = 'true' + + telemetry._safe_telemetry_operation(mock_operation) + mock_operation.assert_not_called()