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
7 import traceback
8 import sys
9 import os
10 import shutil
11 import datetime as pyDT
12 import re as regex
13
14
15 import wx
16
17
18 from Gnumed.pycommon import gmDispatcher
19 from Gnumed.pycommon import gmCfg2
20 from Gnumed.pycommon import gmI18N
21 from Gnumed.pycommon import gmLog2
22 from Gnumed.pycommon import gmPG2
23 from Gnumed.pycommon import gmExceptions
24 from Gnumed.pycommon import gmNetworkTools
25 from Gnumed.pycommon.gmTools import u_box_horiz_single
26
27 from Gnumed.business import gmPraxis
28
29 from Gnumed.wxpython import gmGuiHelpers
30
31
32 _log = logging.getLogger('gm.gui')
33
34 _prev_excepthook = None
35 application_is_closing = False
36
37
41
42
44 global _sender_email
45 _sender_email = email
46
47
49 global _helpdesk
50 _helpdesk = helpdesk
51
52
54 global _staff_name
55 _staff_name = staff_name
56
57
59 global _is_public_database
60 _is_public_database = value
61
62
63
64
66
67 if t != RuntimeError:
68 return False
69
70 wx.EndBusyCursor()
71
72
73
74 _log.error('RuntimeError = dead object: %s', v)
75 _log.warning('continuing and hoping for the best')
76 return True
77
78
90
91
93
94 if t == OSError:
95 if not hasattr(t, 'winerror'):
96 return False
97 if getattr(t, 'winerror') != 126:
98 return False
99 else:
100 if t != ImportError:
101 return False
102
103 wx.EndBusyCursor()
104
105 _log.error('module [%s] not installed', v)
106 gmGuiHelpers.gm_show_error (
107 aTitle = _('Missing GNUmed module'),
108 aMessage = _(
109 'GNUmed detected that parts of it are not\n'
110 'properly installed. The following message\n'
111 'names the missing part:\n'
112 '\n'
113 ' "%s"\n'
114 '\n'
115 'Please make sure to get the missing\n'
116 'parts installed. Otherwise some of the\n'
117 'functionality will not be accessible.'
118 ) % v
119 )
120 return True
121
122
124
125 if t != KeyboardInterrupt:
126 return False
127
128 print("<Ctrl-C>: Shutting down ...")
129 top_win = wx.GetApp().GetTopWindow()
130 wx.CallAfter(top_win.Close)
131 return True
132
133
135
136 if t != gmExceptions.AccessDenied:
137 return False
138
139 _log.error('access permissions violation detected')
140 wx.EndBusyCursor()
141 gmLog2.flush()
142 txt = ' ' + v.errmsg
143 if v.source is not None:
144 txt += _('\n Source: %s') % v.source
145 if v.code is not None:
146 txt += _('\n Code: %s') % v.code
147 if v.details is not None:
148 txt += _('\n Details (first 250 characters):\n%s\n%s\n%s') % (
149 u_box_horiz_single * 50,
150 v.details[:250],
151 u_box_horiz_single * 50
152 )
153 gmGuiHelpers.gm_show_error (
154 aTitle = _('Access violation'),
155 aMessage = _(
156 'You do not have access to this part of GNUmed.\n'
157 '\n'
158 '%s'
159 ) % txt
160 )
161 return True
162
163
186
187
189 if t != wx.wxAssertionError:
190 return False
191 _log.exception('a wxGTK assertion fired:')
192 _log.warning('continuing and hoping for the best')
193 return True
194
195
197
198 _log.debug('unhandled exception caught:', exc_info = (t, v, tb))
199
200 if __handle_access_violation(t, v, tb):
201 return
202
203 if __handle_ctrl_c(t, v, tb):
204 return
205
206 if __handle_exceptions_on_shutdown(t, v, tb):
207 return
208
209 if __ignore_dead_objects_from_async(t, v, tb):
210 return
211
212 if __handle_import_error(t, v, tb):
213 return
214
215 if __handle_wxgtk_assertion(t, v, tb):
216 return
217
218
219 _cfg = gmCfg2.gmCfgData()
220 if _cfg.get(option = 'debug') is False:
221 _log.error('enabling debug mode')
222 _cfg.set_option(option = 'debug', value = True)
223 root_logger = logging.getLogger()
224 root_logger.setLevel(logging.DEBUG)
225 _log.debug('unhandled exception caught:', exc_info = (t, v, tb))
226
227 if __handle_lost_db_connection(t, v, tb):
228 return
229
230 gmLog2.log_stack_trace(None, t, v, tb)
231
232
233
234
235 wx.EndBusyCursor()
236
237 name = os.path.basename(_logfile_name)
238 name, ext = os.path.splitext(name)
239 new_name = os.path.expanduser(os.path.join (
240 '~',
241 '.gnumed',
242 'error_logs',
243 '%s_%s%s' % (name, pyDT.datetime.now().strftime('%Y-%m-%d_%H-%M-%S'), ext)
244 ))
245
246 dlg = cUnhandledExceptionDlg(None, -1, exception = (t, v, tb), logfile = new_name)
247 dlg.ShowModal()
248 comment = dlg._TCTRL_comment.GetValue()
249 dlg.DestroyLater()
250 if (comment is not None) and (comment.strip() != ''):
251 _log.error('user comment: %s', comment.strip())
252
253 _log.warning('syncing log file for backup to [%s]', new_name)
254 gmLog2.flush()
255
256 shutil.copy2(_logfile_name, new_name)
257
258
284
285
292
293
299
300
301 -def mail_log(parent=None, comment=None, helpdesk=None, sender=None):
302
303 if (comment is None) or (comment.strip() == ''):
304 comment = wx.GetTextFromUser (
305 message = _(
306 'Please enter a short note on what you\n'
307 'were about to do in GNUmed:'
308 ),
309 caption = _('Sending bug report'),
310 parent = parent
311 )
312 if comment.strip() == '':
313 comment = '<user did not comment on bug report>'
314
315 receivers = []
316 if helpdesk is not None:
317 receivers = regex.findall (
318 '[\S]+@[\S]+',
319 helpdesk.strip(),
320 flags = regex.UNICODE
321 )
322 if len(receivers) == 0:
323 if _is_public_database:
324 receivers = ['gnumed-bugs@gnu.org']
325
326 receiver_string = wx.GetTextFromUser (
327 message = _(
328 'Edit the list of email addresses to send the\n'
329 'bug report to (separate addresses by spaces).\n'
330 '\n'
331 'Note that <gnumed-bugs@gnu.org> refers to\n'
332 'the public (!) GNUmed bugs mailing list.'
333 ),
334 caption = _('Sending bug report'),
335 default_value = ','.join(receivers),
336 parent = parent
337 )
338 if receiver_string.strip() == '':
339 return
340
341 receivers = regex.findall (
342 '[\S]+@[\S]+',
343 receiver_string,
344 flags = regex.UNICODE
345 )
346
347 dlg = gmGuiHelpers.c2ButtonQuestionDlg (
348 parent,
349 -1,
350 caption = _('Sending bug report'),
351 question = _(
352 'Your bug report will be sent to:\n'
353 '\n'
354 '%s\n'
355 '\n'
356 'Make sure you have reviewed the log file for potentially\n'
357 'sensitive information before sending out the bug report.\n'
358 '\n'
359 'Note that emailing the report may take a while depending\n'
360 'on the speed of your internet connection.\n'
361 ) % '\n'.join(receivers),
362 button_defs = [
363 {'label': _('Send report'), 'tooltip': _('Yes, send the bug report.')},
364 {'label': _('Cancel'), 'tooltip': _('No, do not send the bug report.')}
365 ],
366 show_checkbox = True,
367 checkbox_msg = _('include log file in bug report')
368 )
369 dlg._CHBOX_dont_ask_again.SetValue(_is_public_database)
370 go_ahead = dlg.ShowModal()
371 if go_ahead == wx.ID_NO:
372 dlg.DestroyLater()
373 return
374
375 include_log = dlg._CHBOX_dont_ask_again.GetValue()
376 if not _is_public_database:
377 if include_log:
378 result = gmGuiHelpers.gm_show_question (
379 _(
380 'The database you are connected to is marked as\n'
381 '"in-production with controlled access".\n'
382 '\n'
383 'You indicated that you want to include the log\n'
384 'file in your bug report. While this is often\n'
385 'useful for debugging the log file might contain\n'
386 'bits of patient data which must not be sent out\n'
387 'without de-identification.\n'
388 '\n'
389 'Please confirm that you want to include the log !'
390 ),
391 _('Sending bug report')
392 )
393 include_log = (result is True)
394
395 if sender is None:
396 sender = _('<not supplied>')
397 else:
398 if sender.strip() == '':
399 sender = _('<not supplied>')
400
401 msg = """\
402 Report sent via GNUmed's handler for unexpected exceptions.
403
404 user comment : %s
405
406 client version: %s
407
408 system account: %s
409 staff member : %s
410 sender email : %s
411
412 # enable Launchpad bug tracking
413 affects gnumed
414 tag automatic-report
415 importance medium
416
417 """ % (comment, _client_version, _local_account, _staff_name, sender)
418 if include_log:
419 _log.error(comment)
420 _log.warning('syncing log file for emailing')
421 gmLog2.flush()
422 attachments = [ [_logfile_name, 'text/plain', 'quoted-printable'] ]
423 else:
424 attachments = None
425
426 dlg.DestroyLater()
427
428 wx.BeginBusyCursor()
429 _cfg = gmCfg2.gmCfgData()
430 try:
431 gmNetworkTools.compose_and_send_email (
432 sender = '%s <%s>' % (_staff_name, gmNetworkTools.default_mail_sender),
433 receiver = receivers,
434 subject = '<bug>: %s' % comment,
435 message = msg,
436 server = gmNetworkTools.default_mail_server,
437 auth = {'user': gmNetworkTools.default_mail_sender, 'password': 'gnumed-at-gmx-net'},
438 debug = _cfg.get(option = 'debug'),
439 attachments = attachments
440 )
441 gmDispatcher.send(signal='statustext', msg = _('Bug report has been emailed.'))
442 except Exception:
443 _log.exception('cannot send bug report')
444 gmDispatcher.send(signal='statustext', msg = _('Bug report COULD NOT be emailed.'))
445 finally:
446 wx.EndBusyCursor()
447
448
449 from Gnumed.wxGladeWidgets import wxgUnhandledExceptionDlg
450
452
454
455 exception = kwargs['exception']
456 del kwargs['exception']
457 self.logfile = kwargs['logfile']
458 del kwargs['logfile']
459
460 wxgUnhandledExceptionDlg.wxgUnhandledExceptionDlg.__init__(self, *args, **kwargs)
461
462 self.Title = '%s [PID %s]' % (self.Title, APP_PID)
463
464 if _sender_email is not None:
465 self._TCTRL_sender.SetValue(_sender_email)
466 self._TCTRL_helpdesk.SetValue(_helpdesk)
467 self._TCTRL_logfile.SetValue(self.logfile)
468 t, v, tb = exception
469 self._TCTRL_traceback.SetValue ('%s: %s\n%s: %s\n%s' % (
470 'type', t,
471 'value', v,
472 ''.join(traceback.format_tb(tb))
473 ))
474 self.Fit()
475
476
490
491
501
502
503
504
505
514
515
521
522
523