Skip to content
Merged
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
6 changes: 4 additions & 2 deletions mellea/telemetry/tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,10 @@ def _setup_tracer_provider() -> Any:
_tracer_provider = _setup_tracer_provider()
# Create separate tracers for application and backend
_mellea_version = version("mellea")
_application_tracer = trace.get_tracer("mellea.application", _mellea_version) # type: ignore
_backend_tracer = trace.get_tracer("mellea.backend", _mellea_version) # type: ignore
_application_tracer = _tracer_provider.get_tracer(
"mellea.application", _mellea_version
)
_backend_tracer = _tracer_provider.get_tracer("mellea.backend", _mellea_version)


def is_application_tracing_enabled() -> bool:
Expand Down
11 changes: 11 additions & 0 deletions test/telemetry/test_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ def clean_metrics_env(monkeypatch):
def enable_metrics(monkeypatch):
"""Enable metrics for tests."""
monkeypatch.setenv("MELLEA_METRICS_ENABLED", "true")
# Clear other env vars to prevent user-set values from leaking into reload
monkeypatch.delenv("MELLEA_METRICS_CONSOLE", raising=False)
monkeypatch.delenv("MELLEA_METRICS_OTLP", raising=False)
monkeypatch.delenv("MELLEA_METRICS_PROMETHEUS", raising=False)
monkeypatch.delenv("OTEL_EXPORTER_OTLP_ENDPOINT", raising=False)
monkeypatch.delenv("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", raising=False)
monkeypatch.delenv("OTEL_METRIC_EXPORT_INTERVAL", raising=False)
monkeypatch.delenv("OTEL_SERVICE_NAME", raising=False)
# Force reload of metrics module to pick up env vars
import importlib

Expand Down Expand Up @@ -454,6 +462,9 @@ def test_otlp_enabled_without_endpoint_warning(monkeypatch):
"""Test that enabling OTLP without endpoint produces helpful warning."""
monkeypatch.setenv("MELLEA_METRICS_ENABLED", "true")
monkeypatch.setenv("MELLEA_METRICS_OTLP", "true")
# Ensure no endpoint env vars are set (user env could have these)
monkeypatch.delenv("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", raising=False)
monkeypatch.delenv("OTEL_EXPORTER_OTLP_ENDPOINT", raising=False)

import importlib

Expand Down
16 changes: 15 additions & 1 deletion test/telemetry/test_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,21 @@ def enable_backend_tracing(monkeypatch):
importlib.reload(mellea.telemetry.tracing)


def test_telemetry_disabled_by_default():
@pytest.fixture
def disable_tracing(monkeypatch):
"""Disable all tracing for tests."""
monkeypatch.delenv("MELLEA_TRACE_APPLICATION", raising=False)
monkeypatch.delenv("MELLEA_TRACE_BACKEND", raising=False)
import importlib

import mellea.telemetry.tracing

importlib.reload(mellea.telemetry.tracing)
yield
importlib.reload(mellea.telemetry.tracing)


def test_telemetry_disabled_by_default(disable_tracing):
"""Test that telemetry is disabled by default."""
from mellea.telemetry import (
is_application_tracing_enabled,
Expand Down
40 changes: 19 additions & 21 deletions test/telemetry/test_tracing_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,18 @@
@pytest.fixture(scope="module", autouse=True)
def setup_telemetry():
"""Set up telemetry for all tests in this module."""
import importlib

import mellea.telemetry.tracing

mp = pytest.MonkeyPatch()
mp.setenv("MELLEA_TRACE_BACKEND", "true")
importlib.reload(mellea.telemetry.tracing)

yield

mp.undo()
mp.setenv("MELLEA_TRACE_BACKEND", "false")
importlib.reload(mellea.telemetry.tracing)


@pytest.fixture
Expand All @@ -67,10 +73,8 @@ def span_exporter():


@pytest.mark.asyncio
async def test_span_duration_captures_async_operation(span_exporter, gh_run):
async def test_span_duration_captures_async_operation(span_exporter):
"""Test that span duration includes the full async operation time."""
if gh_run:
pytest.skip("Skipping in CI - requires Ollama")

backend = OllamaModelBackend(model_id=IBM_GRANITE_4_HYBRID_MICRO.ollama_name) # type: ignore
ctx = SimpleContext()
Expand Down Expand Up @@ -113,17 +117,19 @@ async def test_span_duration_captures_async_operation(span_exporter, gh_run):


@pytest.mark.asyncio
async def test_context_propagation_parent_child(span_exporter, gh_run):
async def test_context_propagation_parent_child(span_exporter):
"""Test that parent-child span relationships are maintained."""
if gh_run:
pytest.skip("Skipping in CI - requires Ollama")

backend = OllamaModelBackend(model_id=IBM_GRANITE_4_HYBRID_MICRO.ollama_name) # type: ignore
ctx = SimpleContext()
ctx = ctx.add(Message(role="user", content="Say 'test' and nothing else"))

# Create a parent span
tracer = trace.get_tracer(__name__)
# Create a parent span using the module's own tracer provider
# (not the global one, which may be pinned to a different provider
# due to OTel's set-once semantics for set_tracer_provider)
from mellea.telemetry import tracing

tracer = tracing._tracer_provider.get_tracer(__name__)
with tracer.start_as_current_span("parent_operation"):
mot, _ = await backend.generate_from_context(
Message(role="assistant", content=""), ctx
Expand Down Expand Up @@ -158,10 +164,8 @@ async def test_context_propagation_parent_child(span_exporter, gh_run):


@pytest.mark.asyncio
async def test_token_usage_recorded_after_completion(span_exporter, gh_run):
async def test_token_usage_recorded_after_completion(span_exporter):
"""Test that token usage metrics are recorded after async completion."""
if gh_run:
pytest.skip("Skipping in CI - requires Ollama")

backend = OllamaModelBackend(model_id=IBM_GRANITE_4_HYBRID_MICRO.ollama_name) # type: ignore
ctx = SimpleContext()
Expand Down Expand Up @@ -209,10 +213,8 @@ async def test_token_usage_recorded_after_completion(span_exporter, gh_run):


@pytest.mark.asyncio
async def test_span_not_closed_prematurely(span_exporter, gh_run):
async def test_span_not_closed_prematurely(span_exporter):
"""Test that spans are not closed before async operations complete."""
if gh_run:
pytest.skip("Skipping in CI - requires Ollama")

backend = OllamaModelBackend(model_id=IBM_GRANITE_4_HYBRID_MICRO.ollama_name) # type: ignore
ctx = SimpleContext()
Expand Down Expand Up @@ -245,10 +247,8 @@ async def test_span_not_closed_prematurely(span_exporter, gh_run):


@pytest.mark.asyncio
async def test_multiple_generations_separate_spans(span_exporter, gh_run):
async def test_multiple_generations_separate_spans(span_exporter):
"""Test that multiple generations create separate spans."""
if gh_run:
pytest.skip("Skipping in CI - requires Ollama")

backend = OllamaModelBackend(model_id=IBM_GRANITE_4_HYBRID_MICRO.ollama_name) # type: ignore
ctx = SimpleContext()
Expand Down Expand Up @@ -279,10 +279,8 @@ async def test_multiple_generations_separate_spans(span_exporter, gh_run):


@pytest.mark.asyncio
async def test_streaming_span_duration(span_exporter, gh_run):
async def test_streaming_span_duration(span_exporter):
"""Test that streaming operations have accurate span durations."""
if gh_run:
pytest.skip("Skipping in CI - requires Ollama")

from mellea.backends.model_options import ModelOption

Expand Down
Loading