user3262424
user3262424

Reputation: 7489

Python -- Send Email When Exception Is Raised?

I have a python class with many methods():

Method1()

Method2()

...........

...........

MethodN()

All methods -- while performing different tasks -- have the same scheme:

do something
do something else
has anything gone wrong?
    raise an exception

I want to be able to get an email whenever an exception is raised anywhere in the class.

Is there some easy way to combine this logic into the class, rather than calling SendEmail() before every raise Exception statement? what is the right, pythonic way to deal with such a case? canh a 'generalized' Exception handler be the solution? I'd be glad for any ideas you may have.

Upvotes: 25

Views: 47855

Answers (12)

Python Hunter
Python Hunter

Reputation: 2156

By 2019, the easiest and best option seems to be sentry.

You need just two lines of code:

import sentry_sdk
sentry_sdk.init("https://[email protected]/xxxxxxx")

and it will send you a detailed email with any raised error.

After this you can further inspect the error on the Sentry website where there is impressive debug info - exceptions traceback, logs, data passed to functions, packages versions etc...

Upvotes: 2

Darek
Darek

Reputation: 837

like @User said before Python has logging.handlers.SMTPHandler to send logged error message. Use logging module! Overriding exception class to send an email is a bad idea.

Quick example:

import logging
import logging.handlers

smtp_handler = logging.handlers.SMTPHandler(mailhost=("smtp.example.com", 25),
                                            fromaddr="[email protected]", 
                                            toaddrs="[email protected]",
                                            subject=u"AppName error!")


logger = logging.getLogger()
logger.addHandler(smtp_handler)

try:
  break
except Exception as e:
  logger.exception('Unhandled Exception')

Upvotes: 82

eric
eric

Reputation: 8108

I like the answers that use the logging module, but I use the smtp library (smtplib) for this. To send error message, I do something like the following in the exception branch of the try/except block:

import smtplib as smtp

s = smtplib.SMTP('smtp.gmail.com', 587)
s.starttls()  
from_user = r"[email protected]"
to_user = r"[email protected]"
password = "xxxxxxxx"  
s.login(from_user, password)
subject = "Uh oh"
text = "XYZ error message you blew it!"
message = f"Subject: {subject}\n\n{text}"
s.sendmail(from_user, to_user, message);

This works well, but isn't the most secure option in the world. You actually have to tell google you want to let less secure apps connect (you can change this setting here: https://myaccount.google.com/lesssecureapps).

Upvotes: 0

Sina Rezaei
Sina Rezaei

Reputation: 529

You can use python alerting library. It supports email (via mailgun and sendgrid), telegram and slack notifications for sending alerts.

https://github.com/sinarezaei/alerting

Sample code:

from alerting import Alerting
from alerting.clients import AlertingMailGunClient, AlertingSlackClient, AlertingTelegramClient

alerts = Alerting(
  clients=[
    AlertingMailGunClient(your_mailgun_api_key, your_domain, from_email, target_email),
    AlertingSlackClient(your_bot_user_oauth, target_channel),
    AlertingTelegramClient(bot_token, chat_id)
  ]
)


try:
  # something
except Exception as ex:
  alerting.send_alert(title='some bad error happened', message=str(ex))

Upvotes: 0

senderle
senderle

Reputation: 151187

Note: Although this is a simple, obvious solution to the problem as stated, the below answer is probably better in most cases.

If the alternative is this:

if problem_test():
    SendEmail()
    raise Exception

Then why don't you just define a custom raise_email method?

def raise_email(self, e):
    SendEmail()
    raise e

Upvotes: 9

guneysus
guneysus

Reputation: 6502

Gist Link

The most important trick is here if secure parameter is not passed, the default value is None which raises exception if you are trying to authenticate with TLS/SSL enabled STMP Servers lik Gmail's, Yahoo's, Yandex's, STMP servers.

We passed an empty tuple to trigger smtp.ehlo() to authenticate correctly with SSL.

...
if self.secure is not None:
    smtp.ehlo()
    smtp.starttls(*self.secure)
    smtp.ehlo()
...


import logging
import logging.handlers

__author__ = 'Ahmed Şeref GÜNEYSU'


def foo():
    raise Exception("Foo Bar")


def main():
    logger = logging.getLogger()
    logger.addHandler(logging.handlers.SMTPHandler(
        mailhost=("smtp.mail.yahoo.com", 587),
        fromaddr="[email protected]",
        toaddrs="[email protected]",
        subject="EXCEPTION",
        credentials=('[email protected]', 'MY SECRET PASSWORD'),
        secure=()))
    try:
        foo()
    except Exception, e:
        logging.exception(e)


if __name__ == '__main__':
    main()

Upvotes: 6

User
User

Reputation: 2158

Python stdlib has dedicated class to do what you want. See logging.handlers.SMTPHandler

Upvotes: 8

Jeannot
Jeannot

Reputation: 1175

You can use an except hook to send an email when an exception is not caught.

see sys.excepthook

Upvotes: 0

Michael Dillon
Michael Dillon

Reputation: 32392

Beware the wizard's apprentice!

It would be better to log those errors, then check to see when the last email was sent, and if the timespan is too short, do not send another message because the human being will already be looking at the log file. For many things, one message per day would be enough, but even for system critical things, if you have already had one failure, what could go wrong if you wait two hours to send the next email?

If you send one email per two hour timespan, then the maximum number of emails per day is 12. And if you get a cascading failure (you will!) then it will most likely happen within a couple of hours of the first failure event.

Most large networking companies offer an SLA of 4 hour to fix a failure, measured from the time it first occurs (because cascading failures tend to repeat) until the customer is satisified that it is fixed. If you have a tighter SLA than that, then unless it is some finance industry service, you probably are offering too high of a service level.

But if you do have a 4 hour SLA, then I would make sure that any email sent within 2 - 4 hours of the last email, should use whatever bells and whistles you can to prioritise it, highlight it, etc. For instance use the X-Priority header and put the word URGENT in the subject so that your mail client can display it in large bold red letters.

Upvotes: 5

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 799560

Something like this, perhaps?

def mailexception(ex):
  # Be creative.
  print 'Mailing... NOW!'

def pokemontrainer(cls):
  class Rye(cls):
    def __getattribute__(self, name):
      def catcher(func):
        def caller(*args, **kwargs):
          try:
            func(*args, **kwargs)
          except Exception, e:
            mailexception(e)
            raise
        return caller
      ref = cls.__getattribute__(self, name)
      if hasattr(cls, name) and hasattr(getattr(cls, name), '__call__'):
        return catcher(ref)
  return Rye

@pokemontrainer
class Exceptor(object):
  def toss(self, e):
    raise e('Incoming salad!')

ex = Exceptor()
ex.toss(ValueError)

Upvotes: 2

benpro
benpro

Reputation: 4425

How about this:

class MyException(Exception):
    def __init__(self):
        SendEmail()

Upvotes: 2

Zach Kelling
Zach Kelling

Reputation: 53879

I'd just subclass Exception and send the e-mail in the custom Exception.

Upvotes: 1

Related Questions