107 lines
3.2 KiB
Python
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
|