What is the best way to collect errors and send a summary?
<p>I have a script executing several independent functions in turn. I would like to collect the errors/exceptions happening along the way, in order to send an email with a summary of the errors.</p>

<p>What is the best way to raise these errors/exceptions and collect them, while allowing the script to complete and go through all the steps? They are independent, so it does not matter if one crashes. The remaining ones can still run.</p>

<pre><code>def step_1():
    # Code that can raise errors/exceptions

def step_2():
    # Code that can raise errors/exceptions

def step_3():
    # Code that can raise errors/exceptions

def main():
    step_1()
    step_2()
    step_3()

    send_email_with_collected_errors()

if '__name__' == '__main__':
    main()
</code></pre>

<p>Should I wrap each step in a try..except block in the main() function? Should I use a decorator on each step function, in addition to an error collector?</p>

<p>You could wrap each function in try/except, usually better for small simple scripts.</p>

<pre><code>def step_1():
    # Code that can raise errors/exceptions

def step_2():
    # Code that can raise errors/exceptions

def step_3():
    # Code that can raise errors/exceptions

def main():
    try:
        step_1_result = step_1()
        log.info('Result of step_1 was {}'.format(result))
    except Exception as e:
        log.error('Exception raised. {}'.format(e))
        step_1_result = e
        continue
    try:
        step_2_result = step_2()
        log.info('Result of step_2 was {}'.format(result))
    except Exception as e:
        log.error('Exception raised. {}'.format(e))
        step_2_result = e
        continue

    try:
        step_3_result = step_3()
        log.info('Result of step_3 was {}'.format(result))
    except Exception as e:
        log.error('Exception raised. {}'.format(e))
        step_3_result = e
        continue


    send_email_with_collected_errors(
        step_1_result,
        step_2_result,
        step_3_result
    )

if '__name__' == '__main__':
    main()
</code></pre>

<p>For something more elaborate you could use a decorator that'd construct a list of errors/exceptions caught. For example</p>

<pre><code>class ErrorIgnore(object):
   def __init__(self, errors, errorreturn = None, errorcall = None):
      self.errors = errors
      self.errorreturn = errorreturn
      self.errorcall = errorcall

   def __call__(self, function):
      def returnfunction(*args, **kwargs):
         try:
            return function(*args, **kwargs)
         except Exception as E:
            if type(E) not in self.errors:
               raise E
            if self.errorcall is not None:
               self.errorcall(E, *args, **kwargs)
            return self.errorreturn
      return returnfunction
</code></pre>

<p>Then you could use it like this:</p>

<pre><code>exceptions = []

def errorcall(E, *args):
    print 'Exception raised {}'.format(E)
    exceptions.append(E)

@ErrorIgnore(errors = [ZeroDivisionError, ValueError], errorreturn = None, errorcall = errorcall)
def step_1():
   # Code that can raise errors/exceptions

...

def main():
    step_1()
    step_2()
    step_3()

    send_email_with_collected_errors(exceptions)

if '__name__' == '__main__':
    main()
</code></pre>

python