"""Tests for structured logging and distributed tracing.""" import json import logging from services.shared.logging import ( JSONFormatter, Span, extract_trace_context, get_service_name, get_span_id, get_trace_id, inject_trace_context, new_span_id, new_trace_id, set_trace_context, setup_logging, ) def test_new_trace_id_format(): tid = new_trace_id() assert len(tid) == 16 assert tid.isalnum() def test_new_span_id_format(): sid = new_span_id() assert len(sid) == 8 assert sid.isalnum() def test_set_and_get_trace_context(): set_trace_context(trace_id="abc123", span_id="sp01", service="test_svc") assert get_trace_id() == "abc123" assert get_span_id() == "sp01" assert get_service_name() == "test_svc" def test_json_formatter_output(): set_trace_context(trace_id="trace42", span_id="span7", service="fmt_test") formatter = JSONFormatter() record = logging.LogRecord( name="test_logger", level=logging.INFO, pathname="", lineno=0, msg="hello world", args=(), exc_info=None, ) output = formatter.format(record) parsed = json.loads(output) assert parsed["message"] == "hello world" assert parsed["level"] == "INFO" assert parsed["trace_id"] == "trace42" assert parsed["span_id"] == "span7" assert parsed["service"] == "fmt_test" assert "timestamp" in parsed def test_json_formatter_extra_fields(): set_trace_context(trace_id="t1", service="extra_test") formatter = JSONFormatter() record = logging.LogRecord( name="test", level=logging.WARNING, pathname="", lineno=0, msg="doc processed", args=(), exc_info=None, ) record.ticker = "AAPL" record.document_id = "doc-123" output = formatter.format(record) parsed = json.loads(output) assert parsed["ticker"] == "AAPL" assert parsed["document_id"] == "doc-123" def test_span_sets_and_restores_context(): set_trace_context(trace_id="parent_trace", span_id="parent_span", service="span_test") parent_span = get_span_id() with Span("test_op", ticker="MSFT") as span: assert get_trace_id() == "parent_trace" assert get_span_id() == span.span_id assert span.span_id != parent_span # Context restored after span exits assert get_span_id() == parent_span def test_span_records_duration(): set_trace_context(service="dur_test") with Span("slow_op") as span: pass # instant assert span.duration_ms >= 0 def test_span_generates_trace_id_if_missing(): set_trace_context(trace_id="", service="gen_test") with Span("auto_trace") as span: assert len(span.trace_id) == 16 def test_inject_trace_context(): set_trace_context(trace_id="inject_trace") payload = inject_trace_context({"ticker": "GOOG"}) assert payload["_trace_id"] == "inject_trace" assert payload["ticker"] == "GOOG" def test_extract_trace_context(): payload = {"ticker": "TSLA", "_trace_id": "extracted_trace"} extract_trace_context(payload) assert get_trace_id() == "extracted_trace" def test_extract_trace_context_generates_new_if_missing(): payload = {"ticker": "AMZN"} extract_trace_context(payload) assert len(get_trace_id()) == 16 def test_setup_logging_json_mode(): setup_logging("test_service", level="DEBUG", json_output=True) root = logging.getLogger() assert len(root.handlers) == 1 assert isinstance(root.handlers[0].formatter, JSONFormatter) assert root.level == logging.DEBUG assert get_service_name() == "test_service" def test_setup_logging_text_mode(): setup_logging("text_service", level="WARNING", json_output=False) root = logging.getLogger() assert len(root.handlers) == 1 assert not isinstance(root.handlers[0].formatter, JSONFormatter) assert root.level == logging.WARNING def test_config_json_logs_field(): from services.shared.config import load_config config = load_config() assert isinstance(config.json_logs, bool)