From 9e028e990aef5d0fc6504d3df531ce313565ae3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Joz=C3=ADfek?= Date: Tue, 14 Nov 2023 19:56:21 +0100 Subject: [PATCH] Add journal as an option for logging downloads --- ChangeLog | 1 + prusa/link/web/main.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/ChangeLog b/ChangeLog index 68212d19..5a014b7d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -8,6 +8,7 @@ * Stop showing the tune menu after connecting to the printer * Use the 2023-10-10 Raspberry Pi OS base image for the image builder * Decrease the severity of web server access log messages + * Add a way to download logs even on systemd journal based systems 0.7.1rc1 (2023-08-10) * Fixed HTTP response status on HEAD request for non-existing files diff --git a/prusa/link/web/main.py b/prusa/link/web/main.py index 417c530e..612e8138 100644 --- a/prusa/link/web/main.py +++ b/prusa/link/web/main.py @@ -1,10 +1,15 @@ """Main pages and core API""" +import datetime import logging +import shlex +import subprocess +import time from os import listdir from os.path import basename, getmtime, getsize, join from socket import gethostname from subprocess import CalledProcessError from sys import version +from typing import BinaryIO, cast from gcode_metadata import get_metadata from pkg_resources import working_set # type: ignore @@ -13,6 +18,7 @@ from poorwsgi.response import ( EmptyResponse, FileResponse, + GeneratorResponse, JSONResponse, Response, ) @@ -104,6 +110,20 @@ def api_logs(req): }) logs_list = sorted(logs_list, key=lambda key: key['name']) + if not logs_list: + try: + subprocess.run(shlex.split("which journalctl"), + check=True, + stdout=subprocess.DEVNULL) + except CalledProcessError: + log.warning("journalctl not found") + else: + logs_list.append({ + 'name': 'journal', + 'size': None, + 'date': int(time.time()), + }) + return JSONResponse(files=logs_list) @@ -112,6 +132,27 @@ def api_logs(req): def api_log(req, filename): """Returns content of intended log file""" # pylint: disable=unused-argument + if filename == "journal": + today = datetime.date.today() + week_ago = today - datetime.timedelta(days=7) + logs_from = week_ago.isoformat() + # pylint: disable=consider-using-with + # We cannot close the process when returning the response + # It needs to stay open until the response quits + # Then it will hopefully get garbage collected + result = subprocess.Popen( + shlex.split(f"journalctl -S {logs_from} --no-pager"), + stdout=subprocess.PIPE, bufsize=32768, + ) + journal_output = result.stdout + if journal_output is None: + raise ValueError("No stdout from journalctl") + slightly_different_journal_output = cast(BinaryIO, journal_output) + # Abusing a generator response because the file object one is broken + # Do not use an attribute if you didn't declare said attribute. EZ + return GeneratorResponse( + slightly_different_journal_output, content_type="text/plain") + if not filename.startswith(LOGS_FILES): return Response(status_code=state.HTTP_NOT_FOUND)