From f88ae54f9657cddeba206f7b0b4a7deab720c965 Mon Sep 17 00:00:00 2001 From: Lorenze Jay <63378463+lorenzejay@users.noreply.github.com> Date: Wed, 10 Jun 2026 17:03:25 -0700 Subject: [PATCH] fix telemetry setup on crewai-login (#6106) * fix telemetry setup on crewai-login * type check fix --- lib/crewai-core/src/crewai_core/telemetry.py | 12 ++++--- lib/crewai-core/tests/test_smoke.py | 34 ++++++++++++++++++++ lib/crewai/src/crewai/telemetry/telemetry.py | 6 +++- lib/crewai/tests/telemetry/test_telemetry.py | 18 +++++++++++ 4 files changed, 65 insertions(+), 5 deletions(-) diff --git a/lib/crewai-core/src/crewai_core/telemetry.py b/lib/crewai-core/src/crewai_core/telemetry.py index a590dbafb..08aef9b71 100644 --- a/lib/crewai-core/src/crewai_core/telemetry.py +++ b/lib/crewai-core/src/crewai_core/telemetry.py @@ -17,7 +17,7 @@ import contextlib import logging import os import threading -from typing import Any, Final +from typing import Any, ClassVar, Final from opentelemetry import trace from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter @@ -27,7 +27,7 @@ from opentelemetry.sdk.trace.export import ( BatchSpanProcessor, SpanExportResult, ) -from opentelemetry.trace import Span, Status, StatusCode +from opentelemetry.trace import ProxyTracerProvider, Span, Status, StatusCode from typing_extensions import Self @@ -72,8 +72,8 @@ class Telemetry: and event-bus signal handlers (see ``crewai.telemetry.telemetry``). """ - _instance = None - _lock = threading.Lock() + _instance: ClassVar[Self | None] = None + _lock: ClassVar[threading.Lock] = threading.Lock() def __new__(cls) -> Self: if cls._instance is None: @@ -149,6 +149,10 @@ class Telemetry: if self.ready and not self.trace_set: try: with suppress_warnings(): + existing_provider = trace.get_tracer_provider() + if not isinstance(existing_provider, ProxyTracerProvider): + self.trace_set = True + return trace.set_tracer_provider(self.provider) self.trace_set = True except Exception as e: diff --git a/lib/crewai-core/tests/test_smoke.py b/lib/crewai-core/tests/test_smoke.py index 93b2e46f9..310e41fff 100644 --- a/lib/crewai-core/tests/test_smoke.py +++ b/lib/crewai-core/tests/test_smoke.py @@ -14,6 +14,7 @@ from crewai_core import ( version, ) import pytest +from opentelemetry.sdk.trace import TracerProvider def test_version_returns_string() -> None: @@ -94,3 +95,36 @@ def test_user_data_decline_blocks( def test_unused_var_warning_silenced() -> None: # Touch os to keep the import (used by env-var fixtures above) assert os.environ is not None + + +def test_core_telemetry_skips_duplicate_tracer_provider( + monkeypatch: pytest.MonkeyPatch, +) -> None: + from crewai_core.telemetry import Telemetry + + Telemetry._instance = None + monkeypatch.delenv("OTEL_SDK_DISABLED", raising=False) + monkeypatch.delenv("CREWAI_DISABLE_TELEMETRY", raising=False) + monkeypatch.delenv("CREWAI_DISABLE_TRACKING", raising=False) + + monkeypatch.setattr( + "crewai_core.telemetry.trace.get_tracer_provider", + lambda: TracerProvider(), + ) + + called = False + + def fail_if_called(provider: object) -> None: + nonlocal called + called = True + + monkeypatch.setattr( + "crewai_core.telemetry.trace.set_tracer_provider", + fail_if_called, + ) + + telemetry = Telemetry() + telemetry.set_tracer() + + assert called is False + assert telemetry.trace_set is True diff --git a/lib/crewai/src/crewai/telemetry/telemetry.py b/lib/crewai/src/crewai/telemetry/telemetry.py index ab3815f6a..f13faed2b 100644 --- a/lib/crewai/src/crewai/telemetry/telemetry.py +++ b/lib/crewai/src/crewai/telemetry/telemetry.py @@ -30,7 +30,7 @@ from opentelemetry.sdk.trace.export import ( BatchSpanProcessor, SpanExportResult, ) -from opentelemetry.trace import Span +from opentelemetry.trace import ProxyTracerProvider, Span from typing_extensions import Self from crewai.events.event_bus import crewai_event_bus @@ -162,6 +162,10 @@ class Telemetry: if self.ready and not self.trace_set: try: with suppress_warnings(): + existing_provider = trace.get_tracer_provider() + if not isinstance(existing_provider, ProxyTracerProvider): + self.trace_set = True + return trace.set_tracer_provider(self.provider) self.trace_set = True except Exception as e: diff --git a/lib/crewai/tests/telemetry/test_telemetry.py b/lib/crewai/tests/telemetry/test_telemetry.py index d0564982d..e0da60c37 100644 --- a/lib/crewai/tests/telemetry/test_telemetry.py +++ b/lib/crewai/tests/telemetry/test_telemetry.py @@ -6,6 +6,7 @@ import pytest from crewai import Agent, Crew, Task from crewai.telemetry import Telemetry from opentelemetry import trace +from opentelemetry.sdk.trace import TracerProvider @pytest.fixture(autouse=True) @@ -53,6 +54,23 @@ def test_telemetry_enabled_by_default(): assert telemetry.ready is True +def test_set_tracer_skips_when_provider_already_configured(): + """A second telemetry instance must not re-install the global provider.""" + with ( + patch.dict(os.environ, {}, clear=True), + patch( + "crewai.telemetry.telemetry.trace.get_tracer_provider", + return_value=TracerProvider(), + ), + patch("crewai.telemetry.telemetry.trace.set_tracer_provider") as mock_set, + ): + telemetry = Telemetry() + telemetry.set_tracer() + + mock_set.assert_not_called() + assert telemetry.trace_set is True + + @patch("crewai.telemetry.telemetry.logger.error") @patch( "opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter.export",