1
0
mirror of https://github.com/craigerl/aprsd.git synced 2024-11-21 23:55:17 -05:00

Compare commits

..

3 Commits

Author SHA1 Message Date
e57a2e2ffc Fixed a bug with multiple notify plugins enabled
This patch fixes an issue with the processing of packets
and updateing the watchlist.  Previously after the
notify plugin processed the packet it would update the watchlist.
This doesn't work when there are more than 1 notify plugins
enabled, only the first notify plugin seeing the packet will
recognize that the callsign is old.
2021-12-10 14:20:57 -05:00
6a1cea63e4
Merge pull request #77 from craigerl/logs
Unify the logging to file and stdout
2021-12-10 11:12:48 -05:00
592b328956 Unify the logging to file and stdout
This patch updates the logging facility to ensure that
logging to a file works even when --quiet mode is selected.
Also update the listen and list-plugins command to show
a console.status line while waiting for results to come in.
2021-12-10 10:49:09 -05:00
10 changed files with 116 additions and 83 deletions

View File

@ -11,6 +11,7 @@ import sys
import click import click
import requests import requests
from rich.console import Console
import aprsd import aprsd
from aprsd import cli_helper, utils from aprsd import cli_helper, utils
@ -21,6 +22,7 @@ from aprsd.aprsd import cli
# setup the global logger # setup the global logger
# logging.basicConfig(level=logging.DEBUG) # level=10 # logging.basicConfig(level=logging.DEBUG) # level=10
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
console = Console()
@cli.command() @cli.command()
@ -42,27 +44,31 @@ LOG = logging.getLogger("APRSD")
@cli_helper.process_standard_options_no_config @cli_helper.process_standard_options_no_config
def healthcheck(ctx, health_url, timeout): def healthcheck(ctx, health_url, timeout):
"""Check the health of the running aprsd server.""" """Check the health of the running aprsd server."""
LOG.debug(f"APRSD HealthCheck version: {aprsd.__version__}") console.log(f"APRSD HealthCheck version: {aprsd.__version__}")
with console.status(f"APRSD HealthCheck version: {aprsd.__version__}") as status:
try: try:
url = health_url url = health_url
response = requests.get(url, timeout=timeout) response = requests.get(url, timeout=timeout)
response.raise_for_status() response.raise_for_status()
except Exception as ex: except Exception as ex:
LOG.error(f"Failed to fetch healthcheck url '{url}' : '{ex}'") console.log(f"Failed to fetch healthcheck url '{url}' : '{ex}'")
sys.exit(-1) sys.exit(-1)
else: else:
stats = json.loads(response.text) import time
LOG.debug(stats) time.sleep(2)
status.update("PISS")
time.sleep(2)
stats = json.loads(response.text)
email_thread_last_update = stats["stats"]["email"]["thread_last_update"] email_thread_last_update = stats["stats"]["email"]["thread_last_update"]
if email_thread_last_update != "never":
delta = utils.parse_delta_str(email_thread_last_update) delta = utils.parse_delta_str(email_thread_last_update)
d = datetime.timedelta(**delta) d = datetime.timedelta(**delta)
max_timeout = {"hours": 0.0, "minutes": 5, "seconds": 0} max_timeout = {"hours": 0.0, "minutes": 5, "seconds": 0}
max_delta = datetime.timedelta(**max_timeout) max_delta = datetime.timedelta(**max_timeout)
if d > max_delta: if d > max_delta:
LOG.error(f"Email thread is very old! {d}") console.log(f"Email thread is very old! {d}")
sys.exit(-1) sys.exit(-1)
aprsis_last_update = stats["stats"]["aprs-is"]["last_update"] aprsis_last_update = stats["stats"]["aprs-is"]["last_update"]

View File

@ -227,6 +227,8 @@ def list_plugins(ctx):
# now find any from pypi? # now find any from pypi?
installed_plugins = get_installed_plugins() installed_plugins = get_installed_plugins()
with console.status("Fetching pypi.org plugins"):
show_pypi_plugins(installed_plugins, console) show_pypi_plugins(installed_plugins, console)
with console.status("Looking for installed aprsd plugins"):
show_installed_plugins(installed_plugins, console) show_installed_plugins(installed_plugins, console)

View File

