blob: 297f1529c902b2de0b878ffecd19dde251dc44e7 [file] [log] [blame]
# Copyright (c) 2011 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Error reporting service, aka Breakpad for Python."""
import datetime
import json
from google.appengine.api import taskqueue
from google.appengine.api import xmpp
from google.appengine.ext import db
from google.appengine.ext import webapp
from base_page import BasePage
import utils
class Report(db.Model):
date = db.DateTimeProperty(auto_now_add=True)
user = db.StringProperty()
stack = db.TextProperty()
args = db.TextProperty()
exception = db.TextProperty()
host = db.StringProperty()
cwd = db.StringProperty()
version = db.StringProperty(multiline=True)
def asDict(self):
return {
'date': self.date.isoformat(),
'user': self.user,
'stack': self.stack,
'args': self.args,
'exception': self.exception,
'host': self.host,
'cwd': self.cwd,
'version': self.version,
}
class Admins(db.Model):
user = db.UserProperty()
# When set to 0, doesn't sent IMs.
nb_lines = db.IntegerProperty(default=5)
class BreakPad(BasePage):
@utils.requires_write_access
def get(self):
limit = int(self.request.get('limit', 30))
reports = Report.gql('ORDER BY date DESC LIMIT %d' % limit)
if self.request.get('json'):
data = [report.asDict() for report in reports]
self.response.headers.add_header("content-type", 'text/plain')
self.response.out.write(json.dumps(data, indent=2, sort_keys=True))
else:
page_value = {'reports': reports}
template_values = self.InitializeTemplate('Breakpad reports')
template_values.update(page_value)
self.DisplayTemplate('breakpad.html', template_values)
def post(self):
"""Adds a new breakpad report.
Anyone can add a stack trace.
"""
user = self.request.get('user')
stack = self.request.get('stack')
args = self.request.get('args')
exception = self.request.get('exception')
host = self.request.get('host')
cwd = self.request.get('cwd')
version = self.request.get('version')
# Cheap blacklisting to keep me sane.
if ('twisted.spread.pb.DeadReferenceError: Calling Stale Broker' in
exception):
self.response.out.write('Ignored.')
return
if user and stack and args:
Report(user=user, stack=stack, args=args, exception=exception, host=host,
cwd=cwd, version=version).put()
params = {'user': user, 'stack': stack}
taskqueue.add(url='/restricted/breakpad/im', params=params)
self.response.out.write('A stack trace has been sent to the maintainers.')
class SendIM(webapp.RequestHandler):
"""A taskqueue."""
@utils.requires_work_queue_login
def post(self):
user = self.request.get('user')
stack = self.request.get('stack')
stacks = stack.split('\n')
for i in Admins.all():
if not i.nb_lines or not i.user or not i.user.email():
continue
stack_text = '\n'.join(stacks[-min(len(stacks), i.nb_lines):])
text = '%s:\n%s' % (user, stack_text)
xmpp.send_message(i.user.email(), text)
class Cleanup(webapp.RequestHandler):
"""A cron job."""
@utils.requires_work_queue_login
def get(self):
"""Delete reports older than 31 days."""
cutoff = datetime.datetime.now() - datetime.timedelta(days=31)
# Will only delete 1000 reports at a time max. Shouldn't be a problem unles
# we get more than 1000 reports/day. I hope not!
for report in db.Query(Report, keys_only=True).filter('date <', cutoff):
db.delete(report)
def bootstrap():
if db.GqlQuery('SELECT __key__ FROM Admins').get() is None:
# Insert a dummy Admins so it can be edited through the admin console
Admins().put()