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
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
All needs for exception handling covered by this recipe using standard Python logging facility: http://code.activestate.com/recipes/52215/
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 yourLogEvent
into a customHandler
class; the recipe linked above is a good addition; see also thetb
package in PyPI.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?
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
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