import structlog from opentelemetry.sdk.resources import Resource from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import SimpleSpanProcessor from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter from structlog.testing import LogCapture from structlog.contextvars import bind_contextvars, clear_contextvars from app.observability.logging import add_trace_context from app.observability.tracing import get_tracer, reset_tracing_for_tests, setup_tracing def test_logging_context_includes_request_id_and_trace_id() -> None: reset_tracing_for_tests() span_exporter = InMemorySpanExporter() tracer_provider = TracerProvider(resource=Resource.create({"service.name": "tests"})) tracer_provider.add_span_processor(SimpleSpanProcessor(span_exporter)) setup_tracing( service_name="tests", otlp_endpoint="", tracer_provider=tracer_provider, ) log_capture = LogCapture() structlog.configure( processors=[ structlog.contextvars.merge_contextvars, add_trace_context, log_capture, ], logger_factory=structlog.stdlib.LoggerFactory(), wrapper_class=structlog.make_filtering_bound_logger(20), cache_logger_on_first_use=False, ) logger = structlog.get_logger("test-logger") clear_contextvars() bind_contextvars(request_id="req-test-id") with get_tracer(__name__).start_as_current_span("test-span"): logger.info("request_log") assert len(log_capture.entries) == 1 entry = log_capture.entries[0] assert entry["event"] == "request_log" assert entry["request_id"] == "req-test-id" assert len(entry["trace_id"]) == 32 assert len(entry["span_id"]) == 16 clear_contextvars() reset_tracing_for_tests()