pyledriver/sharedLogging.py

134 lines
3.7 KiB
Python

import logging, os
from subprocess import run, PIPE, CalledProcessError
from logging.handlers import TimedRotatingFileHandler, SMTPHandler
'''
Logger conventions
- CRITICAL: for things that cause crashes. only level with gmail
- ERROR: for things that cause startup/shutdown issues
- WARNING: for recoverable issues that may cause future problems
- INFO: state changes and sensor readings
- DEBUG: all extraneous crap
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):
'''
formats console output depending on whether we have gluster
'''
c = '' if gluster else '[CONSOLE ONLY] '
fmt = logging.Formatter('[%(name)s] [%(levelname)s] ' + c + '%(message)s')
console.setFormatter(fmt)
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):
if not os.path.exists(mountpoint):
raise FileNotFoundError
self._mountpoint = mountpoint
self._server = server
self._volume = volume
self._options = options
logdest = mountpoint + '/logs'
if not os.path.exists(logdest):
os.mkdir(logdest)
elif os.path.isfile(logdest):
_logger.error('%s is present but is a file (vs a directory). ' \
'Please (re)move this file to prevent data loss', logdest)
raise SystemExit
self._mount()
super().__init__(logdest + '/pyledriver-log', when='midnight')
fmt = logging.Formatter('[%(asctime)s] [%(name)s] [%(levelname)s] %(message)s')
self.setFormatter(fmt)
def _mount(self):
if os.path.ismount(self._mountpoint):
# this assumes that the already-mounted device is the one intended
_logger.warning('Device already mounted at {}'.format(self._mountpoint))
else:
dst = self._server + ':/' + self._volume
cmd = ['mount', '-t', 'glusterfs', dst, self._mountpoint]
if self._options:
cmd[1:1] = ['-o', self._options]
self._run(cmd)
def _unmount(self):
self._run(['umount', self._mountpoint])
def _run(self, cmd):
try:
run(cmd, check=True, stdout=PIPE, stderr=PIPE)
except CalledProcessError as e:
stderr = e.stderr.decode('ascii').rstrip()
_logger.error(stderr)
raise SystemExit
def close(self):
'''
Close file and dismount (must be in this order). Called when
'removeHandler' is invoked
'''
TimedRotatingFileHandler.close(self)
self._unmount()
'''
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(
server = '192.168.11.39',
volume = 'pyledriver',
mountpoint = '/mnt/glusterfs/pyledriver',
options = 'backupvolfile-server=192.168.11.48'
)
# 4
_formatConsole(gluster = True)
rootLogger.addHandler(gluster)
# 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():
rootLogger.removeHandler(gluster)
_formatConsole(gluster = False)