Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine if an exception was raised once you're in the finally block?

Is it possible to tell if there was an exception once you're in the finally clause? Something like:

try:     funky code finally:     if ???:         print('the funky code raised') 

I'm looking to make something like this more DRY:

try:     funky code except HandleThis:     # handle it     raised = True except DontHandleThis:     raised = True     raise else:     raised = False finally:     logger.info('funky code raised %s', raised) 

I don't like that it requires to catch an exception, which you don't intend to handle, just to set a flag.


Since some comments are asking for less "M" in the MCVE, here is some more background on the use-case. The actual problem is about escalation of logging levels.

  • The funky code is third party and can't be changed.
  • The failure exception and stack trace does not contain any useful diagnostic information, so using logger.exception in an except block is not helpful here.
  • If the funky code raised then some information which I need to see has already been logged, at level DEBUG. We do not and can not handle the error, but want to escalate the DEBUG logging because the information needed is in there.
  • The funky code does not raise, most of the time. I don't want to escalate logging levels for the general case, because it is too verbose.

Hence, the code runs under a log capture context (which sets up custom handlers to intercept log records) and some debug info gets re-logged retrospectively:

try:     with LogCapture() as log:         funky_code()  # <-- third party badness finally:     # log events are buffered in memory. if there was an exception,     # emit everything that was captured at a WARNING level     for record in log.captured:         if <there was an exception>:             log_fn = mylogger.warning         else:             log_fn = getattr(mylogger, record.levelname.lower())         log_fn(record.msg, record.args) 
like image 840
wim Avatar asked Mar 04 '18 19:03

wim


People also ask

What happens if there is an exception inside finally block?

The "finally" block execution stops at the point where the exception is thrown. Irrespective of whether there is an exception or not "finally" block is guaranteed to execute. Then the original exception that occurred in the try block is lost.

What happens if there is an exception in the finally block Python?

Exception handling with try, except, else, and finally First try clause is executed i.e. the code between try and except clause. If there is no exception, then only try clause will run, except clause will not get executed. If any exception occurs, the try clause will be skipped and except clause will run.

How do I check if an exception is raised in Python?

assertRaises() – It allows an exception to be encapsulated, meaning that the test can throw an exception without exiting the execution, as is normally the case for unhandled exceptions. The test passes if exception is raised, gives an error if another exception is raised, or fails if no exception is raised.

How do I get exception message in finally block?

try { ..... throw new Exception("Exception Reason!"); } catch(Exception e){ msg=e. getMessage(); finally{ //USE String msg here. }


2 Answers

raised = True try:     funky code     raised = False except HandleThis:     # handle it finally:     logger.info('funky code raised %s', raised) 

Given the additional background information added to the question about selecting a log level, this seems very easily adapted to the intended use-case:

mylog = WARNING try:     funky code     mylog = DEBUG except HandleThis:     # handle it finally:     mylog(...) 
like image 37
Jean-Paul Calderone Avatar answered Sep 18 '22 09:09

Jean-Paul Calderone


Using a contextmanager

You could use a custom contextmanager, for example:

class DidWeRaise:     __slots__ = ('exception_happened', )  # instances will take less memory      def __enter__(self):         return self      def __exit__(self, exc_type, exc_val, exc_tb):         # If no exception happened the `exc_type` is None         self.exception_happened = exc_type is not None 

And then use that inside the try:

try:     with DidWeRaise() as error_state:         # funky code finally:     if error_state.exception_happened:         print('the funky code raised') 

It's still an additional variable but it's probably a lot easier to reuse if you want to use it in multiple places. And you don't need to toggle it yourself.

Using a variable

In case you don't want the contextmanager I would reverse the logic of the trigger and toggle it only in case no exception has happened. That way you don't need an except case for exceptions that you don't want to handle. The most appropriate place would be the else clause that is entered in case the try didn't threw an exception:

exception_happened = True try:     # funky code except HandleThis:     # handle this kind of exception else:     exception_happened = False finally:     if exception_happened:         print('the funky code raised') 

And as already pointed out instead of having a "toggle" variable you could replace it (in this case) with the desired logging function:

mylog = mylogger.WARNING try:     with LogCapture() as log:         funky_code() except HandleThis:     # handle this kind of exception else:     # In case absolutely no exception was thrown in the try we can log on debug level     mylog = mylogger.DEBUG finally:     for record in log.captured:         mylog(record.msg, record.args) 

Of course it would also work if you put it at the end of your try (as other answers here suggested) but I prefer the else clause because it has more meaning ("that code is meant to be executed only if there was no exception in the try block") and may be easier to maintain in the long run. Although it's still more to maintain than the context manager because the variable is set and toggled in different places.

Using sys.exc_info (works only for unhandled exceptions)

The last approach I want to mention is probably not useful for you but maybe useful for future readers who only want to know if there's an unhandled exception (an exception that was not caught in any except block or has been raised inside an except block). In that case you can use sys.exc_info:

import sys  try:     # funky code except HandleThis:     pass finally:     if sys.exc_info()[0] is not None:         # only entered if there's an *unhandled* exception, e.g. NOT a HandleThis exception         print('funky code raised') 
like image 145
MSeifert Avatar answered Sep 20 '22 09:09

MSeifert