move critical email errors to gmail logging handler

This commit is contained in:
petrucci4prez 2017-05-31 00:21:23 -04:00
parent af32fc6d26
commit f64e01f896
4 changed files with 102 additions and 61 deletions

View File

@ -1,3 +1,6 @@
recipientList: gmail:
- natedwarshuis@gmail.com username: natedwarshuis@gmail.com
passwd: bwsasfxqjbookmed
recipientList:
- natedwarshuis@gmail.com
state: disarmed state: disarmed

11
main.py
View File

@ -35,8 +35,6 @@ if __name__ == '__main__':
GPIO.setmode(GPIO.BCM) GPIO.setmode(GPIO.BCM)
resetUSBDevice('1-1') resetUSBDevice('1-1')
from notifier import criticalError
stateMachine = StateMachine() stateMachine = StateMachine()
# TODO: segfaults are annoying :( # TODO: segfaults are annoying :(
@ -47,14 +45,7 @@ if __name__ == '__main__':
time.sleep(31536000) time.sleep(31536000)
except Exception: except Exception:
t = traceback.format_exc() logger.critical(traceback.format_exc())
try:
criticalError(t)
except NameError:
pass
logger.critical(t)
finally: finally:
clean() clean()

View File

@ -5,60 +5,70 @@ from datetime import datetime
from email.mime.multipart import MIMEMultipart from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
COMMASPACE=', ' gmail = ConfigFile('config.yaml')['gmail']
RECIPIENT_LIST=ConfigFile('config.yaml')['recipientList'] def _getNextDate():
GMAIL_USER='natedwarshuis@gmail.com'
GMAIL_PWD='bwsasfxqjbookmed'
def getNextDate():
m = datetime.now().month + 1 m = datetime.now().month + 1
y = datetime.now().year y = datetime.now().year
y = y + 1 if m > 12 else y y = y + 1 if m > 12 else y
return datetime(year=y, month=m%12, day=1, hour=12, minute=0) return datetime(year=y, month=m%12, day=1, hour=12, minute=0)
@async(daemon=True) @async(daemon=True)
def scheduleAction(action): def _scheduleAction(action):
while 1: while 1:
nextDate = getNextDate() nextDate = _getNextDate()
sleepTime = nextDate - datetime.today() sleepTime = nextDate - datetime.today()
logger.info('Next monthly test scheduled at %s (%s)', nextDate, sleepTime) _logger.info('Next monthly test scheduled at %s (%s)', nextDate, sleepTime)
time.sleep(sleepTime.days * 86400 + sleepTime.seconds) time.sleep(sleepTime.days * 86400 + sleepTime.seconds)
action() action()
# probably an easier way to do this in logging module
@async(daemon=False) @async(daemon=False)
def sendEmail(subject, body): def _sendToGmail(username, passwd, recipiantList, subject, body, server='smtp.gmail.com', port=587):
msg = MIMEMultipart() msg = MIMEMultipart()
msg['Subject'] = subject msg['Subject'] = subject
msg['From'] = GMAIL_USER msg['From'] = username
msg['To'] = COMMASPACE.join(RECIPIENT_LIST) msg['To'] = ', '.join(recipiantList)
msg.attach(MIMEText(body, 'plain')) msg.attach(MIMEText(body, 'plain'))
s = SMTP('smtp.gmail.com', 587) s = SMTP(server, port)
s.starttls() s.starttls()
s.login(GMAIL_USER, GMAIL_PWD) s.login(username, passwd)
s.send_message(msg) s.send_message(msg)
s.quit() s.quit()
def monthlyTest(): def monthlyTest():
subject = 'harrison4hegemon - automated monthly test' subject = 'harrison4hegemon - automated monthly test'
body = 'this is an automated message - please do not reply\n\nin the future this may have useful information' body = 'this is an automated message - please do not reply\n\nin the future this may have useful information'
sendEmail(subject, body) sendEmail(gmail['username'], gmail['passwd'], gmail['recipientList'], subject, body)
logger.info('Sending monthly test to email list') _logger.debug('Sending monthly test to email list')
def intruderAlert(): def intruderAlert():
subject = 'harrison4hegemon - intruder detected' subject = 'harrison4hegemon - intruder detected'
body = 'intruder detected - alarm was tripped on ' + time.strftime("%H:%M:%S - %d/%m/%Y") body = 'intruder detected - alarm was tripped on ' + time.strftime("%H:%M:%S - %d/%m/%Y")
sendEmail(subject, body) sendEmail(gmail['username'], gmail['passwd'], gmail['recipientList'], subject, body)
logger.info('Sending intruder alert to email list') _logger.info('intruder detected')
_logger.debug('Sending intruder alert to email list')
def criticalError(err):
subject = 'harrison4hegemon - critical error'
sendEmail(subject, err)
logger.info('Sending critical error to email list')
scheduleAction(monthlyTest) class GmailHandler(logging.Handler):
'''
Logging handler that sends records to gmail. This is almost like the
SMTPHandler except that the username and fromaddr are the same and
credentials are mandatory
'''
def __init__(self, username, passwd, recipientList, subject):
super().__init__()
self.username = username
self.passwd = passwd
self.recipientList = recipientList
self.subject = subject
def emit(self, record):
try:
_sendToGmail(self.username, self.passwd, self.recipientList,
self.subject, self.format(record))
except:
self.handleError(record)
_scheduleAction(monthlyTest)

View File

