Welcome, guest | Sign In | My Account | Store | Cart

Being new to Python and trying to chase where in my project exceptions were taking place always seems to take up a large amount of time. So I created this class that I now pretty use in all my projects to help me save time tracking exceptions and errors. its also has the ability to generate error logs or to output SQL query and data tuples for Database logging. Ive added sample code to give you an idea of how I normally use it. Hope this helps its saved me hours in tracking error/exception. Feed back would be great, mind you havent been Python all that long

Python, 377 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
import sys, traceback, string, os
from datetime import datetime

"""
XceptionHandler: Rev 4

Change Log:

04/2/2010    -Added option to allow for exception to be raised rather than
             returned and still have logging and messaging options as well

04/2/2010    -Added option for overriding default logging by passing in a logging 
             object of the main project or parent class

04/6/2010    -Replace query builder with raw traceback dump return to allow more
              code flexibility
              
INIT VARS:

DEBUG       bool   Turn ON/OFF Debug Messages

LOG_FILE    str    Name of Logfile default to XceptLog.txt

LOG_TABLE   str    Name of LOGGING DB TABLE if used input query and data tuple
                   are generated for feeding into MySQLdb or SQLlite

MSG_TYPE    str    Detailed or Simple Versions of Debug Messages

EXC_RETURN  ref    Indicate an exception has occured and will be returned

EXC_RAISE   ref    Indicates an exception occured and to raise it rather than return

EXC_RAW     ref    Indicate and exception occured and will return traceback dump
                   as a dict.
                   
LOG_EVENTS  bool   Set default for logging can be overridden as needed in methods                   

"""

