feat: add logging routes and update health page to display system logs
CI / lint-test-build (push) Failing after 12s

This commit is contained in:
2026-06-07 18:10:50 +02:00
parent cf5ff2e2d8
commit 1e4086a0fd
3 changed files with 162 additions and 3 deletions
+46
View File
@@ -1493,3 +1493,49 @@ async def dashboard_api_pairings_sync(request: Request) -> HTMLResponse:
name="partials/pairings_table.html",
context={"request": request, "pairings": pairings},
)
# ── Log routes ──────────────────────────────────────────────────────────────
@router.get("/dashboard/fragment/logs", response_class=HTMLResponse)
async def dashboard_logs_fragment(
request: Request,
level: str | None = None,
page: int = 1,
per_page: int = 50,
) -> HTMLResponse:
"""HTMX fragment: log table for health page."""
repo = LogRepository(request.app.state.store)
offset = (page - 1) * per_page
records = await repo.query(level=level, limit=per_page, offset=offset)
total = await repo.count_filtered(level=level)
total_pages = max(1, (total + per_page - 1) // per_page)
return templates.TemplateResponse(
request=request,
name="partials/log_table.html",
context={
"request": request,
"records": records,
"page": page,
"total_pages": total_pages,
"total": total,
"current_level": level or "all",
},
)
@router.post("/dashboard/api/logging/aggregate", response_class=JSONResponse)
async def dashboard_logging_aggregate(request: Request) -> JSONResponse:
from arbitrade.logging.maintenance import run_log_aggregation
await run_log_aggregation(request.app.state.store)
return JSONResponse({"status": "ok"})
@router.post("/dashboard/api/logging/archive", response_class=JSONResponse)
async def dashboard_logging_archive(request: Request) -> JSONResponse:
from arbitrade.logging.maintenance import run_log_archive
count = await run_log_archive(request.app.state.store)
return JSONResponse({"status": "ok", "archived": count})
+44 -3
View File
@@ -1,8 +1,10 @@
{% extends "_base.html" %} {% block title %}Arbitrade Health Check{% endblock %}
{% block header %} {% with page_title="Arbitrade Health Check",
page_subtitle="Live system state." %} {% include "_header.html" %} {% endwith %}
{% endblock %} {% block main_class %}shell{% endblock %} {% block content %}
<section class="card">
page_subtitle="Live system state and logs." %} {% include "_header.html" %} {%
endwith %} {% endblock %} {% block main_class %}shell{% endblock %} {% block
content %}
<section class="card" style="margin-bottom: 24px">
<h1>Arbitrade Bootstrap Complete</h1>
<p><span class="badge">Status: {{ status }}</span></p>
<p>UTC: {{ time }}</p>
@@ -18,4 +20,43 @@ page_subtitle="Live system state." %} {% include "_header.html" %} {% endwith %}
</p>
<pre id="health-json">{"status":"ok","service":"arbitrade"}</pre>
</section>
<section class="card">
<h2>System Logs</h2>
<div class="toolbar" style="display: flex; gap: 8px; margin-bottom: 12px">
<form
hx-post="/dashboard/api/logging/aggregate"
hx-target="#aggregate-result"
hx-swap="innerHTML"
style="display: inline"
>
<button type="submit" class="button secondary" style="font-size: 0.85rem">
Aggregate Now
</button>
</form>
<form
hx-post="/dashboard/api/logging/archive"
hx-target="#archive-result"
hx-swap="innerHTML"
style="display: inline"
>
<button type="submit" class="button secondary" style="font-size: 0.85rem">
Archive Old Logs
</button>
</form>
<span id="aggregate-result" style="font-size: 0.85rem; opacity: 0.6"></span>
<span id="archive-result" style="font-size: 0.85rem; opacity: 0.6"></span>
</div>
<div
id="log-table-container"
hx-get="/dashboard/fragment/logs"
hx-trigger="load, every 30s"
hx-swap="outerHTML"
>
<div style="text-align: center; padding: 20px; opacity: 0.5">
Loading logs...
</div>
</div>
</section>
{% endblock %} {% block scripts %}{% endblock %}
@@ -0,0 +1,72 @@
<div id="log-table-container">
<div class="toolbar" style="display: flex; gap: 8px; margin-bottom: 12px">
<select
name="level"
hx-get="/dashboard/fragment/logs"
hx-target="#log-table-container"
hx-trigger="change"
hx-swap="outerHTML"
>
<option value="" {{ 'selected' if current_level == 'all' else '' }}>All</option>
<option value="INFO" {{ 'selected' if current_level == 'INFO' else '' }}>INFO</option>
<option value="WARNING" {{ 'selected' if current_level == 'WARNING' else '' }}>WARNING</option>
<option value="ERROR" {{ 'selected' if current_level == 'ERROR' else '' }}>ERROR</option>
<option value="CRITICAL" {{ 'selected' if current_level == 'CRITICAL' else '' }}>CRITICAL</option>
</select>
<span style="opacity: 0.6; font-size: 0.85rem">{{ total }} entries</span>
</div>
<table style="width: 100%; border-collapse: collapse; font-size: 0.82rem">
<thead>
<tr style="border-bottom: 1px solid rgba(255,255,255,0.1); text-align: left">
<th style="padding: 6px 8px">Time</th>
<th style="padding: 6px 8px">Level</th>
<th style="padding: 6px 8px">Logger</th>
<th style="padding: 6px 8px">Message</th>
</tr>
</thead>
<tbody>
{% for r in records %}
<tr style="border-bottom: 1px solid rgba(255,255,255,0.04)">
<td style="padding: 4px 8px; white-space: nowrap">
{{ r.recorded_at.strftime('%H:%M:%S') if r.recorded_at else '—' }}
</td>
<td style="padding: 4px 8px">
<span class="badge level-{{ r.level.lower() }}">{{ r.level }}</span>
</td>
<td style="padding: 4px 8px; opacity: 0.7; max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap">
{{ r.logger }}
</td>
<td style="padding: 4px 8px; max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap">
{{ r.message }}
</td>
</tr>
{% endfor %}
{% if not records %}
<tr>
<td colspan="4" style="padding: 20px; text-align: center; opacity: 0.5">No log entries found.</td>
</tr>
{% endif %}
</tbody>
</table>
<div class="toolbar" style="display: flex; gap: 8px; justify-content: center; margin-top: 12px">
{% if page > 1 %}
<button
class="button secondary"
hx-get="/dashboard/fragment/logs?level={{ current_level }}&page={{ page - 1 }}"
hx-target="#log-table-container"
hx-swap="outerHTML"
>Previous</button>
{% endif %}
<span style="opacity: 0.6; font-size: 0.85rem; padding: 0 8px">Page {{ page }} / {{ total_pages }}</span>
{% if page < total_pages %}
<button
class="button secondary"
hx-get="/dashboard/fragment/logs?level={{ current_level }}&page={{ page + 1 }}"
hx-target="#log-table-container"
hx-swap="outerHTML"
>Next</button>
{% endif %}
</div>
</div>