Coverage for seedboxsync/front/apis/logs.py: 91%
46 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-31 17:10 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-31 17:10 +0000
1#
2# Copyright (C) 2015-2026 Guillaume Kulakowski <guillaume@kulakowski.fr>
3#
4# For the full copyright and license information, please view the LICENSE
5# file that was distributed with this source code.
6"""SeedboxSync API Logs view."""
8from datetime import timedelta
9from typing import Any
10from flask_restx import Namespace, fields, reqparse
11from seedboxsync.core.database.models import Log
12from seedboxsync.front.apis import Resource, parser_pagination, parser_period
13from seedboxsync.front.login_manager import admin_required
15api = Namespace("logs", description="View logs information")
18# ==========================
19# Models
20# ==========================
21log_model = api.model(
22 "Log",
23 {
24 "id": fields.Integer(
25 required=True,
26 description="Unique identifier of the log record",
27 example=999,
28 ),
29 "level": fields.String(
30 required=True,
31 description="Severity level of the record (e.g. DEBUG, INFO, WARNING, ERROR, CRITICAL)",
32 example="ConvallisMorbi.doc",
33 ),
34 "logger": fields.String(
35 required=True,
36 description="Name of the logger that emitted the record",
37 example="doc",
38 ),
39 "module": fields.String(
40 required=True,
41 description="Module of the logger that emitted the record",
42 example="doc",
43 ),
44 "message": fields.String(
45 required=True,
46 description="Formatted message recorded by the logger",
47 example="ConvallisMorbi.doc",
48 ),
49 "timestamp": fields.DateTime(
50 dt_format="iso8601",
51 required=True,
52 description="Date and time when the log record was created",
53 ),
54 },
55)
56log_list_envelope = Resource.build_envelope_model(api, "LogList", nested_model=log_model)
57log_envelope = Resource.build_envelope_model(api, "Log", nested_model=log_model, as_list=False)
58log_message_envelope = Resource.build_envelope_model(api, "LogMessage", as_message=True)
61# ==========================
62# Request parser
63# ==========================
64parser = reqparse.RequestParser()
65parser.add_argument("search", type=str, required=False, help="Optional search string to filter items")
66parser.add_argument("level", choices=("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"), required=False, help="Optional level to filter items")
67parser.args.extend(parser_period.args)
68parser.args.extend(parser_pagination.args)
71# ==========================
72# Endpoints
73# ==========================
74@api.route("")
75class Logs(Resource):
76 """Endpoint for managing log viewing."""
78 @api.doc("list_logs") # type: ignore[untyped-decorator]
79 @api.expect(parser) # type: ignore[untyped-decorator]
80 @api.marshal_with(log_list_envelope, code=200, description="List of logs") # type: ignore[untyped-decorator]
81 @admin_required
82 def get(self) -> dict[str, Any]:
83 """
84 Retrieve a list of recent logs.
86 Query Parameters:
87 - offset: Number of items to skip before starting to collect the result set (default: 0)
88 - limit: Maximum number of logs to return (default=50)
89 - search: Optional search string to filter items
90 """
91 args = parser.parse_args()
92 offset = args.get("offset")
93 limit = self.set_limit(args.get("limit", 50))
94 search = args.get("search")
95 level = args.get("level")
96 start_date = args.get("start_date")
97 end_date = args.get("end_date")
99 count = Log.select()
100 select = (
101 Log.select(
102 Log.id,
103 Log.level,
104 Log.logger,
105 Log.module,
106 Log.message,
107 Log.timestamp,
108 )
109 .limit(limit)
110 .offset(offset)
111 .order_by(Log.timestamp.desc())
112 )
114 if search:
115 count = count.where(Log.message.contains(search) | Log.level.contains(search) | Log.module.contains(search))
116 select = select.where(Log.message.contains(search) | Log.level.contains(search) | Log.module.contains(search))
118 if start_date:
119 count = count.where(Log.timestamp >= start_date)
120 select = select.where(Log.timestamp >= start_date)
122 if level:
123 count = count.where(Log.level == level)
124 select = select.where(Log.level == level)
126 if end_date:
127 end_exclusive = end_date + timedelta(days=1)
128 count = count.where(Log.timestamp < end_exclusive)
129 select = select.where(Log.timestamp < end_exclusive)
131 return self.build_envelope(list(select.dicts()), data_total=count.count(), type="LogList")