Extract webserver metrics into a separate class to avoid passing it into every API call. Change the display to requests per hour rather than just last request time. Add SSE and telnet client connected count.

This commit is contained in:
Ian Renton
2026-09-11 22:32:04 +01:00
parent 4a09e46fe0
commit 7c458a8c5b
25 changed files with 161 additions and 230 deletions
+36 -37
View File
@@ -33,6 +33,7 @@ from webserver.handlers.manifesthandler import ManifestHandler
from webserver.handlers.metrics import PrometheusMetricsHandler
from webserver.handlers.pagetemplate import PageTemplateHandler
from webserver.sse_broadcaster import SSEBroadcaster
from webserver.webserver_metrics import WebServerMetrics
logger = logging.getLogger(__name__)
@@ -52,19 +53,19 @@ class WebServer:
self._port = WEB_SERVER_PORT
self._api_only_mode = API_ONLY_MODE
self._shutdown_event = asyncio.Event()
self.web_server_metrics = {
"last_page_access_time": None,
"last_api_access_time": None,
"page_access_counter": 0,
"api_access_counter": 0,
"status": "Starting",
}
self.web_server_metrics = WebServerMetrics()
def setup(self):
# Listen for new spots and alerts being added to the cache, so we can notify SSE clients immediately
DATA_STORE.spots.add_listener(self._spot_broadcaster.publish)
DATA_STORE.alerts.add_listener(self._alert_broadcaster.publish)
@property
def sse_client_count(self) -> int:
"""Number of connected SSE clients, across both the spots and alerts streams."""
return self._spot_broadcaster.client_count + self._alert_broadcaster.client_count
def start(self):
"""Start the web server"""
@@ -82,62 +83,57 @@ class WebServer:
self._spot_broadcaster.bind_to_web_server_loop()
self._alert_broadcaster.bind_to_web_server_loop()
# Prepare a list of common arguments that are passed in to every API & page handler. This is just a basic thing
# to avoid copy-pasting the same thing to every route declaration below.
handler_opts = {"web_server_metrics": self.web_server_metrics}
# API endpoints are always enabled
api_routes = [
(
r"/api/v2/spots",
APISpotsHandler,
{"spots": self._data_store.spots, **handler_opts},
{"spots": self._data_store.spots},
),
(
r"/api/v2/alerts",
APIAlertsHandler,
{"alerts": self._data_store.alerts, **handler_opts},
{"alerts": self._data_store.alerts},
),
(
r"/api/v2/spots/stream",
APISpotsStreamHandler,
{"sse_spot_broadcaster": self._spot_broadcaster, **handler_opts},
{"sse_spot_broadcaster": self._spot_broadcaster},
),
(
r"/api/v2/alerts/stream",
APIAlertsStreamHandler,
{"sse_alert_broadcaster": self._alert_broadcaster, **handler_opts},
{"sse_alert_broadcaster": self._alert_broadcaster},
),
(
r"/api/v2/solar",
APISolarConditionsHandler,
{"solar_conditions": self._data_store.solar_conditions.get(), **handler_opts},
{"solar_conditions": self._data_store.solar_conditions.get()},
),
(
r"/api/v2/dxstats",
APIDxStatsHandler,
{"spots": self._data_store.spots, **handler_opts},
{"spots": self._data_store.spots},
),
(
r"/api/v2/options",
APIOptionsHandler,
{"status_data": self._data_store.status.get(), **handler_opts},
{"status_data": self._data_store.status.get()},
),
(
r"/api/v2/status",
APIStatusHandler,
{"status_data": self._data_store.status.get(), **handler_opts},
{"status_data": self._data_store.status.get()},
),
(r"/api/v2/lookup/call", APILookupCallHandler, {**handler_opts}),
(r"/api/v2/lookup/sigref", APILookupSIGRefHandler, {**handler_opts}),
(r"/api/v2/lookup/grid", APILookupGridHandler, {**handler_opts}),
(r"/api/v2/lookup/call", APILookupCallHandler),
(r"/api/v2/lookup/sigref", APILookupSIGRefHandler),
(r"/api/v2/lookup/grid", APILookupGridHandler),
(
r"/api/v2/spot",
APISpotHandler,
{
"spots": self._data_store.spots,
"spot_providers": self._data_providers,
**handler_opts,
},
),
]
@@ -148,19 +144,18 @@ class WebServer:
(
r"/api/v1/spots",
V1APISpotsHandler,
{"spots": self._data_store.spots, **handler_opts},
{"spots": self._data_store.spots},
),
(
r"/api/v1/spots/stream",
V1APISpotsStreamHandler,
{"sse_spot_broadcaster": self._spot_broadcaster, **handler_opts},
{"sse_spot_broadcaster": self._spot_broadcaster},
),
(
r"/api/v1/spot",
V1APISpotHandler,
{
"spots": self._data_store.spots,
**handler_opts,
},
),
(r"/api/v1/(.*)", V1RedirectHandler),
@@ -173,41 +168,41 @@ class WebServer:
(
r"/",
PageTemplateHandler,
{"template_name": "api_only_home", **handler_opts},
{"template_name": "api_only_home"},
)
]
else:
ui_routes = [
(r"/", PageTemplateHandler, {"template_name": "spots", **handler_opts}),
(r"/", PageTemplateHandler, {"template_name": "spots"}),
(
r"/map",
PageTemplateHandler,
{"template_name": "map", **handler_opts},
{"template_name": "map"},
),
(
r"/bands",
PageTemplateHandler,
{"template_name": "bands", **handler_opts},
{"template_name": "bands"},
),
(
r"/alerts",
PageTemplateHandler,
{"template_name": "alerts", **handler_opts},
{"template_name": "alerts"},
),
(
r"/conditions",
PageTemplateHandler,
{"template_name": "conditions", **handler_opts},
{"template_name": "conditions"},
),
(
r"/status",
PageTemplateHandler,
{"template_name": "status", **handler_opts},
{"template_name": "status"},
),
(
r"/about",
PageTemplateHandler,
{"template_name": "about", **handler_opts},
{"template_name": "about"},
),
]
# Only allow the Add Spot page if spotting is allowed
@@ -216,7 +211,7 @@ class WebServer:
(
r"/add-spot",
PageTemplateHandler,
{"template_name": "add_spot", **handler_opts},
{"template_name": "add_spot"},
)
]
@@ -226,7 +221,7 @@ class WebServer:
(
r"/apidocs",
PageTemplateHandler,
{"template_name": "apidocs", **handler_opts},
{"template_name": "apidocs"},
),
(r"/metrics", PrometheusMetricsHandler),
(r"/manifest.webmanifest", ManifestHandler),
@@ -254,7 +249,11 @@ class WebServer:
def request_log(handler):
"""Custom log function to provide more data about requests when enabled, and to provide the ability to turn off
web request logging altogetether."""
web request logging altogetether. Also records the time of the request and status in the webserver metrics. Probably
not what this method is supposed to be used for but it's a convenient thing that gets called on every request, so
saves having to pass the metrics around each handler individually."""
WEB_SERVER.web_server_metrics.record(handler.request.path, handler.get_status())
if LOG_WEB_REQUESTS:
if handler.get_status() < 500: