Replace root logger calls with module-specific loggers

This commit is contained in:
Ian Renton
2026-08-15 08:35:06 +01:00
parent 74bcd9cded
commit dd89ff4af7
64 changed files with 328 additions and 216 deletions
+9 -7
View File
@@ -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)
+3 -1
View File
@@ -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
+4 -2
View File
@@ -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
+5 -3
View File
@@ -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
+4 -2
View File
@@ -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):
+10 -8
View File
@@ -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
+12 -10
View File
@@ -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):
+3 -1
View File
@@ -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.")
+3 -1
View File
@@ -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(
+9 -7
View File
@@ -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
+5 -3
View File
@@ -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):
+9 -7
View File
@@ -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)
+12 -10
View File
@@ -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")
+5 -3
View File
@@ -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 -9
View File
@@ -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"
+8 -6
View File
@@ -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
+4 -2
View File
@@ -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):
+9 -7
View File
@@ -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):
+3 -1
View File
@@ -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
View File
@@ -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"
+4 -2
View File
@@ -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):
+9 -7
View File
@@ -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
+7 -5
View File
@@ -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
+3 -1
View File
@@ -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):
+3 -1
View File
@@ -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):
+3 -1
View File
@@ -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
+3 -1
View File
@@ -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