class XceptionHandler:

    def __init__(self, DEBUG=False, LOG_FILE="XceptLog.txt", LOG_TABLE="",
                 LOG_PATH= os.path.abspath(os.curdir)+os.sep+"xLOGS"+os.sep,  
                 MSG_TYPE="Detailed", EXC_RETURN=-1, EXC_RAW=-2, EXC_RAISE=-3, 
                 LOG_OBJ=None, LOG_EVENTS=False):
        
        self.xname      = str(self.__class__).split(".")[1]
        self.Debug      = DEBUG
        self.MSG_TYPE   = MSG_TYPE
        self.LOG_TABLE  = LOG_TABLE
        self.LOG_FILE   = LOG_FILE
        self.LOG_PATH   = LOG_PATH
        self.LOG_OBJ    = LOG_OBJ
        self.EXC_RETURN = EXC_RETURN
        self.EXC_RAISE  = EXC_RAISE
        self.EXC_RAW    = EXC_RAW
        self.LOG_EVENTS = LOG_EVENTS
        
        
    """
    Formatter for Debug Messages
    vars:
    ARGS    dict
    
    """    
    def ReturnFormat(self, ARGS, CallType):
        DetailedErr = ""
        
        DetailedErr1 = """
        --- EXCEPTION_ERROR ---
        File           : """+ARGS["filename"]
        
        DetailedErr2    = """
        Class          : """+ARGS["classname"]
        
        DetailedErr3    = """
        <CALL_TYPE>    : """+ARGS["methodname"]+"""
        Line           : """+ARGS["lineNumber"]+"""
        DTS            : """+str(datetime.now())+"""
        
        Exception Type : """+ARGS["exc_type"]+"""
        Exception Value: """+ARGS["exc_value"]+"""
        Exception Msg  : """+ARGS["exc_info"] +"""
        
        ------------------------
        """
        
        SimpleErr = """
        --- ERROR ---
        
        ErrorType : """+ARGS["exc_type"]+"""
        ErrorValue: """+ARGS["exc_value"]+"""
        ErrorMsg  : """+ARGS["exc_info"]+"""
        
        ------------------------
        """
        
        if self.MSG_TYPE == "Detailed":
            if CallType.find("Method") != -1:
                DetailedErr = DetailedErr1+DetailedErr2+DetailedErr3
            else:
                DetailedErr = DetailedErr1+DetailedErr3
                
            DetailedErr = DetailedErr.replace("<CALL_TYPE>", CallType)
            return DetailedErr
        else:
            return SimpleErr
        
        
        
    """
    Function exception handler
    Not intended to be directly called from the function but rather via ProcessReturn
    vars:
    retval    str
    
    """    
    def FunctionXHandler(self, retval):
        myObject            = sys.exc_info()[2]
        myTraceBack         = traceback.extract_tb(myObject)
        
        fileName            = myTraceBack[0][0]
        lineNumber          = str(myTraceBack[0][1])
        functionName        = myTraceBack[0][2]
        className           = " - "
        
        ARGS = dict()
        ARGS["filename"]    = fileName      
        ARGS["classname"]   = className
        ARGS["methodname"]  = functionName
        ARGS["lineNumber"]  = lineNumber
        ARGS["exc_type"]    = str(sys.exc_type)
        ARGS["exc_value"]   = str(sys.exc_value)
        ARGS["exc_info"]    = str(sys.exc_info()[0])
        ARGS["Message"]     = self.ReturnFormat(ARGS, "Function   ")
        
        return ARGS

    """
    Class Method exception handler
    Not intended to be directly called from the method but rather through ProcessReturn
    vars:
    className    str    Name of calling class
    """
    def ClassXHandler(self, className):
        myObject            = sys.exc_info()[2]
        myTraceBack         = traceback.extract_tb(myObject)
        
        fileName            = myTraceBack[0][0]
        lineNumber          = str(myTraceBack[0][1])
        methodName          = myTraceBack[0][2]
        
        ARGS = dict()
        ARGS["filename"]    = fileName   
        ARGS["Return"]      = ARGS.get("Return", "EXCEPTION_ERROR")
        ARGS["classname"]   = className
        ARGS["methodname"]  = methodName
        ARGS["lineNumber"]  = lineNumber
        ARGS["exc_type"]    = str(sys.exc_type)
        ARGS["exc_value"]   = str(sys.exc_value)
        ARGS["exc_info"]    = str(sys.exc_info()[0])
        ARGS["Message"]     = self.ReturnFormat(ARGS, "Method     ")
        
        if className == self.xname:
            print ARGS["Message"]
        return ARGS
  
  
  
  
    def ParseArgs(self, args):
        try:
            args    = args[0]
            retVal  = None
            logEvent= self.LOG_EVENTS
            logMsg  = ""
               
            if len(args)== 3:
                retVal=args[0] 
                if type(args[1]) == type(""):
                    logMsg = args[1]
                    logEvent = args[2]
                elif type(args[1]) == type(bool()):
                    logEvent = args[1]
                    logMsg   = args[2]
            elif len(args) == 2:
                retVal=args[0]                
                if type(args[1]) == type(""):
                    logMsg = args[1]
                else:
                    logEvent = args[1]
            elif len(args) == 1:
                retVal=args[0]
            
            return retVal, logEvent, logMsg
            
        except:
            return self.ClassXHandler(self.xname)
            
            
            
            
    """
     Main Method for handling return values, determine if exception/error/good
     and react accordingly
     vars:
     retVal       dynamicType    a -1 value indicates an exception
                                 and will return -1 gracefully.
                                 a -2 value indicates an exception
                                 and will return the raw traceback elements
                                 as a dict.
                                 a -3 value indicates an exception
                                 and will raise it.
                                 everything else is passed through
     className    str            name of calling class if called from a class
     logEvent     bool           True = logging, False = no logging
                                 2 = create DB query and data tuple
     logMsg       str            Custom pass though message to be logged
     
    """
    def ProcessReturn(self, className=None, *args):
        retVal, logEvent, logMsg = self.ParseArgs(args)
     
        ARGS = dict()
        
        if retVal == self.EXC_RETURN or retVal == self.EXC_RAISE or retVal == self.EXC_RAW:
            
            if className != None:
                ARGS = self.ClassXHandler(className)
            else:
                ARGS = self.FunctionXHandler(retVal)
      
        if logMsg !="":
            ARGS["Message"] = logMsg
            
        if self.Debug:
            print ARGS["Message"]
                      
        if logEvent:
            self.LogEvent(ARGS)
                    
        if retVal == self.EXC_RAISE:
            raise
        elif retVal == self.EXC_RAW:
            return ARGS
        
        return retVal
     
     
   
    
    def LogEvent(self, ARGS):
        try:  
            if  self.LOG_OBJ != None:
                #logging object
                self.LOG_OBJ(ARGS["Message"])
            
     
            elif self.LOG_OBJ == None:
                #default log 
                LogPath = self.LOG_PATH+self.LOG_FILE
                if not os.path.exists(LogPath):
                    os.mkdir(LogPath)
                fp = open(LogPath, "a+")
                fp.write(ARGS["Message"])
                fp.close()
          
        except:
            return self.ClassXHandler(self.xname)
        
            
            
   
       


 
