Flask + React web UI with audio player, podcast queue, feed management, episode browser, music library, schedule viewer, and log tail. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
49 lines
1.3 KiB
Python
49 lines
1.3 KiB
Python
"""GET /api/logs — tail log files."""
|
|
|
|
import os
|
|
|
|
from flask import Blueprint, jsonify, request
|
|
|
|
from ..config import LOG_DIR
|
|
|
|
bp = Blueprint("logs", __name__)
|
|
|
|
ALLOWED_LOGS = {"poller", "liquidsoap", "icecast_access", "icecast_error"}
|
|
|
|
|
|
@bp.route("/logs")
|
|
def get_logs():
|
|
log_name = request.args.get("file", "poller")
|
|
lines_count = request.args.get("lines", 50, type=int)
|
|
lines_count = min(lines_count, 500)
|
|
|
|
if log_name not in ALLOWED_LOGS:
|
|
return jsonify({"error": f"Unknown log file. Allowed: {ALLOWED_LOGS}"}), 400
|
|
|
|
log_path = LOG_DIR / f"{log_name}.log"
|
|
|
|
if not log_path.exists():
|
|
return jsonify({"lines": [], "file": log_name, "exists": False})
|
|
|
|
# Read last N lines efficiently
|
|
try:
|
|
with open(log_path, "rb") as f:
|
|
f.seek(0, os.SEEK_END)
|
|
size = f.tell()
|
|
# Read up to 1MB from the end
|
|
read_size = min(size, 1024 * 1024)
|
|
f.seek(max(0, size - read_size))
|
|
content = f.read().decode("utf-8", errors="replace")
|
|
|
|
all_lines = content.splitlines()
|
|
tail = all_lines[-lines_count:]
|
|
except Exception as e:
|
|
return jsonify({"error": str(e)}), 500
|
|
|
|
return jsonify({
|
|
"lines": tail,
|
|
"file": log_name,
|
|
"exists": True,
|
|
"total_lines": len(all_lines),
|
|
})
|