feat: add logging routes and update health page to display system logs
CI / lint-test-build (push) Failing after 12s
CI / lint-test-build (push) Failing after 12s
This commit is contained in:
@@ -1493,3 +1493,49 @@ async def dashboard_api_pairings_sync(request: Request) -> HTMLResponse:
|
|||||||
name="partials/pairings_table.html",
|
name="partials/pairings_table.html",
|
||||||
context={"request": request, "pairings": pairings},
|
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})
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
{% extends "_base.html" %} {% block title %}Arbitrade Health Check{% endblock %}
|
{% extends "_base.html" %} {% block title %}Arbitrade Health Check{% endblock %}
|
||||||
{% block header %} {% with page_title="Arbitrade Health Check",
|
{% block header %} {% with page_title="Arbitrade Health Check",
|
||||||
page_subtitle="Live system state." %} {% include "_header.html" %} {% endwith %}
|
page_subtitle="Live system state and logs." %} {% include "_header.html" %} {%
|
||||||
{% endblock %} {% block main_class %}shell{% endblock %} {% block content %}
|
endwith %} {% endblock %} {% block main_class %}shell{% endblock %} {% block
|
||||||
<section class="card">
|
content %}
|
||||||
|
|
||||||
|
<section class="card" style="margin-bottom: 24px">
|
||||||
<h1>Arbitrade Bootstrap Complete</h1>
|
<h1>Arbitrade Bootstrap Complete</h1>
|
||||||
<p><span class="badge">Status: {{ status }}</span></p>
|
<p><span class="badge">Status: {{ status }}</span></p>
|
||||||
<p>UTC: {{ time }}</p>
|
<p>UTC: {{ time }}</p>
|
||||||
@@ -18,4 +20,43 @@ page_subtitle="Live system state." %} {% include "_header.html" %} {% endwith %}
|
|||||||
</p>
|
</p>
|
||||||
<pre id="health-json">{"status":"ok","service":"arbitrade"}</pre>
|
<pre id="health-json">{"status":"ok","service":"arbitrade"}</pre>
|
||||||
</section>
|
</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 %}
|
{% 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>
|
||||||
Reference in New Issue
Block a user