First real UNIX daemon based version of the code. 1000 Cleanups and Fixes... :)

This commit is contained in:
Johannes Findeisen 2021-11-13 07:30:05 +01:00
commit 3e5222f97c
7 changed files with 380 additions and 132 deletions

136
uplink
View file

@ -21,32 +21,23 @@
# OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import argparse
import calendar
import json
import logging
import logging.handlers
import os
import os.path as path
import pymysql
import socket
import sys
import time
from fritzconnection.lib.fritzstatus import FritzStatus
from threading import Thread
from uplink import Uplink
# Version format: MAJOR.FEATURE.FIXES
__version__ = "0.2.3"
__version__ = "0.3.0-development"
logger = logging.getLogger(__name__)
PURPLE = "\033[95m"
BLUE = "\033[94m"
YELLOW = "\033[93m"
GRAY = "\033[90m"
END = "\033[0m"
# TODO.md: Implement logging
# TODO.md: Implement --no-daemon for a single run e.g. "cron mode"
# TODO.md: Implement CLI output messages
# TODO.md: IDEA: Implement a small webserver inline to get statistics and graphs over the network? Or maybe better as a
# separate daemon
# TODO.md: Make use of the args passed to the script
# TODO.md: Cleanup args and reduce to only what makes sense
def parse_args():
parser = argparse.ArgumentParser(
description="uplink is a tool to monitor the link status of AVM FRITZ!Box Cable and DSL based routers.",
@ -76,25 +67,25 @@ def parse_args():
parser.add_argument("-l", "--logfile", metavar="FILE", help="logfile to use")
parser.add_argument("-c", "--logcount", default=5, type=int,
parser.add_argument("-c", "--log-count", default=5, type=int,
help="maximum number of logfiles in rotation (default: 5)")
parser.add_argument("-m", "--logsize", default=10485760, type=int,
parser.add_argument("-m", "--log-size", default=10485760, type=int,
help="maximum logfile size in bytes (default: 10485760)")
parser.add_argument("-s", "--stdout", default=False, dest="stdout", action="store_true", help="log to stdout")
output = parser.add_mutually_exclusive_group()
output.add_argument("-q", "--quiet", action="store_const", dest="loglevel", const=logging.ERROR,
output.add_argument("-q", "--quiet", action="store_const", dest="log_level", const=logging.ERROR,
help="output only errors")
output.add_argument("-w", "--warning", action="store_const", dest="loglevel", const=logging.WARNING,
output.add_argument("-w", "--warning", action="store_const", dest="log_level", const=logging.WARNING,
help="output warnings")
output.add_argument("-v", "--verbose", action="store_const", dest="loglevel", const=logging.INFO,
output.add_argument("-v", "--verbose", action="store_const", dest="log_level", const=logging.INFO,
help="output info messages")
output.add_argument("-d", "--debug", action="store_const", dest="loglevel", const=logging.DEBUG,
output.add_argument("-d", "--debug", action="store_const", dest="log_level", const=logging.DEBUG,
help="output debug messages")
output.set_defaults(loglevel=logging.ERROR)
@ -103,106 +94,21 @@ def parse_args():
return parser.parse_args()
def get_data(config, inc):
try:
con = pymysql.connect(host=config["database_host"],
user=config["database_user"],
password=config["database_password"],
database=config["database"])
except Exception as err:
logger.error("Database connection failed: " + str(err))
exit(1)
timestamp = calendar.timegm(time.gmtime())
local_time = time.localtime(timestamp)
d = time.strftime("%Y-%m-%d ", local_time)
t = time.strftime("%H:%M:%S", local_time)
try:
fc = FritzStatus(address=config["uplinks"][inc]["ip"], password=config["uplinks"][inc]["password"])
except Exception as err:
logger.error(str(err) + " on device with ip: " + config["uplinks"][inc]["ip"])
sql = 'INSERT INTO log (timestamp, date, time, internal_ip, is_linked, is_connected, provider, message, ' \
'source_host) VALUES (\"' + str(timestamp) + '\",\"' + str(d) + '\",\"' + str(t) + '\",\"' + \
str(config["uplinks"][inc]["ip"]) + '\",\"' + "0" + '\",\"' + "0" + '\",\"' + \
str(config["uplinks"][inc]["provider"]) + '\",\"ERROR: ' + str(err) + '\",\"' + socket.gethostname() + \
'\")'
logger.debug(sql)
with con.cursor() as cur:
cur.execute(sql)
con.commit()
exit(1)
if fc.is_connected:
status = "UP"
else:
status = "DOWN"
logger.info(config["uplinks"][inc]["provider"] + " " + status)
sql = 'INSERT INTO log (timestamp, date, time, uptime, internal_ip, external_ip, external_ipv6, is_linked, ' \
'is_connected, str_transmission_rate_up, str_transmission_rate_down, str_max_bit_rate_up, ' \
'str_max_bit_rate_down, str_max_linked_bit_rate_up, str_max_linked_bit_rate_down, modelname, ' \
'system_version, provider, message, source_host) VALUES (\"' + str(timestamp) + '\",\"' + str(d) + '\",\"' + \
str(t) + '\",\"' + str(fc.uptime) + '\",\"' + str(config["uplinks"][inc]["ip"]) + '\",\"' + \
str(fc.external_ip) + '\",\"' + str(fc.external_ipv6) + '\",\"' + str(int(fc.is_linked)) + '\",\"' + \
str(int(fc.is_connected)) + '\",\"' + str(fc.str_transmission_rate[0]) + '\",\"' + \
str(fc.str_transmission_rate[1]) + '\",\"' + str(fc.str_max_bit_rate[0]) + '\",\"' + \
str(fc.str_max_bit_rate[1]) + '\",\"' + str(fc.str_max_linked_bit_rate[0]) + '\",\"' + \
str(fc.str_max_linked_bit_rate[1]) + '\",\"' + str(fc.modelname) + '\",\"' + \
str(fc.fc.system_version) + '\",\"' + str(config["uplinks"][inc]["provider"]) + '\",\"' + status + '\",\"' + \
socket.gethostname() + '\")'
logger.debug(sql)
with con.cursor() as cur:
cur.execute(sql)
con.commit()
con.close()
def run(config):
while True:
for i in range(len(config["uplinks"])):
t = Thread(target=get_data, args=(config, i))
t.start()
if config["cron"]:
break
time.sleep(config["interval"])
if __name__ == "__main__":
args = parse_args()
root_logger = logging.getLogger()
if args.stdout:
formatter = logging.Formatter(
PURPLE + "%(asctime)s" + END + ":" + BLUE + "%(levelname)s" + END + ":" + YELLOW + "%(name)s" + END + ":" + GRAY + "%(message)s" + END)
handler1 = logging.StreamHandler(sys.stdout)
handler1.setFormatter(formatter)
root_logger.addHandler(handler1)
if args.logfile:
logfile = path.expanduser(args.logfile)
if not path.exists(path.dirname(logfile)):
os.makedirs(path.dirname(logfile))
formatter = logging.Formatter("%(asctime)s:%(levelname)s:%(name)s:%(message)s")
handler2 = logging.handlers.RotatingFileHandler(args.logfile, maxBytes=args.logsize, backupCount=args.logcount)
handler2.setFormatter(formatter)
root_logger.addHandler(handler2)
root_logger.setLevel(args.loglevel)
# TODO.md: Read --version argument and print program version; then exit.
# TODO.md: Cleanup config.json and only define what really is needed
config_path = args.config
try:
with open(config_path, 'r') as configfile:
config_data = configfile.read()
_config = json.loads(config_data)
except Exception as err:
logger.error("Reading configuration file '" + config_path + "' failed: " + str(err))
# TODO.md: Replace all lines like this with generic Python logging
print(str("Uplink: Configuration Error!"))
exit(1)
try:
run(_config)
except KeyboardInterrupt:
logger.info("Program terminated!")
exit(0)
uplink = Uplink("/tmp/uplink.pid", _config)
uplink.start()