INF-3 Правка последовательности выводы сообщений
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "plba"
|
name = "plba"
|
||||||
version = "0.3.18"
|
version = "0.3.19"
|
||||||
description = "Platform runtime for business applications"
|
description = "Platform runtime for business applications"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.11"
|
requires-python = ">=3.11"
|
||||||
|
|||||||
@@ -84,9 +84,12 @@ class TraceResponseRenderer:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _render_text(self, trace_view: TraceLogView, request: TraceQueryRequest) -> PlainTextResponse:
|
def _render_text(self, trace_view: TraceLogView, request: TraceQueryRequest) -> PlainTextResponse:
|
||||||
lines = self._text_trace_lines(trace_view, request)
|
lineage = [*trace_view.ancestors, trace_view]
|
||||||
for index, ancestor in enumerate(trace_view.ancestors, start=1):
|
lines: list[str] = []
|
||||||
lines.extend(["", f"ancestor[{index}]:", *self._text_trace_lines(ancestor, request)])
|
for index, entry in enumerate(lineage):
|
||||||
|
if index > 0:
|
||||||
|
lines.append("")
|
||||||
|
lines.extend(self._text_trace_lines(entry, request))
|
||||||
return PlainTextResponse(content="\n".join(lines))
|
return PlainTextResponse(content="\n".join(lines))
|
||||||
|
|
||||||
def _render_html(self, trace_view: TraceLogView, request: TraceQueryRequest) -> HTMLResponse:
|
def _render_html(self, trace_view: TraceLogView, request: TraceQueryRequest) -> HTMLResponse:
|
||||||
@@ -179,15 +182,12 @@ class TraceResponseRenderer:
|
|||||||
return f"/traces/{trace_id}?{query}"
|
return f"/traces/{trace_id}?{query}"
|
||||||
|
|
||||||
def _html_lines(self, trace_view: TraceLogView, request: TraceQueryRequest) -> str:
|
def _html_lines(self, trace_view: TraceLogView, request: TraceQueryRequest) -> str:
|
||||||
lines = self._html_trace_lines(trace_view, request)
|
lineage = [*trace_view.ancestors, trace_view]
|
||||||
for index, ancestor in enumerate(trace_view.ancestors, start=1):
|
lines: list[str] = []
|
||||||
lines.extend(
|
for index, entry in enumerate(lineage):
|
||||||
[
|
if index > 0:
|
||||||
self._html_plain_line(""),
|
lines.append(self._html_plain_line(""))
|
||||||
self._html_plain_line(f"ancestor[{index}]:"),
|
lines.extend(self._html_trace_lines(entry, request))
|
||||||
*self._html_trace_lines(ancestor, request),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
return "".join(lines)
|
return "".join(lines)
|
||||||
|
|
||||||
def _trace_payload(self, trace_view: TraceLogView, request: TraceQueryRequest) -> dict[str, object]:
|
def _trace_payload(self, trace_view: TraceLogView, request: TraceQueryRequest) -> dict[str, object]:
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ class MySqlTraceLogReader(TraceLogReader):
|
|||||||
current_trace_id = current_parent_id
|
current_trace_id = current_parent_id
|
||||||
if remaining_depth is not None:
|
if remaining_depth is not None:
|
||||||
remaining_depth -= 1
|
remaining_depth -= 1
|
||||||
|
ancestors.reverse()
|
||||||
return ancestors
|
return ancestors
|
||||||
|
|
||||||
def _trace_exists(self, trace_id: str) -> bool:
|
def _trace_exists(self, trace_id: str) -> bool:
|
||||||
|
|||||||
+131
-10
@@ -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}),
|
_trace_record(row_id=3, level="INFO", message="done", attrs_json={"batch": 7}),
|
||||||
),
|
),
|
||||||
ancestors=(
|
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(
|
TraceLogView(
|
||||||
trace_id="parent-1",
|
trace_id="parent-1",
|
||||||
parent_id="root-1",
|
parent_id="root-1",
|
||||||
child_ids=("trace-1", "sibling-1"),
|
child_ids=("trace-1", "sibling-1"),
|
||||||
records=(
|
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.status_code == 200
|
||||||
assert response.json()["ancestors"] == [
|
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",
|
"trace_id": "parent-1",
|
||||||
"parent_id": "root-1",
|
"parent_id": "root-1",
|
||||||
"child_ids": ["trace-1", "sibling-1"],
|
"child_ids": ["trace-1", "sibling-1"],
|
||||||
"messages": [
|
"messages": [
|
||||||
{
|
{
|
||||||
"id": 4,
|
"id": 5,
|
||||||
"trace_id": "trace-1",
|
"trace_id": "trace-1",
|
||||||
"event_time": "2026-04-28T10:11:12+00:00",
|
"event_time": "2026-04-28T10:11:12+00:00",
|
||||||
"step": "process",
|
"step": "process",
|
||||||
@@ -317,11 +341,17 @@ def test_trace_endpoint_renders_ancestors_in_text_mode() -> None:
|
|||||||
child_ids=(),
|
child_ids=(),
|
||||||
records=(_trace_record(row_id=1, level="INFO", message="child message"),),
|
records=(_trace_record(row_id=1, level="INFO", message="child message"),),
|
||||||
ancestors=(
|
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(
|
TraceLogView(
|
||||||
trace_id="parent-1",
|
trace_id="parent-1",
|
||||||
parent_id="root-1",
|
parent_id="root-1",
|
||||||
child_ids=("trace-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.status_code == 200
|
||||||
assert response.text == (
|
assert response.text == (
|
||||||
"trace_id: trace-1\n"
|
"trace_id: root-1\n"
|
||||||
"parent_id: parent-1\n"
|
"parent_id: \n"
|
||||||
"child_ids:\n"
|
"child_ids:\n"
|
||||||
|
" - parent-1\n"
|
||||||
"------------------------------\n"
|
"------------------------------\n"
|
||||||
"step: process\n"
|
"step: process\n"
|
||||||
"child message\n"
|
"root message\n"
|
||||||
"\n"
|
"\n"
|
||||||
"ancestor[1]:\n"
|
|
||||||
"trace_id: parent-1\n"
|
"trace_id: parent-1\n"
|
||||||
"parent_id: root-1\n"
|
"parent_id: root-1\n"
|
||||||
"child_ids:\n"
|
"child_ids:\n"
|
||||||
" - trace-1\n"
|
" - trace-1\n"
|
||||||
"------------------------------\n"
|
"------------------------------\n"
|
||||||
"step: process\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",),
|
child_ids=("child-1",),
|
||||||
records=(_trace_record(row_id=1, level="INFO", message="loaded prices"),),
|
records=(_trace_record(row_id=1, level="INFO", message="loaded prices"),),
|
||||||
ancestors=(
|
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(
|
TraceLogView(
|
||||||
trace_id="parent-1",
|
trace_id="parent-1",
|
||||||
parent_id="root-1",
|
parent_id="root-1",
|
||||||
child_ids=("trace-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()
|
client.close()
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
assert response.text.index('href="/traces/root-1?format=html&levels=ERROR%2CWARNING%2CINFO&attrs_json=true&ancestor_depth=all"') < response.text.index('href="/traces/parent-1?format=html&levels=ERROR%2CWARNING%2CINFO&attrs_json=true&ancestor_depth=all"') < response.text.index('href="/traces/trace-1?format=html&levels=ERROR%2CWARNING%2CINFO&attrs_json=true&ancestor_depth=all"')
|
||||||
assert 'href="/traces/trace-1?format=html&levels=ERROR%2CWARNING%2CINFO&attrs_json=true&ancestor_depth=all"' in response.text
|
assert 'href="/traces/trace-1?format=html&levels=ERROR%2CWARNING%2CINFO&attrs_json=true&ancestor_depth=all"' in response.text
|
||||||
|
assert 'href="/traces/root-1?format=html&levels=ERROR%2CWARNING%2CINFO&attrs_json=true&ancestor_depth=all"' in response.text
|
||||||
assert 'href="/traces/parent-1?format=html&levels=ERROR%2CWARNING%2CINFO&attrs_json=true&ancestor_depth=all"' in response.text
|
assert 'href="/traces/parent-1?format=html&levels=ERROR%2CWARNING%2CINFO&attrs_json=true&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
|
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].trace_id == "parent-1"
|
||||||
assert view.ancestors[0].parent_id == "root-1"
|
assert view.ancestors[0].parent_id == "root-1"
|
||||||
assert view.ancestors[0].child_ids == ("trace-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")
|
||||||
|
|||||||
Reference in New Issue
Block a user