forked from snarfed/bridgy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
logs.py
120 lines (97 loc) · 4.2 KB
/
logs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""Handlers and utilities for exposing app logs to users.
"""
import cgi
import datetime
import logging
import re
import urllib
import appengine_config
import util
from google.appengine.api import logservice
from google.appengine.ext import ndb
import webapp2
LEVELS = {
logservice.LOG_LEVEL_DEBUG: 'D',
logservice.LOG_LEVEL_INFO: 'I',
logservice.LOG_LEVEL_WARNING: 'W',
logservice.LOG_LEVEL_ERROR: 'E',
logservice.LOG_LEVEL_CRITICAL: 'F',
}
SANITIZE_RE = re.compile(
r"""((?:access|api|oauth)?[ _]?
(?:consumer_key|consumer_secret|nonce|secret|signature|token|verifier)
(?:=|:|\ |',\ u?'|%3D)\ *)
[^ &=']+""",
flags=re.VERBOSE | re.IGNORECASE)
def sanitize(msg):
"""Sanitizes access tokens and Authorization headers."""
return SANITIZE_RE.sub(r'\1...', msg)
# datastore string keys are url-safe-base64 of, say, at least 32(ish) chars.
# https://cloud.google.com/appengine/docs/python/ndb/keyclass#Key_urlsafe
# http://tools.ietf.org/html/rfc3548.html#section-4
DATASTORE_KEY_RE = re.compile("'(([A-Za-z0-9-_=]{8})[A-Za-z0-9-_=]{24,})'")
def linkify_datastore_keys(msg):
"""Converts string datastore keys to links to the admin console viewer."""
def linkify_key(match):
try:
key = ndb.Key(urlsafe=match.group(1))
tokens = [(kind, '%s:%s' % ('id' if isinstance(id, (int, long)) else 'name', id))
for kind, id in key.pairs()]
key_str = '|'.join('%d/%s|%d/%s' % (len(kind), kind, len(id), id)
for kind, id in tokens)
return "'<a title='%s' href='https://console.developers.google.com/datastore/editentity?project=brid-gy&kind=%s&queryType=GQLQuery&queryText&key=0/|%s'>%s...</a>'" % (
match.group(1), key.kind(), key_str, match.group(2))
except BaseException:
logging.debug("Couldn't linkify candidate datastore key.", exc_info=True)
return msg
return DATASTORE_KEY_RE.sub(linkify_key, msg)
class LogHandler(webapp2.RequestHandler):
"""Searches for and renders the app logs for a single task queue request.
"""
@util.canonicalize_domain
def get(self):
"""URL parameters:
start_time: float, seconds since the epoch
key: string that should appear in the first app log
"""
start_time = util.get_required_param(self, 'start_time')
if not util.is_float(start_time):
self.abort(400, "Couldn't convert start_time to float: %r" % start_time)
start_time = float(start_time)
key = util.get_required_param(self, 'key')
if not util.is_base64(key):
self.abort(400, 'key is not base64: %r' % key)
key = urllib.unquote(key)
# the propagate task logs the poll task's URL, which includes the source
# entity key as a query param. exclude that with this heuristic.
key_re = re.compile('[^=]' + key)
self.response.headers['Content-Type'] = 'text/html; charset=utf-8'
offset = None
for log in logservice.fetch(start_time=start_time, end_time=start_time + 120,
offset=offset, include_app_logs=True,
version_ids=['2', '3', '4', '5', '6', '7']):
first_lines = '\n'.join([line.message.decode('utf-8') for line in
log.app_logs[:min(10, len(log.app_logs))]])
if log.app_logs and key_re.search(first_lines):
# found it! render and return
self.response.out.write("""\
<html>
<body style="font-family: monospace; white-space: pre">
""")
self.response.out.write(sanitize(log.combined))
self.response.out.write('<br /><br />')
for a in log.app_logs:
msg = a.message.decode('utf-8')
# don't sanitize poll task URLs since they have a key= query param
msg = linkify_datastore_keys(util.linkify(cgi.escape(
msg if msg.startswith('Created by this poll:') else sanitize(msg))))
self.response.out.write('%s %s %s<br />' %
(datetime.datetime.utcfromtimestamp(a.time), LEVELS[a.level],
msg.replace('\n', '<br />')))
self.response.out.write('</body>\n</html>')
return
offset = log.offset
self.response.out.write('No log found!')
application = webapp2.WSGIApplication([
('/log', LogHandler),
], debug=appengine_config.DEBUG)