ytptube/app/routes/api/logs.py
2026-01-21 22:07:12 +03:00

107 lines
3.2 KiB
Python

import asyncio
import logging
from pathlib import Path
from aiohttp import web
from aiohttp.web import Request, Response
from app.library.config import Config
from app.library.encoder import Encoder
from app.library.router import route
from app.library.Utils import read_logfile, tail_log
LOG: logging.Logger = logging.getLogger(__name__)
@route("GET", "api/logs/", "logs")
async def logs(request: Request, config: Config, encoder: Encoder) -> Response:
"""
Get recent logs
Args:
request (Request): The request object.
config (Config): The configuration instance.
encoder (Encoder): The encoder instance.
Returns:
Response: The response object.
"""
if not config.file_logging:
return web.json_response(data={"error": "File logging is not enabled."}, status=web.HTTPNotFound.status_code)
offset = int(request.query.get("offset", 0))
limit = int(request.query.get("limit", 100))
if limit < 1 or limit > 150:
limit = 50
logs_data = await read_logfile(
file=Path(config.config_path) / "logs" / "app.log",
offset=offset,
limit=limit,
)
return web.json_response(
data={
"logs": logs_data["logs"],
"offset": offset,
"limit": limit,
"next_offset": logs_data["next_offset"],
"end_is_reached": logs_data["end_is_reached"],
},
status=web.HTTPOk.status_code,
dumps=encoder.encode,
)
@route("GET", "api/logs/stream", "logs.stream")
async def stream_logs(request: Request, config: Config, encoder: Encoder) -> Response | web.StreamResponse:
if not config.file_logging:
return web.json_response(
data={"error": "File logging is not enabled."},
status=web.HTTPNotFound.status_code,
)
log_file = Path(config.config_path) / "logs" / "app.log"
if not log_file.exists():
return web.json_response(
data={"error": "Log file is not available."},
status=web.HTTPNotFound.status_code,
)
response = web.StreamResponse(
status=web.HTTPOk.status_code,
headers={
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
},
)
await response.prepare(request)
async def emit_log(data: dict) -> None:
if request.transport is None or request.transport.is_closing():
raise asyncio.CancelledError
payload = f"event: log_lines\ndata: {encoder.encode(data)}\n\n"
await response.write(payload.encode("utf-8"))
log_task: asyncio.Task[None] = asyncio.create_task(tail_log(file=log_file, emitter=emit_log), name="log_stream")
try:
LOG.debug("Log streaming connected.")
while not log_task.done():
await asyncio.sleep(0.5)
if request.transport is None or request.transport.is_closing():
log_task.cancel()
break
await log_task
except asyncio.CancelledError:
pass
finally:
LOG.debug("Log streaming disconnected.")
try:
await response.write_eof()
except ConnectionResetError:
pass
return response