diff --git a/langfuse/_client/client.py b/langfuse/_client/client.py index af06e69ef..10b9b52e4 100644 --- a/langfuse/_client/client.py +++ b/langfuse/_client/client.py @@ -9,7 +9,6 @@ import re import urllib.parse import uuid -import warnings from datetime import datetime from hashlib import sha256 from time import time_ns @@ -251,19 +250,6 @@ def mask_otel_spans( langfuse = Langfuse(mask_otel_spans=mask_otel_spans) ``` - blocked_instrumentation_scopes (Optional[List[str]]): Deprecated. Use `should_export_span` instead. Equivalent behavior: - ```python - from langfuse.span_filter import is_default_export_span - blocked = {"sqlite", "requests"} - - should_export_span = lambda span: ( - is_default_export_span(span) - and ( - span.instrumentation_scope is None - or span.instrumentation_scope.name not in blocked - ) - ) - ``` should_export_span (Optional[Callable[[ReadableSpan], bool]]): Callback to decide whether to export a span. If omitted, Langfuse uses the default filter (Langfuse SDK spans, spans with `gen_ai.*` attributes, and known LLM instrumentation scopes). additional_headers (Optional[Dict[str, str]]): Additional headers to include in all API requests and in the default OTLPSpanExporter requests. These headers will be merged with default headers. Note: If httpx_client is provided, additional_headers must be set directly on your custom httpx_client as well. If `span_exporter` is provided, these headers are not wired into that exporter and must be configured on the exporter instance directly. tracer_provider(Optional[TracerProvider]): OpenTelemetry TracerProvider to use for Langfuse. This can be useful to set to have disconnected tracing between Langfuse and other OpenTelemetry-span emitting libraries. Note: To track active spans, the context is still shared between TracerProviders. This may lead to broken trace trees. @@ -330,7 +316,6 @@ def __init__( sample_rate: Optional[float] = None, mask: Optional[MaskFunction] = None, mask_otel_spans: Optional[MaskOtelSpansFunction] = None, - blocked_instrumentation_scopes: Optional[List[str]] = None, should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[TracerProvider] = None, @@ -403,18 +388,6 @@ def __init__( "OTEL_SDK_DISABLED is set. Langfuse tracing will be disabled and no traces will appear in the UI." ) - if blocked_instrumentation_scopes is not None: - warnings.warn( - "`blocked_instrumentation_scopes` is deprecated and will be removed in a future release. " - "Use `should_export_span` instead. Example: " - "from langfuse.span_filter import is_default_export_span; " - 'blocked={"scope"}; should_export_span=lambda span: ' - "is_default_export_span(span) and (span.instrumentation_scope is None or " - "span.instrumentation_scope.name not in blocked).", - DeprecationWarning, - stacklevel=2, - ) - # Initialize api and tracer if requirements are met self._resources = LangfuseResourceManager( public_key=public_key, @@ -431,7 +404,6 @@ def __init__( mask=mask, mask_otel_spans=mask_otel_spans, tracing_enabled=self._tracing_enabled, - blocked_instrumentation_scopes=blocked_instrumentation_scopes, should_export_span=should_export_span, additional_headers=additional_headers, tracer_provider=tracer_provider, @@ -4137,7 +4109,6 @@ def create_prompt( Keyword Args: name : The name of the prompt to be created. prompt : The content of the prompt to be created. - is_active [DEPRECATED] : A flag indicating whether the prompt is active or not. This is deprecated and will be removed in a future release. Please use the 'production' label instead. labels: The labels of the prompt. Defaults to None. To create a default-served prompt, add the 'production' label. tags: The tags of the prompt. Defaults to None. Will be applied to all versions of the prompt. config: Additional structured data to be saved with the prompt. Defaults to None. diff --git a/langfuse/_client/get_client.py b/langfuse/_client/get_client.py index ff06c7d29..eceaed5ef 100644 --- a/langfuse/_client/get_client.py +++ b/langfuse/_client/get_client.py @@ -51,7 +51,6 @@ def _create_client_from_instance( sample_rate=instance.sample_rate, mask=instance.mask, mask_otel_spans=instance.mask_otel_spans, - blocked_instrumentation_scopes=instance.blocked_instrumentation_scopes, should_export_span=instance.should_export_span, additional_headers=instance.additional_headers, tracer_provider=instance.tracer_provider, diff --git a/langfuse/_client/resource_manager.py b/langfuse/_client/resource_manager.py index 4395872db..e1e091d31 100644 --- a/langfuse/_client/resource_manager.py +++ b/langfuse/_client/resource_manager.py @@ -127,7 +127,6 @@ def __new__( mask: Optional[MaskFunction] = None, mask_otel_spans: Optional[MaskOtelSpansFunction] = None, tracing_enabled: Optional[bool] = None, - blocked_instrumentation_scopes: Optional[List[str]] = None, should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[TracerProvider] = None, @@ -166,7 +165,6 @@ def __new__( tracing_enabled=tracing_enabled if tracing_enabled is not None else True, - blocked_instrumentation_scopes=blocked_instrumentation_scopes, should_export_span=should_export_span, additional_headers=additional_headers, tracer_provider=tracer_provider, @@ -196,7 +194,6 @@ def _initialize_instance( mask: Optional[MaskFunction] = None, mask_otel_spans: Optional[MaskOtelSpansFunction] = None, tracing_enabled: bool = True, - blocked_instrumentation_scopes: Optional[List[str]] = None, should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, tracer_provider: Optional[TracerProvider] = None, @@ -220,7 +217,6 @@ def _initialize_instance( self.release = release self.media_upload_thread_count = media_upload_thread_count self.sample_rate = sample_rate - self.blocked_instrumentation_scopes = blocked_instrumentation_scopes self.should_export_span = should_export_span self.additional_headers = additional_headers self.id_generator = id_generator @@ -259,7 +255,6 @@ def _initialize_instance( timeout=timeout, flush_at=flush_at, flush_interval=flush_interval, - blocked_instrumentation_scopes=blocked_instrumentation_scopes, should_export_span=should_export_span, additional_headers=additional_headers, span_exporter=span_exporter, diff --git a/langfuse/_client/span_processor.py b/langfuse/_client/span_processor.py index 89b4f5bd6..ba2f5b2de 100644 --- a/langfuse/_client/span_processor.py +++ b/langfuse/_client/span_processor.py @@ -15,7 +15,7 @@ import logging import os import threading -from typing import Callable, Dict, List, Literal, Optional, cast +from typing import Callable, Dict, Literal, Optional, cast from opentelemetry import context as context_api from opentelemetry.context import Context @@ -121,7 +121,6 @@ def __init__( timeout: Optional[int] = None, flush_at: Optional[int] = None, flush_interval: Optional[float] = None, - blocked_instrumentation_scopes: Optional[List[str]] = None, should_export_span: Optional[Callable[[ReadableSpan], bool]] = None, additional_headers: Optional[Dict[str, str]] = None, span_exporter: Optional[SpanExporter] = None, @@ -130,11 +129,6 @@ def __init__( otel_compression: Optional[Literal["gzip", "none"]] = None, ): self.public_key = public_key - self.blocked_instrumentation_scopes = ( - blocked_instrumentation_scopes - if blocked_instrumentation_scopes is not None - else [] - ) self._should_export_span = should_export_span or is_default_export_span self._app_root_lock = threading.Lock() @@ -258,16 +252,6 @@ def on_end(self, span: ReadableSpan) -> None: ) return - # Do not export spans from blocked instrumentation scopes - if self._is_blocked_instrumentation_scope(span): - langfuse_logger.debug( - "Trace: Dropping span due to blocked instrumentation scope | " - "span_name='%s' | instrumentation_scope='%s'", - span.name, - self._get_scope_name(span), - ) - return - # Apply custom or default span filter try: should_export = self._should_export_span(span) @@ -342,9 +326,6 @@ def _is_expected_exported_at_start(self, span: Span) -> bool: ): return False - if self._is_blocked_instrumentation_scope(readable_span): - return False - try: return bool(self._should_export_span(readable_span)) except Exception as error: @@ -359,12 +340,6 @@ def _is_expected_exported_at_start(self, span: Span) -> bool: return False - def _is_blocked_instrumentation_scope(self, span: ReadableSpan) -> bool: - return ( - span.instrumentation_scope is not None - and span.instrumentation_scope.name in self.blocked_instrumentation_scopes - ) - def _is_langfuse_project_span(self, span: ReadableSpan) -> bool: if not is_langfuse_span(span): return False diff --git a/tests/conftest.py b/tests/conftest.py index 0163842c6..97a78d212 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -102,10 +102,6 @@ def mock_init(self: Any, **kwargs: Any) -> None: from langfuse._client.span_filter import is_default_export_span self.public_key = kwargs.get("public_key", "test-public-key") - blocked_scopes = kwargs.get("blocked_instrumentation_scopes") - self.blocked_instrumentation_scopes = ( - blocked_scopes if blocked_scopes is not None else [] - ) self._should_export_span = ( kwargs.get("should_export_span") or is_default_export_span ) diff --git a/tests/unit/test_app_root_detection.py b/tests/unit/test_app_root_detection.py index 7fa346d1e..fdf19639a 100644 --- a/tests/unit/test_app_root_detection.py +++ b/tests/unit/test_app_root_detection.py @@ -252,12 +252,15 @@ def test_active_langfuse_scope_sets_baggage_after_root_start( assert "langfuse.trace.metadata.trace_id" not in spans["child"].attributes -def test_blocked_instrumentation_scope_parent_marks_child_as_app_root( +def test_filtered_scope_parent_marks_child_as_app_root( memory_exporter, ): tracer_provider, processor = _create_processor( memory_exporter, - blocked_instrumentation_scopes=["blocked.scope"], + should_export_span=lambda span: ( + span.instrumentation_scope is None + or span.instrumentation_scope.name != "blocked.scope" + ), ) blocked_tracer = tracer_provider.get_tracer("blocked.scope") langfuse_tracer = _langfuse_tracer(tracer_provider) diff --git a/tests/unit/test_initialization.py b/tests/unit/test_initialization.py index 7181ae45e..1f682d3fe 100644 --- a/tests/unit/test_initialization.py +++ b/tests/unit/test_initialization.py @@ -100,6 +100,16 @@ def test_env_host_fallback(self, cleanup_env_vars): assert client._base_url == "http://env-host.com" + def test_env_host_fallback_logs_no_warning(self, cleanup_env_vars, caplog): + """Test that LANGFUSE_HOST keeps working without a deprecation warning.""" + caplog.set_level("WARNING", logger="langfuse") + os.environ["LANGFUSE_HOST"] = "http://env-host.com" + + client = Langfuse(public_key="test_pk", secret_key="test_sk") + + assert client._base_url == "http://env-host.com" + assert not any("deprecated" in record.message for record in caplog.records) + def test_default_base_url(self, cleanup_env_vars): """Test that default base_url is used when nothing is set.""" client = Langfuse( diff --git a/tests/unit/test_otel.py b/tests/unit/test_otel.py index 46a085a71..ccdec39fd 100644 --- a/tests/unit/test_otel.py +++ b/tests/unit/test_otel.py @@ -120,10 +120,6 @@ def mock_init(self, **kwargs): from langfuse._client.span_filter import is_default_export_span self.public_key = kwargs.get("public_key", "test-key") - blocked_scopes = kwargs.get("blocked_instrumentation_scopes") - self.blocked_instrumentation_scopes = ( - blocked_scopes if blocked_scopes is not None else [] - ) self._should_export_span = ( kwargs.get("should_export_span") or is_default_export_span ) @@ -2408,10 +2404,6 @@ def mock_processor_init(self, **kwargs): from langfuse._client.span_filter import is_default_export_span self.public_key = kwargs.get("public_key", "test-key") - blocked_scopes = kwargs.get("blocked_instrumentation_scopes") - self.blocked_instrumentation_scopes = ( - blocked_scopes if blocked_scopes is not None else [] - ) self._should_export_span = ( kwargs.get("should_export_span") or is_default_export_span ) @@ -2446,16 +2438,13 @@ def mock_initialize(self, **kwargs): # Call original_initialize to set up all the necessary attributes original_initialize(self, **kwargs) - # Now create our custom LangfuseSpanProcessor with the actual blocked_instrumentation_scopes + # Now create our custom LangfuseSpanProcessor with the actual should_export_span from langfuse._client.span_processor import LangfuseSpanProcessor processor = LangfuseSpanProcessor( public_key=self.public_key, secret_key=self.secret_key, base_url=self.base_url, - blocked_instrumentation_scopes=kwargs.get( - "blocked_instrumentation_scopes" - ), should_export_span=kwargs.get("should_export_span"), ) # Replace its exporter with our test exporter @@ -2661,38 +2650,6 @@ def test_custom_should_export_span_with_composition( assert "known-span" in exported_span_names assert "unknown-span" not in exported_span_names - def test_blocked_scopes_override_should_export( - self, instrumentation_filtering_setup - ): - """Test that blocked scopes are dropped even when callback allows all.""" - with pytest.warns(DeprecationWarning, match="blocked_instrumentation_scopes"): - Langfuse( - public_key=instrumentation_filtering_setup["test_key"], - secret_key="test-secret-key", - base_url="http://localhost:3000", - blocked_instrumentation_scopes=["my-framework.worker"], - should_export_span=lambda span: True, - ) - - tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] - blocked_tracer = tracer_provider.get_tracer("my-framework.worker") - allowed_tracer = tracer_provider.get_tracer("custom.allowed") - - blocked_span = blocked_tracer.start_span("blocked-span") - blocked_span.end() - allowed_span = allowed_tracer.start_span("allowed-span") - allowed_span.end() - tracer_provider.force_flush() - - exported_span_names = [ - span.name - for span in instrumentation_filtering_setup[ - "blocked_exporter" - ].get_finished_spans() - ] - assert "blocked-span" not in exported_span_names - assert "allowed-span" in exported_span_names - def test_should_export_span_with_none_uses_default( self, instrumentation_filtering_setup ): @@ -2758,34 +2715,6 @@ def _failing_filter(_span): for record in caplog.records ) - def test_blocked_scope_drop_logs_scope_name( - self, instrumentation_filtering_setup, caplog - ): - """Test that blocked scope drops include scope names in debug logs.""" - caplog.set_level("DEBUG", logger="langfuse") - - with pytest.warns(DeprecationWarning, match="blocked_instrumentation_scopes"): - Langfuse( - public_key=instrumentation_filtering_setup["test_key"], - secret_key="test-secret-key", - base_url="http://localhost:3000", - blocked_instrumentation_scopes=["my.blocked.scope"], - should_export_span=lambda span: True, - ) - - tracer_provider = instrumentation_filtering_setup["test_tracer_provider"] - blocked_tracer = tracer_provider.get_tracer("my.blocked.scope") - - span = blocked_tracer.start_span("blocked-debug-span") - span.end() - tracer_provider.force_flush() - - assert any( - "Dropping span due to blocked instrumentation scope" in record.message - and "my.blocked.scope" in record.message - for record in caplog.records - ) - class TestConcurrencyAndAsync(TestOTelBase): """Tests for asynchronous and concurrent span operations."""