logutils.py #2

  • //
  • guest/
  • alan_petersen/
  • p4transfer/
  • logutils.py
  • View
  • Commits
  • Open Download .zip Download (9 KB)
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
# logutils.py - Utilities for logging etc
#
#####################################################
# OVERVIEW:
#
# Logging utilities
#   For enhancement ideas, see http://astropy.readthedocs.org/en/latest/logging.html
######################################################
"""

import sys
import re
import os
import time
import stat
import logging
import smtplib
from email.mime.text import MIMEText

def python3():
    return sys.version_info[0] >= 3

if python3():
    import urllib.parse
    import urllib.request
else:
    import urllib

import traceback

def notify_users_by_email(mail_from, mail_to, mail_server, subject, body):
    "Uses simple form to send an email to fixed set of users"
    if not mail_to or not mail_from or not mail_server:
        return
    try:
        smtp = smtplib.SMTP(mail_server)
        msg = MIMEText(body)
        msg['Subject'] = subject
        msg['From'] = mail_from
        msg['To'] = mail_to
        smtp.sendmail(mail_from, mail_to.split(","), msg.as_string())
        smtp.quit()
    except Exception as e:
        print("Failed to send mail:", str(e))
        sys.stdout.flush()

def notify_users_by_form(mail_form_url, subject, msg):
    "Uses simple form to send an email to fixed set of users"
    if not mail_form_url:
        return
    response = None
    try:
        if python3():
            data = urllib.parse.urlencode({'subject': subject, 'message': msg})
            response = urllib.request.urlopen(mail_form_url, data.encode('ascii'))
        else:
            data = urllib.urlencode({'subject': subject, 'message':msg})
            response = urllib.urlopen(mail_form_url, data)
    except Exception as e:
        print("Failed to notify:", str(e))
        sys.stdout.flush()
    return response

def save_existing_file(file_name):
    "Ensures we don't overwrite existing files by renaming them"
    if os.path.exists(file_name):
        new_name = os.path.basename(file_name)
        (new_name, ext) = new_name.rsplit(".", 1)
        new_name = "%s-%s.%s" % (new_name,
                    time.strftime("%Y%m%d%H%M%S", time.localtime()), ext)
        os.rename(file_name, new_name)
        return new_name
    return None

def get_unique_file_name(file_path):
    "Ensures we have a unique file name"
    i = 1
    ldir = os.path.dirname(file_path)
    (base_file_name, ext) = os.path.basename(file_path).rsplit(".", 1)
    while os.path.exists(file_path):
        file_path = os.path.join(ldir, "%s-%d.%s" % (base_file_name, i, ext))
        i += 1
    return file_path

def get_log_file_name():
    prefix = sys.argv[0].split(".py")[0]
    return get_unique_file_name(os.path.join(os.getcwd(),
                            "log-%s-%s.log" % (os.path.basename(prefix),
                            time.strftime('%Y%m%d%H%M%S', time.localtime()))))

class ArgLogRecord(logging.LogRecord):
    """Custom formatting - just prints out any arguments passed"""
    def __init__(self, name, level, pathname, lineno,
                 msg, args, exc_info, func=None, extra=None, sinfo=None):
        if sys.version_info[0] < 3 or \
           (sys.version_info[0] == 3 and sys.version_info[1] < 2):
            logging.LogRecord.__init__(self, name, level, pathname, lineno, msg,
                                     args, exc_info, func)
        else:
            logging.LogRecord.__init__(self, name, level, pathname, lineno, msg,
                                     args, exc_info, func=func, extra=extra,
                                     sinfo=sinfo)

    def getMessage(self):
        """Return the message for this LogRecord.
        Just prints any arguments left over"""
        msg = str(self.msg)
        if self.args:
            try:
                msg = msg % self.args
            except TypeError as ex:
                msg += ", ".join([str(x) for x in self.args])
        return msg

class ArgLogger(logging.getLoggerClass()):
    "Specific logging class to use our logrecord"
    def __init__(self, name, **kwargs):
        logging.Logger.__init__(self, name, **kwargs)
        self.saved_output = []
        self.saved_log = []
        self.mail_form_url = None
        self.mail_to = None
        self.mail_from = None
        self.mail_server = None
        self.report_interval = 30     # minutes
        self.time_last_notified = time.time()

    def setReportingOptions(self, instance_name=None, mail_form_url=None, mail_to=None, mail_from=None, mail_server=None,
                            report_interval=30):
        self.instance_name = instance_name if instance_name else sys.argv[0]
        self.mail_form_url = mail_form_url
        self.mail_to = mail_to
        self.mail_from = mail_from
        self.mail_server = mail_server
        self.report_interval = report_interval
        
    def _formatRecord(self, record):
        "Find and call formatter"
        for h in self.handlers:
            if h.formatter:
                fmt = h.formatter
            else:
                fmt = logging._defaultFormatter
            return fmt.format(record)

    def _saveRecord(self, record):
        "Save to circular buffers"
        line = self._formatRecord(record)
        if record.levelno == logging.DEBUG:
            self.saved_log.append(line)
            if len(self.saved_log) > 100:
                del self.saved_log[0]
        else:
            self.saved_output.append(line)
            if len(self.saved_output) > 50:
                del self.saved_output[0]
        if time.time() - self.time_last_notified > self.report_interval * 60:
            self.notify("Regular update for %s" % self.instance_name, "")

    def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
                   func=None, extra=None, sinfo=None):
        if sys.version_info[0] < 3 or \
           (sys.version_info[0] == 3 and sys.version_info[1] < 2):
            record = ArgLogRecord(name, level, fn, lno, msg, args, exc_info, func, extra)
        else:
            record = ArgLogRecord(name, level, fn, lno, msg, args, exc_info, func=func, extra=extra, sinfo=sinfo)
        self._saveRecord(record)
        return record

    def report_exception(self):
        "Notify users that we have had a problem"
        self.notify("Exception in %s" % self.instance_name, "", use_log=True)

    def notify(self, subject, body, include_output=True, include_log=False):
        "Notify users of a message"
        self.time_last_notified = time.time()
        subject = "%s: %s" % (self.instance_name, subject)
        body += "\n"
        if include_log:
            body += "\n".join([str(x) for x in self.saved_log])
            self.saved_log = []
        elif include_output:
            body += "\n".join([str(x) for x in self.saved_output])
            self.saved_output = []
        if self.mail_form_url:
            return notify_users_by_form(self.mail_form_url, subject, body)
        else:
            notify_users_by_email(self.mail_from, self.mail_to, self.mail_server, subject, body)

def addFileHandler(logger):
    filename = get_log_file_name()
    formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s: %(message)s')
    fh = logging.FileHandler(filename=filename)
    fh.setLevel(logging.DEBUG)
    fh.setFormatter(formatter)
    logger.addHandler(fh)
    logger.info("Logging to file: %s" % filename)

def addStreamHandler(logger, stream):
    formatter = logging.Formatter('%(asctime)s:%(name)s:%(levelname)s: %(message)s')
    ch = logging.StreamHandler(stream)
    ch.setLevel(logging.INFO)
    ch.setFormatter(formatter)
    logger.addHandler(ch)

def getLogger(logger_name, stream=None):
    if stream is None:
        stream = sys.stdout
    "Register our logger and initialise everything"
    logging.setLoggerClass(ArgLogger)
    logger = logging.getLogger(logger_name)
    if len(logger.handlers) > 0:     # Only set them up once!
        return logger
    addStreamHandler(logger, stream)
    logger.setLevel(logging.DEBUG)
    addFileHandler(logger)
    return logger

def resetLogger(logger_name):
    "Reset - which creates new files etc"
    logger = logging.getLogger(logger_name)
    for hdlr in logger.handlers:
        if isinstance(hdlr, logging.FileHandler):
            logger.removeHandler(hdlr)
            hdlr.flush()
            hdlr.close()
    addFileHandler(logger)

def resetStreamLogger(logger_name, stream):
    logger = logging.getLogger(logger_name)
    for hdlr in logger.handlers:
        if isinstance(hdlr, logging.StreamHandler):
            logger.removeHandler(hdlr)
            hdlr.flush()
            hdlr.close()
    addStreamHandler(logger, stream)

def test():
    "Test the above"
    logger = getLogger('testlogger')
    logger.debug('debug message')
    logger.info('info message')
    logger.warn('warn message')
    logger.error('error message')
    logger.critical('critical message')

    logger.info("Some", "text", "params")
    logger.info([1, 2, "three", 1.2], "more")
    logger.info("Unicode text", u"file1uåäö")
    logger.info("Unicode text", '\ufffd')
    logging.shutdown()

if __name__ == "__main__":
    test()
# Change User Description Committed
#2 15072 alan_petersen Merging

//guest/perforce_software/p4transfer/...

to //guest/alan_petersen/p4transfer/...
#1 10153 alan_petersen Populate //guest/alan_petersen/p4transfer/...
from //guest/perforce_software/p4transfer/....
//guest/perforce_software/p4transfer/logutils.py
#1 9641 Robert Cowham Latest changes by Robert.
Added new options:
--repeat for continuous operation
--sample-config to produce sample config
-Improved logging and notification options (via emails if configured)
-Retries in case of error.
//guest/robert_cowham/perforce/utils/python/scripts/logutils.py
#22 9636 Robert Cowham Added new option summary_report_interval - to send regular emails when the transfer is set on repeat.
#21 9631 Robert Cowham Added error_report_interval in configuration file.
Intended to set interval after which to report errors (usually less than report_interval).
Useful when being run with --repeat option.
#20 9023 Robert Cowham Reset saved_output after notify to reduce duplicates in output.
#19 8976 Robert Cowham Allow multiple email recipients
#18 8970 Robert Cowham Fix formatting output
Log/notify instance_name instead of script path to distinguish between instances
Read config file every time round loop to allow updates by user
#17 8956 Robert Cowham Forgot to save passed parameter
#16 8939 Robert Cowham Added configuration options - particularly for email notification via smtp
#15 8937 Robert Cowham Avoid creating multiple logging handlers.
#14 8936 Robert Cowham Works OK with python 2 - but not 3
#13 8935 Robert Cowham On the way to making it python 2/3 compatible
#12 8930 Robert Cowham Logging works OK for python 2.
#11 8925 Robert Cowham Migrating to standard logging utilities
#10 8897 Robert Cowham Make sure we don't overwrite log files - useful for testing purposes.
#9 8893 Robert Cowham Implement warning function
#8 8884 Robert Cowham Fix Python 2.7 problems
#7 8880 Robert Cowham Fix a warning
#6 8872 Robert Cowham Handle unicode errors when logging
#5 8871 Robert Cowham Bump up logging
#4 8869 Robert Cowham Added better logging.
Added -repeat flag for regular transfers
Repeat in any case if we see an exception (e.g. conneciton lost)
#3 8857 Robert Cowham Sucessfully got the notifycation via mail working with Python3 - helps to read the docs more carefully!
#2 8853 Robert Cowham Make loggin a configurable parameter in the options file
#1 8817 Robert Cowham Improve status reporting