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
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.
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
55 """Determines if a textual value represents 'true'.
58 val: A string, which may be 'true', 'yes', 't', '1' to indicate True.
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.
82 A query to retrieve the exceptions required.
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
)
93 def GenerateReport(self
, exceptions
):
94 """Generates an HTML exception report.
97 exceptions: A list of ExceptionRecord objects. This argument will be
98 modified by this function.
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
)]
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.
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
))
131 'sender': self
.sender
,
137 mail_args
['to'] = self
.to
138 self
.send_mail(**mail_args
)
140 self
.send_mail_to_admins(**mail_args
)
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)
148 self
.yesterday
= datetime
.date(*[int(x
) for x
in report_date
.split('-')])
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'))
161 exceptions
= self
.GetQuery(order
='-minor_version').fetch(self
.max_results
)
162 except db
.NeedIndexError
:
163 exceptions
= self
.GetQuery().fetch(self
.max_results
)
166 report
= self
.GenerateReport(exceptions
)
168 self
.response
.out
.write(report
)
170 self
.SendReport(report
)
173 db
.delete(exceptions
)
176 application
= webapp
.WSGIApplication([('.*', ReportGenerator
)])
180 run_wsgi_app(application
)
183 if __name__
== '__main__':