Commit 7a1ecb30 authored by Trevor Cappallo's avatar Trevor Cappallo
Browse files

clean up, add doc, excise ProjectConfiguration

parent 70da5a96
#!/usr/local/CentOS5/bin/python2.7
#!/usr/bin/env python
import sys,os,pwd
import logging,itertools
"""Utilities to provide logging and email notification for an operational product."""
import sys
import os
import logging
import itertools
import subprocess
import socket
import tempfile
import atexit
import OperationalMail
import ProjectConfiguration
import datetime
import StringIO
def __exitHandler(logger,addresses,critical_addresses) :
if ProjectConfiguration.ProjectConfiguration.isServerOperational() :
if logger.hasCritical() :
addresses = list(set([a for a in itertools.chain(addresses,
critical_addresses)]))
if not logger.hasErrors() :
logging.info('No errors found. Email not sent')
else :
if not addresses :
logging.warning('No recipients found. Email not sent')
else :
import OperationalMail
def __exitHandler(logger, addresses, critical_addresses):
"""Email given addresses if there are any buffered errors."""
if logger.hasCritical():
addresses = list(set([a for a in itertools.chain(addresses, critical_addresses)]))
if not logger.hasErrors():
logging.info('No errors found. Email not sent')
else:
if not addresses:
logging.warning('No recipients found. Email not sent')
else:
logger.emailLog(addresses)
class OperationalLogger :
def __init__(self,log_file='log.txt',log_level=logging.DEBUG) :
class OperationalLogger:
"""Main class for Operational Logging."""
def __init__(self, log_file='log.txt', log_level=logging.DEBUG):
"""Initialize logging handlers, formats, etc."""
self.__file_name = log_file
self.__server_name = socket.gethostbyaddr(socket.gethostname())[0]
self.__full_buffer = StringIO.StringIO()
......@@ -38,8 +45,10 @@ class OperationalLogger :
datefmt='%Y/%m/%d %H:%M:%S',
level=log_level)
formatter = logging.Formatter(fmt='[%(asctime)s][%(levelname)s][p%(process)d][%(filename)s:%(lineno)d]:%(message)s',
datefmt='%m/%d %H:%M:%S')
formatter = logging.Formatter(
fmt='[%(asctime)s][%(levelname)s][p%(process)d][%(filename)s:%(lineno)d]:%(message)s',
datefmt='%m/%d %H:%M:%S'
)
full_trigger = logging.StreamHandler(self.__full_buffer)
full_trigger.setLevel(logging.DEBUG)
......@@ -55,36 +64,38 @@ class OperationalLogger :
logging.getLogger('').addHandler(critical_trigger)
logging.getLogger('').addHandler(full_trigger)
if sys.stdout.isatty() :
if sys.stdout.isatty():
console = logging.StreamHandler(sys.stdout)
console.setLevel(logging.DEBUG)
console.setFormatter(logging.Formatter(fmt='[%(asctime)s][%(levelname)s][%(filename)s:%(lineno)d]:%(message)s',
datefmt='%m/%d %H:%M:%S'))
console.setFormatter(logging.Formatter(
fmt='[%(asctime)s][%(levelname)s][%(filename)s:%(lineno)d]:%(message)s',
datefmt='%m/%d %H:%M:%S'
))
logging.getLogger('').addHandler(console)
def hasErrors(self) :
def hasErrors(self):
"""Return number of ERROR-level log statements in buffer."""
return len(self.__error_buffer.getvalue()) > 0
def hasCritical(self) :
def hasCritical(self):
"""Return number of CRITICAL-level log statements in buffer."""
return len(self.__critical_buffer.getvalue()) > 0
def emailLog(self,addresses) :
def emailLog(self, addresses):
"""Send email to addresses with current buffered errors."""
message = "<br />\n".join([OperationalLogger.__colorize(l) for l in self.__error_buffer.getvalue().split('\n')])
OperationalMail.sendStatus('Errors detected {0}:{1}'.format(self.__server_name,
os.path.basename(sys.argv[0])),
addresses, message,
text_attachment=self.__full_buffer.getvalue())
OperationalMail.sendStatus('Errors detected {}:{}'.format(self.__server_name, os.path.basename(sys.argv[0])),
addresses, message, text_attachment=self.__full_buffer.getvalue())
@staticmethod
def __colorize(line) :
if '[CRITICAL]' in line :
return '<span style="color:red">{0}</span>'.format(line)
if '[ERROR]' in line :
return '<span style="color:red">{0}</span>'.format(line)
if '[WARNING]' in line :
return '<span style="color:blue">{0}</span>'.format(line)
def __colorize(line):
"""Add HTML markup for severity of log message."""
if '[CRITICAL]' in line:
return '<span style="color:red">{}</span>'.format(line)
if '[ERROR]' in line:
return '<span style="color:red">{}</span>'.format(line)
if '[WARNING]' in line:
return '<span style="color:blue">{}</span>'.format(line)
return line
......@@ -95,105 +106,112 @@ class OperationalLogger :
##########################################################################################
def defaultLogging(log_file_name = None,
addresses = ProjectConfiguration.ProjectConfiguration.defaultRecipients(),
critical_addresses = ProjectConfiguration.ProjectConfiguration.operationsAddresses(),
) :
'''Set an intelligent,context-dependent
default logging scheme for the current script.'''
if not log_file_name :
def defaultLogging(log_root_dir, log_file_name, addresses, critical_addresses):
"""Set an intelligent,context-dependent default logging scheme for the current script."""
if not log_file_name:
log_file_name = os.path.basename(sys.argv[0])
log_directory = os.path.join(ProjectConfiguration.ProjectConfiguration.projectRoot(),
'log', datetime.datetime.utcnow().strftime('%Y-%m-%d'))
# log_directory = os.path.join(ProjectConfiguration.ProjectConfiguration.projectRoot(),
# 'log', datetime.datetime.utcnow().strftime('%Y-%m-%d'))
if not os.path.exists(log_directory) :
try :
os.makedirs(log_directory,0755)
except Exception,e:
log_directory = os.path.join(log_root_dir, 'log', datetime.datetime.utcnow().strftime('%Y-%m-%d'))
if not os.path.exists(log_directory):
try:
os.makedirs(log_directory, 0755)
except Exception as err:
# Potential race condition. Warn, but do not error
logging.warning('Unable to create log directory {0}:{1}'.format(log_directory,
str(e)))
logging.warning('Unable to create log directory {}:{}'.format(log_directory, str(err)))
logger = OperationalLogger(os.path.join(log_directory,'{0}.log'.format(log_file_name)),
logging.DEBUG)
logger = OperationalLogger(os.path.join(log_directory, '{}.log'.format(log_file_name)), logging.DEBUG)
logging.info('Logging output from: {0}'.format(' '.join(sys.argv)))
logging.info('Process id {0}, parent process id {1}'.format(os.getpid(),
os.getppid()))
logging.info('Logging output from: {}'.format(' '.join(sys.argv)))
logging.info('Process id {}, parent process id {}'.format(os.getpid(), os.getppid()))
logging.info('-' * 60)
if addresses and not sys.stdout.isatty() :
atexit.register(__exitHandler,logger,addresses,critical_addresses)
if addresses and not sys.stdout.isatty():
atexit.register(__exitHandler, logger, addresses, critical_addresses)
return
def longLog(logging_level=logging.INFO,
message='') :
''' For program output, multi-line messages, etc. '''
def longLog(logging_level=logging.INFO, message=''):
"""For program output, multi-line messages, etc.
When message has linebreaks, will break it into individual log calls for consistent formatting.
"""
message_list = message.split("\n")
for msg in message_list :
logging.log(logging_level,msg)
for msg in message_list:
logging.log(logging_level, msg)
return
def longCritical(message) :
longLog(logging.CRITICAL,message)
def longError(message) :
longLog(logging.ERROR,message)
def longCritical(message):
"""Log a multi-line CRITICAL message."""
longLog(logging.CRITICAL, message)
def longError(message):
"""Log a multi-line ERROR message."""
longLog(logging.ERROR, message)
def longWarning(message) :
longLog(logging.WARNING,message)
def longWarning(message):
"""Log a multi-line WARNING message."""
longLog(logging.WARNING, message)
def longInfo(message) :
longLog(logging.INFO,message)
def longDebug(message) :
longLog(logging.DEBUG,message)
def longInfo(message):
"""Log a multi-line INFO message."""
longLog(logging.INFO, message)
def loggedSubprocess(arg_list) :
logging.info('Child process of [p{0}]'.format(os.getppid()))
logging.info('Attempting {0}'.format(' '.join(arg_list)))
try :
results = subprocess.check_output(arg_list,stderr=subprocess.STDOUT)
except subprocess.CalledProcessError,cpe :
logging.error('Call {0} failed with exit code {1}'.format(' '.join(arg_list),
str(cpe.returncode)))
def longDebug(message):
"""Log a multi-line DEBUG message."""
longLog(logging.DEBUG, message)
def loggedSubprocess(arg_list):
"""Pass arg_list to a subprocess call and log all output."""
logging.info('Child process of [p{}]'.format(os.getppid()))
logging.info('Attempting {}'.format(' '.join(arg_list)))
try:
results = subprocess.check_output(arg_list, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as cpe:
logging.error('Call {} failed with exit code {}'.format(' '.join(arg_list), str(cpe.returncode)))
longError(cpe.output)
except Exception,e:
logging.error('Unknown exception resulting from {0}:{1}'.format(' '.join(arg_list),
str(e)))
else :
logging.info('Successfully completed {0}'.format(' '.join(arg_list)))
except Exception as err:
logging.error('Unknown exception resulting from {}:{}'.format(' '.join(arg_list), str(err)))
else:
logging.info('Successfully completed {}'.format(' '.join(arg_list)))
longInfo(results)
return 0
logging.warning('Failure on {0}'.format(' '.join(arg_list)))
logging.warning('Failure on {}'.format(' '.join(arg_list)))
return -1
def stackTrace(logging_level=logging.ERROR) :
def stackTrace(logging_level=logging.ERROR):
"""Log a stack trace at the given level."""
import traceback
tb = traceback.format_exc()
longLog(logging_level,tb)
longLog(logging_level, tb)
return
if __name__ == '__main__' :
defaultLogging('basic_unit_test.foo.bar',addresses=['emhughes@aer.com'])
test = ''' Blah
if __name__ == '__main__':
defaultLogging('basic_unit_test.foo.bar', addresses=['tcappallo@veriskclimate.com'])
test = """Blah
foo
bar
beer
bear
'''
"""
logging.info('Blah')
logging.debug('Fin')
logging.error('An error in testing, oh, no!!')
logging.critical('Put up the Jimsignal!')
longError(test)
try :
beer_hat
except Exception,e:
try:
raise Exception("I AM ERROR.")
except Exception as e:
logging.debug(str(e))
stackTrace(logging.ERROR)
mport sys,os,datetime,re
import smtplib,codecs
#!/usr/bin/env python
"""Utility for sending email."""
import os
import smtplib
import logging
import ProjectConfiguration
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
def sendStatus(subject, to_list, message,
from_address=ProjectConfiguration.ProjectConfiguration.mailSender(),
password=None, text_attachment=None,
**kargs) :
'''Send an email message to a given recipient list.'''
def sendStatus(subject, to_list, message, from_address,
password=None, text_attachment=None, reply_to='no-reply@aer.com',
**kwargs):
"""Send an email message to a given recipient list."""
mail = MIMEMultipart('mixed')
mail['From'] = from_address
mail['Subject'] = subject
mail['To'] = ', '.join(to_list)
mail['Reply-To'] = 'no-reply@aer.com'
mail['Reply-To'] = reply_to
for key,value in kargs.iteritems() :
for key, value in kwargs.iteritems():
mail[key] = value
''' TODO: Efficient method of stripping Unicode?'''
try :
# TODO: Efficient method of stripping Unicode?
try:
# NOTE: requires pypi's html2text
import html2text
final_text = html2text.html2text(message)
except Exception,e:
logging.warning('Unable to generate text version of email: {0}'.format(str(e)))
except Exception as err:
logging.warning('Unable to generate text version of email: {0}'.format(str(err)))
final_text = 'HTML ONLY'
contentPart = MIMEMultipart('alternative')
contentPart.attach(MIMEText(final_text, 'plain'))
contentPart.attach(MIMEText(message, 'html'))
mail.attach(contentPart)
if text_attachment :
if text_attachment:
attachment = None
if os.path.exists(text_attachment) :
if os.path.exists(text_attachment):
attachment = MIMEText(file(text_attachment).read())
else :
else:
attachment = MIMEText(text_attachment)
attachment.add_header('Content-Disposition',
'attachment',
filename='full_log.txt')
attachment.add_header('Content-Disposition', 'attachment', filename='full_log.txt')
mail.attach(attachment)
server = smtplib.SMTP('smtp.aer.com')
if password :
if password:
user_name = from_address.partition('@')[0]
try :
server.login(user_name,password)
except Exception, e:
logging.error('Unable to log in to smtp server: {0}'.format(str(e)))
raise e
try:
server.login(user_name, password)
except Exception as err:
logging.error('Unable to log in to smtp server: {0}'.format(str(err)))
raise err
server.sendmail(from_address,to_list,mail.as_string())
server.sendmail(from_address, to_list, mail.as_string())
server.quit()
return
if __name__ == '__main__' :
msg = '''<P>Test of the OperationalMail module</P>
if __name__ == '__main__':
msg = """<P>Test of the OperationalMail module</P>
<P>Let us see if this works.</P>
......@@ -72,9 +71,7 @@ if __name__ == '__main__' :
</UL>
<P>Fin</P>
'''
default_addresses = ProjectConfiguration.ProjectConfiguration.developerEmails()
sendStatus('Non-pyProcess Email Module', default_addresses,
msg)
"""
default_addresses = ['acappallo@aer.com']
sendStatus('Non-pyProcess Email Module', default_addresses, msg)
#!/usr/local/CentOS5/bin/python2.7
import sys,os,re,logging
import itertools,datetime,re
not_blank = re.compile(r'\S')
def addressDirectory(file_name) :
return os.path.join(ProjectConfiguration.projectRoot(),
'project_config',
file_name)
def addressesFromFile(file_name) :
addresses = list()
# Allow errors to flow up
with open(file_name,'r') as mailing_list :
addresses = ''.join(mailing_list.readlines()).split(',')
addresses = [a.strip() for a in addresses if not_blank.search(a)]
return addresses
class ProjectConfiguration :
'''Currently a dummy file for project configuration settings'''
def __init__(self) :
pass
@staticmethod
def projectRoot() :
''' Root directory for the project.
Used mainly for log files and library path location. '''
return os.path.abspath(os.path.dirname(sys.argv[0]))
@staticmethod
def operationsAddresses() :
return addressesFromFile(addressDirectory('operations_management_email.txt'))
@staticmethod
def defaultRecipients() :
return addressesFromFile(addressDirectory('developers_email.txt'))
@staticmethod
def sysAdminsAddresses() :
return addressesFromFile(addressDirectory('is_email.txt'))
@staticmethod
def mailSender() :
return 'lightning_slurp@aer.com'
@staticmethod
def isServerOperational() :
return True
if __name__ == '__main__' :
''' Nothing worth testing yet. '''
pass
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment