##// END OF EJS Templates
api: make all tests for API pass
api: make all tests for API pass

File last commit:

r4811:201c0563 default
r5048:a6860778 default
Show More
exc_tracking.py
232 lines | 7.7 KiB | text/x-python | PythonLexer
exceptions: added new exception tracking capability....
r2907 # -*- coding: utf-8 -*-
code: update copyrights to 2020
r4306 # Copyright (C) 2010-2020 RhodeCode GmbH
exceptions: added new exception tracking capability....
r2907 #
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License, version 3
# (only), as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This program is dual-licensed. If you wish to learn more about the
# RhodeCode Enterprise Edition, including its added features, Support services,
# and proprietary license terms, please see https://rhodecode.com/licenses/
import os
import time
exceptions: store exception about failed exc email sending
r4811 import sys
exceptions: added new exception tracking capability....
r2907 import datetime
import msgpack
import logging
import traceback
import tempfile
cache exc store, and use glob.glob for scaning exc store
r3975 import glob
exceptions: added new exception tracking capability....
r2907
log = logging.getLogger(__name__)
# NOTE: Any changes should be synced with exc_tracking at vcsserver.lib.exc_tracking
global_prefix = 'rhodecode'
exc_tracker: allow setting custom store via .ini file...
r3019 exc_store_dir_name = 'rc_exception_store_v1'
exceptions: added new exception tracking capability....
r2907
exception_tracker: store request info if available.
r4301 def exc_serialize(exc_id, tb, exc_type, extra_data=None):
exceptions: added new exception tracking capability....
r2907
data = {
'version': 'v1',
'exc_id': exc_id,
'exc_utc_date': datetime.datetime.utcnow().isoformat(),
'exc_timestamp': repr(time.time()),
'exc_message': tb,
'exc_type': exc_type,
}
exception_tracker: store request info if available.
r4301 if extra_data:
data.update(extra_data)
exceptions: added new exception tracking capability....
r2907 return msgpack.packb(data), data
def exc_unserialize(tb):
return msgpack.unpackb(tb)
cache exc store, and use glob.glob for scaning exc store
r3975 _exc_store = None
exceptions: added new exception tracking capability....
r2907
def get_exc_store():
"""
Get and create exception store if it's not existing
"""
cache exc store, and use glob.glob for scaning exc store
r3975 global _exc_store
exc_tracker: allow setting custom store via .ini file...
r3019 import rhodecode as app
exceptions: added new exception tracking capability....
r2907
cache exc store, and use glob.glob for scaning exc store
r3975 if _exc_store is not None:
# quick global cache
return _exc_store
exception_store: rename .ini option for future
r3024 exc_store_dir = app.CONFIG.get('exception_tracker.store_path', '') or tempfile.gettempdir()
exc_tracker: allow setting custom store via .ini file...
r3019 _exc_store_path = os.path.join(exc_store_dir, exc_store_dir_name)
exceptions: added new exception tracking capability....
r2907
_exc_store_path = os.path.abspath(_exc_store_path)
if not os.path.isdir(_exc_store_path):
os.makedirs(_exc_store_path)
log.debug('Initializing exceptions store at %s', _exc_store_path)
cache exc store, and use glob.glob for scaning exc store
r3975 _exc_store = _exc_store_path
exceptions: added new exception tracking capability....
r2907 return _exc_store_path
exception-tracker: enable send email on exception
r4276 def _store_exception(exc_id, exc_type_name, exc_traceback, prefix, send_email=None):
api: added store_exception_api for remote exception storage....
r3317 """
Low level function to store exception in the exception tracker
"""
exception_tracker: store request info if available.
r4301 from pyramid.threadlocal import get_current_request
exception-tracker: enable send email on exception
r4276 import rhodecode as app
exception_tracker: store request info if available.
r4301 request = get_current_request()
extra_data = {}
# NOTE(marcink): store request information into exc_data
if request:
extra_data['client_address'] = getattr(request, 'client_addr', '')
extra_data['user_agent'] = getattr(request, 'user_agent', '')
extra_data['method'] = getattr(request, 'method', '')
extra_data['url'] = getattr(request, 'url', '')
exceptions: added new exception tracking capability....
r2907
exc_store_path = get_exc_store()
exception_tracker: store request info if available.
r4301 exc_data, org_data = exc_serialize(exc_id, exc_traceback, exc_type_name, extra_data=extra_data)
exceptions: added new exception tracking capability....
r2907 exc_pref_id = '{}_{}_{}'.format(exc_id, prefix, org_data['exc_timestamp'])
if not os.path.isdir(exc_store_path):
os.makedirs(exc_store_path)
stored_exc_path = os.path.join(exc_store_path, exc_pref_id)
with open(stored_exc_path, 'wb') as f:
f.write(exc_data)
log.debug('Stored generated exception %s as: %s', exc_id, stored_exc_path)
exception-tracker: enable send email on exception
r4276 if send_email is None:
# NOTE(marcink): read app config unless we specify explicitly
send_email = app.CONFIG.get('exception_tracker.send_email', False)
exc_tracker: disable send-email if mail server is not configured.
r4297 mail_server = app.CONFIG.get('smtp_server') or None
send_email = send_email and mail_server
exc-tracker: don't fail on empty request in context of celery app for example.
r4541 if send_email and request:
exception-tracker: enable send email on exception
r4276 try:
exception_tracker: store request info if available.
r4301 send_exc_email(request, exc_id, exc_type_name)
exception-tracker: enable send email on exception
r4276 except Exception:
log.exception('Failed to send exception email')
exceptions: store exception about failed exc email sending
r4811 exc_info = sys.exc_info()
store_exception(id(exc_info), exc_info, send_email=False)
exception-tracker: enable send email on exception
r4276
exception_tracker: store request info if available.
r4301 def send_exc_email(request, exc_id, exc_type_name):
exception-tracker: enable send email on exception
r4276 import rhodecode as app
from rhodecode.apps._base import TemplateArgs
from rhodecode.lib.utils2 import aslist
from rhodecode.lib.celerylib import run_task, tasks
from rhodecode.lib.base import attach_context_attributes
from rhodecode.model.notification import EmailNotificationModel
recipients = aslist(app.CONFIG.get('exception_tracker.send_email_recipients', ''))
log.debug('Sending Email exception to: `%s`', recipients or 'all super admins')
# NOTE(marcink): needed for email template rendering
exc-tracker: make context attribute behave like in API case...
r4277 user_id = None
exc-tracking: fixed send-email bug when called from a source not attached to user session.
r4497 if hasattr(request, 'user'):
exc-tracker: make context attribute behave like in API case...
r4277 user_id = request.user.user_id
attach_context_attributes(TemplateArgs(), request, user_id=user_id, is_api=True)
exception-tracker: enable send email on exception
r4276
email_kwargs = {
'email_prefix': app.CONFIG.get('exception_tracker.email_prefix', '') or '[RHODECODE ERROR]',
'exc_url': request.route_url('admin_settings_exception_tracker_show', exception_id=exc_id),
'exc_id': exc_id,
'exc_type_name': exc_type_name,
'exc_traceback': read_exception(exc_id, prefix=None),
}
emails: set References header for threading in mail user agents even with different subjects...
r4447 (subject, email_body, email_body_plaintext) = EmailNotificationModel().render_email(
exception-tracker: enable send email on exception
r4276 EmailNotificationModel.TYPE_EMAIL_EXCEPTION, **email_kwargs)
run_task(tasks.send_email, recipients, subject,
email_body_plaintext, email_body)
exceptions: added new exception tracking capability....
r2907
api: added store_exception_api for remote exception storage....
r3317 def _prepare_exception(exc_info):
exc_type, exc_value, exc_traceback = exc_info
exc_type_name = exc_type.__name__
tb = ''.join(traceback.format_exception(
exc_type, exc_value, exc_traceback, None))
return exc_type_name, tb
exceptions: store exception about failed exc email sending
r4811 def store_exception(exc_id, exc_info, prefix=global_prefix, send_email=None):
exception-tracker: store event sending exception for easier event fail debugging.
r3007 """
Example usage::
exc_info = sys.exc_info()
store_exception(id(exc_info), exc_info)
"""
exceptions: added new exception tracking capability....
r2907 try:
api: added store_exception_api for remote exception storage....
r3317 exc_type_name, exc_traceback = _prepare_exception(exc_info)
_store_exception(exc_id=exc_id, exc_type_name=exc_type_name,
exceptions: store exception about failed exc email sending
r4811 exc_traceback=exc_traceback, prefix=prefix, send_email=send_email)
exception-tracker: send exc id headers on failed API calls for tracking errors that server generated.
r4112 return exc_id, exc_type_name
exceptions: added new exception tracking capability....
r2907 except Exception:
log.exception('Failed to store exception `%s` information', exc_id)
# there's no way this can fail, it will crash server badly if it does.
pass
def _find_exc_file(exc_id, prefix=global_prefix):
exc_store_path = get_exc_store()
if prefix:
exc_id = '{}_{}'.format(exc_id, prefix)
else:
# search without a prefix
exc_id = '{}'.format(exc_id)
cache exc store, and use glob.glob for scaning exc store
r3975 found_exc_id = None
matches = glob.glob(os.path.join(exc_store_path, exc_id) + '*')
if matches:
found_exc_id = matches[0]
exceptions: added new exception tracking capability....
r2907
cache exc store, and use glob.glob for scaning exc store
r3975 return found_exc_id
exceptions: added new exception tracking capability....
r2907
def _read_exception(exc_id, prefix):
exc_id_file_path = _find_exc_file(exc_id=exc_id, prefix=prefix)
if exc_id_file_path:
with open(exc_id_file_path, 'rb') as f:
return exc_unserialize(f.read())
else:
log.debug('Exception File `%s` not found', exc_id_file_path)
return None
def read_exception(exc_id, prefix=global_prefix):
try:
return _read_exception(exc_id=exc_id, prefix=prefix)
except Exception:
log.exception('Failed to read exception `%s` information', exc_id)
# there's no way this can fail, it will crash server badly if it does.
return None
def delete_exception(exc_id, prefix=global_prefix):
try:
exc_id_file_path = _find_exc_file(exc_id, prefix=prefix)
if exc_id_file_path:
os.remove(exc_id_file_path)
except Exception:
log.exception('Failed to remove exception `%s` information', exc_id)
# there's no way this can fail, it will crash server badly if it does.
pass
api: added store_exception_api for remote exception storage....
r3317
def generate_id():
return id(object())