mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-09-20 22:37:44 +00:00
Replace root logger calls with module-specific loggers
This commit is contained in:
+3
-1
@@ -6,6 +6,8 @@ import pytz
|
||||
|
||||
from core.data_store import DATA_STORE
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CleanupTimer:
|
||||
"""Provides a timed cleanup of the spot list."""
|
||||
@@ -63,7 +65,7 @@ class CleanupTimer:
|
||||
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception("Exception in Cleanup thread")
|
||||
logger.exception("Exception in Cleanup thread")
|
||||
self._stop_event.wait(timeout=1)
|
||||
|
||||
|
||||
|
||||
+4
-2
@@ -4,9 +4,11 @@ import os
|
||||
|
||||
import yaml
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Check you have a config file
|
||||
if not os.path.isfile("config.yml"):
|
||||
logging.error(
|
||||
logger.error(
|
||||
"Your config file is missing. Ensure you have copied config-example.yml to config.yml and updated it according to your needs."
|
||||
)
|
||||
exit()
|
||||
@@ -14,7 +16,7 @@ if not os.path.isfile("config.yml"):
|
||||
# Load config
|
||||
with open("config.yml") as f:
|
||||
config = yaml.safe_load(f)
|
||||
logging.info("Loaded config.")
|
||||
logger.info("Loaded config.")
|
||||
|
||||
BASE_URL = config.get("base_url", "http://localhost:8080")
|
||||
MAX_SPOT_AGE = config.get("max_spot_age_sec", 3600)
|
||||
|
||||
@@ -3,6 +3,8 @@ import threading
|
||||
|
||||
from core.config import config, create_provider_from_config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DataProviders:
|
||||
"""Global object for storing data providers."""
|
||||
@@ -33,7 +35,7 @@ class DataProviders:
|
||||
def start_providers(providers, provider_type):
|
||||
"""Helper method to activate enabled providers in the list."""
|
||||
|
||||
logging.info(f"Starting {provider_type} providers...")
|
||||
logger.info(f"Starting {provider_type} providers...")
|
||||
for p in providers:
|
||||
if p.enabled:
|
||||
p.start()
|
||||
|
||||
+6
-4
@@ -8,6 +8,8 @@ from core.config import MAX_ALERT_AGE, MAX_SPOT_AGE
|
||||
from core.live_data_cache import LiveDataCache
|
||||
from data.solar_conditions import SolarConditions
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CACHE_DIR = "./cache/"
|
||||
|
||||
|
||||
@@ -64,7 +66,7 @@ class DataStore:
|
||||
# of dict in diskcache absolutely destroys performance with unpickling huge dicts, so we have an ugly "SIG:ref"
|
||||
# syntax for keys to keep it a single level.
|
||||
self.sigrefs = diskcache.Cache(f"{CACHE_DIR}sigrefs")
|
||||
logging.info(f"Loaded data for {len(self.sigrefs)} SIG references.")
|
||||
logger.info(f"Loaded data for {len(self.sigrefs)} SIG references.")
|
||||
|
||||
# Standard disk cache for callsign data. This data does have a TTL to trigger an occasional re-lookup.
|
||||
# Old data *is* better than no data, but we can't have a background thread re-looking-up every callsign
|
||||
@@ -83,7 +85,7 @@ class DataStore:
|
||||
self.callsign_data_hamqth,
|
||||
]:
|
||||
unique_keys.update(c)
|
||||
logging.info(f"Loaded data for {len(unique_keys)} callsigns.")
|
||||
logger.info(f"Loaded data for {len(unique_keys)} callsigns.")
|
||||
|
||||
# Special caches for spots and alerts, which have TTL and write snapshots to disk at an interval. We
|
||||
# specifically load these caches *last* so that any sigref and callsign data is already loaded from disk cache
|
||||
@@ -94,7 +96,7 @@ class DataStore:
|
||||
snapshot_dir=f"{CACHE_DIR}spots",
|
||||
snapshot_interval_sec=self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC,
|
||||
)
|
||||
logging.info(f"Loaded {len(self.spots.keys())} spots from a previous run.")
|
||||
logger.info(f"Loaded {len(self.spots.keys())} spots from a previous run.")
|
||||
|
||||
self.alerts = LiveDataCache(
|
||||
maxsize=self._MAX_ALERT_COUNT,
|
||||
@@ -102,7 +104,7 @@ class DataStore:
|
||||
snapshot_dir=f"{CACHE_DIR}alerts",
|
||||
snapshot_interval_sec=self._SPOT_ALERT_SNAPSHOT_INTERVAL_SEC,
|
||||
)
|
||||
logging.info(f"Loaded {len(self.alerts.keys())} alerts from a previous run.")
|
||||
logger.info(f"Loaded {len(self.alerts.keys())} alerts from a previous run.")
|
||||
|
||||
def regenerate_call_regex_to_dxcc_entity_map(self):
|
||||
"""DXCC entity data from K0SWE includes a regex which we can use to match a callsign, and determine which DXCC
|
||||
|
||||
+3
-1
@@ -7,6 +7,8 @@ from shapely.geometry import Point, Polygon
|
||||
|
||||
from core.data_store import DATA_STORE
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
TRANSFORMER_OS_GRID_TO_WGS84 = Transformer.from_crs("EPSG:27700", "EPSG:4326")
|
||||
TRANSFORMER_IRISH_GRID_TO_WGS84 = Transformer.from_crs("EPSG:29903", "EPSG:4326")
|
||||
TRANSFORMER_CI_UTM_GRID_TO_WGS84 = Transformer.from_crs("+proj=utm +zone=30 +ellps=WGS84", "EPSG:4326")
|
||||
@@ -172,7 +174,7 @@ def wab_wai_square_to_lat_lon(ref):
|
||||
elif re.match(r"^W[AV][0-9]{2}$", ref):
|
||||
return utm_grid_square_to_lat_lon(ref)
|
||||
else:
|
||||
logging.warning(f"Invalid WAB/WAI square: {ref}")
|
||||
logger.warning(f"Invalid WAB/WAI square: {ref}")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@ import time
|
||||
import diskcache
|
||||
from cachetools import TTLCache
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LiveDataCache:
|
||||
"""Cache for spots and alerts. Uses the fast in-memory TTLCache for normal data I/O, including the TTL to enforce
|
||||
@@ -34,7 +36,7 @@ class LiveDataCache:
|
||||
try:
|
||||
callback(value)
|
||||
except Exception:
|
||||
logging.exception(f"Listener raised an exception for key {key}")
|
||||
logger.exception(f"Listener raised an exception for key {key}")
|
||||
|
||||
def get(self, key, default=None):
|
||||
with self._lock:
|
||||
@@ -70,7 +72,7 @@ class LiveDataCache:
|
||||
try:
|
||||
self._disk_cache.set("snapshot", data)
|
||||
except Exception as e:
|
||||
logging.exception(f"Failed to write snapshot to {self._snapshot_dir}")
|
||||
logger.exception(f"Failed to write snapshot to {self._snapshot_dir}")
|
||||
|
||||
def _load_snapshot(self):
|
||||
data = self._disk_cache.get("snapshot")
|
||||
@@ -83,7 +85,7 @@ class LiveDataCache:
|
||||
# Only restore entries that would still be within TTL
|
||||
if now - saved_at < self._ttl:
|
||||
self._cache[key] = value
|
||||
logging.info(f"Loaded snapshot from {self._snapshot_dir}")
|
||||
logger.info(f"Loaded snapshot from {self._snapshot_dir}")
|
||||
|
||||
def _start_periodic_snapshot(self, interval):
|
||||
def loop():
|
||||
|
||||
@@ -6,6 +6,8 @@ from core.data_store import DATA_STORE
|
||||
from core.geo_utils import wab_wai_square_to_lat_lon
|
||||
from data.sig_ref import SIGRef
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_sig_ref_info(sig, ref_id):
|
||||
"""Look up details of a SIG reference (e.g. POTA park) such as name, lat/lon, and grid. Takes in a sig name and
|
||||
@@ -14,7 +16,7 @@ def get_sig_ref_info(sig, ref_id):
|
||||
SIG we are getting data for."""
|
||||
|
||||
if sig is None or sig == "" or ref_id is None or ref_id == "":
|
||||
logging.debug("Failed to look up sig_ref info, sig or ref were not set.")
|
||||
logger.debug("Failed to look up sig_ref info, sig or ref were not set.")
|
||||
return None
|
||||
|
||||
sig_ref = SIGRef(sig=sig, id=ref_id)
|
||||
@@ -59,7 +61,7 @@ def get_sig_ref_info(sig, ref_id):
|
||||
sig_ref.latitude = ll[0]
|
||||
sig_ref.longitude = ll[1]
|
||||
except:
|
||||
logging.warning("Invalid lat/lon received for WAB/WAI reference")
|
||||
logger.warning("Invalid lat/lon received for WAB/WAI reference")
|
||||
return sig_ref
|
||||
|
||||
elif sig.upper() == "BOTA":
|
||||
@@ -83,10 +85,10 @@ def get_sig_ref_info(sig, ref_id):
|
||||
else:
|
||||
# Maybe a super new reference we don't know about yet, but more likely a typo or a test reference,
|
||||
# just silently ignore it.
|
||||
logging.debug(f"{sig} database did not contain data for ref {ref_id}")
|
||||
logger.debug(f"{sig} database did not contain data for ref {ref_id}")
|
||||
|
||||
except Exception:
|
||||
logging.exception(f"Exception when looking up sig_ref info for {sig} ref {ref_id}")
|
||||
logger.exception(f"Exception when looking up sig_ref info for {sig} ref {ref_id}")
|
||||
return sig_ref
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -16,6 +16,8 @@ from core.constants import (
|
||||
from core.data_store import DATA_STORE
|
||||
from data.callsign import Callsign
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def safe_json_dumps(obj):
|
||||
"""Safe version of json.dumps that also converts objects to dicts so they can be output, and ignores NaN floats
|
||||
@@ -47,7 +49,7 @@ def infer_mode_type_from_mode(mode):
|
||||
return "DATA"
|
||||
else:
|
||||
if mode.upper() != "OTHER":
|
||||
logging.warning(f"Found an unrecognised mode: {mode}. Developer should categorise this.")
|
||||
logger.warning(f"Found an unrecognised mode: {mode}. Developer should categorise this.")
|
||||
return None
|
||||
|
||||
|
||||
|
||||
+3
-1
@@ -10,6 +10,8 @@ from core.call_lookup_helper import get_call_info
|
||||
from core.sig_lookup_helper import populate_missing_sig_ref_info
|
||||
from core.utils import get_flag_for_dxcc
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Alert:
|
||||
@@ -128,7 +130,7 @@ class Alert:
|
||||
self.dx_names = list(map(lambda c: get_call_info(c, credentials).name, self.dx_calls))
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception while inferring missing data from spot")
|
||||
logger.exception("Exception while inferring missing data from spot")
|
||||
|
||||
def to_json(self):
|
||||
"""JSON serialise"""
|
||||
|
||||
+6
-4
@@ -27,6 +27,8 @@ from core.utils import (
|
||||
)
|
||||
from data.sig_ref import SIGRef
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Spot:
|
||||
@@ -340,7 +342,7 @@ class Spot:
|
||||
self.propagation_mode = PROPAGATION_MODES[mode_tag]
|
||||
else:
|
||||
self.propagation_mode = mode_tag
|
||||
logging.info(f"Seen a new propagation mode tag not yet in the system: {mode_tag}")
|
||||
logger.info(f"Seen a new propagation mode tag not yet in the system: {mode_tag}")
|
||||
|
||||
# Parse "de_grid -> dx_grid" structures from the comment
|
||||
if self.comment:
|
||||
@@ -363,12 +365,12 @@ class Spot:
|
||||
self.dx_latitude = ll[0]
|
||||
self.dx_longitude = ll[1]
|
||||
except:
|
||||
logging.debug("Invalid grid received for spot")
|
||||
logger.debug("Invalid grid received for spot")
|
||||
if self.dx_latitude and self.dx_longitude and not self.dx_grid:
|
||||
try:
|
||||
self.dx_grid = latlong_to_locator(self.dx_latitude, self.dx_longitude, 8)
|
||||
except:
|
||||
logging.debug("Invalid lat/lon received for spot")
|
||||
logger.debug("Invalid lat/lon received for spot")
|
||||
|
||||
# QRT comment detection
|
||||
if self.comment and not self.qrt:
|
||||
@@ -444,7 +446,7 @@ class Spot:
|
||||
self.de_grid = de_call_info.grid
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception while inferring missing data from spot")
|
||||
logger.exception("Exception while inferring missing data from spot")
|
||||
|
||||
def to_json(self):
|
||||
"""JSON serialise"""
|
||||
|
||||
@@ -9,6 +9,8 @@ from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
|
||||
from core.constants import HTTP_HEADERS
|
||||
from providers.alert.alert_provider import AlertProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HTTPAlertProvider(AlertProvider):
|
||||
"""Generic alert provider class for providers that request data via HTTP(S). Just for convenience to avoid code
|
||||
@@ -24,7 +26,7 @@ class HTTPAlertProvider(AlertProvider):
|
||||
def start(self):
|
||||
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
|
||||
# subsequent polls, so start() returns immediately and the application can continue starting.
|
||||
logging.info(f"Set up query of {self.name} alert API every {self._poll_interval!s} seconds.")
|
||||
logger.info(f"Set up query of {self.name} alert API every {self._poll_interval!s} seconds.")
|
||||
self._thread = Thread(target=self._run, name=f"HTTPAlertProvider-{self.name}")
|
||||
self._thread.start()
|
||||
|
||||
@@ -40,7 +42,7 @@ class HTTPAlertProvider(AlertProvider):
|
||||
def _poll(self):
|
||||
try:
|
||||
# Request data from API
|
||||
logging.debug(f"Polling {self.name} alert API...")
|
||||
logger.debug(f"Polling {self.name} alert API...")
|
||||
http_response = requests.get(self._url, headers=HTTP_HEADERS, timeout=(5, 30))
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -52,18 +54,18 @@ class HTTPAlertProvider(AlertProvider):
|
||||
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.debug(f"Received data from {self.name} alert API.")
|
||||
logger.debug(f"Received data from {self.name} alert API.")
|
||||
else:
|
||||
self.status = "Error"
|
||||
logging.warning(f"HTTP {http_response.status_code} when calling {self.name} alerts API.")
|
||||
logger.warning(f"HTTP {http_response.status_code} when calling {self.name} alerts API.")
|
||||
|
||||
except ConnectionError:
|
||||
logging.warning(f"Connection error when accessing {self.name} alerts API.")
|
||||
logger.warning(f"Connection error when accessing {self.name} alerts API.")
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning(f"Timeout when accessing {self.name} alerts API.")
|
||||
logger.warning(f"Timeout when accessing {self.name} alerts API.")
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in HTTP JSON Alert Provider ({self.name})")
|
||||
logger.exception(f"Exception in HTTP JSON Alert Provider ({self.name})")
|
||||
# Brief pause on error before the next poll, but still respond promptly to stop()
|
||||
self._stop_event.wait(timeout=1)
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ from data.alert import Alert
|
||||
from data.sig_ref import SIGRef
|
||||
from providers.alert.http_alert_provider import HTTPAlertProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ParksNPeaks(HTTPAlertProvider):
|
||||
"""Alert provider for Parks n Peaks"""
|
||||
@@ -64,7 +66,7 @@ class ParksNPeaks(HTTPAlertProvider):
|
||||
"LLOTA",
|
||||
"QRP",
|
||||
]:
|
||||
logging.warning(f"PNP alert found with sig {sig}, developer needs to add support for this!")
|
||||
logger.warning(f"PNP alert found with sig {sig}, developer needs to add support for this!")
|
||||
|
||||
# If this is POTA, SOTA or WWFF data we already have it through other means, so ignore. Otherwise, add to
|
||||
# the alert list. Note that while ZLOTA has its own spots API, it doesn't have its own alerts API. So that
|
||||
|
||||
@@ -11,6 +11,8 @@ from providers.callsigndata.api_query_callsign_data_provider import (
|
||||
APIQueryCallsignDataProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClublogAPI(APIQueryCallsignDataProvider):
|
||||
"""Callsign data provider for Clublog's API."""
|
||||
@@ -25,7 +27,7 @@ class ClublogAPI(APIQueryCallsignDataProvider):
|
||||
self._callinfo = Callinfo(lookuplib)
|
||||
else:
|
||||
provider_config["enabled"] = False
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
"Clublog XML callsign data provider configured but no api key was provided, this has been disabled."
|
||||
)
|
||||
|
||||
@@ -45,6 +47,6 @@ class ClublogAPI(APIQueryCallsignDataProvider):
|
||||
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception("Exception when looking up data from Clublog API")
|
||||
logger.exception("Exception when looking up data from Clublog API")
|
||||
|
||||
return callsign_data
|
||||
|
||||
@@ -10,6 +10,8 @@ from providers.callsigndata.file_download_callsign_data_provider import (
|
||||
FileDownloadCallsignDataProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClublogXML(FileDownloadCallsignDataProvider):
|
||||
"""Callsign data provider for ClubLog's Country File, which provides basic callsign to DXCC entity mapping."""
|
||||
@@ -25,7 +27,7 @@ class ClublogXML(FileDownloadCallsignDataProvider):
|
||||
self._api_key = provider_config.get("api_key", "")
|
||||
if self._api_key == "":
|
||||
provider_config["enabled"] = False
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
"Clublog XML callsign data provider configured but no api key was provided, this has been disabled."
|
||||
)
|
||||
|
||||
@@ -55,7 +57,7 @@ class ClublogXML(FileDownloadCallsignDataProvider):
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when loading Clublog XML.")
|
||||
logger.exception("Exception when loading Clublog XML.")
|
||||
return False
|
||||
|
||||
def _perform_new_lookup(self, callsign, lookup_credentials):
|
||||
@@ -71,6 +73,6 @@ class ClublogXML(FileDownloadCallsignDataProvider):
|
||||
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception("Exception when looking up data from Clublog XML data")
|
||||
logger.exception("Exception when looking up data from Clublog XML data")
|
||||
|
||||
return callsign_data
|
||||
|
||||
@@ -9,6 +9,8 @@ from providers.callsigndata.file_download_callsign_data_provider import (
|
||||
FileDownloadCallsignDataProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CountryFiles(FileDownloadCallsignDataProvider):
|
||||
"""Callsign data provider for Country-files.com, which provides basic callsign to DXCC entity mapping."""
|
||||
@@ -35,7 +37,7 @@ class CountryFiles(FileDownloadCallsignDataProvider):
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when loading Country Files cty.plist.")
|
||||
logger.exception("Exception when loading Country Files cty.plist.")
|
||||
return False
|
||||
|
||||
def _perform_new_lookup(self, callsign, lookup_credentials):
|
||||
@@ -51,6 +53,6 @@ class CountryFiles(FileDownloadCallsignDataProvider):
|
||||
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception("Exception when looking up data from Country file")
|
||||
logger.exception("Exception when looking up data from Country file")
|
||||
|
||||
return callsign_data
|
||||
|
||||
@@ -10,6 +10,8 @@ from core.constants import HTTP_HEADERS
|
||||
from core.url_data_cache import URLDataCache
|
||||
from providers.callsigndata.callsign_data_provider import CallsignDataProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FileDownloadCallsignDataProvider(CallsignDataProvider):
|
||||
"""Generic callsign data provider class for providers that fetch their data from the web by downloading a file."""
|
||||
@@ -30,7 +32,7 @@ class FileDownloadCallsignDataProvider(CallsignDataProvider):
|
||||
def start(self):
|
||||
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
|
||||
# subsequent polls, so start() returns immediately and the application can continue starting.
|
||||
logging.info(f"Set up query of {self.name} callsign reference data every {self._poll_interval!s} days.")
|
||||
logger.info(f"Set up query of {self.name} callsign reference data every {self._poll_interval!s} days.")
|
||||
self._thread = Thread(target=self._run, name=f"FileDownloadCallsignDataProvider-{self.name}")
|
||||
self._thread.start()
|
||||
|
||||
@@ -47,7 +49,7 @@ class FileDownloadCallsignDataProvider(CallsignDataProvider):
|
||||
try:
|
||||
# Request the file. Use the data cache (with a TTL of 1 day) here, not as the main mechanism for
|
||||
# caching, but just so continual restarts of the software during testing don't hammer the servers.
|
||||
logging.debug(f"Downloading {self.name} callsign reference data...")
|
||||
logger.debug(f"Downloading {self.name} callsign reference data...")
|
||||
http_response = self._url_data_cache.get(self._url, headers=HTTP_HEADERS)
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -60,26 +62,26 @@ class FileDownloadCallsignDataProvider(CallsignDataProvider):
|
||||
if ok:
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.info(f"Updated callsign reference data from {self.name}")
|
||||
logger.info(f"Updated callsign reference data from {self.name}")
|
||||
else:
|
||||
self.status = "Error"
|
||||
logging.warning(f"Error updating callsign reference data from {self.name}.")
|
||||
logger.warning(f"Error updating callsign reference data from {self.name}.")
|
||||
|
||||
else:
|
||||
self.status = "Error"
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"HTTP {http_response.status_code} when downloading callsign reference data from {self.name}."
|
||||
)
|
||||
|
||||
except ConnectionError:
|
||||
self.status = "Error"
|
||||
logging.warning(f"Connection error when downloading callsign reference data from {self.name}.")
|
||||
logger.warning(f"Connection error when downloading callsign reference data from {self.name}.")
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
self.status = "Error"
|
||||
logging.warning(f"Timeout when downloading callsign reference data from {self.name}.")
|
||||
logger.warning(f"Timeout when downloading callsign reference data from {self.name}.")
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in callsign reference data provider ({self.name})")
|
||||
logger.exception(f"Exception in callsign reference data provider ({self.name})")
|
||||
self._stop_event.wait(timeout=1)
|
||||
|
||||
def _handle_file(self, path):
|
||||
|
||||
@@ -17,6 +17,8 @@ from providers.callsigndata.api_query_callsign_data_provider import (
|
||||
APIQueryCallsignDataProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HamQTH(APIQueryCallsignDataProvider):
|
||||
"""Callsign data provider for HamQTH."""
|
||||
@@ -55,10 +57,10 @@ class HamQTH(APIQueryCallsignDataProvider):
|
||||
session_id = str(dict_data["HamQTH"]["session"]["session_id"])
|
||||
else:
|
||||
# Log this failure at debug level only, not our problem if user entered the wrong password.
|
||||
logging.debug("HamQTH login details incorrect, failed to look up with HamQTH.")
|
||||
logger.debug("HamQTH login details incorrect, failed to look up with HamQTH.")
|
||||
return None
|
||||
except Exception:
|
||||
logging.error("Exception when getting HamQTH session key")
|
||||
logger.error("Exception when getting HamQTH session key")
|
||||
return None
|
||||
|
||||
if not session_id:
|
||||
@@ -71,7 +73,7 @@ class HamQTH(APIQueryCallsignDataProvider):
|
||||
if home_call != callsign:
|
||||
calls_to_try.append(home_call)
|
||||
except ValueError:
|
||||
logging.debug(f"Could not look up home call for callsign {callsign}")
|
||||
logger.debug(f"Could not look up home call for callsign {callsign}")
|
||||
|
||||
# Try looking up each call using the API
|
||||
for lookup_call in calls_to_try:
|
||||
@@ -90,18 +92,18 @@ class HamQTH(APIQueryCallsignDataProvider):
|
||||
return self.hamqth_response_to_callsign(callsign, data)
|
||||
|
||||
elif not response.from_cache:
|
||||
logging.warning(f"HTTP {response.status_code} looking up callsign {lookup_call} using HamQTH")
|
||||
logger.warning(f"HTTP {response.status_code} looking up callsign {lookup_call} using HamQTH")
|
||||
|
||||
except (KeyError, ValueError):
|
||||
continue
|
||||
except ConnectionError:
|
||||
logging.warning(f"Connection error when looking up callsign {lookup_call} using HamQTH")
|
||||
logger.warning(f"Connection error when looking up callsign {lookup_call} using HamQTH")
|
||||
continue
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning(f"Timeout when looking up callsign {lookup_call} using HamQTH")
|
||||
logger.warning(f"Timeout when looking up callsign {lookup_call} using HamQTH")
|
||||
continue
|
||||
except Exception:
|
||||
logging.exception(f"Exception when looking up callsign {lookup_call} using HamQTH")
|
||||
logger.exception(f"Exception when looking up callsign {lookup_call} using HamQTH")
|
||||
continue
|
||||
|
||||
# Not found in HamQTH; return a Callsign object with no data so we cache that and don't keep retrying
|
||||
@@ -109,7 +111,7 @@ class HamQTH(APIQueryCallsignDataProvider):
|
||||
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception("Exception when looking up data from HamQTH")
|
||||
logger.exception("Exception when looking up data from HamQTH")
|
||||
# Return None, this won't be cached so we will be asked to query data again for this call next time.
|
||||
return None
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ from providers.callsigndata.api_query_callsign_data_provider import (
|
||||
APIQueryCallsignDataProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QRZ(APIQueryCallsignDataProvider):
|
||||
"""Callsign data provider for QRZ.com."""
|
||||
@@ -53,10 +55,10 @@ class QRZ(APIQueryCallsignDataProvider):
|
||||
session_key = str(session["Key"])
|
||||
else:
|
||||
# Log this failure at debug level only, not our problem if user entered the wrong password.
|
||||
logging.debug("QRZ.com login details incorrect, failed to look up with QRZ.")
|
||||
logger.debug("QRZ.com login details incorrect, failed to look up with QRZ.")
|
||||
return None
|
||||
except Exception:
|
||||
logging.error("Exception when getting QRZ.com session key")
|
||||
logger.error("Exception when getting QRZ.com session key")
|
||||
return None
|
||||
|
||||
if not session_key:
|
||||
@@ -69,7 +71,7 @@ class QRZ(APIQueryCallsignDataProvider):
|
||||
if home_call != callsign:
|
||||
calls_to_try.append(home_call)
|
||||
except ValueError:
|
||||
logging.debug(f"Could not look up home call for callsign {callsign}")
|
||||
logger.debug(f"Could not look up home call for callsign {callsign}")
|
||||
|
||||
# Try looking up each call using the API
|
||||
for lookup_call in calls_to_try:
|
||||
@@ -93,25 +95,25 @@ class QRZ(APIQueryCallsignDataProvider):
|
||||
elif "Session" in qrz_response and "Error" in qrz_response.get("Session"):
|
||||
# Errors here are normally just "callsign not in database", no need to log that ourselves
|
||||
# above debug level.
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
f"QRZ returned an error looking up callsign {lookup_call}: {qrz_response.get('Session').get('Error')}"
|
||||
)
|
||||
|
||||
elif not response.from_cache:
|
||||
logging.warning(f"QRZ returned a malformed response looking up callsign {lookup_call}")
|
||||
logger.warning(f"QRZ returned a malformed response looking up callsign {lookup_call}")
|
||||
elif not response.from_cache:
|
||||
logging.warning(f"HTTP {response.status_code} looking up callsign {lookup_call} using QRZ")
|
||||
logger.warning(f"HTTP {response.status_code} looking up callsign {lookup_call} using QRZ")
|
||||
|
||||
except (KeyError, ValueError):
|
||||
continue
|
||||
except ConnectionError:
|
||||
logging.warning(f"Connection error when looking up callsign {lookup_call} using QRZ")
|
||||
logger.warning(f"Connection error when looking up callsign {lookup_call} using QRZ")
|
||||
continue
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning(f"Timeout when looking up callsign {lookup_call} using QRZ.")
|
||||
logger.warning(f"Timeout when looking up callsign {lookup_call} using QRZ.")
|
||||
continue
|
||||
except Exception:
|
||||
logging.exception(f"Exception when looking up callsign {lookup_call} using QRZ")
|
||||
logger.exception(f"Exception when looking up callsign {lookup_call} using QRZ")
|
||||
continue
|
||||
|
||||
# Not found in QRZ; return a Callsign object with no data so we cache that and don't keep retrying
|
||||
@@ -119,7 +121,7 @@ class QRZ(APIQueryCallsignDataProvider):
|
||||
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception("Exception when looking up data from QRZ.com")
|
||||
logger.exception("Exception when looking up data from QRZ.com")
|
||||
# Return None, this won't be cached so we will be asked to query data again for this call next time.
|
||||
return None
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ from core.constants import HTTP_HEADERS
|
||||
from core.url_data_cache import URLDataCache
|
||||
from providers.sigrefdata.sig_ref_data_provider import SIGRefDataProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FileDownloadSIGRefDataProvider(SIGRefDataProvider):
|
||||
"""Generic SIG ref data provider class for providers that fetch their data from the web by downloading a file."""
|
||||
@@ -26,7 +28,7 @@ class FileDownloadSIGRefDataProvider(SIGRefDataProvider):
|
||||
def start(self):
|
||||
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
|
||||
# subsequent polls, so start() returns immediately and the application can continue starting.
|
||||
logging.info(f"Set up query of {self.sig_name} SIG ref data every {self._poll_interval!s} days.")
|
||||
logger.info(f"Set up query of {self.sig_name} SIG ref data every {self._poll_interval!s} days.")
|
||||
self._thread = Thread(target=self._run, name=f"FileDownloadSIGRefDataProvider-{self.sig_name}")
|
||||
self._thread.start()
|
||||
|
||||
@@ -44,7 +46,7 @@ class FileDownloadSIGRefDataProvider(SIGRefDataProvider):
|
||||
try:
|
||||
# Request data from API. Use the data cache (with a TTL of 1 day) here, not as the main mechanism for
|
||||
# caching, but just so continual restarts of the software during testing don't hammer the servers.
|
||||
logging.debug(f"Downloading {self.sig_name} SIG ref data...")
|
||||
logger.debug(f"Downloading {self.sig_name} SIG ref data...")
|
||||
http_response = self._url_data_cache.get(self._url, headers=HTTP_HEADERS)
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -56,20 +58,20 @@ class FileDownloadSIGRefDataProvider(SIGRefDataProvider):
|
||||
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.debug(f"Received SIG ref data for {self.sig_name}")
|
||||
logger.debug(f"Received SIG ref data for {self.sig_name}")
|
||||
else:
|
||||
self.status = "Error"
|
||||
logging.warning(f"HTTP {http_response.status_code} when downloading SIG ref data for {self.sig_name}.")
|
||||
logger.warning(f"HTTP {http_response.status_code} when downloading SIG ref data for {self.sig_name}.")
|
||||
|
||||
except ConnectionError:
|
||||
self.status = "Error"
|
||||
logging.warning(f"Connection error when downloading SIG ref data for {self.sig_name}.")
|
||||
logger.warning(f"Connection error when downloading SIG ref data for {self.sig_name}.")
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
self.status = "Error"
|
||||
logging.warning(f"Timeout when downloading SIG ref data for {self.sig_name}.")
|
||||
logger.warning(f"Timeout when downloading SIG ref data for {self.sig_name}.")
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in HTTP SIG Ref Data Provider ({self.sig_name})")
|
||||
logger.exception(f"Exception in HTTP SIG Ref Data Provider ({self.sig_name})")
|
||||
self._stop_event.wait(timeout=1)
|
||||
|
||||
def _http_response_to_data(self, http_response):
|
||||
|
||||
@@ -8,6 +8,8 @@ from providers.sigrefdata.file_download_sig_ref_data_provider import (
|
||||
FileDownloadSIGRefDataProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IOTA(FileDownloadSIGRefDataProvider):
|
||||
"""SIG ref data provider for Islands on the Air"""
|
||||
@@ -31,7 +33,7 @@ class IOTA(FileDownloadSIGRefDataProvider):
|
||||
try:
|
||||
grid = latlong_to_locator(latitude, longitude, 6)
|
||||
except ValueError:
|
||||
logging.debug(
|
||||
logger.debug(
|
||||
"Error converting lat/lon to locator for an IOTA reference %f %f",
|
||||
latitude,
|
||||
longitude,
|
||||
|
||||
@@ -5,6 +5,8 @@ import pytz
|
||||
|
||||
from providers.sigrefdata.sig_ref_data_provider import SIGRefDataProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LocalFileSIGRefDataProvider(SIGRefDataProvider):
|
||||
"""Generic SIG ref data provider class for providers that fetch their data from a local file on startup."""
|
||||
@@ -14,7 +16,7 @@ class LocalFileSIGRefDataProvider(SIGRefDataProvider):
|
||||
self._path = path
|
||||
|
||||
def start(self):
|
||||
logging.debug(f"Loading {self.sig_name} SIG ref data from file.")
|
||||
logger.debug(f"Loading {self.sig_name} SIG ref data from file.")
|
||||
try:
|
||||
new_data = self._file_to_data(self._path)
|
||||
if new_data:
|
||||
@@ -23,10 +25,10 @@ class LocalFileSIGRefDataProvider(SIGRefDataProvider):
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
else:
|
||||
self.status = "Error"
|
||||
logging.info(f"Failed to load SIG ref data for {self.sig_name}")
|
||||
logger.info(f"Failed to load SIG ref data for {self.sig_name}")
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in local file SIG Ref Data Provider ({self.sig_name})")
|
||||
logger.exception(f"Exception in local file SIG Ref Data Provider ({self.sig_name})")
|
||||
|
||||
def _file_to_data(self, path):
|
||||
"""Load a file on the given path and turn it into SIG Ref data."""
|
||||
|
||||
@@ -5,6 +5,8 @@ import pytz
|
||||
|
||||
from core.data_store import DATA_STORE
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SIGRefDataProvider:
|
||||
"""Generic SIG reference data provider class. Subclasses of this query the individual URLs or files for data."""
|
||||
@@ -45,4 +47,4 @@ class SIGRefDataProvider:
|
||||
break
|
||||
|
||||
self.reference_count = len(new_data)
|
||||
logging.info(f"Loaded {self.reference_count} references for {self.sig_name} into the data store.")
|
||||
logger.info(f"Loaded {self.reference_count} references for {self.sig_name} into the data store.")
|
||||
|
||||
@@ -9,6 +9,8 @@ from providers.sigrefdata.file_download_sig_ref_data_provider import (
|
||||
FileDownloadSIGRefDataProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WCA(FileDownloadSIGRefDataProvider):
|
||||
"""SIG ref data provider for World Castles Award"""
|
||||
@@ -36,7 +38,7 @@ class WCA(FileDownloadSIGRefDataProvider):
|
||||
longitude = float(split[1])
|
||||
grid = latlong_to_locator(latitude, longitude)
|
||||
except ValueError:
|
||||
logging.debug(f"Encountered dodgy formatting in WCA CSV, skipping location data for {ref_id}")
|
||||
logger.debug(f"Encountered dodgy formatting in WCA CSV, skipping location data for {ref_id}")
|
||||
|
||||
new_data.append(
|
||||
SIGRef(
|
||||
|
||||
@@ -11,6 +11,8 @@ from core.constants import HTTP_HEADERS
|
||||
from providers.solarconditions.ionosonde_utils import compute_band_states
|
||||
from providers.solarconditions.solar_conditions_provider import SolarConditionsProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Each station gets polled roughly once every hour (3600 seconds). Note that to avoid a burst of requests to the server
|
||||
# every hour, the requests for data from each station are spaced out throughout the hour, leading to one request being
|
||||
# sent every 1-2 minutes.
|
||||
@@ -64,7 +66,7 @@ class GIROIonosonde(SolarConditionsProvider):
|
||||
return stations
|
||||
|
||||
def start(self):
|
||||
logging.info(f"Set up query of GIRO ionosonde data API every {POLL_INTERVAL} seconds.")
|
||||
logger.info(f"Set up query of GIRO ionosonde data API every {POLL_INTERVAL} seconds.")
|
||||
self._thread = Thread(target=self._run, name="GIROIonosondeDataProvider")
|
||||
self._thread.start()
|
||||
|
||||
@@ -86,7 +88,7 @@ class GIROIonosonde(SolarConditionsProvider):
|
||||
ursi = station["ursi"]
|
||||
name = station["name"]
|
||||
try:
|
||||
logging.debug(f"Polling GIRO ionosonde data for {ursi} ({name})...")
|
||||
logger.debug(f"Polling GIRO ionosonde data for {ursi} ({name})...")
|
||||
now = datetime.now(timezone.utc)
|
||||
from_time = now - timedelta(hours=HISTORY_HOURS)
|
||||
cutoff_ts = from_time.timestamp()
|
||||
@@ -127,11 +129,11 @@ class GIROIonosonde(SolarConditionsProvider):
|
||||
self.update_data({"ionosonde_data": ionosonde_data})
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.debug(f"Updated ionosonde data for {ursi} ({name}).")
|
||||
logger.debug(f"Updated ionosonde data for {ursi} ({name}).")
|
||||
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception fetching GIRO ionosonde data for {ursi} ({name})")
|
||||
logger.exception(f"Exception fetching GIRO ionosonde data for {ursi} ({name})")
|
||||
|
||||
def _fetch_station_data(self, ursi, from_time, to_time):
|
||||
"""Fetch foF2, MUF and LUF readings for a station. Returns (fof2_dict, muf_dict, luf_dict) keyed by UNIX timestamp."""
|
||||
@@ -142,14 +144,14 @@ class GIROIonosonde(SolarConditionsProvider):
|
||||
try:
|
||||
http_response = requests.get(url, headers=HTTP_HEADERS, timeout=(5, 15))
|
||||
if not http_response.ok:
|
||||
logging.warning(f"HTTP {http_response.status_code} when calling Giro ionosonde API.")
|
||||
logger.warning(f"HTTP {http_response.status_code} when calling Giro ionosonde API.")
|
||||
return None, None, None
|
||||
return self._parse_all(http_response.text)
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning("Timeout when accessing Giro ionosonde API.")
|
||||
logger.warning("Timeout when accessing Giro ionosonde API.")
|
||||
return None, None, None
|
||||
except ConnectionError:
|
||||
logging.warning("Connection error when accessing Giro ionosonde API.")
|
||||
logger.warning("Connection error when accessing Giro ionosonde API.")
|
||||
return None, None, None
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -9,6 +9,8 @@ from providers.solarconditions.http_solar_conditions_provider import (
|
||||
HTTPSolarConditionsProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
POLL_INTERVAL = 3600 # 1 hour
|
||||
URL = "https://www.hamqsl.com/solarxml.php"
|
||||
|
||||
@@ -24,14 +26,14 @@ class HamQSL(HTTPSolarConditionsProvider):
|
||||
root = ElementTree.fromstring(http_response.text)
|
||||
sd = root.find("solardata")
|
||||
if sd is None:
|
||||
logging.warning("HamQSL solar conditions API returned unexpected XML structure")
|
||||
logger.warning("HamQSL solar conditions API returned unexpected XML structure")
|
||||
return None
|
||||
|
||||
# Some error checking functions in case the data is janky.
|
||||
|
||||
def text(tag, default=None):
|
||||
if sd is None:
|
||||
logging.warning("HamQSL solar conditions API returned unexpected XML structure")
|
||||
logger.warning("HamQSL solar conditions API returned unexpected XML structure")
|
||||
return default
|
||||
el = sd.find(tag)
|
||||
return el.text.strip() if el is not None and el.text else default
|
||||
@@ -79,7 +81,7 @@ class HamQSL(HTTPSolarConditionsProvider):
|
||||
dt = dateutil_parser.parse(updated_str, tzinfos={tz_abbr: timezone})
|
||||
updated = dt.astimezone(pytz.UTC).timestamp()
|
||||
except (ValueError, IndexError):
|
||||
logging.warning(f"HamQSL solar conditions API returned unrecognised timestamp format: {updated_str}")
|
||||
logger.warning(f"HamQSL solar conditions API returned unrecognised timestamp format: {updated_str}")
|
||||
|
||||
# Return the data ready to be put into the solar conditions object.
|
||||
return {
|
||||
|
||||
@@ -9,6 +9,8 @@ from requests.exceptions import ConnectionError, ConnectTimeout, ReadTimeout
|
||||
from core.constants import HTTP_HEADERS
|
||||
from providers.solarconditions.solar_conditions_provider import SolarConditionsProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HTTPSolarConditionsProvider(SolarConditionsProvider):
|
||||
"""Generic solar conditions provider for providers that request data via HTTP(S). Subclasses implement
|
||||
@@ -22,7 +24,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
|
||||
self._stop_event = Event()
|
||||
|
||||
def start(self):
|
||||
logging.info(f"Set up query of {self.name} solar conditions API every {self._poll_interval!s} seconds.")
|
||||
logger.info(f"Set up query of {self.name} solar conditions API every {self._poll_interval!s} seconds.")
|
||||
self._thread = Thread(target=self._run, name=f"HTTPSolarConditionsProvider-{self.name}")
|
||||
self._thread.start()
|
||||
|
||||
@@ -37,7 +39,7 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
|
||||
|
||||
def _poll(self):
|
||||
try:
|
||||
logging.debug(f"Polling {self.name} solar conditions API...")
|
||||
logger.debug(f"Polling {self.name} solar conditions API...")
|
||||
http_response = requests.get(self._url, headers=HTTP_HEADERS, timeout=(5, 30))
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -46,18 +48,18 @@ class HTTPSolarConditionsProvider(SolarConditionsProvider):
|
||||
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.debug(f"Received data from {self.name} solar conditions API.")
|
||||
logger.debug(f"Received data from {self.name} solar conditions API.")
|
||||
else:
|
||||
self.status = "Error"
|
||||
logging.warning(f"HTTP {http_response.status_code} when calling {self.name} solar conditions API.")
|
||||
logger.warning(f"HTTP {http_response.status_code} when calling {self.name} solar conditions API.")
|
||||
|
||||
except ConnectionError:
|
||||
logging.warning(f"Connection error when accessing {self.name} solar conditions API.")
|
||||
logger.warning(f"Connection error when accessing {self.name} solar conditions API.")
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning(f"Timeout when accessing {self.name} solar conditions API.")
|
||||
logger.warning(f"Timeout when accessing {self.name} solar conditions API.")
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in HTTP Solar Conditions Provider ({self.name})")
|
||||
logger.exception(f"Exception in HTTP Solar Conditions Provider ({self.name})")
|
||||
self._stop_event.wait(timeout=1)
|
||||
|
||||
def _http_response_to_solar_conditions(self, http_response):
|
||||
|
||||
@@ -10,6 +10,8 @@ from core.constants import HTTP_HEADERS
|
||||
from providers.solarconditions.ionosonde_utils import compute_band_states
|
||||
from providers.solarconditions.solar_conditions_provider import SolarConditionsProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
POLL_INTERVAL = 900 # 15 minutes
|
||||
KC2G_URL = "https://prop.kc2g.com/api/stations.json"
|
||||
HISTORY_HOURS = 24
|
||||
@@ -29,7 +31,7 @@ class KC2GProp(SolarConditionsProvider):
|
||||
self._stop_event = Event()
|
||||
|
||||
def start(self):
|
||||
logging.info(f"Set up query of KC2G ionosonde data API every {POLL_INTERVAL} seconds.")
|
||||
logger.info(f"Set up query of KC2G ionosonde data API every {POLL_INTERVAL} seconds.")
|
||||
self._thread = Thread(target=self._run, name="KC2GPropProvider")
|
||||
self._thread.start()
|
||||
|
||||
@@ -44,10 +46,10 @@ class KC2GProp(SolarConditionsProvider):
|
||||
|
||||
def _poll(self):
|
||||
try:
|
||||
logging.debug("Polling KC2G ionosonde data...")
|
||||
logger.debug("Polling KC2G ionosonde data...")
|
||||
http_response = requests.get(KC2G_URL, headers=HTTP_HEADERS, timeout=(5, 30))
|
||||
if not http_response.ok:
|
||||
logging.warning(f"HTTP {http_response.status_code} when calling KG2G ionosonde API.")
|
||||
logger.warning(f"HTTP {http_response.status_code} when calling KG2G ionosonde API.")
|
||||
return
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
@@ -114,13 +116,13 @@ class KC2GProp(SolarConditionsProvider):
|
||||
self.update_data({"ionosonde_data": ionosonde_data})
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.debug(f"Updated KC2G ionosonde data for {updated_count} stations.")
|
||||
logger.debug(f"Updated KC2G ionosonde data for {updated_count} stations.")
|
||||
|
||||
except ConnectionError:
|
||||
logging.warning("Connection error when accessing KC2G ionosonde API.")
|
||||
logger.warning("Connection error when accessing KC2G ionosonde API.")
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning("Timeout when accessing KC2G ionosonde API.")
|
||||
logger.warning("Timeout when accessing KC2G ionosonde API.")
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception("Exception in KC2G ionosonde data provider")
|
||||
logger.exception("Exception in KC2G ionosonde data provider")
|
||||
self._stop_event.wait(timeout=1)
|
||||
|
||||
@@ -6,6 +6,8 @@ from providers.solarconditions.http_solar_conditions_provider import (
|
||||
HTTPSolarConditionsProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
POLL_INTERVAL = 10800 # Every 3 hours
|
||||
URL = "https://services.swpc.noaa.gov/text/3-day-forecast.txt"
|
||||
|
||||
@@ -28,7 +30,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
start_idx = i
|
||||
break
|
||||
if start_idx is None:
|
||||
logging.warning(f"NOAA 3-day forecast: could not find '{section_header}' section")
|
||||
logger.warning(f"NOAA 3-day forecast: could not find '{section_header}' section")
|
||||
return None
|
||||
|
||||
# Find the date header line by scanning the next few lines for month & day patterns
|
||||
@@ -38,11 +40,11 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
date_header_idx = j
|
||||
break
|
||||
if date_header_idx is None:
|
||||
logging.warning(f"NOAA 3-day forecast: could not find date header after '{section_header}'")
|
||||
logger.warning(f"NOAA 3-day forecast: could not find date header after '{section_header}'")
|
||||
return None
|
||||
date_matches = re.findall(r"([A-Za-z]{3})\s+(\d{2})", lines[date_header_idx])
|
||||
if not date_matches:
|
||||
logging.warning(f"NOAA 3-day forecast: no dates in header: {lines[date_header_idx]}")
|
||||
logger.warning(f"NOAA 3-day forecast: no dates in header: {lines[date_header_idx]}")
|
||||
return None
|
||||
|
||||
# Figure out the date based on the line found
|
||||
@@ -52,7 +54,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
dt = datetime.strptime(f"{day_str} {month_str} {year}", "%d %b %Y").replace(tzinfo=timezone.utc)
|
||||
column_timestamps.append(dt.timestamp())
|
||||
except ValueError:
|
||||
logging.warning(f"NOAA 3-day forecast: could not parse date: {month_str} {day_str} {year}")
|
||||
logger.warning(f"NOAA 3-day forecast: could not parse date: {month_str} {day_str} {year}")
|
||||
return None
|
||||
|
||||
# Parse data rows. Each non-empty line should have a text label followed by percentage values
|
||||
@@ -91,26 +93,26 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
start_idx = i
|
||||
break
|
||||
if start_idx is None:
|
||||
logging.warning("NOAA K-index forecast: could not find 'NOAA Kp index breakdown' section")
|
||||
logger.warning("NOAA K-index forecast: could not find 'NOAA Kp index breakdown' section")
|
||||
return None
|
||||
|
||||
# Extract the year from the header line, e.g. "NOAA Kp index breakdown Apr 2-Apr 4, 2026"
|
||||
header_line = lines[start_idx]
|
||||
year_match = re.search(r"\b(\d{4})\b", header_line)
|
||||
if not year_match:
|
||||
logging.warning(f"NOAA K-index forecast: could not extract year from: {header_line}")
|
||||
logger.warning(f"NOAA K-index forecast: could not extract year from: {header_line}")
|
||||
return None
|
||||
year = int(year_match.group(1))
|
||||
|
||||
# Parse the column date headers on the next line, e.g. " Apr 02 Apr 03 Apr 04"
|
||||
if start_idx + 1 >= len(lines):
|
||||
logging.warning("NOAA K-index forecast: missing date header line")
|
||||
logger.warning("NOAA K-index forecast: missing date header line")
|
||||
return None
|
||||
|
||||
date_header_line = lines[start_idx + 2]
|
||||
date_matches = re.findall(r"([A-Za-z]{3})\s+(\d{2})", date_header_line)
|
||||
if not date_matches:
|
||||
logging.warning(f"NOAA K-index forecast: could not parse date headers from: {date_header_line}")
|
||||
logger.warning(f"NOAA K-index forecast: could not parse date headers from: {date_header_line}")
|
||||
return None
|
||||
|
||||
column_dates = []
|
||||
@@ -118,7 +120,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
try:
|
||||
column_dates.append(datetime.strptime(f"{day_str} {month_str} {year}", "%d %b %Y").date())
|
||||
except ValueError:
|
||||
logging.warning(f"NOAA K-index forecast: could not parse date: {month_str} {day_str} {year}")
|
||||
logger.warning(f"NOAA K-index forecast: could not parse date: {month_str} {day_str} {year}")
|
||||
return None
|
||||
|
||||
# Parse each data row, e.g. "00-03UT 2.00 3.00 2.00"
|
||||
@@ -159,7 +161,7 @@ class NOAA3dayForecast(HTTPSolarConditionsProvider):
|
||||
k_index_forecast[key] = kp
|
||||
|
||||
if not k_index_forecast:
|
||||
logging.warning("NOAA K-index forecast: no data rows parsed")
|
||||
logger.warning("NOAA K-index forecast: no data rows parsed")
|
||||
return None
|
||||
|
||||
# Parse Solar Radiation Storm Forecast (single row: "S1 or greater")
|
||||
|
||||
@@ -9,6 +9,8 @@ from core.config import SERVER_OWNER_CALLSIGN
|
||||
from data.spot import Spot
|
||||
from providers.spot.spot_provider import SpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APRSIS(SpotProvider):
|
||||
"""Spot provider for the APRS-IS."""
|
||||
@@ -25,10 +27,10 @@ class APRSIS(SpotProvider):
|
||||
def _connect(self):
|
||||
self._aprsis = aprslib.IS(SERVER_OWNER_CALLSIGN)
|
||||
self.status = "Connecting"
|
||||
logging.info("APRS-IS connecting...")
|
||||
logger.info("APRS-IS connecting...")
|
||||
self._aprsis.connect()
|
||||
self._aprsis.consumer(self._handle)
|
||||
logging.info("APRS-IS connected.")
|
||||
logger.info("APRS-IS connected.")
|
||||
|
||||
def stop(self):
|
||||
self.status = "Shutting down"
|
||||
@@ -60,4 +62,4 @@ class APRSIS(SpotProvider):
|
||||
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.debug("Data received from APRS-IS.")
|
||||
logger.debug("Data received from APRS-IS.")
|
||||
|
||||
@@ -11,6 +11,8 @@ from core.config import SERVER_OWNER_CALLSIGN
|
||||
from data.spot import Spot
|
||||
from providers.spot.spot_provider import SpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DXCluster(SpotProvider):
|
||||
"""Spot provider for a DX Cluster. Hostname, port, login_prompt, login_callsign and allow_rbn_spots are provided in config.
|
||||
@@ -59,19 +61,19 @@ class DXCluster(SpotProvider):
|
||||
while not connected and self._running:
|
||||
try:
|
||||
self.status = "Connecting"
|
||||
logging.info(f"DX Cluster {self._hostname} connecting...")
|
||||
logger.info(f"DX Cluster {self._hostname} connecting...")
|
||||
self._telnet = telnetlib3.Telnet(self._hostname, self._port)
|
||||
self._telnet.read_until(self._login_prompt.encode("latin-1"))
|
||||
self._telnet.write(f"{self._login_callsign}\n".encode("latin-1"))
|
||||
connected = True
|
||||
logging.info(f"DX Cluster {self._hostname} connected.")
|
||||
logger.info(f"DX Cluster {self._hostname} connected.")
|
||||
except ConnectionRefusedError:
|
||||
self.status = "Error"
|
||||
logging.warning(f"Connection refused to DX cluster {self._hostname}")
|
||||
logger.warning(f"Connection refused to DX cluster {self._hostname}")
|
||||
sleep(300)
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception while connecting to DX Cluster Provider ({self._hostname}).")
|
||||
logger.exception(f"Exception while connecting to DX Cluster Provider ({self._hostname}).")
|
||||
sleep(5)
|
||||
|
||||
self.status = "Waiting for Data"
|
||||
@@ -101,25 +103,25 @@ class DXCluster(SpotProvider):
|
||||
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.debug(f"Data received from DX Cluster {self._hostname}.")
|
||||
logger.debug(f"Data received from DX Cluster {self._hostname}.")
|
||||
|
||||
except EOFError:
|
||||
connected = False
|
||||
if self._running:
|
||||
self.status = "Restarting"
|
||||
logging.warning(f"Disconnected from DX Cluster {self._hostname}. Reconnecting...")
|
||||
logger.warning(f"Disconnected from DX Cluster {self._hostname}. Reconnecting...")
|
||||
sleep(5)
|
||||
else:
|
||||
logging.info(f"DX Cluster {self._hostname} shutting down...")
|
||||
logger.info(f"DX Cluster {self._hostname} shutting down...")
|
||||
self.status = "Shutting down"
|
||||
except Exception:
|
||||
connected = False
|
||||
if self._running:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in DX Cluster Provider ({self._hostname})")
|
||||
logger.exception(f"Exception in DX Cluster Provider ({self._hostname})")
|
||||
sleep(5)
|
||||
else:
|
||||
logging.info(f"DX Cluster {self._hostname} shutting down...")
|
||||
logger.info(f"DX Cluster {self._hostname} shutting down...")
|
||||
self.status = "Shutting down"
|
||||
|
||||
self.status = "Disconnected"
|
||||
|
||||
@@ -9,6 +9,8 @@ from data.sig_ref import SIGRef
|
||||
from data.spot import Spot
|
||||
from providers.spot.http_spot_provider import HTTPSpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GMA(HTTPSpotProvider):
|
||||
"""Spot provider for General Mountain Activity"""
|
||||
@@ -24,7 +26,7 @@ class GMA(HTTPSpotProvider):
|
||||
self._api_key = provider_config.get("api_key", "")
|
||||
if self._api_key == "":
|
||||
provider_config["enabled"] = False
|
||||
logging.warning("GMA spot provider configured but no api key was provided, this API will not be queried.")
|
||||
logger.warning("GMA spot provider configured but no api key was provided, this API will not be queried.")
|
||||
self._url_data_cache = URLDataCache("GMA")
|
||||
|
||||
super().__init__(
|
||||
@@ -127,7 +129,7 @@ class GMA(HTTPSpotProvider):
|
||||
spot.sig_refs[0].sig = "MOTA"
|
||||
spot.sig = "MOTA"
|
||||
case _:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"GMA spot found with ref type {ref_info['reftype']}, developer needs to add support for this!"
|
||||
)
|
||||
spot.sig_refs[0].sig = ref_info["reftype"]
|
||||
@@ -139,19 +141,19 @@ class GMA(HTTPSpotProvider):
|
||||
|
||||
elif not ref_response.from_cache:
|
||||
if not ref_response.ok:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"HTTP {ref_response.status_code} when looking up GMA ref {source_spot['REF']}"
|
||||
)
|
||||
else:
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"GMA API returned a malformed response when looking up ref {source_spot['REF']}"
|
||||
)
|
||||
except:
|
||||
logging.exception(
|
||||
logger.exception(
|
||||
f"Exception when looking up {self.REF_INFO_URL_ROOT}{source_spot['REF']}, ignoring this spot for now"
|
||||
)
|
||||
else:
|
||||
logging.warning(f"The GMA API returned an unexpected response (HTTP {http_response.status_code}).")
|
||||
logger.warning(f"The GMA API returned an unexpected response (HTTP {http_response.status_code}).")
|
||||
|
||||
return new_spots
|
||||
|
||||
|
||||
@@ -11,6 +11,8 @@ from data.sig_ref import SIGRef
|
||||
from data.spot import Spot
|
||||
from providers.spot.http_spot_provider import HTTPSpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HEMA(HTTPSpotProvider):
|
||||
"""Spot provider for HuMPs Excluding Marilyns Award"""
|
||||
@@ -80,9 +82,9 @@ class HEMA(HTTPSpotProvider):
|
||||
# code will do that for us.
|
||||
new_spots.append(spot)
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning("Timeout when accessing HEMA spots API.")
|
||||
logger.warning("Timeout when accessing HEMA spots API.")
|
||||
except ConnectionError:
|
||||
logging.warning("Connection error when accessing HEMA spots API.")
|
||||
logger.warning("Connection error when accessing HEMA spots API.")
|
||||
return new_spots
|
||||
|
||||
def can_submit_spot(self, sig):
|
||||
|
||||
@@ -10,6 +10,8 @@ from requests.exceptions import ConnectionError, ConnectTimeout
|
||||
from core.constants import HTTP_HEADERS
|
||||
from providers.spot.spot_provider import SpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class HTTPSpotProvider(SpotProvider):
|
||||
"""Generic spot provider class for providers that request data via HTTP(S). Just for convenience to avoid code
|
||||
@@ -26,7 +28,7 @@ class HTTPSpotProvider(SpotProvider):
|
||||
def start(self):
|
||||
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
|
||||
# subsequent polls, so start() returns immediately and the application can continue starting.
|
||||
logging.info(f"Set up query of {self.name} spot API every {self._poll_interval!s} seconds.")
|
||||
logger.info(f"Set up query of {self.name} spot API every {self._poll_interval!s} seconds.")
|
||||
self._thread = Thread(target=self._run, name=f"HTTPSpotProvider-{self.name}")
|
||||
self._thread.start()
|
||||
|
||||
@@ -50,7 +52,7 @@ class HTTPSpotProvider(SpotProvider):
|
||||
def _poll(self):
|
||||
try:
|
||||
# Request data from API
|
||||
logging.debug(f"Polling {self.name} spot API...")
|
||||
logger.debug(f"Polling {self.name} spot API...")
|
||||
http_response = requests.get(self._url, headers=HTTP_HEADERS, timeout=(5, 30))
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -62,18 +64,18 @@ class HTTPSpotProvider(SpotProvider):
|
||||
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.debug(f"Received data from {self.name} spot API.")
|
||||
logger.debug(f"Received data from {self.name} spot API.")
|
||||
else:
|
||||
self.status = "Error"
|
||||
logging.warning(f"HTTP {http_response.status_code} when calling {self.name} spot API.")
|
||||
logger.warning(f"HTTP {http_response.status_code} when calling {self.name} spot API.")
|
||||
|
||||
except ConnectionError:
|
||||
logging.warning(f"Connection error when accessing {self.name} spots API.")
|
||||
logger.warning(f"Connection error when accessing {self.name} spots API.")
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning(f"Timeout when accessing {self.name} spots API.")
|
||||
logger.warning(f"Timeout when accessing {self.name} spots API.")
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in HTTP Spot Provider ({self.name})")
|
||||
logger.exception(f"Exception in HTTP Spot Provider ({self.name})")
|
||||
self._stop_event.wait(timeout=1)
|
||||
|
||||
def _http_response_to_spots(self, http_response):
|
||||
|
||||
@@ -10,6 +10,8 @@ from data.sig_ref import SIGRef
|
||||
from data.spot import Spot
|
||||
from providers.spot.http_spot_provider import HTTPSpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ParksNPeaks(HTTPSpotProvider):
|
||||
"""Spot provider for Parks n Peaks"""
|
||||
@@ -91,7 +93,7 @@ class ParksNPeaks(HTTPSpotProvider):
|
||||
"SANPCPA",
|
||||
"LLOTA",
|
||||
]:
|
||||
logging.warning(f"PNP spot found with sig {sig}, developer needs to add support for this!")
|
||||
logger.warning(f"PNP spot found with sig {sig}, developer needs to add support for this!")
|
||||
|
||||
# Add new spot to the list
|
||||
new_spots.append(spot)
|
||||
|
||||
+10
-8
@@ -11,6 +11,8 @@ from core.config import SERVER_OWNER_CALLSIGN
|
||||
from data.spot import Spot
|
||||
from providers.spot.spot_provider import SpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RBN(SpotProvider):
|
||||
"""Spot provider for the Reverse Beacon Network. Connects to a single port, if you want both CW/RTTY (port 7000) and FT8
|
||||
@@ -46,15 +48,15 @@ class RBN(SpotProvider):
|
||||
while not connected and self._running:
|
||||
try:
|
||||
self.status = "Connecting"
|
||||
logging.info(f"RBN port {self._port!s} connecting...")
|
||||
logger.info(f"RBN port {self._port!s} connecting...")
|
||||
self._telnet = telnetlib3.Telnet("telnet.reversebeacon.net", self._port)
|
||||
self._telnet.read_until("Please enter your call: ".encode("latin-1"))
|
||||
self._telnet.write(f"{SERVER_OWNER_CALLSIGN}\n".encode("latin-1"))
|
||||
connected = True
|
||||
logging.info(f"RBN port {self._port!s} connected.")
|
||||
logger.info(f"RBN port {self._port!s} connected.")
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception while connecting to RBN (port {self._port!s}).")
|
||||
logger.exception(f"Exception while connecting to RBN (port {self._port!s}).")
|
||||
sleep(5)
|
||||
|
||||
self.status = "Waiting for Data"
|
||||
@@ -84,25 +86,25 @@ class RBN(SpotProvider):
|
||||
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.debug(f"Data received from RBN on port {self._port!s}.")
|
||||
logger.debug(f"Data received from RBN on port {self._port!s}.")
|
||||
|
||||
except EOFError:
|
||||
connected = False
|
||||
if self._running:
|
||||
self.status = "Restarting"
|
||||
logging.warning(f"Disconnected from RBN provider (port {self._port!s}). Reconnecting...")
|
||||
logger.warning(f"Disconnected from RBN provider (port {self._port!s}). Reconnecting...")
|
||||
sleep(5)
|
||||
else:
|
||||
logging.info(f"RBN provider (port {self._port!s}) shutting down...")
|
||||
logger.info(f"RBN provider (port {self._port!s}) shutting down...")
|
||||
self.status = "Shutting down"
|
||||
except Exception:
|
||||
connected = False
|
||||
if self._running:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in RBN provider (port {self._port!s})")
|
||||
logger.exception(f"Exception in RBN provider (port {self._port!s})")
|
||||
sleep(5)
|
||||
else:
|
||||
logging.info(f"RBN provider (port {self._port!s}) shutting down...")
|
||||
logger.info(f"RBN provider (port {self._port!s}) shutting down...")
|
||||
self.status = "Shutting down"
|
||||
|
||||
self.status = "Disconnected"
|
||||
|
||||
@@ -9,6 +9,8 @@ from data.sig_ref import SIGRef
|
||||
from data.spot import Spot
|
||||
from providers.spot.http_spot_provider import HTTPSpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SOTA(HTTPSpotProvider):
|
||||
"""Spot provider for Summits on the Air"""
|
||||
@@ -73,9 +75,9 @@ class SOTA(HTTPSpotProvider):
|
||||
# that for us.
|
||||
new_spots.append(spot)
|
||||
except ConnectionError:
|
||||
logging.warning("Connection error when accessing SOTA spots API")
|
||||
logger.warning("Connection error when accessing SOTA spots API")
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
logging.warning("Timeout when accessing SOTA spots API.")
|
||||
logger.warning("Timeout when accessing SOTA spots API.")
|
||||
return new_spots
|
||||
|
||||
def can_submit_spot(self, sig):
|
||||
|
||||
@@ -8,6 +8,8 @@ from requests_sse import EventSource
|
||||
from core.constants import HTTP_HEADERS
|
||||
from providers.spot.spot_provider import SpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SSESpotProvider(SpotProvider):
|
||||
"""Spot provider using Server-Sent Events."""
|
||||
@@ -22,7 +24,7 @@ class SSESpotProvider(SpotProvider):
|
||||
self._event_source = None
|
||||
|
||||
def start(self):
|
||||
logging.info(f"Set up SSE connection to {self.name} spot API.")
|
||||
logger.info(f"Set up SSE connection to {self.name} spot API.")
|
||||
self._stop_event.clear()
|
||||
self._thread = Thread(target=self._run, name=f"SSESpotProvider-{self.name}")
|
||||
self._thread.daemon = True
|
||||
@@ -37,12 +39,12 @@ class SSESpotProvider(SpotProvider):
|
||||
try:
|
||||
event_source.close()
|
||||
except Exception:
|
||||
logging.exception(f"Exception closing SSE connection for {self.name} during stop()")
|
||||
logger.exception(f"Exception closing SSE connection for {self.name} during stop()")
|
||||
|
||||
if self._thread:
|
||||
self._thread.join(timeout=15)
|
||||
if self._thread.is_alive():
|
||||
logging.warning(f"{self.name} SSE worker thread did not exit on time and will be killed.")
|
||||
logger.warning(f"{self.name} SSE worker thread did not exit on time and will be killed.")
|
||||
|
||||
def _on_open(self):
|
||||
self.status = "Waiting for Data"
|
||||
@@ -57,7 +59,7 @@ class SSESpotProvider(SpotProvider):
|
||||
def _run(self):
|
||||
while not self._stop_event.is_set():
|
||||
try:
|
||||
logging.debug(f"Connecting to {self.name} spot API...")
|
||||
logger.debug(f"Connecting to {self.name} spot API...")
|
||||
self.status = "Connecting"
|
||||
with EventSource(
|
||||
self._url,
|
||||
@@ -81,10 +83,10 @@ class SSESpotProvider(SpotProvider):
|
||||
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.debug(f"Received data from {self.name} spot API.")
|
||||
logger.debug(f"Received data from {self.name} spot API.")
|
||||
|
||||
except Exception:
|
||||
logging.exception(
|
||||
logger.exception(
|
||||
f"Exception processing message from SSE Spot Provider ({self.name})"
|
||||
)
|
||||
finally:
|
||||
@@ -92,7 +94,7 @@ class SSESpotProvider(SpotProvider):
|
||||
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in SSE Spot Provider ({self.name})")
|
||||
logger.exception(f"Exception in SSE Spot Provider ({self.name})")
|
||||
else:
|
||||
self.status = "Disconnected"
|
||||
self._stop_event.wait(timeout=5) # Wait before trying to reconnect
|
||||
|
||||
@@ -9,6 +9,8 @@ from websocket import create_connection
|
||||
from core.constants import HTTP_HEADERS
|
||||
from providers.spot.spot_provider import SpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebsocketSpotProvider(SpotProvider):
|
||||
"""Spot provider using websockets."""
|
||||
@@ -22,7 +24,7 @@ class WebsocketSpotProvider(SpotProvider):
|
||||
self._last_event_id = None
|
||||
|
||||
def start(self):
|
||||
logging.info(f"Set up websocket connection to {self.name} spot API.")
|
||||
logger.info(f"Set up websocket connection to {self.name} spot API.")
|
||||
self._stopped = False
|
||||
self._thread = Thread(target=self._run, name=f"WebsocketSpotProvider-{self.name}")
|
||||
self._thread.daemon = True
|
||||
@@ -44,7 +46,7 @@ class WebsocketSpotProvider(SpotProvider):
|
||||
def _run(self):
|
||||
while not self._stopped:
|
||||
try:
|
||||
logging.debug(f"Connecting to {self.name} spot API...")
|
||||
logger.debug(f"Connecting to {self.name} spot API...")
|
||||
self.status = "Connecting"
|
||||
self._ws = create_connection(self._url, header=HTTP_HEADERS)
|
||||
self.status = "Connected"
|
||||
@@ -57,14 +59,14 @@ class WebsocketSpotProvider(SpotProvider):
|
||||
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.debug(f"Received data from {self.name} spot API.")
|
||||
logger.debug(f"Received data from {self.name} spot API.")
|
||||
|
||||
except Exception:
|
||||
logging.exception(f"Exception processing message from Websocket Spot Provider ({self.name})")
|
||||
logger.exception(f"Exception processing message from Websocket Spot Provider ({self.name})")
|
||||
|
||||
except Exception as e:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in Websocket Spot Provider ({self.name})", e)
|
||||
logger.exception(f"Exception in Websocket Spot Provider ({self.name})", e)
|
||||
else:
|
||||
self.status = "Disconnected"
|
||||
sleep(5) # Wait before trying to reconnect
|
||||
|
||||
@@ -11,6 +11,8 @@ from data.sig_ref import SIGRef
|
||||
from data.spot import Spot
|
||||
from providers.spot.http_spot_provider import HTTPSpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WOTA(HTTPSpotProvider):
|
||||
"""Spot provider for Wainwrights on the Air"""
|
||||
@@ -82,7 +84,7 @@ class WOTA(HTTPSpotProvider):
|
||||
|
||||
new_spots.append(spot)
|
||||
except Exception as e:
|
||||
logging.error("Exception parsing WOTA spot", e)
|
||||
logger.error("Exception parsing WOTA spot", e)
|
||||
return new_spots
|
||||
|
||||
def can_submit_spot(self, sig):
|
||||
|
||||
@@ -9,6 +9,8 @@ from providers.staticdata.local_file_static_data_provider import (
|
||||
LocalFileStaticDataProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CQZoneData(LocalFileStaticDataProvider):
|
||||
"""Static data provider for CQ zone geodata."""
|
||||
@@ -34,5 +36,5 @@ class CQZoneData(LocalFileStaticDataProvider):
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when loading CQ zone data.")
|
||||
logger.exception("Exception when loading CQ zone data.")
|
||||
return False
|
||||
|
||||
@@ -10,6 +10,8 @@ from core.constants import HTTP_HEADERS
|
||||
from core.url_data_cache import URLDataCache
|
||||
from providers.staticdata.static_data_provider import StaticDataProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FileDownloadStaticDataProvider(StaticDataProvider):
|
||||
"""Generic static reference data provider class for providers that fetch their data from the web by downloading a
|
||||
@@ -27,7 +29,7 @@ class FileDownloadStaticDataProvider(StaticDataProvider):
|
||||
def start(self):
|
||||
# Fire off the polling thread. It will poll immediately on startup, then sleep for poll_interval between
|
||||
# subsequent polls, so start() returns immediately and the application can continue starting.
|
||||
logging.info(f"Set up query of {self.name} static reference data every {self._poll_interval!s} days.")
|
||||
logger.info(f"Set up query of {self.name} static reference data every {self._poll_interval!s} days.")
|
||||
self._thread = Thread(target=self._run, name=f"FileDownloadStaticDataProvider-{self.name}")
|
||||
self._thread.start()
|
||||
|
||||
@@ -44,7 +46,7 @@ class FileDownloadStaticDataProvider(StaticDataProvider):
|
||||
try:
|
||||
# Request data from API. Use the data cache (with a TTL of 1 day) here, not as the main mechanism for
|
||||
# caching, but just so continual restarts of the software during testing don't hammer the servers.
|
||||
logging.debug(f"Downloading {self.name} static reference data...")
|
||||
logger.debug(f"Downloading {self.name} static reference data...")
|
||||
http_response = self._url_data_cache.get(self._url, headers=HTTP_HEADERS)
|
||||
# Check response code was good
|
||||
if http_response.ok:
|
||||
@@ -53,22 +55,22 @@ class FileDownloadStaticDataProvider(StaticDataProvider):
|
||||
if ok:
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.info(f"Updated static reference data for {self.name}")
|
||||
logger.info(f"Updated static reference data for {self.name}")
|
||||
else:
|
||||
self.status = "Error"
|
||||
logging.warning(
|
||||
logger.warning(
|
||||
f"HTTP {http_response.status_code} when downloading static reference data for {self.name}."
|
||||
)
|
||||
|
||||
except ConnectionError:
|
||||
self.status = "Error"
|
||||
logging.warning(f"Connection error when downloading static reference data for {self.name}.")
|
||||
logger.warning(f"Connection error when downloading static reference data for {self.name}.")
|
||||
except (ConnectTimeout, ReadTimeout):
|
||||
self.status = "Error"
|
||||
logging.warning(f"Timeout when downloading static reference data for {self.name}.")
|
||||
logger.warning(f"Timeout when downloading static reference data for {self.name}.")
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in HTTP static reference data provider ({self.name})")
|
||||
logger.exception(f"Exception in HTTP static reference data provider ({self.name})")
|
||||
self._stop_event.wait(timeout=1)
|
||||
|
||||
def _handle_http_response(self, http_response):
|
||||
|
||||
@@ -9,6 +9,8 @@ from providers.staticdata.local_file_static_data_provider import (
|
||||
LocalFileStaticDataProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ITUZoneData(LocalFileStaticDataProvider):
|
||||
"""Static data provider for ITU zone geodata."""
|
||||
@@ -34,5 +36,5 @@ class ITUZoneData(LocalFileStaticDataProvider):
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when loading ITU zone data.")
|
||||
logger.exception("Exception when loading ITU zone data.")
|
||||
return False
|
||||
|
||||
@@ -5,6 +5,8 @@ from providers.staticdata.file_download_static_data_provider import (
|
||||
FileDownloadStaticDataProvider,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class K0SWE(FileDownloadStaticDataProvider):
|
||||
"""Static data provider for K0SWE's dxcc.json, which provides callsign regex to DXCC entity mapping, plus DXCC to
|
||||
@@ -44,5 +46,5 @@ class K0SWE(FileDownloadStaticDataProvider):
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when loading K0SWE dxcc.json.")
|
||||
logger.exception("Exception when loading K0SWE dxcc.json.")
|
||||
return False
|
||||
|
||||
@@ -5,6 +5,8 @@ import pytz
|
||||
|
||||
from providers.staticdata.static_data_provider import StaticDataProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LocalFileStaticDataProvider(StaticDataProvider):
|
||||
"""Generic static reference data provider class for providers that fetch their data from a local file on startup."""
|
||||
@@ -15,19 +17,19 @@ class LocalFileStaticDataProvider(StaticDataProvider):
|
||||
self._stop = False
|
||||
|
||||
def start(self):
|
||||
logging.debug(f"Loading {self.name} static reference data from file.")
|
||||
logger.debug(f"Loading {self.name} static reference data from file.")
|
||||
try:
|
||||
ok = self._load_data(self._path)
|
||||
if ok:
|
||||
self.status = "OK"
|
||||
self.last_update_time = datetime.now(pytz.UTC)
|
||||
logging.info(f"Updated static reference data for {self.name}")
|
||||
logger.info(f"Updated static reference data for {self.name}")
|
||||
else:
|
||||
self.status = "Error"
|
||||
logging.error(f"Failed to load data for {self.name}")
|
||||
logger.error(f"Failed to load data for {self.name}")
|
||||
except Exception:
|
||||
self.status = "Error"
|
||||
logging.exception(f"Exception in local file Static Data Provider ({self.name})")
|
||||
logger.exception(f"Exception in local file Static Data Provider ({self.name})")
|
||||
|
||||
def stop(self):
|
||||
self._stop = True
|
||||
|
||||
@@ -18,6 +18,8 @@ from core.utils import infer_band_from_freq, safe_json_dumps
|
||||
from data.spot import Spot
|
||||
from providers.spot.spot_provider import SpotProvider
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RECAPTCHA_VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify"
|
||||
|
||||
|
||||
@@ -220,7 +222,7 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
except NotImplementedError as e:
|
||||
upstream_warning = str(e)
|
||||
except Exception:
|
||||
logging.exception(f"Failed to submit spot upstream to {upstream_provider_name}")
|
||||
logger.exception(f"Failed to submit spot upstream to {upstream_provider_name}")
|
||||
upstream_warning = (
|
||||
f"Spot was saved locally but upstream submission to {upstream_provider_name} failed."
|
||||
)
|
||||
@@ -244,7 +246,7 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when handling client request to add spot API")
|
||||
logger.exception("Exception when handling client request to add spot API")
|
||||
self.write(safe_json_dumps("Error - an internal server error occurred."))
|
||||
self.set_status(500)
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
@@ -270,5 +272,5 @@ class APISpotHandler(tornado.web.RequestHandler):
|
||||
)
|
||||
return response.ok and response.json().get("success", False)
|
||||
except Exception:
|
||||
logging.exception("reCAPTCHA verification request failed")
|
||||
logger.exception("reCAPTCHA verification request failed")
|
||||
return False
|
||||
|
||||
@@ -13,6 +13,8 @@ from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.utils import safe_json_dumps
|
||||
from data.lookup_credentials import extract_credentials
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APIAlertsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/alerts"""
|
||||
@@ -63,7 +65,7 @@ class APIAlertsHandler(tornado.web.RequestHandler):
|
||||
self.write(safe_json_dumps(f"Bad request - {e!s}"))
|
||||
self.set_status(400)
|
||||
except Exception:
|
||||
logging.exception("Exception when handling client request to alerts API")
|
||||
logger.exception("Exception when handling client request to alerts API")
|
||||
self.write(safe_json_dumps("Error - an internal server error occurred."))
|
||||
self.set_status(500)
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
@@ -110,7 +112,7 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
||||
self._sse_alert_broadcaster.register(self)
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when serving SSE socket")
|
||||
logger.exception("Exception when serving SSE socket")
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
@@ -129,7 +131,7 @@ class APIAlertsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
||||
alert.infer_missing(self._credentials)
|
||||
self.write_message(msg=safe_json_dumps(alert))
|
||||
except Exception:
|
||||
logging.exception("Exception in SSE callback, connection will be closed")
|
||||
logger.exception("Exception in SSE callback, connection will be closed")
|
||||
self.close()
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ from tornado.web import Application
|
||||
from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.utils import safe_json_dumps
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CONTINENTS = ["EU", "NA", "SA", "AS", "AF", "OC", "AN"]
|
||||
BANDS = ["160m", "80m", "60m", "40m", "30m", "20m", "17m", "15m", "12m", "10m", "6m"]
|
||||
CONTINENTS_SET = frozenset(CONTINENTS)
|
||||
@@ -68,6 +70,6 @@ class APIDxStatsHandler(tornado.web.RequestHandler):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when handling client request to dx stats API")
|
||||
logger.exception("Exception when handling client request to dx stats API")
|
||||
self.write(safe_json_dumps("Error - an internal server error occurred."))
|
||||
self.set_status(500)
|
||||
|
||||
@@ -22,6 +22,8 @@ from core.utils import safe_json_dumps
|
||||
from data.lookup_credentials import extract_credentials
|
||||
from data.sig_ref import SIGRef
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APILookupCallHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/lookup/call"""
|
||||
@@ -66,7 +68,7 @@ class APILookupCallHandler(tornado.web.RequestHandler):
|
||||
self.set_status(422)
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when handling client request to call lookup API")
|
||||
logger.exception("Exception when handling client request to call lookup API")
|
||||
self.write(safe_json_dumps("Error - an internal server error occurred."))
|
||||
self.set_status(500)
|
||||
|
||||
@@ -124,7 +126,7 @@ class APILookupSIGRefHandler(tornado.web.RequestHandler):
|
||||
self.set_status(422)
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when handling client request to sig ref lookup API")
|
||||
logger.exception("Exception when handling client request to sig ref lookup API")
|
||||
self.write(safe_json_dumps("Error - an internal server error occurred."))
|
||||
self.set_status(500)
|
||||
|
||||
@@ -192,7 +194,7 @@ class APILookupGridHandler(tornado.web.RequestHandler):
|
||||
self.set_status(422)
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when handling client request to grid ref lookup API")
|
||||
logger.exception("Exception when handling client request to grid ref lookup API")
|
||||
self.write(safe_json_dumps("Error - an internal server error occurred."))
|
||||
self.set_status(500)
|
||||
|
||||
|
||||
@@ -19,6 +19,8 @@ from core.constants import (
|
||||
from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.utils import safe_json_dumps
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APIOptionsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/options"""
|
||||
@@ -119,6 +121,6 @@ class APIOptionsHandler(tornado.web.RequestHandler):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when handling client request to options API")
|
||||
logger.exception("Exception when handling client request to options API")
|
||||
self.write(safe_json_dumps("Error - an internal server error occurred."))
|
||||
self.set_status(500)
|
||||
|
||||
@@ -10,6 +10,8 @@ from tornado.web import Application
|
||||
from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.utils import safe_json_dumps
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APISolarConditionsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/solar"""
|
||||
@@ -42,6 +44,6 @@ class APISolarConditionsHandler(tornado.web.RequestHandler):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when handling client request to solar conditions API")
|
||||
logger.exception("Exception when handling client request to solar conditions API")
|
||||
self.write(safe_json_dumps("Error - an internal server error occurred."))
|
||||
self.set_status(500)
|
||||
|
||||
@@ -13,6 +13,8 @@ from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.utils import safe_json_dumps
|
||||
from data.lookup_credentials import extract_credentials
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APISpotsHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/spots"""
|
||||
@@ -63,7 +65,7 @@ class APISpotsHandler(tornado.web.RequestHandler):
|
||||
self.write(safe_json_dumps(f"Bad request - {e!s}"))
|
||||
self.set_status(400)
|
||||
except Exception:
|
||||
logging.exception("Excedption when handling client request to spots API")
|
||||
logger.exception("Excedption when handling client request to spots API")
|
||||
self.write(safe_json_dumps("Error - an internal server error occurred."))
|
||||
self.set_status(500)
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
@@ -112,7 +114,7 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
||||
self._sse_spot_broadcaster.register(self)
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when serving SSE socket")
|
||||
logger.exception("Exception when serving SSE socket")
|
||||
self.close()
|
||||
|
||||
def close(self):
|
||||
@@ -132,7 +134,7 @@ class APISpotsStreamHandler(tornado_eventsource.handler.EventSourceHandler):
|
||||
spot.infer_missing(self._credentials)
|
||||
self.write_message(msg=safe_json_dumps(spot))
|
||||
except Exception:
|
||||
logging.exception("Exception in SSE callback, connection will be closed")
|
||||
logger.exception("Exception in SSE callback, connection will be closed")
|
||||
self.close()
|
||||
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ from tornado.web import Application
|
||||
from core.prometheus_metrics_handler import api_requests_counter
|
||||
from core.utils import safe_json_dumps
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class APIStatusHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v2/status"""
|
||||
@@ -42,6 +44,6 @@ class APIStatusHandler(tornado.web.RequestHandler):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when handling client request to status API")
|
||||
logger.exception("Exception when handling client request to status API")
|
||||
self.write(safe_json_dumps("Error - an internal server error occurred."))
|
||||
self.set_status(500)
|
||||
|
||||
@@ -15,6 +15,8 @@ from core.sig_utils import get_ref_regex_for_sig
|
||||
from core.utils import infer_band_from_freq, safe_json_dumps
|
||||
from data.spot import Spot
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class V1APISpotHandler(tornado.web.RequestHandler):
|
||||
"""API request handler for /api/v1/spot (POST). Included in early Spothole v2 for backwards compatibility."""
|
||||
@@ -143,7 +145,7 @@ class V1APISpotHandler(tornado.web.RequestHandler):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
|
||||
except Exception:
|
||||
logging.exception("Exception when handling client request to add spot API")
|
||||
logger.exception("Exception when handling client request to add spot API")
|
||||
self.write(safe_json_dumps("Error - an internal server error occurred."))
|
||||
self.set_status(500)
|
||||
self.set_header("Cache-Control", "no-store")
|
||||
|
||||
@@ -3,6 +3,8 @@ import threading
|
||||
|
||||
from tornado.ioloop import IOLoop
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SSEBroadcaster:
|
||||
"""Bridge between DataStore listener callbacks (which fire on provider threads) to Tornado's async SSE handlers
|
||||
@@ -35,5 +37,5 @@ class SSEBroadcaster:
|
||||
handler.callback(value)
|
||||
except Exception:
|
||||
# Connection probably dropped, ignore and de-register the handler to stop getting future items.
|
||||
logging.debug("Failed to push to an SSE client; dropping it")
|
||||
logger.debug("Failed to push to an SSE client; dropping it")
|
||||
self.unregister(handler)
|
||||
|
||||
+7
-5
@@ -34,6 +34,8 @@ from server.handlers.metrics import PrometheusMetricsHandler
|
||||
from server.handlers.pagetemplate import PageTemplateHandler
|
||||
from server.sse_broadcaster import SSEBroadcaster
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_HERE = os.path.dirname(__file__ or "")
|
||||
|
||||
|
||||
@@ -159,7 +161,7 @@ class WebServer:
|
||||
|
||||
# If in API-only mode, serve a basic homepage; in normal mode, serve the usual UI routes
|
||||
if self._api_only_mode:
|
||||
logging.info("API-only mode is enabled. Web UI will not be served.")
|
||||
logger.info("API-only mode is enabled. Web UI will not be served.")
|
||||
ui_routes = [
|
||||
(
|
||||
r"/",
|
||||
@@ -238,8 +240,8 @@ class WebServer:
|
||||
debug=False,
|
||||
)
|
||||
app.listen(self._port, xheaders=True)
|
||||
logging.info(f"Web server running on port {WEB_SERVER_PORT!s}")
|
||||
logging.info(f"You can access your copy of Spothole at {BASE_URL}")
|
||||
logger.info(f"Web server running on port {WEB_SERVER_PORT!s}")
|
||||
logger.info(f"You can access your copy of Spothole at {BASE_URL}")
|
||||
await self._shutdown_event.wait()
|
||||
|
||||
|
||||
@@ -249,9 +251,9 @@ def request_log(handler):
|
||||
|
||||
if LOG_WEB_REQUESTS:
|
||||
if handler.get_status() < 500:
|
||||
log_method = logging.info
|
||||
log_method = logger.info
|
||||
else:
|
||||
log_method = logging.warning
|
||||
log_method = logger.warning
|
||||
|
||||
request = handler.request
|
||||
client_ip = request.remote_ip
|
||||
|
||||
+5
-3
@@ -12,6 +12,8 @@ from core.data_store import DATA_STORE
|
||||
from core.status_reporter import StatusReporter
|
||||
from server.webserver import WEB_SERVER
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Globals
|
||||
run = True
|
||||
|
||||
@@ -21,7 +23,7 @@ def shutdown(_signum=None, _frame=None):
|
||||
|
||||
global run
|
||||
|
||||
logging.info("Stopping program...")
|
||||
logger.info("Stopping program...")
|
||||
WEB_SERVER.stop()
|
||||
DATA_PROVIDERS.stop()
|
||||
CLEANUP_TIMER.stop()
|
||||
@@ -41,8 +43,8 @@ if __name__ == "__main__":
|
||||
root.handlers.clear()
|
||||
root.addHandler(handler)
|
||||
|
||||
logging.info("Starting...")
|
||||
logging.info(f"This is Spothole version {SOFTWARE_VERSION}. This instance is run by {SERVER_OWNER_CALLSIGN}.")
|
||||
logger.info("Starting...")
|
||||
logger.info(f"This is Spothole version {SOFTWARE_VERSION}. This instance is run by {SERVER_OWNER_CALLSIGN}.")
|
||||
|
||||
# Shut down gracefully on SIGINT
|
||||
signal.signal(signal.SIGINT, shutdown)
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/js/add-spot.js?v=1786779098"></script>
|
||||
<script src="/static/js/add-spot.js?v=1786779306"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-add-spot").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/js/alerts.js?v=1786779098"></script>
|
||||
<script src="/static/js/alerts.js?v=1786779307"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-alerts").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -79,8 +79,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1786779098"></script>
|
||||
<script src="/static/js/bands.js?v=1786779098"></script>
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1786779307"></script>
|
||||
<script src="/static/js/bands.js?v=1786779307"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-bands").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
+5
-5
@@ -1,6 +1,6 @@
|
||||
{% extends "skeleton.html" %}
|
||||
{% block head_extra %}
|
||||
<link rel="stylesheet" href="/static/css/style.css?v=1786779098" type="text/css">
|
||||
<link rel="stylesheet" href="/static/css/style.css?v=1786779306" type="text/css">
|
||||
<link href="/static/vendor/css/bootstrap-5.3.8.min.css" rel="stylesheet">
|
||||
<link href="/static/vendor/css/fontawesome-6.7.2.min.css" rel="stylesheet">
|
||||
<link href="/static/vendor/css/solid-6.7.2.min.css" rel="stylesheet">
|
||||
@@ -15,10 +15,10 @@
|
||||
window.fetchEventSource = fetchEventSource;
|
||||
</script>
|
||||
|
||||
<script src="/static/js/utils.js?v=1786779098"></script>
|
||||
<script src="/static/js/ui-ham.js?v=1786779098"></script>
|
||||
<script src="/static/js/geo.js?v=1786779098"></script>
|
||||
<script src="/static/js/common.js?v=1786779098"></script>
|
||||
<script src="/static/js/utils.js?v=1786779306"></script>
|
||||
<script src="/static/js/ui-ham.js?v=1786779306"></script>
|
||||
<script src="/static/js/geo.js?v=1786779306"></script>
|
||||
<script src="/static/js/common.js?v=1786779306"></script>
|
||||
{% end %}
|
||||
{% block body %}
|
||||
<div class="container">
|
||||
|
||||
@@ -284,7 +284,7 @@
|
||||
</div>
|
||||
|
||||
<script src="/static/vendor/js/chart-4.4.9.umd.min.js"></script>
|
||||
<script src="/static/js/conditions.js?v=1786779098"></script>
|
||||
<script src="/static/js/conditions.js?v=1786779307"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-conditions").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
+2
-2
@@ -112,8 +112,8 @@
|
||||
<script src="/static/vendor/js/leaflet-cqzones.js"></script>
|
||||
<script src="/static/vendor/js/leaflet-workedallbritainireland.js" type="module"></script>
|
||||
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1786779098"></script>
|
||||
<script src="/static/js/map.js?v=1786779098"></script>
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1786779307"></script>
|
||||
<script src="/static/js/map.js?v=1786779307"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-map").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -118,8 +118,8 @@
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1786779098"></script>
|
||||
<script src="/static/js/spots.js?v=1786779098"></script>
|
||||
<script src="/static/js/spotsbandsandmap.js?v=1786779306"></script>
|
||||
<script src="/static/js/spots.js?v=1786779306"></script>
|
||||
<script>$(document).ready(function () {
|
||||
$("#nav-link-spots").addClass("active");
|
||||
}); <!-- highlight active page in nav --></script>
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/status.js?v=1786779098"></script>
|
||||
<script src="/static/js/status.js?v=1786779307"></script>
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$("#nav-link-status").addClass("active");
|
||||
|
||||
Reference in New Issue
Block a user