Add google appengine to repo
[frozenviper.git] / google_appengine / google / appengine / ext / ereporter / report_generator.py
blobf173f475031f216f2f8a6dca1252684eff52741d
1 #!/usr/bin/env python
3 # Copyright 2007 Google Inc.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
18 """Generates and emails daily exception reports.
20 See google/appengine/ext/ereporter/__init__.py for usage details.
22 Valid query string arguments to the report_generator script include:
23 delete: Set to 'false' to prevent deletion of exception records from the
24 datastore after sending a report. Defaults to 'true'.
25 debug: Set to 'true' to return the report in the response instead of
26 emailing it.
27 date: The date to generate the report for, in yyyy-mm-dd format. Defaults to
28 yesterday's date. Useful for debugging.
29 max_results: Maximum number of entries to include in a report.
30 sender: The email address to use as the sender. Must be an administrator.
31 to: If specified, send reports to this address. If not specified, all
32 admins are sent the report.
33 versions: 'all' to report on all minor versions, or 'latest' for the latest.
34 """
40 import datetime
41 import itertools
42 import os
43 import re
44 from xml.sax import saxutils
46 from google.appengine.api import mail
47 from google.appengine.ext import db
48 from google.appengine.ext import ereporter
49 from google.appengine.ext import webapp
50 from google.appengine.ext.webapp import template
51 from google.appengine.ext.webapp.util import run_wsgi_app
54 def isTrue(val):
55 """Determines if a textual value represents 'true'.
57 Args:
58 val: A string, which may be 'true', 'yes', 't', '1' to indicate True.
59 Returns:
60 True or False
61 """
62 val = val.lower()
63 return val == 'true' or val == 't' or val == '1' or val == 'yes'
66 class ReportGenerator(webapp.RequestHandler):
67 """Handler class to generate and email an exception report."""
69 DEFAULT_MAX_RESULTS = 100
71 def __init__(self, send_mail=mail.send_mail,
72 mail_admins=mail.send_mail_to_admins):
73 super(ReportGenerator, self).__init__()
75 self.send_mail = send_mail
76 self.send_mail_to_admins = mail_admins
78 def GetQuery(self, order=None):
79 """Creates a query object that will retrieve the appropriate exceptions.
81 Returns:
82 A query to retrieve the exceptions required.
83 """
84 q = ereporter.ExceptionRecord.all()
85 q.filter('date =', self.yesterday)
86 q.filter('major_version =', self.major_version)
87 if self.version_filter.lower() == 'latest':
88 q.filter('minor_version =', self.minor_version)
89 if order:
90 q.order(order)
91 return q
93 def GenerateReport(self, exceptions):
94 """Generates an HTML exception report.
96 Args:
97 exceptions: A list of ExceptionRecord objects. This argument will be
98 modified by this function.
99 Returns:
100 An HTML exception report.
102 exceptions.sort(key=lambda e: (e.minor_version, -e.count))
103 versions = [(minor, list(excs)) for minor, excs
104 in itertools.groupby(exceptions, lambda e: e.minor_version)]
106 template_values = {
107 'version_filter': self.version_filter,
108 'version_count': len(versions),
110 'exception_count': sum(len(excs) for _, excs in versions),
112 'occurrence_count': sum(y.count for x in versions for y in x[1]),
113 'app_id': self.app_id,
114 'major_version': self.major_version,
115 'date': self.yesterday,
116 'versions': versions,
118 path = os.path.join(os.path.dirname(__file__), 'templates', 'report.html')
119 return template.render(path, template_values)
121 def SendReport(self, report):
122 """Emails an exception report.
124 Args:
125 report: A string containing the report to send.
127 subject = ('Daily exception report for app "%s", major version "%s"'
128 % (self.app_id, self.major_version))
129 report_text = saxutils.unescape(re.sub('<[^>]+>', '', report))
130 mail_args = {
131 'sender': self.sender,
132 'subject': subject,
133 'body': report_text,
134 'html': report,
136 if self.to:
137 mail_args['to'] = self.to
138 self.send_mail(**mail_args)
139 else:
140 self.send_mail_to_admins(**mail_args)
142 def get(self):
143 self.version_filter = self.request.GET.get('versions', 'all')
144 self.sender = self.request.GET['sender']
145 self.to = self.request.GET.get('to', None)
146 report_date = self.request.GET.get('date', None)
147 if report_date:
148 self.yesterday = datetime.date(*[int(x) for x in report_date.split('-')])
149 else:
150 self.yesterday = datetime.date.today() - datetime.timedelta(days=1)
151 self.app_id = os.environ['APPLICATION_ID']
152 version = os.environ['CURRENT_VERSION_ID']
153 self.major_version, self.minor_version = version.rsplit('.', 1)
154 self.minor_version = int(self.minor_version)
155 self.max_results = int(self.request.GET.get('max_results',
156 self.DEFAULT_MAX_RESULTS))
157 self.debug = isTrue(self.request.GET.get('debug', 'false'))
158 self.delete = isTrue(self.request.GET.get('delete', 'true'))
160 try:
161 exceptions = self.GetQuery(order='-minor_version').fetch(self.max_results)
162 except db.NeedIndexError:
163 exceptions = self.GetQuery().fetch(self.max_results)
165 if exceptions:
166 report = self.GenerateReport(exceptions)
167 if self.debug:
168 self.response.out.write(report)
169 else:
170 self.SendReport(report)
172 if self.delete:
173 db.delete(exceptions)
176 application = webapp.WSGIApplication([('.*', ReportGenerator)])
179 def main():
180 run_wsgi_app(application)
183 if __name__ == '__main__':
184 main()