1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8 """
9
10 import sys
11 import cPickle
12 import traceback
13 import types
14 import os
15 import logging
16
17 from storage import Storage
18 from http import HTTP
19 from html import BEAUTIFY
20
21 logger = logging.getLogger("web2py")
22
23 __all__ = ['RestrictedError', 'restricted', 'TicketStorage', 'compile2']
24
26
27 """
28 defines the ticket object and the default values of its members (None)
29 """
30
31 - def __init__(
32 self,
33 db=None,
34 tablename='web2py_ticket'
35 ):
36 self.db = db
37 self.tablename = tablename
38
39 - def store(self, request, ticket_id, ticket_data):
40 """
41 stores the ticket. It will figure out if this must be on disk or in db
42 """
43 if self.db:
44 self._store_in_db(request, ticket_id, ticket_data)
45 else:
46 self._store_on_disk(request, ticket_id, ticket_data)
47
49 table = self._get_table(self.db, self.tablename, request.application)
50 table.insert(ticket_id=ticket_id,
51 ticket_data=cPickle.dumps(ticket_data),
52 created_datetime=request.now)
53 logger.error('In FILE: %(layer)s\n\n%(traceback)s\n' % ticket_data)
54
56 ef = self._error_file(request, ticket_id, 'wb')
57 try:
58 cPickle.dump(ticket_data, ef)
59 finally:
60 ef.close()
61
62 - def _error_file(self, request, ticket_id, mode, app=None):
63 root = request.folder
64 if app:
65 root = os.path.join(os.path.join(root, '..'), app)
66 errors_folder = os.path.abspath(os.path.join(root, 'errors'))
67 return open(os.path.join(errors_folder, ticket_id), mode)
68
70 tablename = tablename + '_' + app
71 table = db.get(tablename, None)
72 if table is None:
73 db.rollback()
74
75 table = db.define_table(
76 tablename,
77 db.Field('ticket_id', length=100),
78 db.Field('ticket_data', 'text'),
79 db.Field('created_datetime', 'datetime'),
80 )
81 return table
82
83 - def load(
84 self,
85 request,
86 app,
87 ticket_id,
88 ):
89 if not self.db:
90 ef = self._error_file(request, ticket_id, 'rb', app)
91 try:
92 return cPickle.load(ef)
93 finally:
94 ef.close()
95 table = self._get_table(self.db, self.tablename, app)
96 rows = self.db(table.ticket_id == ticket_id).select()
97 if rows:
98 return cPickle.loads(rows[0].ticket_data)
99 return None
100
101
103 """
104 class used to wrap an exception that occurs in the restricted environment
105 below. the traceback is used to log the exception and generate a ticket.
106 """
107
108 - def __init__(
109 self,
110 layer='',
111 code='',
112 output='',
113 environment=None,
114 ):
115 """
116 layer here is some description of where in the system the exception
117 occurred.
118 """
119 if environment is None: environment = {}
120 self.layer = layer
121 self.code = code
122 self.output = output
123 self.environment = environment
124 if layer:
125 try:
126 self.traceback = traceback.format_exc()
127 except:
128 self.traceback = 'no traceback because template parting error'
129 try:
130 self.snapshot = snapshot(context=10,code=code,
131 environment=self.environment)
132 except:
133 self.snapshot = {}
134 else:
135 self.traceback = '(no error)'
136 self.snapshot = {}
137
138 - def log(self, request):
139 """
140 logs the exception.
141 """
142
143 try:
144 d = {
145 'layer': str(self.layer),
146 'code': str(self.code),
147 'output': str(self.output),
148 'traceback': str(self.traceback),
149 'snapshot': self.snapshot,
150 }
151 ticket_storage = TicketStorage(db=request.tickets_db)
152 ticket_storage.store(request, request.uuid.split('/',1)[1], d)
153 return request.uuid
154 except:
155 logger.error(self.traceback)
156 return None
157
158
159 - def load(self, request, app, ticket_id):
160 """
161 loads a logged exception.
162 """
163 ticket_storage = TicketStorage(db=request.tickets_db)
164 d = ticket_storage.load(request, app, ticket_id)
165
166 self.layer = d['layer']
167 self.code = d['code']
168 self.output = d['output']
169 self.traceback = d['traceback']
170 self.snapshot = d.get('snapshot')
171
183
184
186 """
187 The +'\n' is necessary else compile fails when code ends in a comment.
188 """
189 return compile(code.rstrip().replace('\r\n','\n')+'\n', layer, 'exec')
190
191 -def restricted(code, environment=None, layer='Unknown'):
192 """
193 runs code in environment and returns the output. if an exception occurs
194 in code it raises a RestrictedError containing the traceback. layer is
195 passed to RestrictedError to identify where the error occurred.
196 """
197 if environment is None: environment = {}
198 environment['__file__'] = layer
199 try:
200 if type(code) == types.CodeType:
201 ccode = code
202 else:
203 ccode = compile2(code,layer)
204 exec ccode in environment
205 except HTTP:
206 raise
207 except RestrictedError:
208
209 raise
210 except Exception, error:
211
212 etype, evalue, tb = sys.exc_info()
213
214 if __debug__ and 'WINGDB_ACTIVE' in os.environ:
215 sys.excepthook(etype, evalue, tb)
216 output = "%s %s" % (etype, evalue)
217 raise RestrictedError(layer, code, output, environment)
218
219 -def snapshot(info=None, context=5, code=None, environment=None):
220 """Return a dict describing a given traceback (based on cgitb.text)."""
221 import os, types, time, linecache, inspect, pydoc, cgitb
222
223
224 etype, evalue, etb = info or sys.exc_info()
225
226 if type(etype) is types.ClassType:
227 etype = etype.__name__
228
229
230 s = {}
231 s['pyver'] = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
232 s['date'] = time.ctime(time.time())
233
234
235 records = inspect.getinnerframes(etb, context)
236 s['frames'] = []
237 for frame, file, lnum, func, lines, index in records:
238 file = file and os.path.abspath(file) or '?'
239 args, varargs, varkw, locals = inspect.getargvalues(frame)
240 call = ''
241 if func != '?':
242 call = inspect.formatargvalues(args, varargs, varkw, locals,
243 formatvalue=lambda value: '=' + pydoc.text.repr(value))
244
245
246 f = {'file': file, 'func': func, 'call': call, 'lines': {}, 'lnum': lnum}
247
248 highlight = {}
249 def reader(lnum=[lnum]):
250 highlight[lnum[0]] = 1
251 try: return linecache.getline(file, lnum[0])
252 finally: lnum[0] += 1
253 vars = cgitb.scanvars(reader, frame, locals)
254
255
256 if file.endswith('html'):
257 lmin = lnum>context and (lnum-context) or 0
258 lmax = lnum+context
259 lines = code.split("\n")[lmin:lmax]
260 index = min(context, lnum) - 1
261
262 if index is not None:
263 i = lnum - index
264 for line in lines:
265 f['lines'][i] = line.rstrip()
266 i += 1
267
268
269 f['dump'] = {}
270 for name, where, value in vars:
271 if name in f['dump']: continue
272 if value is not cgitb.__UNDEF__:
273 if where == 'global': name = 'global ' + name
274 elif where != 'local': name = where + name.split('.')[-1]
275 f['dump'][name] = pydoc.text.repr(value)
276 else:
277 f['dump'][name] = 'undefined'
278
279 s['frames'].append(f)
280
281
282 s['etype'] = str(etype)
283 s['evalue'] = str(evalue)
284 s['exception'] = {}
285 if isinstance(evalue, BaseException):
286 for name in dir(evalue):
287
288 if name!='message' or sys.version_info<(2.6):
289 value = pydoc.text.repr(getattr(evalue, name))
290 s['exception'][name] = value
291
292
293 s['locals'] = {}
294 for name, value in locals.items():
295 s['locals'][name] = pydoc.text.repr(value)
296
297
298 for k,v in environment.items():
299 if k in ('request', 'response', 'session'):
300 s[k] = BEAUTIFY(v)
301
302 return s
303