thirdparty/google_appengine/google/appengine/ext/ereporter/report_generator.py
changeset 2864 2e0b0af889be
equal deleted inserted replaced
2862:27971a13089f 2864:2e0b0af889be
       
     1 #!/usr/bin/env python
       
     2 #
       
     3 # Copyright 2007 Google Inc.
       
     4 #
       
     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
       
     8 #
       
     9 #     http://www.apache.org/licenses/LICENSE-2.0
       
    10 #
       
    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.
       
    16 #
       
    17 
       
    18 """Generates and emails daily exception reports.
       
    19 
       
    20 See google/appengine/ext/ereporter/__init__.py for usage details.
       
    21 
       
    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 """
       
    35 
       
    36 
       
    37 
       
    38 
       
    39 
       
    40 import datetime
       
    41 import itertools
       
    42 import os
       
    43 import re
       
    44 from xml.sax import saxutils
       
    45 
       
    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
       
    52 
       
    53 
       
    54 def isTrue(val):
       
    55   """Determines if a textual value represents 'true'.
       
    56 
       
    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'
       
    64 
       
    65 
       
    66 class ReportGenerator(webapp.RequestHandler):
       
    67   """Handler class to generate and email an exception report."""
       
    68 
       
    69   DEFAULT_MAX_RESULTS = 100
       
    70 
       
    71   def __init__(self, send_mail=mail.send_mail,
       
    72                mail_admins=mail.send_mail_to_admins):
       
    73     super(ReportGenerator, self).__init__()
       
    74 
       
    75     self.send_mail = send_mail
       
    76     self.send_mail_to_admins = mail_admins
       
    77 
       
    78   def GetQuery(self, order=None):
       
    79     """Creates a query object that will retrieve the appropriate exceptions.
       
    80 
       
    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
       
    92 
       
    93   def GenerateReport(self, exceptions):
       
    94     """Generates an HTML exception report.
       
    95 
       
    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.
       
   101     """
       
   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)]
       
   105 
       
   106     template_values = {
       
   107         'version_filter': self.version_filter,
       
   108         'version_count': len(versions),
       
   109 
       
   110         'exception_count': sum(len(excs) for _, excs in versions),
       
   111 
       
   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,
       
   117     }
       
   118     path = os.path.join(os.path.dirname(__file__), 'templates', 'report.html')
       
   119     return template.render(path, template_values)
       
   120 
       
   121   def SendReport(self, report):
       
   122     """Emails an exception report.
       
   123 
       
   124     Args:
       
   125       report: A string containing the report to send.
       
   126     """
       
   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,
       
   135     }
       
   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)
       
   141 
       
   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'))
       
   159 
       
   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)
       
   164 
       
   165     if exceptions:
       
   166       report = self.GenerateReport(exceptions)
       
   167       if self.debug:
       
   168         self.response.out.write(report)
       
   169       else:
       
   170         self.SendReport(report)
       
   171 
       
   172       if self.delete:
       
   173         db.delete(exceptions)
       
   174 
       
   175 
       
   176 application = webapp.WSGIApplication([('.*', ReportGenerator)])
       
   177 
       
   178 
       
   179 def main():
       
   180   run_wsgi_app(application)
       
   181 
       
   182 
       
   183 if __name__ == '__main__':
       
   184   main()