Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions langfuse/_client/span_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"""

import base64
import logging
import os
import threading
from typing import Callable, Dict, List, Optional, cast
Expand Down Expand Up @@ -218,9 +219,10 @@ def on_end(self, span: ReadableSpan) -> None:
)
return

langfuse_logger.debug(
f"Trace: Processing span name='{span._name}' | Full details:\n{span_formatter(span)}"
)
if langfuse_logger.isEnabledFor(logging.DEBUG):
langfuse_logger.debug(
f"Trace: Processing span name='{span._name}' | Full details:\n{span_formatter(span)}"
)

super().on_end(span)
finally:
Expand Down
63 changes: 63 additions & 0 deletions tests/unit/test_span_processor.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
import logging
from typing import Sequence
from unittest.mock import Mock, patch

from opentelemetry.sdk.trace import ReadableSpan
from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult

import langfuse._client.span_processor as span_processor_module
from langfuse._client.environment_variables import (
LANGFUSE_FLUSH_AT,
LANGFUSE_FLUSH_INTERVAL,
)
from langfuse._client.span_processor import LangfuseSpanProcessor
from langfuse.logger import langfuse_logger


class NoOpSpanExporter(SpanExporter):
Expand Down Expand Up @@ -54,3 +58,62 @@ def test_span_processor_uses_env_flush_settings_when_constructor_omits_them(
assert processor._batch_processor._schedule_delay_millis == 3250
finally:
processor.shutdown()


def _make_processor() -> LangfuseSpanProcessor:
return LangfuseSpanProcessor(
public_key="pk-test",
secret_key="sk-test",
base_url="http://localhost:3000",
span_exporter=NoOpSpanExporter(),
)


def _reach_debug_log_line(processor, monkeypatch):
"""Force on_end past its early-return guards to the debug-log statement."""
monkeypatch.setattr(processor, "_is_langfuse_project_span", lambda span: True)
monkeypatch.setattr(
processor, "_is_blocked_instrumentation_scope", lambda span: False
)
monkeypatch.setattr(processor, "_should_export_span", lambda span: True)
monkeypatch.setattr(processor, "_cleanup_app_root_state", lambda span: None)
# Isolate from the batch exporter's on_end
monkeypatch.setattr(
span_processor_module.BatchSpanProcessor,
"on_end",
lambda self, span: None,
)


def test_on_end_does_not_call_span_formatter_when_debug_disabled(monkeypatch):
processor = _make_processor()
_reach_debug_log_line(processor, monkeypatch)
span = Mock()
span._name = "test-span"

original_level = langfuse_logger.level
try:
langfuse_logger.setLevel(logging.WARNING)
with patch.object(span_processor_module, "span_formatter") as mock_formatter:
processor.on_end(span)
mock_formatter.assert_not_called()
finally:
langfuse_logger.setLevel(original_level)
processor.shutdown()


def test_on_end_calls_span_formatter_when_debug_enabled(monkeypatch):
processor = _make_processor()
_reach_debug_log_line(processor, monkeypatch)
span = Mock()
span._name = "test-span"

original_level = langfuse_logger.level
try:
langfuse_logger.setLevel(logging.DEBUG)
with patch.object(span_processor_module, "span_formatter") as mock_formatter:
processor.on_end(span)
mock_formatter.assert_called_once()
finally:
langfuse_logger.setLevel(original_level)
processor.shutdown()