1# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) 2# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php 3 4from email.mime.text import MIMEText 5from email.mime.multipart import MIMEMultipart 6import smtplib 7import time 8try: 9 from socket import sslerror 10except ImportError: 11 sslerror = None 12from paste.exceptions import formatter 13 14class Reporter(object): 15 16 def __init__(self, **conf): 17 for name, value in conf.items(): 18 if not hasattr(self, name): 19 raise TypeError( 20 "The keyword argument %s was not expected" 21 % name) 22 setattr(self, name, value) 23 self.check_params() 24 25 def check_params(self): 26 pass 27 28 def format_date(self, exc_data): 29 return time.strftime('%c', exc_data.date) 30 31 def format_html(self, exc_data, **kw): 32 return formatter.format_html(exc_data, **kw) 33 34 def format_text(self, exc_data, **kw): 35 return formatter.format_text(exc_data, **kw) 36 37class EmailReporter(Reporter): 38 39 to_addresses = None 40 from_address = None 41 smtp_server = 'localhost' 42 smtp_username = None 43 smtp_password = None 44 smtp_use_tls = False 45 subject_prefix = '' 46 47 def report(self, exc_data): 48 msg = self.assemble_email(exc_data) 49 server = smtplib.SMTP(self.smtp_server) 50 if self.smtp_use_tls: 51 server.ehlo() 52 server.starttls() 53 server.ehlo() 54 if self.smtp_username and self.smtp_password: 55 server.login(self.smtp_username, self.smtp_password) 56 server.sendmail(self.from_address, 57 self.to_addresses, msg.as_string()) 58 try: 59 server.quit() 60 except sslerror: 61 # sslerror is raised in tls connections on closing sometimes 62 pass 63 64 def check_params(self): 65 if not self.to_addresses: 66 raise ValueError("You must set to_addresses") 67 if not self.from_address: 68 raise ValueError("You must set from_address") 69 if isinstance(self.to_addresses, (str, unicode)): 70 self.to_addresses = [self.to_addresses] 71 72 def assemble_email(self, exc_data): 73 short_html_version = self.format_html( 74 exc_data, show_hidden_frames=False) 75 long_html_version = self.format_html( 76 exc_data, show_hidden_frames=True) 77 text_version = self.format_text( 78 exc_data, show_hidden_frames=False) 79 msg = MIMEMultipart() 80 msg.set_type('multipart/alternative') 81 msg.preamble = msg.epilogue = '' 82 text_msg = MIMEText(text_version) 83 text_msg.set_type('text/plain') 84 text_msg.set_param('charset', 'ASCII') 85 msg.attach(text_msg) 86 html_msg = MIMEText(short_html_version) 87 html_msg.set_type('text/html') 88 # @@: Correct character set? 89 html_msg.set_param('charset', 'UTF-8') 90 html_long = MIMEText(long_html_version) 91 html_long.set_type('text/html') 92 html_long.set_param('charset', 'UTF-8') 93 msg.attach(html_msg) 94 msg.attach(html_long) 95 subject = '%s: %s' % (exc_data.exception_type, 96 formatter.truncate(str(exc_data.exception_value))) 97 msg['Subject'] = self.subject_prefix + subject 98 msg['From'] = self.from_address 99 msg['To'] = ', '.join(self.to_addresses) 100 return msg 101 102class LogReporter(Reporter): 103 104 filename = None 105 show_hidden_frames = True 106 107 def check_params(self): 108 assert self.filename is not None, ( 109 "You must give a filename") 110 111 def report(self, exc_data): 112 text = self.format_text( 113 exc_data, show_hidden_frames=self.show_hidden_frames) 114 f = open(self.filename, 'a') 115 try: 116 f.write(text + '\n' + '-'*60 + '\n') 117 finally: 118 f.close() 119 120class FileReporter(Reporter): 121 122 file = None 123 show_hidden_frames = True 124 125 def check_params(self): 126 assert self.file is not None, ( 127 "You must give a file object") 128 129 def report(self, exc_data): 130 text = self.format_text( 131 exc_data, show_hidden_frames=self.show_hidden_frames) 132 self.file.write(text + '\n' + '-'*60 + '\n') 133 134class WSGIAppReporter(Reporter): 135 136 def __init__(self, exc_data): 137 self.exc_data = exc_data 138 139 def __call__(self, environ, start_response): 140 start_response('500 Server Error', [('Content-type', 'text/html')]) 141 return [formatter.format_html(self.exc_data)] 142