if __name__ == '__main__':       
            
    ############################################################################
    #Simple Setup To Show How To Reuse Your Already Instantiated Logging 
    #Xceptions.py to override the default internal log
    import logging
    logging.basicConfig(filename="ProjectLog.txt",level=logging.DEBUG) 
    Log_Object = logging.debug
    
   
            
    ##########################################################################
    # Function Usage Example
    
    XH =XceptionHandler(DEBUG=True, LOG_TABLE="DBLogTable", 
                        LOG_OBJ=Log_Object, LOG_EVENTS=True)
    
    def _Return(*args):
        return XH.ProcessReturn(None, args)
    
    
    def XampleFunction1():
        try:
            x = 2/0
            return _Return(1, "SUCCESS")
        except:
            tb_dump = _Return(-2)
            print tb_dump
            return tb_dump
            
     
    def XampleFunction2():
        try:
            x = 2/0
            return _Return(1, "SUCCESS", False) #override LOG_EVENTS one time
        except:
            return _Return(-1)
        
     
    def XampleFunction3():
        try:
            x = 2/1
            return _Return(1, "SUCCESS")
        except:
            _Return(-3)
        
    ############################################################################
    
    ############################################################################
    # Class Usage Example
    
    class XampleClass(XceptionHandler):
    
       def __init__(self):
            self.name       = str(self.__class__).split(".")[1]
            XceptionHandler.__init__(self, DEBUG=True, LOG_EVENTS=False)
           
       def _Return(self, *args):
           return self.ProcessReturn(self.name, args)
       
       def Test1(self):
           try:
               print x
               return self._Return(1)
           except:
               return self._Return(-1)
           
       def Test2(self):
           try:
               print z
               return self._Return(True, "Success")
           except:
               return self._Return(-1, "FAIL", True)
              
       def Test3(self):
           try:
               print w
               return self._Return(True, "Success")
           except:
               return self._Return(-2, "FAIL returning traceback dump")
           
       def Test4(self):
           try:
               print w
               return self._Return(True, "Success")
           except:
               self._Return(-3)
           
    ############################################################################  
  
    
    XampleFunction1()
    XampleFunction2()
    XampleFunction3()
    
    xc = XampleClass()
    xc.Test1()
    xc.Test2()
    xc.Test3()
    xc.Test4()
    
    
              

Taking the advice of feedback (thank you all) Ive added options accordingly see code comments for details, thanks again and please vote/leave feedback

5 comments

Denis Barmenkov 13 years, 12 months ago  # | flag

All needs for exception handling covered by this recipe using standard Python logging facility: http://code.activestate.com/recipes/52215/

Gabriel Genellina 13 years, 12 months ago  # | flag

Routinely converting exceptions to return values is an anti-pattern. Now you have to check each and every call to see whether it returned a good value or an error, and decide what to do at that very moment; failure to do so leads to strange bugs. Exceptions allow to defer such decision to the right level where there is enough information / context.

Use the standard logging package; factor the database stuff in your LogEvent into a custom Handler class; the recipe linked above is a good addition; see also the tb package in PyPI.

AJ. Mayorga (author) 13 years, 11 months ago  # | flag

Thank you all for your comments. I suppose I could and I did think about using the standard Python logging but didn't really see the need, the standard logging can be very verbose and a mix of different level messages Debug,Info,Error etc. and I only wanted to log custom messages and exception errors and make the script somewhat learner. Now I might be missing something I admit, but can you tell me the benefit of using the standard logging for this vice how it is?

Gabriel I don't understand what you mean by anti-pattern?

Gabriel Genellina 13 years, 11 months ago  # | flag

A good introduction to the logging package: http://antonym.org/2005/03/a-real-python-logging-example.html

How verbose the output is: you control that thru the 'severity' parameter, or using the predefined methods debug()/info()/warning()/error()/critical().

The logging package has a lot of flexibility, is already there, and has been tested for many people along several years. You can make your own, of course, but eventually you'll replicate half of it (with your own bugs and fixes). Learning how to use the existing tools is an investment, not a waste of time.

The anti-pattern is routinely replacing true exception handling with a return value: don't do that. I'll refer you to this blog post for discussion: http://nedbatchelder.com/text/exceptions-vs-status.html

AJ. Mayorga (author) 13 years, 11 months ago  # | flag

Just posted Rev.2 and added options for raising rather than returning exceptions. Done alot of reading on this the past two days and seems raising vs returning is a pretty hot topic for some, SO now Xceptions can do both easily.

I also considered logging and decided to keep the old way as the default but allow the programmer to override the default by passing in his/her own instantiated/configured logging object and just have Xceptions use that instead as there are far too many options in the standard logging package and passing the options through Xceptions seems very unnecessary

Enjoy and please vote/leave feedback