1 """GNUmed exception handling widgets."""
2
3 __author__ = "K. Hilbert <Karsten.Hilbert@gmx.net>"
4 __license__ = "GPL v2 or later (details at http://www.gnu.org)"
5
6 import logging, traceback, re as regex, sys, os, shutil, datetime as pyDT
7
8
9 import wx
10
11
12 from Gnumed.pycommon import gmDispatcher
13 from Gnumed.pycommon import gmCfg2
14 from Gnumed.pycommon import gmI18N
15 from Gnumed.pycommon import gmLog2
16 from Gnumed.pycommon import gmPG2
17 from Gnumed.pycommon import gmExceptions
18 from Gnumed.pycommon import gmNetworkTools
19 from Gnumed.pycommon.gmTools import u_box_horiz_single
20
21 from Gnumed.business import gmPraxis
22
23 from Gnumed.wxpython import gmGuiHelpers
24
25
26 _log = logging.getLogger('gm.gui')
27
28 _prev_excepthook = None
29 application_is_closing = False
30
31
33 global _client_version
34 _client_version = version
35
36
38 global _sender_email
39 _sender_email = email
40
41
43 global _helpdesk
44 _helpdesk = helpdesk
45
46
48 global _staff_name
49 _staff_name = staff_name
50
51
53 global _is_public_database
54 _is_public_database = value
55
56
57
58
60
61 if t != RuntimeError:
62 return False
63
64 wx.EndBusyCursor()
65
66
67
68 _log.error('RuntimeError = dead object: %s', v)
69 _log.warning('continuing and hoping for the best')
70 return True
71
72
84
85
87
88 if t == OSError:
89 if not hasattr(t, 'winerror'):
90 return False
91 if getattr(t, 'winerror') != 126:
92 return False
93 else:
94 if t != ImportError:
95 return False
96
97 wx.EndBusyCursor()
98
99 _log.error('module [%s] not installed', v)
100 gmGuiHelpers.gm_show_error (
101 aTitle = _('Missing GNUmed module'),
102 aMessage = _(
103 'GNUmed detected that parts of it are not\n'
104 'properly installed. The following message\n'
105 'names the missing part:\n'
106 '\n'
107 ' "%s"\n'
108 '\n'
109 'Please make sure to get the missing\n'
110 'parts installed. Otherwise some of the\n'
111 'functionality will not be accessible.'
112 ) % v
113 )
114 return True
115
116
118
119 if t != KeyboardInterrupt:
120 return False
121
122 print("<Ctrl-C>: Shutting down ...")
123 top_win = wx.GetApp().GetTopWindow()
124 wx.CallAfter(top_win.Close)
125 return True
126
127
129
130 if t != gmExceptions.AccessDenied:
131 return False
132
133 _log.error('access permissions violation detected')
134 wx.EndBusyCursor()
135 gmLog2.flush()
136 txt = ' ' + v.errmsg
137 if v.source is not None:
138 txt += _('\n Source: %s') % v.source
139 if v.code is not None:
140 txt += _('\n Code: %s') % v.code
141 if v.details is not None:
142 txt += _('\n Details (first 250 characters):\n%s\n%s\n%s') % (
143 u_box_horiz_single * 50,
144 v.details[:250],
145 u_box_horiz_single * 50
146 )
147 gmGuiHelpers.gm_show_error (
148 aTitle = _('Access violation'),
149 aMessage = _(
150 'You do not have access to this part of GNUmed.\n'
151 '\n'
152 '%s'
153 ) % txt
154 )
155 return True
156
157
159
160 if t not in [gmPG2.dbapi.OperationalError, gmPG2.dbapi.InterfaceError]:
161 return False
162
163 try:
164 msg = gmPG2.extract_msg_from_pg_exception(exc = v)
165 except:
166 msg = 'cannot extract message from PostgreSQL exception'
167 print(msg)
168 print(v)
169 return False
170
171 conn_lost = False
172
173 if t == gmPG2.dbapi.OperationalError:
174 conn_lost = (
175 ('erver' in msg)
176 and
177 (
178 ('term' in msg)
179 or
180 ('abnorm' in msg)
181 or
182 ('end' in msg)
183 or
184 ('oute' in msg)
185 )
186 )
187
188 if t == gmPG2.dbapi.InterfaceError:
189 conn_lost = (
190 ('onnect' in msg)
191 and
192 (('close' in msg) or ('end' in msg))
193 )
194
195 if not conn_lost:
196 return False
197
198 gmLog2.log_stack_trace('lost connection', t, v, tb)
199 wx.EndBusyCursor()
200 gmLog2.flush()
201 gmGuiHelpers.gm_show_error (
202 aTitle = _('Lost connection'),
203 aMessage = _(
204 'Since you were last working in GNUmed,\n'
205 'your database connection timed out.\n'
206 '\n'
207 'This GNUmed session is now expired.\n'
208 '\n'
209 'You will have to close this client and\n'
210 'restart a new GNUmed session.'
211 )
212 )
213 return True
214
215
217 if t != wx.wxAssertionError:
218 return False
219 _log.exception('a wxGTK assertion failed:')
220 _log.warning('continuing and hoping for the best')
221 return True
222
223
225
226 _log.debug('unhandled exception caught:', exc_info = (t, v, tb))
227
228 if __handle_access_violation(t, v, tb):
229 return
230
231 if __handle_ctrl_c(t, v, tb):
232 return
233
234 if __handle_exceptions_on_shutdown(t, v, tb):
235 return
236
237 if __ignore_dead_objects_from_async(t, v, tb):
238 return
239
240 if __handle_import_error(t, v, tb):
241 return
242
243
244
245
246
247 _cfg = gmCfg2.gmCfgData()
248 if _cfg.get(option = 'debug') is False:
249 _log.error('enabling debug mode')
250 _cfg.set_option(option = 'debug', value = True)
251 root_logger = logging.getLogger()
252 root_logger.setLevel(logging.DEBUG)
253 _log.debug('unhandled exception caught:', exc_info = (t, v, tb))
254
255 if __handle_lost_db_connection(t, v, tb):
256 return
257
258 gmLog2.log_stack_trace(None, t, v, tb)
259
260
261
262
263 wx.EndBusyCursor()
264
265 name = os.path.basename(_logfile_name)
266 name, ext = os.path.splitext(name)
267 new_name = os.path.expanduser(os.path.join (
268 '~',
269 '.gnumed',
270 'error_logs',
271 '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
272 ))
273
274 dlg = cUnhandledExceptionDlg(None, -1, exception = (t, v, tb), logfile = new_name)
275 dlg.ShowModal()
276 comment = dlg._TCTRL_comment.GetValue()
277 dlg.Destroy()
278 if (comment is not None) and (comment.strip() != ''):
279 _log.error('user comment: %s', comment.strip())
280
281 _log.warning('syncing log file for backup to [%s]', new_name)
282 gmLog2.flush()
283
284 shutil.copy2(_logfile_name, new_name)
285
286
308
309
316
317
323
324
325 -def mail_log(parent=None, comment=None, helpdesk=None, sender=None):
326
327 if (comment is None) or (comment.strip() == ''):
328 comment = wx.GetTextFromUser (
329 message = _(
330 'Please enter a short note on what you\n'
331 'were about to do in GNUmed:'
332 ),
333 caption = _('Sending bug report'),
334 parent = parent
335 )
336 if comment.strip() == '':
337 comment = '<user did not comment on bug report>'
338
339 receivers = []
340 if helpdesk is not None:
341 receivers = regex.findall (
342 '[\S]+@[\S]+',
343 helpdesk.strip(),
344 flags = regex.UNICODE
345 )
346 if len(receivers) == 0:
347 if _is_public_database:
348 receivers = ['gnumed-bugs@gnu.org']
349
350 receiver_string = wx.GetTextFromUser (
351 message = _(
352 'Edit the list of email addresses to send the\n'
353 'bug report to (separate addresses by spaces).\n'
354 '\n'
355 'Note that <gnumed-bugs@gnu.org> refers to\n'
356 'the public (!) GNUmed bugs mailing list.'
357 ),
358 caption = _('Sending bug report'),
359 default_value = ','.join(receivers),
360 parent = parent
361 )
362 if receiver_string.strip() == '':
363 return
364
365 receivers = regex.findall (
366 '[\S]+@[\S]+',
367 receiver_string,
368 flags = regex.UNICODE
369 )
370
371 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
372 parent,
373 -1,
374 caption = _('Sending bug report'),
375 question = _(
376 'Your bug report will be sent to:\n'
377 '\n'
378 '%s\n'
379 '\n'
380 'Make sure you have reviewed the log file for potentially\n'
381 'sensitive information before sending out the bug report.\n'
382 '\n'
383 'Note that emailing the report may take a while depending\n'
384 'on the speed of your internet connection.\n'
385 ) % '\n'.join(receivers),
386 button_defs = [
387 {'label': _('Send report'), 'tooltip': _('Yes, send the bug report.')},
388 {'label': _('Cancel'), 'tooltip': _('No, do not send the bug report.')}
389 ],
390 show_checkbox = True,
391 checkbox_msg = _('include log file in bug report')
392 )
393 dlg._CHBOX_dont_ask_again.SetValue(_is_public_database)
394 go_ahead = dlg.ShowModal()
395 if go_ahead == wx.ID_NO:
396 dlg.Destroy()
397 return
398
399 include_log = dlg._CHBOX_dont_ask_again.GetValue()
400 if not _is_public_database:
401 if include_log:
402 result = gmGuiHelpers.gm_show_question (
403 _(
404 'The database you are connected to is marked as\n'
405 '"in-production with controlled access".\n'
406 '\n'
407 'You indicated that you want to include the log\n'
408 'file in your bug report. While this is often\n'
409 'useful for debugging the log file might contain\n'
410 'bits of patient data which must not be sent out\n'
411 'without de-identification.\n'
412 '\n'
413 'Please confirm that you want to include the log !'
414 ),
415 _('Sending bug report')
416 )
417 include_log = (result is True)
418
419 if sender is None:
420 sender = _('<not supplied>')
421 else:
422 if sender.strip() == '':
423 sender = _('<not supplied>')
424
425 msg = """\
426 Report sent via GNUmed's handler for unexpected exceptions.
427
428 user comment : %s
429
430 client version: %s
431
432 system account: %s
433 staff member : %s
434 sender email : %s
435
436 # enable Launchpad bug tracking
437 affects gnumed
438 tag automatic-report
439 importance medium
440
441 """ % (comment, _client_version, _local_account, _staff_name, sender)
442 if include_log:
443 _log.error(comment)
444 _log.warning('syncing log file for emailing')
445 gmLog2.flush()
446 attachments = [ [_logfile_name, 'text/plain', 'quoted-printable'] ]
447 else:
448 attachments = None
449
450 dlg.Destroy()
451
452 wx.BeginBusyCursor()
453 _cfg = gmCfg2.gmCfgData()
454 try:
455 gmNetworkTools.compose_and_send_email (
456 sender = '%s <%s>' % (_staff_name, gmNetworkTools.default_mail_sender),
457 receiver = receivers,
458 subject = '<bug>: %s' % comment,
459 message = msg,
460 server = gmNetworkTools.default_mail_server,
461 auth = {'user': gmNetworkTools.default_mail_sender, 'password': 'gnumed-at-gmx-net'},
462 debug = _cfg.get(option = 'debug'),
463 attachments = attachments
464 )
465 gmDispatcher.send(signal='statustext', msg = _('Bug report has been emailed.'))
466 except:
467 _log.exception('cannot send bug report')
468 gmDispatcher.send(signal='statustext', msg = _('Bug report COULD NOT be emailed.'))
469 wx.EndBusyCursor()
470
471
472 from Gnumed.wxGladeWidgets import wxgUnhandledExceptionDlg
473
475
477
478 exception = kwargs['exception']
479 del kwargs['exception']
480 self.logfile = kwargs['logfile']
481 del kwargs['logfile']
482
483 wxgUnhandledExceptionDlg.wxgUnhandledExceptionDlg.__init__(self, *args, **kwargs)
484
485 if _sender_email is not None:
486 self._TCTRL_sender.SetValue(_sender_email)
487 self._TCTRL_helpdesk.SetValue(_helpdesk)
488 self._TCTRL_logfile.SetValue(self.logfile)
489 t, v, tb = exception
490 self._TCTRL_exc_type.SetValue(str(t))
491 self._TCTRL_exc_value.SetValue(str(v))
492 self._TCTRL_traceback.SetValue(''.join(traceback.format_tb(tb)))
493
494 self.Fit()
495
509
520
526
527