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 notified if disaster strikes and one of your pages bombs
out. This is especially a good idea during so-called “beta testing” (otherwise known as
“releasing unfinished product”) since you can’t always rely on your users informing you of
such problems.

There are several approaches to the problem, depending on whether you want to catch exceptions
in a specific controller or method or application wide.

You can set a controller method as the handler for specific exceptions using the
errorhandling.dispatch_error decorator. The following example code shows how to
catch any exception that occurs in the same controller and return a custom error page.
You could also send and email to the programmer in this method, but this is not covered
here. See the code from method two or check out the Python library reference for examples
on how to send email from your code.

And a matching template (called unhandled_exception.html if you follow the code above),
like this one:

<!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://genshi.edgewall.org/"
xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="master.html" />
<head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
<title py:content="title">Error</title>
</head>
<body>
<h1>Oops! An error occurred</h1>
<p>Something went wonky and we weren't expecting it. Sorry about that.</p>
<p>This is commonly known as an &quot;Error 500 &quot; or
&quot;Internal Server Error&quot;. It means we messed up.</p>
<py:choose>
<p py:when="programmer_notified">A programmer has been notified
and will try to fix the problem as soon as possible.</p>
<p py:otherwise="">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>
</py:choose>
<p>Our sincerest apologies,</p>
<p><i>-- The Team</i></p>
</body>
</html>

