wiki:ErrorReporting
Warning: Can't synchronize with repository "(default)" (Unsupported version control system "svn": No module named svn). Look in the Trac log for more information.

Version 4 (modified by owen, 13 years ago) (diff)

--

Custom Error Reporting

We're not perfect. Sometimes we don't catch every exception, even after piles of testing. This is why it's a good idea to be told if disaster strikes and one of your pages bombs out. This is a good idea during so-called "beta testing" (otherwise known as "releasing unfinished product") as you can't always rely on your users informing you of such problems.

Here's a brief rundown on how I did it. I won't go in to how to send an E-mail as that's covered better elsewhere, such as Python's documentation.

# Your controller:

    @turbogears.errorhandling.dispatch_error.when("tg_exceptions is not None")
    def unhandled_exception(self, tg_source, tg_errors, tg_exception):
    	try:
    		# Spam me here, whatever
    		programmer_notified = True
    		pass
    	except:
    		programmer_notified = False
    	return dict(tg_template=".templates.unhandled_exception", title="500 Internal error", programmer_notified=programmer_notified)
# And an example template (unhandled_exception.kid in mine)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:py="http://purl.org/kid/ns#"
    py:extends="'master.kid'">

<head>
    <title py:content="title">Title</title>
</head>

<body>
    <h1>Oops! An error occured</h1>
    <p>Something went wonky and we weren't expecting it. Sorry about that. This is commonly known as an &quot;Error 500&quot; or &quot;Internal Server Error&quot;. It means we messed up.</p>
    <p py:if="programmer_notified">A programmer has been notified and will try to fix the problem as soon as possible.</p>
    <p py:if="not programmer_notified">The problem was actually so bad that we couldn't even send an E-mail to our team to sort the problem out! If you keep getting this message, please send us an E-mail with some information about what you were doing when this happened, so we can try and smooth things over :-)</p>
    <p>Our sincerest apologies,</p>
    <p style="font-style: italic">-The Team</p> <!--! No <em> because this is just convention !-->
</body>
</html>

Of course, you don't really wanna be hiding your exceptions while you're developing, so I recommend commenting out the @turbogears.errorhandling.dispatch_error decorator until people are going to start visiting the site while you're not sitting over the server output.

Bear in mind that this method supresses exception output in the log so there is a chance that the exception could be lost forever.

Custom Error Reporting - Method 2 (Application Wide Catch All)

Personally I found the following worked much better if you have multiple controllers. You hook the code into your Root controller, and your done. This however makes use of CherryPy? instead of TurboGears specifics. Readers should also check out the HowDoesErrorHandlingWork page for info on the exception_handler to do custom handling for each method.

The following code has pretty much everything you need to provide a custom error page, and send an email to someone with the error. You will need to change the email settings though, but other then that should be good to go. (Tested in TG 0.9a6)

class Root(controllers.RootController):
	@turbogears.expose()
	@identity.require(identity.in_group("user"))
	def index(self):
		return self.main()

	def cp_on_http_error(self, status, message):
		from cherrypy import _cputil
		_cputil._cp_on_http_error(status, message)

		# Go to a specific 404 page (don't want a lot of spam about people typing in wrong URLs)
		if status == 404:
			url = "%s %s" % (cherrypy.request.method, cherrypy.request.path)
			output = dict(
				status = status,
				message = message or '-',
				theError = '404 - Page Not Found',
				admin = 'admin' in identity.current.identity().groups,
				url = url)

			template = ".templates.404_exception"
			format = 'html'
			content_type = 'text/html'
			mapping = None

			# Probably don't want to send an email, but it is here if you do
			#self.send_exception_to_developer(status, url, '404 error')

			# Return customized page
			body = controllers._process_output(output, template, format, content_type, mapping)
			cherrypy.response.headers['Content-Length'] = len(body)
			cherrypy.response.body = body
			return

		import logging
		log = logging.getLogger("turbogears.controllers")
		log.exception('CherryPy %s error (%s)', status, message)

		import traceback, StringIO
		buf = StringIO.StringIO()
		traceback.print_exc(file=buf)
		url = "%s %s" % (cherrypy.request.method, cherrypy.request.path)

		self.send_exception_to_developer(status, url, buf.getvalue())

		theError = {
			400: u'400 - Bad Request',
			401: u'401 - Unauthorized',
			403: u'403 - Forbidden',
			404: u'404 - Not Found',
			500: u'500 - Internal Server Error',
			501: u'501 - Not Implemented',
			502: u'502 - Bad Gateway',
		}.get(status, message or u'General Error') 

		output = dict(
			status = status,
			message = message or '-',
			theError = theError,
			admin = 'admin' in identity.current.identity().groups,
			url = url,
			details = buf.getvalue())

		template = ".templates.unhandled_exception"
		format = 'html'
		content_type = 'text/html'
		mapping = None

		# Return customized page
		body = controllers._process_output(output, template, format, content_type, mapping)
		cherrypy.response.headers['Content-Length'] = len(body)
		cherrypy.response.body = body 

	# Hook in error stuff for production only
	if turbogears.config.get('server.environment') == 'production':
		_cp_on_http_error = cp_on_http_error

	def send_exception_to_developer(self, status, url, data):
		import smtplib
		from email.MIMEMultipart import MIMEMultipart
		from email.MIMEBase import MIMEBase
		from email.MIMEText import MIMEText
		from email.Utils import formatdate

		fromAddy = "Server <server@mydomain.com>"
		to = "servererrors@server@mydomain.com"

		msg = MIMEMultipart()
		msg['From'] = fromAddy
		msg['To'] = to
		msg['Date'] = formatdate(localtime=True)
		msg['Subject'] = '%d ERROR on the Server' % status

		text = "----------URL----------\n%s\n----------DATA:----------\n%s" % (url, data)

		msg.attach(MIMEText(text))

		smtp = smtplib.SMTP("mailserver.net")
		smtp.login('username', 'password')
		smtp.sendmail(fromAddy, to, msg.as_string())
		smtp.close()

For the lazy out there who wanted to know how to handle an exception on each mehod but didn't follow the above link, here is the code you want:

class Root(controllers.RootController):
    @turbogears.expose()
    def error(self):
        return "An error ocured"

    @turbogears.expose()
    @turbogears.controllers.exception_handler(error, "ValueError")
    def index(self):
        raise ValueError

References:  http://groups.google.com/group/turbogears/browse_frm/thread/61a48b1f7fdfe354/de325500f437f632?lnk=gst&q=custom+500&rnum=1#de325500f437f632  http://groups.google.com/group/turbogears/browse_thread/thread/ba80c6d7ecc4a349/5522ae1cfceab877?lnk=gst&q=_cp_on_error&rnum=3#5522ae1cfceab877  http://groups.google.com/group/turbogears/browse_thread/thread/e7c103acb7b786ab/263115b72e9dfcd4?lnk=gst&q=exception_handler&rnum=5#263115b72e9dfcd4