@ -10,6 +10,7 @@ import time
import aprslib import aprslib
import click import click
from rich.console import Console
# local imports here # local imports here
import aprsd import aprsd
@ -22,6 +23,7 @@ from aprsd.aprsd import cli
# setup the global logger # setup the global logger
# logging.basicConfig(level=logging.DEBUG) # level=10 # logging.basicConfig(level=logging.DEBUG) # level=10
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
console = Console()
def signal_handler(sig, frame): def signal_handler(sig, frame):
@ -113,7 +115,7 @@ def listen(
resp = packet.get("response", None) resp = packet.get("response", None)
if resp == "ack": if resp == "ack":
ack_num = packet.get("msgNo") ack_num = packet.get("msgNo")
LOG.info(f"We saw an ACK {ack_num} Ignoring") console.log(f"We saw an ACK {ack_num} Ignoring")
messaging.log_packet(packet) messaging.log_packet(packet)
else: else:
message = packet.get("message_text", None) message = packet.get("message_text", None)
@ -125,6 +127,7 @@ def listen(
message, message,
fromcall=fromcall, fromcall=fromcall,
ack=msg_number, ack=msg_number,
console=console,
) )
# Initialize the client factory and create # Initialize the client factory and create
@ -148,6 +151,7 @@ def listen(
# This will register a packet consumer with aprslib # This will register a packet consumer with aprslib
# When new packets come in the consumer will process # When new packets come in the consumer will process
# the packet # the packet
with console.status("Listening for packets"):
aprs_client.consumer(rx_packet, raw=False) aprs_client.consumer(rx_packet, raw=False)
except aprslib.exceptions.ConnectionDrop: except aprslib.exceptions.ConnectionDrop:
LOG.error("Connection dropped, reconnecting") LOG.error("Connection dropped, reconnecting")

View File

@ -1,9 +1,7 @@
import datetime import datetime
import json import json
import logging import logging
from logging import NullHandler
from logging.handlers import RotatingFileHandler from logging.handlers import RotatingFileHandler
import sys
import threading import threading
import time import time
@ -11,6 +9,7 @@ import aprslib
from aprslib.exceptions import LoginError from aprslib.exceptions import LoginError
import flask import flask
from flask import request from flask import request
from flask.logging import default_handler
import flask_classful import flask_classful
from flask_httpauth import HTTPBasicAuth from flask_httpauth import HTTPBasicAuth
from flask_socketio import Namespace, SocketIO from flask_socketio import Namespace, SocketIO
@ -21,6 +20,7 @@ from aprsd import client
from aprsd import config as aprsd_config from aprsd import config as aprsd_config
from aprsd import log, messaging, packets, plugin, stats, threads, utils from aprsd import log, messaging, packets, plugin, stats, threads, utils
from aprsd.clients import aprsis from aprsd.clients import aprsis
from aprsd.logging import logging as aprsd_logging
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -552,6 +552,15 @@ class LoggingNamespace(Namespace):
def setup_logging(config, flask_app, loglevel, quiet): def setup_logging(config, flask_app, loglevel, quiet):
flask_log = logging.getLogger("werkzeug") flask_log = logging.getLogger("werkzeug")
flask_app.logger.removeHandler(default_handler)
flask_log.removeHandler(default_handler)
log_level = aprsd_config.LOG_LEVELS[loglevel]
flask_log.setLevel(log_level)
date_format = config["aprsd"].get(
"dateformat",
aprsd_config.DEFAULT_DATE_FORMAT,
)
if not config["aprsd"]["web"].get("logging_enabled", False): if not config["aprsd"]["web"].get("logging_enabled", False):
# disable web logging # disable web logging
@ -559,29 +568,31 @@ def setup_logging(config, flask_app, loglevel, quiet):
flask_app.logger.disabled = True flask_app.logger.disabled = True
return return
log_level = aprsd_config.LOG_LEVELS[loglevel] if config["aprsd"].get("rich_logging", False) and not quiet:
LOG.setLevel(log_level) log_format = "%(message)s"
log_format = config["aprsd"].get("logformat", aprsd_config.DEFAULT_LOG_FORMAT)
date_format = config["aprsd"].get("dateformat", aprsd_config.DEFAULT_DATE_FORMAT)
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format) log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
rh = aprsd_logging.APRSDRichHandler(
show_thread=True, thread_width=15,
rich_tracebacks=True, omit_repeated_times=False,
)
rh.setFormatter(log_formatter)
flask_log.addHandler(rh)
log_file = config["aprsd"].get("logfile", None) log_file = config["aprsd"].get("logfile", None)
if log_file: if log_file:
fh = RotatingFileHandler(log_file, maxBytes=(10248576 * 5), backupCount=4) log_format = config["aprsd"].get(
else: "logformat",
fh = NullHandler() aprsd_config.DEFAULT_LOG_FORMAT,
)
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
fh = RotatingFileHandler(
log_file, maxBytes=(10248576 * 5),
backupCount=4,
)
fh.setFormatter(log_formatter) fh.setFormatter(log_formatter)
for handler in flask_app.logger.handlers:
handler.setFormatter(log_formatter)
print(handler)
flask_log.addHandler(fh) flask_log.addHandler(fh)
if not quiet:
sh = logging.StreamHandler(sys.stdout)
sh.setFormatter(log_formatter)
flask_log.addHandler(sh)
def init_flask(config, loglevel, quiet): def init_flask(config, loglevel, quiet):
global socketio global socketio

