From bcdc4ee06e770e28c8af0c863ae0b6578745de34 Mon Sep 17 00:00:00 2001 From: arabcoders Date: Thu, 10 Apr 2025 02:32:38 +0300 Subject: [PATCH] Increase log retrieval limit and enhance log pagination metadata --- app/library/HttpAPI.py | 15 ++++--- app/library/Utils.py | 55 +++++++++++++++----------- ui/pages/logs.vue | 88 ++++++++++++++++++++++++------------------ 3 files changed, 92 insertions(+), 66 deletions(-) diff --git a/app/library/HttpAPI.py b/app/library/HttpAPI.py index 926eac03..e00e0fc8 100644 --- a/app/library/HttpAPI.py +++ b/app/library/HttpAPI.py @@ -749,19 +749,22 @@ class HttpAPI(Common): ) offset = int(request.query.get("offset", 0)) - limit = int(request.query.get("limit", 50)) + limit = int(request.query.get("limit", 100)) if limit < 1 or limit > 150: limit = 50 + logs_data = await read_logfile( + file=os.path.join(self.config.config_path, "logs", "app.log"), + offset=offset, + limit=limit, + ) return web.json_response( data={ - "logs": await read_logfile( - file=os.path.join(self.config.config_path, "logs", "app.log"), - offset=offset, - limit=limit, - ), + "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=self.encoder.encode, diff --git a/app/library/Utils.py b/app/library/Utils.py index c81e4dbb..9bd32f27 100644 --- a/app/library/Utils.py +++ b/app/library/Utils.py @@ -942,54 +942,64 @@ def strip_newline(string: str) -> str: return res.strip() if res else "" -async def read_logfile(file: str, offset: int = 0, limit: int = 50) -> list[str]: +async def read_logfile(file: str, offset: int = 0, limit: int = 50) -> dict: """ - Read log file and return limited lines from the end. + Read a log file and return a set of log lines along with pagination metadata. Args: file (str): The log file path. - offset (int): The number of lines to skip from the end. - limit (int): The number of lines to read. + offset (int): Number of lines to skip from the end (newer entries). + limit (int): Number of lines to return. Returns: - list[str]: A list of log lines. - + dict: A dictionary containing: + - logs: List of log entries. + - next_offset: Offset for the next page or None. + - end_is_reached: True if there are no older logs. """ from anyio import open_file - - if not os.path.exists(file): - return [] - from hashlib import sha256 + if not pathlib.Path(file).exists(): + return {"logs": [], "next_offset": None, "end_is_reached": True} + + result = [] try: async with await open_file(file, "rb") as f: await f.seek(0, os.SEEK_END) - size = await f.tell() + file_size = await f.tell() block_size = 1024 - block_end = size + block_end = file_size buffer = b"" lines = [] - while len(lines) < (offset + limit) and block_end > 0: + required_count = offset + limit + 1 + + while len(lines) < required_count and block_end > 0: block_start = max(0, block_end - block_size) await f.seek(block_start) chunk = await f.read(block_end - block_start) - buffer = chunk + buffer + buffer = chunk + buffer # prepend the chunk lines = buffer.splitlines() block_end = block_start - selected_lines = lines[-(offset + limit) :] if offset else lines[-limit:] - if offset: - selected_lines = selected_lines[:-offset] or [] + if len(lines) > offset + limit: + next_offset = offset + limit + end_is_reached = False + else: + next_offset = None + end_is_reached = True + + if offset: + selected_lines = lines[-(offset + limit) : -offset] + else: + selected_lines = lines[-limit:] - result = [] for line in selected_lines: line_bytes = line if isinstance(line, bytes) else line.encode() msg = line.decode(errors="replace") dt_match = DATETIME_PATTERN.match(msg) - result.append( { "id": sha256(line_bytes).hexdigest(), @@ -998,10 +1008,9 @@ async def read_logfile(file: str, offset: int = 0, limit: int = 50) -> list[str] } ) - return result - + return {"logs": result, "next_offset": next_offset, "end_is_reached": end_is_reached} except Exception: - return [] + return {"logs": [], "next_offset": None, "end_is_reached": True} async def tail_log(file: str, emitter: callable, sleep_time: float = 0.5): @@ -1019,7 +1028,7 @@ async def tail_log(file: str, emitter: callable, sleep_time: float = 0.5): from anyio import open_file - if not os.path.exists(file): + if not pathlib(file).exists(): return async with await open_file(file, "rb") as f: diff --git a/ui/pages/logs.vue b/ui/pages/logs.vue index 31d8cd82..7660ddc1 100644 --- a/ui/pages/logs.vue +++ b/ui/pages/logs.vue @@ -14,7 +14,7 @@ } .logbox { - background-color: #333; + background-color: #1f2229 !important; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); min-width: 100%; max-height: 73vh; @@ -22,13 +22,13 @@ overflow-x: auto; } -div.logbox pre { - background-color: rgb(31, 34, 41); +code { + background-color: unset; } .logline { word-break: break-all; - line-height: 2.3em; + line-height: 1.75rem; padding: 1em; color: #fff1b8; } @@ -39,13 +39,13 @@ div.logbox pre {
- + Logs
- @@ -73,26 +73,42 @@ div.logbox pre {
- The logs are displayed in real-time. You can scroll up to view older logs. + The logs are being streamed in real-time. You can scroll up to view older logs.
-
-
-
-
{{
-          log.line }}No logs found for the filter: {{ query
-      }}No logs available
-
-
+
+
+ + + + + No more logs available for this file. + + + + + {{ log.line }} + + + + + + No logs match this query: {{ query }} + + + + No logs available + + +
+
+