@ -1,33 +1,40 @@
import logging, os import logging, os
from subprocess import run, PIPE, CalledProcessError from subprocess import run, PIPE, CalledProcessError
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler, SMTPHandler
""" '''
Logger conventions Logger conventions
- CRITICAL: for things that cause crashes. sends email - CRITICAL: for things that cause crashes. only level with gmail
- ERROR: for things that cause startup/shutdown issues - ERROR: for things that cause startup/shutdown issues
- WARNING: for recoverable issues that may cause future problems - WARNING: for recoverable issues that may cause future problems
- INFO: state changes and sensor readings - INFO: state changes and sensor readings
- DEBUG: all extraneous crap - DEBUG: all extraneous crap
"""
# formats console output depending on whether we have gluster Init order (very essential)
1) init console output (this will go to journald) and format as console only
2) init the module level _logger so we can log anything that happens as we build
the other loggers
3) mount glusterfs, any errors here will go to console output
4) once gluster is mounted, add to root _logger and remove "console only" warning
from console
5) import gmail, this must come here as it uses loggers for some of its setup
6) init gmail handler
'''
def _formatConsole(gluster = False): def _formatConsole(gluster = False):
'''
formats console output depending on whether we have gluster
'''
c = '' if gluster else '[CONSOLE ONLY] ' c = '' if gluster else '[CONSOLE ONLY] '
fmt = logging.Formatter('[%(name)s] [%(levelname)s] ' + c + '%(message)s') fmt = logging.Formatter('[%(name)s] [%(levelname)s] ' + c + '%(message)s')
console.setFormatter(fmt) console.setFormatter(fmt)
# init console, but don't expect gluster to be here yet
console = logging.StreamHandler()
_formatConsole(gluster = False)
rootLogger = logging.getLogger()
rootLogger.setLevel(logging.DEBUG)
rootLogger.addHandler(console)
logger = logging.getLogger(__name__)
class GlusterFSHandler(TimedRotatingFileHandler): class GlusterFSHandler(TimedRotatingFileHandler):
'''
Logic to mount timed rotating file within a gluster volume. Note that this
class will mount itself automatically. Note that the actual filepaths for
logging are hardcoded here
'''
def __init__(self, server, volume, mountpoint, options=None): def __init__(self, server, volume, mountpoint, options=None):
if not os.path.exists(mountpoint): if not os.path.exists(mountpoint):
raise FileNotFoundError raise FileNotFoundError
@ -42,7 +49,7 @@ class GlusterFSHandler(TimedRotatingFileHandler):
if not os.path.exists(logdest): if not os.path.exists(logdest):
os.mkdir(logdest) os.mkdir(logdest)
elif os.path.isfile(logdest): elif os.path.isfile(logdest):
logger.error('%s is present but is a file (vs a directory). ' \ _logger.error('%s is present but is a file (vs a directory). ' \
'Please (re)move this file to prevent data loss', logdest) 'Please (re)move this file to prevent data loss', logdest)
raise SystemExit raise SystemExit
@ -55,8 +62,8 @@ class GlusterFSHandler(TimedRotatingFileHandler):
def _mount(self): def _mount(self):
if os.path.ismount(self._mountpoint): if os.path.ismount(self._mountpoint):
# NOTE: this assumes that the already-mounted device is the one intended # this assumes that the already-mounted device is the one intended
logger.warning('Device already mounted at {}'.format(self._mountpoint)) _logger.warning('Device already mounted at {}'.format(self._mountpoint))
else: else:
dst = self._server + ':/' + self._volume dst = self._server + ':/' + self._volume
cmd = ['mount', '-t', 'glusterfs', dst, self._mountpoint] cmd = ['mount', '-t', 'glusterfs', dst, self._mountpoint]
@ -72,14 +79,32 @@ class GlusterFSHandler(TimedRotatingFileHandler):
run(cmd, check=True, stdout=PIPE, stderr=PIPE) run(cmd, check=True, stdout=PIPE, stderr=PIPE)
except CalledProcessError as e: except CalledProcessError as e:
stderr = e.stderr.decode('ascii').rstrip() stderr = e.stderr.decode('ascii').rstrip()
logger.error(stderr) _logger.error(stderr)
raise SystemExit raise SystemExit
def close(self): def close(self):
TimedRotatingFileHandler.close(self) # must close file stream before unmounting '''
Close file and dismount (must be in this order). Called when
'removeHandler' is invoked
'''
TimedRotatingFileHandler.close(self)
self._unmount() self._unmount()
# ...now activate gluster '''
Init sequence (see above)
'''
# 1
console = logging.StreamHandler()
_formatConsole(gluster = False)
rootLogger = logging.getLogger()
rootLogger.setLevel(logging.DEBUG)
rootLogger.addHandler(console)
# 2
_logger = logging.getLogger(__name__)
# 3
gluster = GlusterFSHandler( gluster = GlusterFSHandler(
server = '192.168.11.39', server = '192.168.11.39',
volume = 'pyledriver', volume = 'pyledriver',
@ -87,10 +112,22 @@ gluster = GlusterFSHandler(
options = 'backupvolfile-server=192.168.11.48' options = 'backupvolfile-server=192.168.11.48'
) )
# 4
_formatConsole(gluster = True) _formatConsole(gluster = True)
rootLogger.addHandler(gluster) rootLogger.addHandler(gluster)
# this should only be called at the end to clean up # 5
from notifier import gmail, GmailHandler
# 6
gmail = GmailHandler(gmail['username'], gmail['passwd'], gmail['recipientList'],
'harrison4hegemon - critical error')
gmail.setLevel(logging.CRITICAL)
rootLogger.addHandler(gmail)
'''
Clean up
'''
def unmountGluster(): def unmountGluster():
rootLogger.removeHandler(gluster) rootLogger.removeHandler(gluster)
_formatConsole(gluster = False) _formatConsole(gluster = False)