Normally, you don’t really want to hide exceptions while you’re developing,
so you may want to comment out the @when(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 suppresses exception output in the log,
so there is a chance that the exception could be lost forever.

If you have multiple controllers, you may want to use the same error handler for all of them.
For this, you need to make use custom CherryPy error handling. This can be enabled by adding
the following code to your root controller:

Here is an example module implementing error_page and handle_error methods
specified in the _cp_config above. You only need to add Genshi templates for the
various error pages, and for the email to be sent out to the admin:

# -*- coding: utf-8 -*-"""Functions for custom error pages and sending email notifications.These error catcher functions can be used with TurboGears 1.5 (CherryPy 3.1).To enable the custom error pages, you must add the following code to the rootcontroller and set ``error_catcher.on = True`` in the deployment configuration:: # Hook in error handler if enabled in configuration if config.get('error_catcher.on', False): _cp_config = { 'error_page.default': error_page, 'request.error_response': handle_error }When the error catcher is enabled and an HTML error (including an unhandledexception) occurs in the controller, an error page is displayed using atemplate whose name is looked up in the ``error_page_templates`` dictionaryby the HTML status code.Currently, there are default templates for the status codes 401, 403 and 404,called ``401_error``, ``403_error`` and ``404_error`` resp. and``unhandled_exception`` for all other errors. The templates are searched in the``templates`` sub-package of the application.Also, if ``mail.on`` is ``True`` sends an email to the admin, when an erroroccurs. No email is sent if the HTML status code is contained in the list setby the option ``error_catcher.no_email_on``. The default is not to send emailsfor 401, 403 and 404 errors.For email sending to work, at least the configuration options``error_catcher.sender_email`` and ``error_catcher.admin_email`` must beset to valid email addresses.See the docstring for the function ``send_exception_email`` for more emailrelated configuration information.See http://docs.turbogears.org/1.5/ErrorReporting"""importdatetimeimportloggingimportsocketimportcherrypyfromcherrypyimport__version__ascherrypy_versionfromturbogearsimportconfig,controllers,identity,utilfromturbogears.releaseimportversionasturbogears_versiontry:importturbomailexceptImportError:turbomail=Nonefromemail.mime.multipartimportMIMEMultipartfromemail.mime.textimportMIMETextfromemail.Utilsimportformatdate__all__=['error_page','handle_error']log=logging.getLogger("turbogears.controllers")admin_group_name='admin'error_codes={None:u'Unknown Error',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',}error_page_templates={None:'.templates.unhandled_exception',401:'.templates.401_error',403:'.templates.403_error',404:'.templates.404_error',}error_mail_templates={None:'.templates.email_unhandled_exception',}defformat_request_info(req):"""Return string with formatted info about important request properties."""data=[]# headers are reported separatelyforattrin['remoteHost','remoteAddr','remotePort','requestLine','body','protocol','method','query_string','browser_url','originalPath']:value=getattr(req,attr,None)ifvalue:data.append(u'%s: %r'%(attr,value))returnu'\n'.join(data)defadd_webpath(req):"""Correct browser_url by adding the webpath."""# correct browser_url by adding the webpathwebpath=config.get('server.webpath')ifwebpathandwebpath!='/':base=getattr(req,'base',None)path=getattr(req,'path',None)if(baseandnotbase.endswith(webpath)andpathandnotpath.startswith(webpath)):req.base+=webpathdefformat_request_headers(req):"""Return string with formatted request headers."""add_webpath(req)data=[]forkey,valueinreq.header_list:data.append(u'%s: %r'%(key,value))returnu'\n'.join(data)defformat_user_info(user):"""Return string with formatted info about request user."""data=[]ifuser:forkeyin['user_id','user_name','display_name','email_address']:value=getattr(user,key,None)ifvalue:data.append(u'%s: %r'%(key,value))else:data.append(u'Anonymous user')returnu'\n'.join(data)deferror_page(status,message,traceback,version):"""Error page for any kind of error."""try:ifstatus:ifnotisinstance(status,int):try:status=int(status.split(None,1)[0])exceptValueError:status=500else:status=500error_msg=get_error_message(status)ifnottracebackand(config.get('error_catcher.show_tracebacks',True)orcherrypy.request.show_tracebacks):traceback=cherrypy._cperror.format_exc()ifnottraceback:traceback="No traceback available (check the log file)."ifnotversion:version=cherrypy_versionurl=cherrypy.request.request_linelog.exception("CherryPy %s error (%s) for request '%s'",status,message,url)url=url.split(None,2)[1]webpath=config.get('server.webpath','')ifnoturl.startswith(webpath):url=webpath+urlifstatus==404:path=url.split('?',1)[0]mailto_redirect=config.get('error_catcher.mailto_redirect')ifmailto_redirectand'/mailto:'inpath:# some bots generate such false links from mailto: addressescherrypy.response.status=302cherrypy.response.headers['Content-Length']=0cherrypy.response.headers['Location']=mailto_redirectreturntry:is_admin=identity.in_group(admin_group_name)user_info=format_user_info(identity.current.user)exceptException,exc:is_admin=Falseuser_info='Error getting user info: '+str(exc)# get request inforequest=cherrypy.requestdata=dict(cherrypy_version=version,error_msg=error_msg,header_info=format_request_headers(request),is_admin=is_admin,message=message,request_info=format_request_info(request),server=request.headers.get('host',socket.getfqdn()),status=status,timestamp=datetime.datetime.now(),traceback=traceback,url=url,user_info=user_info,turbogears_version=turbogears_version)no_email_on=config.get('error_catcher.no_email_on',(401,403,404))ifconfig.get('mail.on')andstatusnotinno_email_on:try:send_exception_email(**data)data['email_sent']=TrueexceptException,err:log.exception('Error email failed: %s',err)data['email_sent']=Falseelse:data['email_sent']=Falsereturnget_error_page(**data)# don't catch SystemExitexceptException,err:log.exception('Error handler failed: %s',err)return"Unrecoverable error %s in the server - %s"%(status,message)defhandle_error():"""Error handling of unanticipated errors (error 500)."""status=500cherrypy.response.status=statuscherrypy.response.body=error_page(status,None,None,None)defget_error_page(**data):"""Render error page using template from error_templates."""template=error_page_templates.get(data['status'],error_page_templates.get(None))returnrender_error_template(template,data=data)defsend_exception_email(**data):"""Send an email with the error info to the admin. Uses TurboMail if installed and activated, otherwise tries to send email with the ``smtplib`` module. The SMTP settings can be configured with the following settings: ``mail.smtp.server`` - Mail server to connect to (default 'localhost'). ``mail.username`` - User name for SMTP authentication. If the value is unset or evaluates to False no SMTP login is performed. ``mail.password`` - Password for SMTP authentication. May be an empty string. See also the module docstring for information on setting the sender and recipient address. """sender_email=config.get('error_catcher.sender_email')admin_email=config.get('error_catcher.admin_email')ifnotsender_emailornotadmin_email:msg=('Configuration error: could not send error notification'' because sender and/or admin email address is not set.')log.exception(msg)raiseRuntimeError(msg)template=error_mail_templates.get(data['status'],error_mail_templates.get(None))subject='Error %(status)s on server %(server)s'%databody=render_error_template(template,'plain','text/plain',data)try:ifturbomail:msg=turbomail.Message(sender_email,admin_email,subject,plain=body)ifnotmsg.send():raiseRuntimeError('TurboMail could not deliver the message.')else:msg=MIMEMultipart()msg['From']=sender_emailmsg['To']=admin_emailmsg['Date']=formatdate(localtime=True)msg['Subject']=subjectmsg.attach(MIMEText(body))send_email_by_smtp(sender_email,admin_email,msg.as_string())exceptException,err:log.exception('Could not send error notification: %s',err)raisedefsend_email_by_smtp(from_addr,to_addr,message):"""Send email via SMTP."""importsmtplibserver=config.get('mail.smtp.server','localhost')smtp=smtplib.SMTP(server)username=config.get('mail.username')password=config.get('mail.password')ifusernameandpasswordisnotNone:smtp.login(username,password)smtp.sendmail(from_addr,to_addr,message)smtp.close()defget_error_message(status,default=None):"""Return string error for HTTP status code."""returnerror_codes.get(status,defaultorerror_codes[None])defrender_error_template(template,format='html',content_type='text/html',data={}):if':'intemplate:prefix,template=template.split(':',1)prefix+=':'else:prefix=''iftemplate.startswith('.'):package=util.get_package_name()else:package=''template="%s%s%s"%(prefix,package,template)returncontrollers._process_output(data,template,format,content_type,None)