diff --git a/core/cleanup.py b/core/cleanup.py index 86fdf5e..a0d0c62 100644 --- a/core/cleanup.py +++ b/core/cleanup.py @@ -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) diff --git a/core/config.py b/core/config.py index 2866ccb..6e6d998 100644 --- a/core/config.py +++ b/core/config.py @@ -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) diff --git a/core/data_providers.py b/core/data_providers.py index 2c014e3..e0a7e5f 100644 --- a/core/data_providers.py +++ b/core/data_providers.py @@ -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() diff --git a/core/data_store.py b/core/data_store.py index 3e160f6..aea5359 100644 --- a/core/data_store.py +++ b/core/data_store.py @@ -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 diff --git a/core/geo_utils.py b/core/geo_utils.py index 973cb4b..8c32131 100644 --- a/core/geo_utils.py +++ b/core/geo_utils.py @@ -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 diff --git a/core/live_data_cache.py b/core/live_data_cache.py index 275c7ef..57526df 100644 --- a/core/live_data_cache.py +++ b/core/live_data_cache.py @@ -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(): diff --git a/core/sig_lookup_helper.py b/core/sig_lookup_helper.py index a0174b8..5388b4b 100644 --- a/core/sig_lookup_helper.py +++ b/core/sig_lookup_helper.py @@ -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 diff --git a/core/utils.py b/core/utils.py index 43e94db..895423a 100644 --- a/core/utils.py +++ b/core/utils.py @@ -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 diff --git a/data/alert.py b/data/alert.py index 7f3de5f..9074f8c 100644 --- a/data/alert.py +++ b/data/alert.py @@ -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""" diff --git a/data/spot.py b/data/spot.py index 514e8f8..5d0e8a3 100644 --- a/data/spot.py +++ b/data/spot.py @@ -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""" diff --git a/providers/alert/http_alert_provider.py b/providers/alert/http_alert_provider.py index ba8f378..93aab50 100644 --- a/providers/alert/http_alert_provider.py +++ b/providers/alert/http_alert_provider.py @@ -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) diff --git a/providers/alert/parksnpeaks.py b/providers/alert/parksnpeaks.py index 849e0ce..1b8d335 100644 --- a/providers/alert/parksnpeaks.py +++ b/providers/alert/parksnpeaks.py @@ -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 diff --git a/providers/callsigndata/clublogapi.py b/providers/callsigndata/clublogapi.py index 23589a3..d7283b5 100644 --- a/providers/callsigndata/clublogapi.py +++ b/providers/callsigndata/clublogapi.py @@ -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 diff --git a/providers/callsigndata/clublogxml.py b/providers/callsigndata/clublogxml.py index 1c79389..59bbf91 100644 --- a/providers/callsigndata/clublogxml.py +++ b/providers/callsigndata/clublogxml.py @@ -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 diff --git a/providers/callsigndata/countryfiles.py b/providers/callsigndata/countryfiles.py index 4dbad09..593219a 100644 --- a/providers/callsigndata/countryfiles.py +++ b/providers/callsigndata/countryfiles.py @@ -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 diff --git a/providers/callsigndata/file_download_callsign_data_provider.py b/providers/callsigndata/file_download_callsign_data_provider.py index 420ced2..8b9a311 100644 --- a/providers/callsigndata/file_download_callsign_data_provider.py +++ b/providers/callsigndata/file_download_callsign_data_provider.py @@ -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): diff --git a/providers/callsigndata/hamqth.py b/providers/callsigndata/hamqth.py index d91851a..1ac0257 100644 --- a/providers/callsigndata/hamqth.py +++ b/providers/callsigndata/hamqth.py @@ -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 diff --git a/providers/callsigndata/qrz.py b/providers/callsigndata/qrz.py index 5f8baae..b9ed558 100644 --- a/providers/callsigndata/qrz.py +++ b/providers/callsigndata/qrz.py @@ -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 diff --git a/providers/sigrefdata/file_download_sig_ref_data_provider.py b/providers/sigrefdata/file_download_sig_ref_data_provider.py index 525d0bf..53cd3f9 100644 --- a/providers/sigrefdata/file_download_sig_ref_data_provider.py +++ b/providers/sigrefdata/file_download_sig_ref_data_provider.py @@ -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): diff --git a/providers/sigrefdata/iota.py b/providers/sigrefdata/iota.py index 57c3efd..6a46196 100644 --- a/providers/sigrefdata/iota.py +++ b/providers/sigrefdata/iota.py @@ -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, diff --git a/providers/sigrefdata/local_file_sig_ref_data_provider.py b/providers/sigrefdata/local_file_sig_ref_data_provider.py index 146e8a6..c810779 100644 --- a/providers/sigrefdata/local_file_sig_ref_data_provider.py +++ b/providers/sigrefdata/local_file_sig_ref_data_provider.py @@ -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.""" diff --git a/providers/sigrefdata/sig_ref_data_provider.py b/providers/sigrefdata/sig_ref_data_provider.py index 8bc228a..7c98345 100644 --- a/providers/sigrefdata/sig_ref_data_provider.py +++ b/providers/sigrefdata/sig_ref_data_provider.py @@ -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.") diff --git a/providers/sigrefdata/wca.py b/providers/sigrefdata/wca.py index e13b94a..5359376 100644 --- a/providers/sigrefdata/wca.py +++ b/providers/sigrefdata/wca.py @@ -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( diff --git a/providers/solarconditions/giroionosonde.py b/providers/solarconditions/giroionosonde.py index 5514549..91ab322 100644 --- a/providers/solarconditions/giroionosonde.py +++ b/providers/solarconditions/giroionosonde.py @@ -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 diff --git a/providers/solarconditions/hamqsl.py b/providers/solarconditions/hamqsl.py index 2b66a48..a905d5b 100644 --- a/providers/solarconditions/hamqsl.py +++ b/providers/solarconditions/hamqsl.py @@ -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 { diff --git a/providers/solarconditions/http_solar_conditions_provider.py b/providers/solarconditions/http_solar_conditions_provider.py index 7e8e5d5..86229dc 100644 --- a/providers/solarconditions/http_solar_conditions_provider.py +++ b/providers/solarconditions/http_solar_conditions_provider.py @@ -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): diff --git a/providers/solarconditions/kc2gprop.py b/providers/solarconditions/kc2gprop.py index a4ebf5e..f80f423 100644 --- a/providers/solarconditions/kc2gprop.py +++ b/providers/solarconditions/kc2gprop.py @@ -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) diff --git a/providers/solarconditions/noaa3dayforecast.py b/providers/solarconditions/noaa3dayforecast.py index efc9f23..184dcb7 100644 --- a/providers/solarconditions/noaa3dayforecast.py +++ b/providers/solarconditions/noaa3dayforecast.py @@ -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") diff --git a/providers/spot/aprsis.py b/providers/spot/aprsis.py index 046a751..26bccfc 100644 --- a/providers/spot/aprsis.py +++ b/providers/spot/aprsis.py @@ -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.") diff --git a/providers/spot/dxcluster.py b/providers/spot/dxcluster.py index c8e2902..769967c 100644 --- a/providers/spot/dxcluster.py +++ b/providers/spot/dxcluster.py @@ -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" diff --git a/providers/spot/gma.py b/providers/spot/gma.py index f398110..3322400 100644 --- a/providers/spot/gma.py +++ b/providers/spot/gma.py @@ -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 diff --git a/providers/spot/hema.py b/providers/spot/hema.py index d4c5b98..26f983b 100644 --- a/providers/spot/hema.py +++ b/providers/spot/hema.py @@ -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): diff --git a/providers/spot/http_spot_provider.py b/providers/spot/http_spot_provider.py index 872ab24..ef44066 100644 --- a/providers/spot/http_spot_provider.py +++ b/providers/spot/http_spot_provider.py @@ -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): diff --git a/providers/spot/parksnpeaks.py b/providers/spot/parksnpeaks.py index d48962c..9ae4f7c 100644 --- a/providers/spot/parksnpeaks.py +++ b/providers/spot/parksnpeaks.py @@ -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) diff --git a/providers/spot/rbn.py b/providers/spot/rbn.py index 55c6e8b..3f79b72 100644 --- a/providers/spot/rbn.py +++ b/providers/spot/rbn.py @@ -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" diff --git a/providers/spot/sota.py b/providers/spot/sota.py index 08d2d26..cbdb239 100644 --- a/providers/spot/sota.py +++ b/providers/spot/sota.py @@ -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): diff --git a/providers/spot/sse_spot_provider.py b/providers/spot/sse_spot_provider.py index 90236fe..4edddc3 100644 --- a/providers/spot/sse_spot_provider.py +++ b/providers/spot/sse_spot_provider.py @@ -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 diff --git a/providers/spot/websocket_spot_provider.py b/providers/spot/websocket_spot_provider.py index bebbe22..868dd5e 100644 --- a/providers/spot/websocket_spot_provider.py +++ b/providers/spot/websocket_spot_provider.py @@ -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 diff --git a/providers/spot/wota.py b/providers/spot/wota.py index 8000f73..95becc3 100644 --- a/providers/spot/wota.py +++ b/providers/spot/wota.py @@ -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): diff --git a/providers/staticdata/cqzonedata.py b/providers/staticdata/cqzonedata.py index 1e0f300..770e3b1 100644 --- a/providers/staticdata/cqzonedata.py +++ b/providers/staticdata/cqzonedata.py @@ -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 diff --git a/providers/staticdata/file_download_static_data_provider.py b/providers/staticdata/file_download_static_data_provider.py index cf3f2f6..f31aa4c 100644 --- a/providers/staticdata/file_download_static_data_provider.py +++ b/providers/staticdata/file_download_static_data_provider.py @@ -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): diff --git a/providers/staticdata/ituzonedata.py b/providers/staticdata/ituzonedata.py index ebc4411..c078f4e 100644 --- a/providers/staticdata/ituzonedata.py +++ b/providers/staticdata/ituzonedata.py @@ -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 diff --git a/providers/staticdata/k0swe.py b/providers/staticdata/k0swe.py index 225c52a..5890349 100644 --- a/providers/staticdata/k0swe.py +++ b/providers/staticdata/k0swe.py @@ -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 diff --git a/providers/staticdata/local_file_static_data_provider.py b/providers/staticdata/local_file_static_data_provider.py index f01a3c4..f5b848f 100644 --- a/providers/staticdata/local_file_static_data_provider.py +++ b/providers/staticdata/local_file_static_data_provider.py @@ -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 diff --git a/server/handlers/api/addspot.py b/server/handlers/api/addspot.py index ce0aa86..72504c1 100644 --- a/server/handlers/api/addspot.py +++ b/server/handlers/api/addspot.py @@ -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 diff --git a/server/handlers/api/alerts.py b/server/handlers/api/alerts.py index 0c708f6..181b6c4 100644 --- a/server/handlers/api/alerts.py +++ b/server/handlers/api/alerts.py @@ -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() diff --git a/server/handlers/api/dxstats.py b/server/handlers/api/dxstats.py index 561db0c..e452e68 100644 --- a/server/handlers/api/dxstats.py +++ b/server/handlers/api/dxstats.py @@ -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) diff --git a/server/handlers/api/lookups.py b/server/handlers/api/lookups.py index 3e7ca30..c726db7 100644 --- a/server/handlers/api/lookups.py +++ b/server/handlers/api/lookups.py @@ -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) diff --git a/server/handlers/api/options.py b/server/handlers/api/options.py index bd3ed47..9cc3960 100644 --- a/server/handlers/api/options.py +++ b/server/handlers/api/options.py @@ -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) diff --git a/server/handlers/api/solar_conditions.py b/server/handlers/api/solar_conditions.py index 582311e..8b71295 100644 --- a/server/handlers/api/solar_conditions.py +++ b/server/handlers/api/solar_conditions.py @@ -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) diff --git a/server/handlers/api/spots.py b/server/handlers/api/spots.py index 377bb39..dee0577 100644 --- a/server/handlers/api/spots.py +++ b/server/handlers/api/spots.py @@ -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() diff --git a/server/handlers/api/status.py b/server/handlers/api/status.py index 662df0c..6687a1c 100644 --- a/server/handlers/api/status.py +++ b/server/handlers/api/status.py @@ -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) diff --git a/server/handlers/api/v1_addspot.py b/server/handlers/api/v1_addspot.py index 35161c1..04cc455 100644 --- a/server/handlers/api/v1_addspot.py +++ b/server/handlers/api/v1_addspot.py @@ -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") diff --git a/server/sse_broadcaster.py b/server/sse_broadcaster.py index 3a76701..60d29db 100644 --- a/server/sse_broadcaster.py +++ b/server/sse_broadcaster.py @@ -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) diff --git a/server/webserver.py b/server/webserver.py index bef5536..28c454f 100644 --- a/server/webserver.py +++ b/server/webserver.py @@ -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 diff --git a/spothole.py b/spothole.py index f2c80bd..7b5a8ce 100644 --- a/spothole.py +++ b/spothole.py @@ -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) diff --git a/templates/add_spot.html b/templates/add_spot.html index 83c5100..0128cde 100644 --- a/templates/add_spot.html +++ b/templates/add_spot.html @@ -76,7 +76,7 @@ - + diff --git a/templates/alerts.html b/templates/alerts.html index a80b5ae..f896921 100644 --- a/templates/alerts.html +++ b/templates/alerts.html @@ -82,7 +82,7 @@ - + diff --git a/templates/bands.html b/templates/bands.html index 7e02a3e..f02f153 100644 --- a/templates/bands.html +++ b/templates/bands.html @@ -79,8 +79,8 @@ - - + + diff --git a/templates/base.html b/templates/base.html index e23b13c..338c005 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,6 +1,6 @@ {% extends "skeleton.html" %} {% block head_extra %} - + @@ -15,10 +15,10 @@ window.fetchEventSource = fetchEventSource; - - - - + + + + {% end %} {% block body %}
diff --git a/templates/conditions.html b/templates/conditions.html index 09e9ee4..bf3ce1e 100644 --- a/templates/conditions.html +++ b/templates/conditions.html @@ -284,7 +284,7 @@
- + diff --git a/templates/map.html b/templates/map.html index 69cd65c..1225bb9 100644 --- a/templates/map.html +++ b/templates/map.html @@ -112,8 +112,8 @@ - - + + diff --git a/templates/spots.html b/templates/spots.html index 5b06d38..dee1e1c 100644 --- a/templates/spots.html +++ b/templates/spots.html @@ -118,8 +118,8 @@ - - + + diff --git a/templates/status.html b/templates/status.html index e6b7afb..d4e2b6c 100644 --- a/templates/status.html +++ b/templates/status.html @@ -86,7 +86,7 @@ - +