Add hooks to send email when uncaught exception occurs
This commit is contained in:
parent
d83204495b
commit
fc58594699
|
@ -25,7 +25,13 @@ WuttaPOS app
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import threading
|
||||||
|
from collections import OrderedDict
|
||||||
|
from traceback import format_exception
|
||||||
|
|
||||||
from rattail import app as base
|
from rattail import app as base
|
||||||
from rattail.config import make_config
|
from rattail.config import make_config
|
||||||
|
@ -37,6 +43,9 @@ import wuttapos
|
||||||
from wuttapos.util import get_pos_batch_handler
|
from wuttapos.util import get_pos_batch_handler
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class WuttaAppHandler(base.AppHandler):
|
class WuttaAppHandler(base.AppHandler):
|
||||||
"""
|
"""
|
||||||
Custom app handler for WuttaPOS
|
Custom app handler for WuttaPOS
|
||||||
|
@ -55,6 +64,61 @@ def main(page: ft.Page):
|
||||||
config = make_config()
|
config = make_config()
|
||||||
app = config.get_app()
|
app = config.get_app()
|
||||||
model = app.model
|
model = app.model
|
||||||
|
handler = get_pos_batch_handler(config)
|
||||||
|
|
||||||
|
# nb. as of python 3.10 the original hook is accessible, we needn't save the ref
|
||||||
|
# cf. https://docs.python.org/3/library/threading.html#threading.__excepthook__
|
||||||
|
orig_thread_hook = threading.excepthook
|
||||||
|
|
||||||
|
hostname = socket.gethostname()
|
||||||
|
email_context = OrderedDict([
|
||||||
|
('hostname', hostname),
|
||||||
|
('ipaddress', socket.gethostbyname(hostname)),
|
||||||
|
('terminal', handler.get_terminal_id() or '??'),
|
||||||
|
])
|
||||||
|
|
||||||
|
def send_error_email(traceback):
|
||||||
|
extra_context = OrderedDict(email_context)
|
||||||
|
|
||||||
|
try:
|
||||||
|
uuid = page.session.get('user_uuid')
|
||||||
|
if uuid:
|
||||||
|
session = app.make_session()
|
||||||
|
user = session.get(model.User, uuid)
|
||||||
|
extra_context['username'] = user.username
|
||||||
|
batch = handler.get_current_batch(user, create=False)
|
||||||
|
if batch:
|
||||||
|
extra_context['batchid'] = batch.id_str
|
||||||
|
session.close()
|
||||||
|
else:
|
||||||
|
extra_context['username'] = 'n/a'
|
||||||
|
|
||||||
|
app.send_email('uncaught_exception', {
|
||||||
|
'extra_context': extra_context,
|
||||||
|
'traceback': traceback,
|
||||||
|
})
|
||||||
|
|
||||||
|
except:
|
||||||
|
log.exception("failed to send error email")
|
||||||
|
|
||||||
|
def sys_exc_hook(exc_type, exc_value, exc_traceback):
|
||||||
|
traceback = ''.join(format_exception(exc_type, exc_value, exc_traceback)).strip()
|
||||||
|
send_error_email(traceback)
|
||||||
|
sys.__excepthook__(exc_type, exc_value, exc_traceback)
|
||||||
|
|
||||||
|
def thread_exc_hook(args):
|
||||||
|
traceback = ''.join(format_exception(args.exc_type, args.exc_value, args.exc_traceback)).strip()
|
||||||
|
send_error_email(traceback)
|
||||||
|
orig_thread_hook(args)
|
||||||
|
|
||||||
|
# custom exception hook for main process
|
||||||
|
sys.excepthook = sys_exc_hook
|
||||||
|
|
||||||
|
# custom exception hook for threads (requires python 3.8)
|
||||||
|
# cf. https://docs.python.org/3/library/threading.html#threading.excepthook
|
||||||
|
v = sys.version_info
|
||||||
|
if v.major >= 3 and v.minor >= 8:
|
||||||
|
threading.excepthook = thread_exc_hook
|
||||||
|
|
||||||
page.title = f"WuttaPOS v{wuttapos.__version__}"
|
page.title = f"WuttaPOS v{wuttapos.__version__}"
|
||||||
page.window_full_screen = True
|
page.window_full_screen = True
|
||||||
|
@ -137,7 +201,6 @@ def main(page: ft.Page):
|
||||||
if user:
|
if user:
|
||||||
page.session.set('user_uuid', uuid)
|
page.session.set('user_uuid', uuid)
|
||||||
page.session.set('user_display', str(user))
|
page.session.set('user_display', str(user))
|
||||||
handler = get_pos_batch_handler(config)
|
|
||||||
batch = handler.get_current_batch(user, create=False)
|
batch = handler.get_current_batch(user, create=False)
|
||||||
if batch:
|
if batch:
|
||||||
page.session.set('txn_display', batch.id_str)
|
page.session.set('txn_display', batch.id_str)
|
||||||
|
|
|
@ -657,7 +657,7 @@ class POSView(WuttaView):
|
||||||
meta_button_width = meta_button_height * 2
|
meta_button_width = meta_button_height * 2
|
||||||
meta_font_size = tenkey_font_size
|
meta_font_size = tenkey_font_size
|
||||||
|
|
||||||
def meta_button(text, on_click=None, bgcolor='blue'):
|
def meta_button(text, on_click=None, bgcolor='blue', data=None):
|
||||||
return ft.Container(content=ft.Text(text, size=meta_font_size,
|
return ft.Container(content=ft.Text(text, size=meta_font_size,
|
||||||
weight=ft.FontWeight.BOLD),
|
weight=ft.FontWeight.BOLD),
|
||||||
height=meta_button_height,
|
height=meta_button_height,
|
||||||
|
@ -666,7 +666,8 @@ class POSView(WuttaView):
|
||||||
alignment=ft.alignment.center,
|
alignment=ft.alignment.center,
|
||||||
border=ft.border.all(1, 'black'),
|
border=ft.border.all(1, 'black'),
|
||||||
border_radius=ft.border_radius.all(5),
|
border_radius=ft.border_radius.all(5),
|
||||||
bgcolor=bgcolor)
|
bgcolor=bgcolor,
|
||||||
|
data=data)
|
||||||
|
|
||||||
self.meta_menu = ft.Container(
|
self.meta_menu = ft.Container(
|
||||||
content=ft.Column(
|
content=ft.Column(
|
||||||
|
@ -695,7 +696,8 @@ class POSView(WuttaView):
|
||||||
ft.Row(
|
ft.Row(
|
||||||
[
|
[
|
||||||
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
|
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
|
||||||
meta_button("TODO", bgcolor='blue', on_click=self.not_supported),
|
meta_button("TODO", bgcolor='blue', on_click=self.not_supported,
|
||||||
|
data={'error': True}),
|
||||||
],
|
],
|
||||||
spacing=0,
|
spacing=0,
|
||||||
),
|
),
|
||||||
|
@ -867,6 +869,11 @@ class POSView(WuttaView):
|
||||||
self.page.update()
|
self.page.update()
|
||||||
|
|
||||||
def not_supported(self, e=None, feature=None):
|
def not_supported(self, e=None, feature=None):
|
||||||
|
|
||||||
|
# test error handler
|
||||||
|
if e.control.data and e.control.data.get('error'):
|
||||||
|
raise ValueError("NOT YET SUPPORTED")
|
||||||
|
|
||||||
text = "NOT YET SUPPORTED"
|
text = "NOT YET SUPPORTED"
|
||||||
if not feature and e:
|
if not feature and e:
|
||||||
feature = e.control.content.value
|
feature = e.control.content.value
|
||||||
|
|
Loading…
Reference in a new issue