View File

@ -18,35 +18,31 @@ logging_queue = queue.Queue()
def setup_logging(config, loglevel, quiet): def setup_logging(config, loglevel, quiet):
log_level = aprsd_config.LOG_LEVELS[loglevel] log_level = aprsd_config.LOG_LEVELS[loglevel]
LOG.setLevel(log_level) LOG.setLevel(log_level)
if config["aprsd"].get("rich_logging", False):
log_format = "%(message)s"
else:
log_format = config["aprsd"].get("logformat", aprsd_config.DEFAULT_LOG_FORMAT)
date_format = config["aprsd"].get("dateformat", aprsd_config.DEFAULT_DATE_FORMAT) date_format = config["aprsd"].get("dateformat", aprsd_config.DEFAULT_DATE_FORMAT)
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
log_file = config["aprsd"].get("logfile", None)
rich_logging = False rich_logging = False
if config["aprsd"].get("rich_logging", False): if config["aprsd"].get("rich_logging", False) and not quiet:
log_format = "%(message)s"
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
rh = aprsd_logging.APRSDRichHandler( rh = aprsd_logging.APRSDRichHandler(
show_thread=True, thread_width=15, show_thread=True, thread_width=20,
rich_tracebacks=True, omit_repeated_times=False, rich_tracebacks=True, omit_repeated_times=False,
) )
rh.setFormatter(log_formatter) rh.setFormatter(log_formatter)
LOG.addHandler(rh) LOG.addHandler(rh)
rich_logging = True rich_logging = True
if log_file: log_file = config["aprsd"].get("logfile", None)
fh = RotatingFileHandler(log_file, maxBytes=(10248576 * 5), backupCount=4)
else:
fh = NullHandler()
if log_file:
log_format = config["aprsd"].get("logformat", aprsd_config.DEFAULT_LOG_FORMAT)
log_formatter = logging.Formatter(fmt=log_format, datefmt=date_format)
fh = RotatingFileHandler(log_file, maxBytes=(10248576 * 5), backupCount=4)
fh.setFormatter(log_formatter) fh.setFormatter(log_formatter)
LOG.addHandler(fh) LOG.addHandler(fh)
imap_logger = None imap_logger = None
if config.get("aprsd.email.enabled", default=False) and config.get("aprsd.email.imap.debug", default=False): if config.get("aprsd.email.enabled", default=False) and config.get("aprsd.email.imap.debug", default=False):
imap_logger = logging.getLogger("imapclient.imaplib") imap_logger = logging.getLogger("imapclient.imaplib")
imap_logger.setLevel(log_level) imap_logger.setLevel(log_level)
imap_logger.addHandler(fh) imap_logger.addHandler(fh)

View File

