Skip to content

Instantly share code, notes, and snippets.

@mvtango
Last active February 4, 2018 18:04
Show Gist options
  • Save mvtango/2cbde131752818be7d8938372c1c6ea5 to your computer and use it in GitHub Desktop.
Save mvtango/2cbde131752818be7d8938372c1c6ea5 to your computer and use it in GitHub Desktop.
loghelper.py - e-mail errors using python's loggier module
credentials.py

loghelper.py

This is the combination of several features I wanted from e-mailing loggers: TLS and not-more-than-10-mails per hour and easy addition to an existing logger.

See the test code for how to use it. Or below.

    # this line relies on credentials being avalable in a credentials.py file with a MAILERRORS dict. 
    # See example_credentials.py. 
    #
    from loghelper import send_email_on 
    #
    import logging

    logger=logging.getLogger()

    from credentials import MAILERRORS
    send_email_on(logger,level=logging.ERROR,flood_level=1)

    print("Look out. Error below should be arriving at {toaddrs} from {fromaddr}".format(**MAILERRORS))
    try :
        1/0
    except Exception as e:
        logger.exception(e)
    print("next one should result in a flood warning")
    try :
        1/0
    except Exception as e:
        logger.exception(e)

MAILERRORS=dict(mailhost=("smtp.gmail.com", 587),
fromaddr="******************",
toaddrs=["mvirtel@dpa-newslab.com",],
subject=u"Baellebad filterset.py error: %(message)s",
credentials=("**********","*********"))
import logging
import logging.handlers
import smtplib
import sys
import datetime
logging.basicConfig(stream=sys.stderr,level=logging.DEBUG,format="%(asctime)s %(name)s %(levelname)s: %(filename)s %(lineno)s: %(message)s")
# https://stackoverflow.com/a/36937462/2743441
# Provide a class to allow SSL (Not TLS) connection for mail handlers by overloading the emit() method
class SSLSMTPHandler(logging.handlers.SMTPHandler):
def emit(self, record):
"""
Emit a record.
"""
try:
port = self.mailport
if not port:
port = smtplib.SMTP_PORT
smtp = smtplib.SMTP_SSL(self.mailhost, port)
msg = self.format(record)
if self.username:
smtp.login(self.username, self.password)
smtp.sendmail(self.fromaddr, self.toaddrs, msg)
smtp.quit()
except (KeyboardInterrupt, SystemExit):
raise
except:
self.handleError(record)
# http://mynthon.net/howto/-/python/python%20-%20logging.SMTPHandler-how-to-use-gmail-smtp-server.txt
# Consider using https://github.com/Simplistix/mailinglogger/blob/master/mailinglogger/MailingLogger.py for flood prevention
flood_template="""
Too Many Log Entries
More than %s entries have been logged that would have resulted in
emails being sent.
No further emails will be sent for log entries generated between
%s and %i:00:00
Please consult any other configured logs, such as a File Logger,
that may contain important entries that have not been emailed.
"""
class TlsSMTPHandler(logging.handlers.SMTPHandler):
def __init__(self,*args,flood_level=10,**kwargs) :
super(TlsSMTPHandler, self).__init__(*args,**kwargs)
self.hour = self.now().hour
self.sent = 0
self.flood_level = flood_level
def now(self) :
return datetime.datetime.now()
# https://stackoverflow.com/a/20801330/2743441
def getSubject(self, record):
formatter = logging.Formatter(fmt=self.subject)
return formatter.format(record)
def emit(self, record):
"""
Emit a record.
Format the record and send it to the specified addressees.
"""
current_time = self.now()
current_hour = current_time.hour
if current_hour != self.hour:
self.hour = current_hour
self.sent = 0
if self.sent == self.flood_level:
# send critical error
record = logging.LogRecord(
name='flood',
level=logging.CRITICAL,
pathname='',
lineno=0,
msg=flood_template % (self.sent,
current_time.strftime('%H:%M:%S'),
current_hour + 1),
args=(),
exc_info=None)
elif self.flood_level and self.sent > self.flood_level:
# do nothing, we've sent too many emails already
return
self.sent += 1
try:
import smtplib
import string # for tls add this line
try:
from email.utils import formatdate
except ImportError:
formatdate = self.date_time
port = self.mailport
if not port:
port = smtplib.SMTP_PORT
smtp = smtplib.SMTP(self.mailhost, port)
msg = self.format(record)
msg = "From: %s\r\nTo: %s\r\nSubject: %s\r\nDate: %s\r\n\r\n%s" % (
self.fromaddr,
",".join(self.toaddrs),
self.getSubject(record),
formatdate(), msg)
if self.username:
smtp.ehlo() # for tls add this line
smtp.starttls() # for tls add this line
smtp.ehlo() # for tls add this line
smtp.login(self.username, self.password)
smtp.sendmail(self.fromaddr, self.toaddrs, msg)
smtp.quit()
except (KeyboardInterrupt, SystemExit):
raise
except:
self.handleError(record)
def send_email_on(logger,level=logging.ERROR,handler=TlsSMTPHandler,flood_level=10) :
from credentials import MAILERRORS
mail_handler = handler(**MAILERRORS,flood_level=flood_level)
mail_handler.setLevel(level)
logger.addHandler(mail_handler)
mail_handler.setFormatter(logging.Formatter('''
Message type: %(levelname)s
Location: %(pathname)s:%(lineno)d
Module: %(module)s
Function: %(funcName)s
Time: %(asctime)s
Message:
%(message)s
'''))
def test() :
logger=logging.getLogger()
from credentials import MAILERRORS
send_email_on(logger,level=logging.ERROR,flood_level=1)
print("Look out. Error below should be arriving at {toaddrs} from {fromaddr}".format(**MAILERRORS))
try :
1/0
except Exception as e:
logger.exception(e)
print("next one should result in a flood warning")
try :
1/0
except Exception as e:
logger.exception(e)
if __name__ == '__main__' :
test()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment