INF-3 Правка последовательности выводы сообщений

This commit is contained in:
2026-05-04 11:51:40 +03:00
parent ec3198dbf1
commit 8cad1d00ec
4 changed files with 145 additions and 23 deletions
+131 -10
View File
@@ -227,12 +227,20 @@ def test_trace_endpoint_returns_json_payload_with_ancestors() -> None:
_trace_record(row_id=3, level="INFO", message="done", attrs_json={"batch": 7}),
),
ancestors=(
TraceLogView(
trace_id="root-1",
parent_id=None,
child_ids=("parent-1",),
records=(
_trace_record(row_id=4, level="INFO", message="root info"),
),
),
TraceLogView(
trace_id="parent-1",
parent_id="root-1",
child_ids=("trace-1", "sibling-1"),
records=(
_trace_record(row_id=4, level="WARNING", message="parent warning"),
_trace_record(row_id=5, level="WARNING", message="parent warning"),
),
),
),
@@ -246,13 +254,29 @@ def test_trace_endpoint_returns_json_payload_with_ancestors() -> None:
assert response.status_code == 200
assert response.json()["ancestors"] == [
{
"trace_id": "root-1",
"parent_id": "",
"child_ids": ["parent-1"],
"messages": [
{
"id": 4,
"trace_id": "trace-1",
"event_time": "2026-04-28T10:11:12+00:00",
"step": "process",
"status": "failed",
"level": "INFO",
"message": "root info",
}
],
},
{
"trace_id": "parent-1",
"parent_id": "root-1",
"child_ids": ["trace-1", "sibling-1"],
"messages": [
{
"id": 4,
"id": 5,
"trace_id": "trace-1",
"event_time": "2026-04-28T10:11:12+00:00",
"step": "process",
@@ -317,11 +341,17 @@ def test_trace_endpoint_renders_ancestors_in_text_mode() -> None:
child_ids=(),
records=(_trace_record(row_id=1, level="INFO", message="child message"),),
ancestors=(
TraceLogView(
trace_id="root-1",
parent_id=None,
child_ids=("parent-1",),
records=(_trace_record(row_id=2, level="INFO", message="root message"),),
),
TraceLogView(
trace_id="parent-1",
parent_id="root-1",
child_ids=("trace-1",),
records=(_trace_record(row_id=2, level="WARNING", message="parent message"),),
records=(_trace_record(row_id=3, level="WARNING", message="parent message"),),
),
),
)
@@ -334,21 +364,28 @@ def test_trace_endpoint_renders_ancestors_in_text_mode() -> None:
assert response.status_code == 200
assert response.text == (
"trace_id: trace-1\n"
"parent_id: parent-1\n"
"trace_id: root-1\n"
"parent_id: \n"
"child_ids:\n"
" - parent-1\n"
"------------------------------\n"
"step: process\n"
"child message\n"
"root message\n"
"\n"
"ancestor[1]:\n"
"trace_id: parent-1\n"
"parent_id: root-1\n"
"child_ids:\n"
" - trace-1\n"
"------------------------------\n"
"step: process\n"
"parent message"
"parent message\n"
"\n"
"trace_id: trace-1\n"
"parent_id: parent-1\n"
"child_ids:\n"
"------------------------------\n"
"step: process\n"
"child message"
)
@@ -360,11 +397,17 @@ def test_trace_endpoint_preserves_ancestor_depth_in_html_links() -> None:
child_ids=("child-1",),
records=(_trace_record(row_id=1, level="INFO", message="loaded prices"),),
ancestors=(
TraceLogView(
trace_id="root-1",
parent_id=None,
child_ids=("parent-1",),
records=(_trace_record(row_id=2, level="INFO", message="root info"),),
),
TraceLogView(
trace_id="parent-1",
parent_id="root-1",
child_ids=("trace-1",),
records=(_trace_record(row_id=2, level="WARNING", message="parent warning"),),
records=(_trace_record(row_id=3, level="WARNING", message="parent warning"),),
),
),
)
@@ -376,9 +419,11 @@ def test_trace_endpoint_preserves_ancestor_depth_in_html_links() -> None:
client.close()
assert response.status_code == 200
assert response.text.index('href="/traces/root-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true&amp;ancestor_depth=all"') < response.text.index('href="/traces/parent-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true&amp;ancestor_depth=all"') < response.text.index('href="/traces/trace-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true&amp;ancestor_depth=all"')
assert 'href="/traces/trace-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true&amp;ancestor_depth=all"' in response.text
assert 'href="/traces/root-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true&amp;ancestor_depth=all"' in response.text
assert 'href="/traces/parent-1?format=html&amp;levels=ERROR%2CWARNING%2CINFO&amp;attrs_json=true&amp;ancestor_depth=all"' in response.text
assert '<div class="line">ancestor[1]:</div>' in response.text
assert "root info" in response.text
assert "parent warning" in response.text
@@ -604,3 +649,79 @@ def test_mysql_trace_log_reader_loads_requested_ancestors() -> None:
assert view.ancestors[0].trace_id == "parent-1"
assert view.ancestors[0].parent_id == "root-1"
assert view.ancestors[0].child_ids == ("trace-1",)
def test_mysql_trace_log_reader_orders_ancestors_root_first() -> None:
class FakeCursor:
def __init__(self) -> None:
self.executed: list[tuple[str, tuple[object, ...]]] = []
self._current_query = ""
def execute(self, query: str, params: tuple[object, ...]) -> None:
self.executed.append((query, params))
self._current_query = query
def fetchone(self) -> dict[str, object] | None:
if self.executed[-1][1] == ("trace-1",):
return {"parent_id": "parent-1"}
if self.executed[-1][1] == ("parent-1",):
return {"parent_id": "root-1"}
if self.executed[-1][1] == ("root-1",):
return {"parent_id": None}
return None
def fetchall(self) -> list[dict[str, object]]:
if "WHERE parent_id = %s" in self._current_query:
parent_id = self.executed[-1][1][0]
if parent_id == "root-1":
return [{"trace_id": "parent-1"}]
if parent_id == "parent-1":
return [{"trace_id": "trace-1"}]
return []
trace_id = self.executed[-1][1][0]
return [
{
"id": 8,
"trace_id": trace_id,
"event_time": datetime(2026, 4, 28, 10, 11, 12, tzinfo=timezone.utc),
"step": "parse",
"status": "failed",
"level": "ERROR",
"message": f"broken:{trace_id}",
"attrs_json": '{"attempt":1}',
}
]
def __enter__(self) -> FakeCursor:
return self
def __exit__(self, exc_type, exc, tb) -> None:
return None
class FakeConnection:
def __init__(self, cursor: FakeCursor) -> None:
self._cursor = cursor
def cursor(self) -> FakeCursor:
return self._cursor
def __enter__(self) -> FakeConnection:
return self
def __exit__(self, exc_type, exc, tb) -> None:
return None
class FakeConnectionFactory:
def __init__(self) -> None:
self.cursor = FakeCursor()
def connect(self) -> FakeConnection:
return FakeConnection(self.cursor)
factory = FakeConnectionFactory()
reader = MySqlTraceLogReader(factory) # type: ignore[arg-type]
view = reader.read_trace("trace-1", ("ERROR",), 2)
assert view is not None
assert tuple(ancestor.trace_id for ancestor in view.ancestors) == ("root-1", "parent-1")