@ -190,6 +190,7 @@ class Message(metaclass=abc.ABCMeta):
fromcall, fromcall,
tocall, tocall,
msg_id=None, msg_id=None,
allow_delay=True,
): ):
self.fromcall = fromcall self.fromcall = fromcall
self.tocall = tocall self.tocall = tocall
@ -199,6 +200,10 @@ class Message(metaclass=abc.ABCMeta):
msg_id = c.value msg_id = c.value
self.id = msg_id self.id = msg_id
# do we try and save this message for later if we don't get
# an ack? Some messages we don't want to do this ever.
self.allow_delay = allow_delay
@abc.abstractmethod @abc.abstractmethod
def send(self): def send(self):
"""Child class must declare.""" """Child class must declare."""
@ -214,8 +219,8 @@ class RawMessage(Message):
message = None message = None
def __init__(self, message): def __init__(self, message, allow_delay=True):
super().__init__(None, None, msg_id=None) super().__init__(fromcall=None, tocall=None, msg_id=None, allow_delay=allow_delay)
self.message = message self.message = message
def dict(self): def dict(self):
@ -269,11 +274,11 @@ class TextMessage(Message):
msg_id=None, msg_id=None,
allow_delay=True, allow_delay=True,
): ):
super().__init__(fromcall, tocall, msg_id) super().__init__(
fromcall=fromcall, tocall=tocall,
msg_id=msg_id, allow_delay=allow_delay,
)
self.message = message self.message = message
# do we try and save this message for later if we don't get
# an ack? Some messages we don't want to do this ever.
self.allow_delay = allow_delay
def dict(self): def dict(self):
now = datetime.datetime.now() now = datetime.datetime.now()
@ -369,6 +374,8 @@ class SendMessageThread(threads.APRSDThread):
# we reached the send limit, don't send again # we reached the send limit, don't send again
# TODO(hemna) - Need to put this in a delayed queue? # TODO(hemna) - Need to put this in a delayed queue?
LOG.info("Message Send Complete. Max attempts reached.") LOG.info("Message Send Complete. Max attempts reached.")
if not msg.allow_delay:
tracker.remove(msg.id)
return False return False
# Message is still outstanding and needs to be acked. # Message is still outstanding and needs to be acked.
@ -527,6 +534,7 @@ def log_packet(packet):
def log_message( def log_message(
header, raw, message, tocall=None, fromcall=None, msg_num=None, header, raw, message, tocall=None, fromcall=None, msg_num=None,
retry_number=None, ack=None, packet_type=None, uuid=None, retry_number=None, ack=None, packet_type=None, uuid=None,
console=None,
): ):
""" """
@ -565,4 +573,7 @@ def log_message(
log_list.append(f" UUID : {uuid}") log_list.append(f" UUID : {uuid}")
log_list.append(f"{header} _______________ Complete") log_list.append(f"{header} _______________ Complete")
if console:
console.log("\n".join(log_list))
else:
LOG.info("\n".join(log_list)) LOG.info("\n".join(log_list))

View File

@ -86,7 +86,7 @@ class WatchList(objectstore.ObjectStoreMixin):
if config: if config:
self.config = config self.config = config
ring_size = config["aprsd"]["watch_list"]["packet_keep_count"] ring_size = config["aprsd"]["watch_list"].get("packet_keep_count", 10)
for callsign in config["aprsd"]["watch_list"].get("callsigns", []): for callsign in config["aprsd"]["watch_list"].get("callsigns", []):
call = callsign.replace("*", "") call = callsign.replace("*", "")

View File

@ -177,7 +177,6 @@ class APRSDWatchListPluginBase(APRSDPluginBase, metaclass=abc.ABCMeta):
) )
if result: if result:
self.tx_inc() self.tx_inc()
wl.update_seen(packet)
else: else:
LOG.warning(f"{self.__class__} plugin is not enabled") LOG.warning(f"{self.__class__} plugin is not enabled")

View File

@ -1,6 +1,6 @@
import logging import logging
from aprsd import messaging, packets, plugin from aprsd import messaging, packets, plugin, trace
LOG = logging.getLogger("APRSD") LOG = logging.getLogger("APRSD")
@ -17,6 +17,7 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
short_description = "Notify me when a CALLSIGN is recently seen on APRS-IS" short_description = "Notify me when a CALLSIGN is recently seen on APRS-IS"
@trace.trace
def process(self, packet): def process(self, packet):
LOG.info("NotifySeenPlugin") LOG.info("NotifySeenPlugin")
@ -46,10 +47,11 @@ class NotifySeenPlugin(plugin.APRSDWatchListPluginBase):
) )
return msg return msg
else: else:
LOG.debug("fromcall and notify_callsign are the same, not notifying")
return messaging.NULL_MESSAGE return messaging.NULL_MESSAGE
else: else:
LOG.debug( LOG.debug(
"Not old enough to notify callsign '{}' : {} < {}".format( "Not old enough to notify on callsign '{}' : {} < {}".format(
fromcall, fromcall,
age, age,
wl.max_delta(), wl.max_delta(),

View File

@ -202,7 +202,7 @@ class APRSDProcessPacketThread(APRSDThread):
self.packet = packet self.packet = packet
self.config = config self.config = config
name = self.packet["raw"][:10] name = self.packet["raw"][:10]
super().__init__(f"RX_PACKET-{name}") super().__init__(f"RXPKT-{name}")
def process_ack_packet(self, packet): def process_ack_packet(self, packet):
ack_num = packet.get("msgNo") ack_num = packet.get("msgNo")
@ -261,6 +261,8 @@ class APRSDProcessPacketThread(APRSDThread):
pm = plugin.PluginManager() pm = plugin.PluginManager()
try: try:
results = pm.run(packet) results = pm.run(packet)
wl = packets.WatchList()
wl.update_seen(packet)
replied = False replied = False
for reply in results: for reply in results:
if isinstance(reply, list): if isinstance(reply, list):