"""구조화 로깅 기반 단위 테스트 (REQ-084, dcy-029 FR-1·FR-7).""" import json import logging import pytest from trex_core.runtime import logging_setup as ls @pytest.fixture(autouse=True) def _reset_corr(): ls.set_correlation_id("") yield ls.set_correlation_id("") def _record(msg="hello", level=logging.INFO, **extra): rec = logging.LogRecord("trex.test", level, __file__, 1, msg, None, None) for k, v in extra.items(): setattr(rec, k, v) return rec class TestTextFormatter: def test_has_iso8601_kst_timestamp_level_and_msg(self): out = ls.TextFormatter().format(_record("boot ok")) # ISO8601 with +09:00 KST offset, level, message assert "+09:00" in out assert "INFO" in out assert "boot ok" in out def test_includes_correlation_id_when_set(self): ls.set_correlation_id("slot-123") rec = _record("x") ls._CorrelationFilter().filter(rec) out = ls.TextFormatter().format(rec) assert "cid=slot-123" in out def test_omits_cid_when_unset(self): rec = _record("x") ls._CorrelationFilter().filter(rec) out = ls.TextFormatter().format(rec) assert "cid=" not in out class TestJsonFormatter: def test_is_valid_json_with_required_keys(self): rec = _record("submitted") ls._CorrelationFilter().filter(rec) obj = json.loads(ls.JsonFormatter().format(rec)) assert obj["msg"] == "submitted" assert obj["level"] == "INFO" assert obj["logger"] == "trex.test" assert "+09:00" in obj["ts"] assert obj["correlation_id"] == "" def test_correlation_id_propagates(self): ls.set_correlation_id("slot-9") rec = _record("y") ls._CorrelationFilter().filter(rec) obj = json.loads(ls.JsonFormatter().format(rec)) assert obj["correlation_id"] == "slot-9" def test_extra_fields_included(self): rec = _record("z", symbol="005930") ls._CorrelationFilter().filter(rec) obj = json.loads(ls.JsonFormatter().format(rec)) assert obj["symbol"] == "005930" class TestConfigureAndContext: def test_configure_text_default(self, monkeypatch, capsys): monkeypatch.delenv("TREX_LOG_FORMAT", raising=False) ls.configure_logging(level=logging.INFO) logging.getLogger("trex.x").info("via root") err = capsys.readouterr().err assert "via root" in err and "INFO" in err and "+09:00" in err def test_configure_json_format(self, monkeypatch, capsys): monkeypatch.setenv("TREX_LOG_FORMAT", "json") ls.configure_logging(level=logging.INFO) with ls.correlation_id("slot-77"): logging.getLogger("trex.x").info("jsonline") line = [ln for ln in capsys.readouterr().err.splitlines() if "jsonline" in ln][-1] obj = json.loads(line) assert obj["msg"] == "jsonline" assert obj["correlation_id"] == "slot-77" # cleanup: restore text formatter for other tests monkeypatch.delenv("TREX_LOG_FORMAT", raising=False) ls.configure_logging(level=logging.INFO) def test_correlation_id_context_manager_restores(self): ls.set_correlation_id("outer") with ls.correlation_id("inner"): assert ls.get_correlation_id() == "inner" assert ls.get_correlation_id() == "outer" def test_configure_is_idempotent_single_handler(self): ls.configure_logging() ls.configure_logging() assert len(logging.getLogger().handlers) == 1