c889a57b6b
Test Suites / Build CI Environment (push) Has been cancelled
Test Suites / Basic Tests (push) Has been cancelled
Test Suites / End-to-End Tests (push) Has been cancelled
Test Suites / CLI Tests (push) Has been cancelled
Test Suites / Slow End-to-End Tests (push) Has been cancelled
Test Suites / Graph Database Tests (push) Has been cancelled
Test Suites / Vector DB Tests (push) Has been cancelled
Test Suites / Temporal Graph Test (push) Has been cancelled
Test Suites / Search Test on Different DBs (push) Has been cancelled
Test Suites / Example Tests (push) Has been cancelled
Test Suites / Notebook Tests (push) Has been cancelled
Test Suites / OS and Python Tests Ubuntu (push) Has been cancelled
Test Suites / OS and Python Tests Extended (push) Has been cancelled
Test Suites / LLM Test Suite (push) Has been cancelled
Test Suites / S3 File Storage Test (push) Has been cancelled
Test Suites / Run Integration Tests (push) Has been cancelled
Test Suites / MCP Tests (push) Has been cancelled
Test Suites / Docker Compose Test (push) Has been cancelled
Test Suites / Docker CI test (push) Has been cancelled
Test Suites / Relational DB Migration Tests (push) Has been cancelled
Test Suites / Distributed Cognee Test (push) Has been cancelled
Test Suites / DB Examples Tests (push) Has been cancelled
Test Suites / Test Completion Status (push) Has been cancelled
Test Suites / Claude Code Review (push) Has been cancelled
Test Suites / basic checks (push) Has been cancelled
build | Build and Push Cognee MCP Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
build | Build and Push Docker Image to dockerhub / docker-build-and-push (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.11) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Core Functionality (3.12) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (kuzu, kuzu) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges with Different Graph Databases (neo4j, neo4j) (push) Has been cancelled
Weighted Edges Tests / Test Weighted Edges Examples (push) Has been cancelled
Weighted Edges Tests / Code Quality for Weighted Edges (push) Has been cancelled
375 lines
11 KiB
Python
375 lines
11 KiB
Python
"""Unit tests for the cognee tracing / observability module."""
|
|
|
|
import os
|
|
import pytest
|
|
|
|
from cognee.modules.observability.tracing import (
|
|
CogneeSpanExporter,
|
|
CogneeTrace,
|
|
redact_secrets,
|
|
setup_tracing,
|
|
shutdown_tracing,
|
|
get_tracer,
|
|
get_exporter,
|
|
)
|
|
from cognee.modules.observability.trace_context import (
|
|
enable_tracing,
|
|
disable_tracing,
|
|
is_tracing_enabled,
|
|
get_last_trace,
|
|
get_all_traces,
|
|
clear_traces,
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Redaction tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestRedactSecrets:
|
|
def test_redacts_openai_key(self):
|
|
text = "key is sk-abcdefghijklmnopqrstuvwxyz"
|
|
result = redact_secrets(text)
|
|
assert "sk-abc" in result
|
|
assert "REDACTED" in result
|
|
assert "abcdefghijklmnopqrstuvwxyz" not in result
|
|
|
|
def test_redacts_bearer_token(self):
|
|
text = "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.long_token"
|
|
result = redact_secrets(text)
|
|
assert "REDACTED" in result
|
|
|
|
def test_preserves_normal_text(self):
|
|
text = "SELECT * FROM users WHERE name = 'Alice'"
|
|
assert redact_secrets(text) == text
|
|
|
|
def test_handles_empty_string(self):
|
|
assert redact_secrets("") == ""
|
|
|
|
def test_handles_none(self):
|
|
assert redact_secrets(None) is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CogneeSpanExporter tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCogneeSpanExporter:
|
|
def test_export_and_retrieve(self):
|
|
"""Test that spans are collected and retrievable."""
|
|
try:
|
|
from opentelemetry.sdk.trace import TracerProvider
|
|
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
|
except ImportError:
|
|
pytest.skip("opentelemetry-sdk not installed")
|
|
|
|
exporter = CogneeSpanExporter()
|
|
provider = TracerProvider()
|
|
provider.add_span_processor(SimpleSpanProcessor(exporter))
|
|
tracer = provider.get_tracer("test")
|
|
|
|
with tracer.start_as_current_span("test_span") as span:
|
|
span.set_attribute("key", "value")
|
|
|
|
provider.force_flush()
|
|
|
|
spans = exporter.get_last_trace_spans()
|
|
assert spans is not None
|
|
assert len(spans) == 1
|
|
assert spans[0]["name"] == "test_span"
|
|
assert spans[0]["attributes"]["key"] == "value"
|
|
assert spans[0]["duration_ms"] >= 0
|
|
|
|
provider.shutdown()
|
|
|
|
def test_clear(self):
|
|
try:
|
|
from opentelemetry.sdk.trace import TracerProvider
|
|
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
|
except ImportError:
|
|
pytest.skip("opentelemetry-sdk not installed")
|
|
|
|
exporter = CogneeSpanExporter()
|
|
provider = TracerProvider()
|
|
provider.add_span_processor(SimpleSpanProcessor(exporter))
|
|
tracer = provider.get_tracer("test")
|
|
|
|
with tracer.start_as_current_span("span1"):
|
|
pass
|
|
|
|
provider.force_flush()
|
|
assert exporter.get_last_trace_spans() is not None
|
|
|
|
exporter.clear()
|
|
assert exporter.get_last_trace_spans() is None
|
|
|
|
provider.shutdown()
|
|
|
|
def test_multiple_traces(self):
|
|
try:
|
|
from opentelemetry.sdk.trace import TracerProvider
|
|
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
|
from opentelemetry import context as otel_context
|
|
except ImportError:
|
|
pytest.skip("opentelemetry-sdk not installed")
|
|
|
|
exporter = CogneeSpanExporter()
|
|
provider = TracerProvider()
|
|
provider.add_span_processor(SimpleSpanProcessor(exporter))
|
|
tracer = provider.get_tracer("test")
|
|
|
|
# Create two separate root spans (separate traces)
|
|
with tracer.start_as_current_span("trace1_root"):
|
|
pass
|
|
|
|
# Detach context to start fresh trace
|
|
token = otel_context.attach(otel_context.Context())
|
|
try:
|
|
with tracer.start_as_current_span("trace2_root"):
|
|
pass
|
|
finally:
|
|
otel_context.detach(token)
|
|
|
|
provider.force_flush()
|
|
|
|
all_traces = exporter.get_all_traces()
|
|
assert len(all_traces) >= 1 # At least one trace
|
|
|
|
provider.shutdown()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# CogneeTrace tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCogneeTrace:
|
|
def test_summary_empty(self):
|
|
trace = CogneeTrace([])
|
|
summary = trace.summary()
|
|
assert summary["operation"] == ""
|
|
assert summary["total_duration_ms"] == 0.0
|
|
assert summary["span_count"] == 0
|
|
|
|
def test_summary_with_spans(self):
|
|
spans = [
|
|
{
|
|
"name": "cognee.search",
|
|
"trace_id": "abc123",
|
|
"span_id": "span1",
|
|
"parent_span_id": None,
|
|
"start_time_ns": 1000000000,
|
|
"end_time_ns": 2000000000,
|
|
"duration_ms": 1000.0,
|
|
"status": "OK",
|
|
"attributes": {},
|
|
},
|
|
{
|
|
"name": "cognee.graph.query",
|
|
"trace_id": "abc123",
|
|
"span_id": "span2",
|
|
"parent_span_id": "span1",
|
|
"start_time_ns": 1100000000,
|
|
"end_time_ns": 1200000000,
|
|
"duration_ms": 100.0,
|
|
"status": "OK",
|
|
"attributes": {"cognee.db.system": "ladybug"},
|
|
},
|
|
]
|
|
|
|
trace = CogneeTrace(spans)
|
|
summary = trace.summary()
|
|
assert summary["operation"] == "cognee.search"
|
|
assert summary["total_duration_ms"] == 1000.0
|
|
assert summary["span_count"] == 2
|
|
assert "cognee.graph.query" in summary["breakdown"]
|
|
assert summary["breakdown"]["cognee.graph.query"]["count"] == 1
|
|
|
|
def test_spans_sorted(self):
|
|
spans = [
|
|
{
|
|
"name": "b",
|
|
"span_id": "s2",
|
|
"parent_span_id": None,
|
|
"start_time_ns": 2000,
|
|
"end_time_ns": 3000,
|
|
"duration_ms": 1.0,
|
|
"status": "OK",
|
|
"attributes": {},
|
|
"trace_id": "t1",
|
|
},
|
|
{
|
|
"name": "a",
|
|
"span_id": "s1",
|
|
"parent_span_id": None,
|
|
"start_time_ns": 1000,
|
|
"end_time_ns": 2000,
|
|
"duration_ms": 1.0,
|
|
"status": "OK",
|
|
"attributes": {},
|
|
"trace_id": "t1",
|
|
},
|
|
]
|
|
trace = CogneeTrace(spans)
|
|
sorted_spans = trace.spans()
|
|
assert sorted_spans[0]["name"] == "a"
|
|
assert sorted_spans[1]["name"] == "b"
|
|
|
|
def test_tree(self):
|
|
spans = [
|
|
{
|
|
"name": "root",
|
|
"span_id": "s1",
|
|
"parent_span_id": None,
|
|
"start_time_ns": 1000,
|
|
"end_time_ns": 3000,
|
|
"duration_ms": 2.0,
|
|
"status": "OK",
|
|
"attributes": {},
|
|
"trace_id": "t1",
|
|
},
|
|
{
|
|
"name": "child",
|
|
"span_id": "s2",
|
|
"parent_span_id": "s1",
|
|
"start_time_ns": 1500,
|
|
"end_time_ns": 2500,
|
|
"duration_ms": 1.0,
|
|
"status": "OK",
|
|
"attributes": {},
|
|
"trace_id": "t1",
|
|
},
|
|
]
|
|
trace = CogneeTrace(spans)
|
|
tree = trace.tree()
|
|
assert tree["name"] == "root"
|
|
assert len(tree["children"]) == 1
|
|
assert tree["children"][0]["name"] == "child"
|
|
|
|
def test_summary_errors(self):
|
|
spans = [
|
|
{
|
|
"name": "failed_op",
|
|
"span_id": "s1",
|
|
"parent_span_id": None,
|
|
"start_time_ns": 1000,
|
|
"end_time_ns": 2000,
|
|
"duration_ms": 1.0,
|
|
"status": "ERROR",
|
|
"attributes": {},
|
|
"trace_id": "t1",
|
|
},
|
|
]
|
|
trace = CogneeTrace(spans)
|
|
summary = trace.summary()
|
|
assert len(summary["errors"]) == 1
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Enable/disable lifecycle
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestTracingLifecycle:
|
|
def setup_method(self):
|
|
"""Ensure clean state before each test."""
|
|
disable_tracing()
|
|
os.environ.pop("COGNEE_TRACING_ENABLED", None)
|
|
|
|
def teardown_method(self):
|
|
disable_tracing()
|
|
os.environ.pop("COGNEE_TRACING_ENABLED", None)
|
|
|
|
def test_disabled_by_default(self):
|
|
assert not is_tracing_enabled()
|
|
|
|
def test_enable_disable(self):
|
|
try:
|
|
import opentelemetry # noqa: F401
|
|
except ImportError:
|
|
pytest.skip("opentelemetry-sdk not installed")
|
|
|
|
enable_tracing()
|
|
assert is_tracing_enabled()
|
|
|
|
disable_tracing()
|
|
assert not is_tracing_enabled()
|
|
|
|
def test_env_var_enables(self):
|
|
os.environ["COGNEE_TRACING_ENABLED"] = "true"
|
|
assert is_tracing_enabled()
|
|
|
|
def test_get_last_trace_when_disabled(self):
|
|
assert get_last_trace() is None
|
|
|
|
def test_get_all_traces_when_disabled(self):
|
|
assert get_all_traces() == []
|
|
|
|
def test_clear_traces_when_disabled(self):
|
|
# Should not raise
|
|
clear_traces()
|
|
|
|
def test_full_lifecycle(self):
|
|
try:
|
|
import opentelemetry # noqa: F401
|
|
except ImportError:
|
|
pytest.skip("opentelemetry-sdk not installed")
|
|
|
|
enable_tracing()
|
|
tracer = get_tracer()
|
|
assert tracer is not None
|
|
|
|
with tracer.start_as_current_span("test_op") as span:
|
|
span.set_attribute("test_key", "test_val")
|
|
|
|
trace = get_last_trace()
|
|
assert trace is not None
|
|
summary = trace.summary()
|
|
assert summary["operation"] == "test_op"
|
|
assert summary["span_count"] == 1
|
|
|
|
clear_traces()
|
|
assert get_last_trace() is None
|
|
|
|
disable_tracing()
|
|
assert get_tracer() is None
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Setup/shutdown
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestSetupTracing:
|
|
def teardown_method(self):
|
|
shutdown_tracing()
|
|
|
|
def test_setup_returns_tracer(self):
|
|
try:
|
|
import opentelemetry # noqa: F401
|
|
except ImportError:
|
|
pytest.skip("opentelemetry-sdk not installed")
|
|
|
|
tracer = setup_tracing()
|
|
assert tracer is not None
|
|
|
|
def test_setup_with_console(self):
|
|
try:
|
|
import opentelemetry # noqa: F401
|
|
except ImportError:
|
|
pytest.skip("opentelemetry-sdk not installed")
|
|
|
|
tracer = setup_tracing(console_output=True)
|
|
assert tracer is not None
|
|
|
|
def test_exporter_available_after_setup(self):
|
|
try:
|
|
import opentelemetry # noqa: F401
|
|
except ImportError:
|
|
pytest.skip("opentelemetry-sdk not installed")
|
|
|
|
setup_tracing()
|
|
assert get_exporter() is not None
|