##// END OF EJS Templates
core: moved str_utils and type_utils to lib so it's consistent with ce, and easier to sync up codebases
core: moved str_utils and type_utils to lib so it's consistent with ce, and easier to sync up codebases

File last commit:

r1166:020860b6 default
r1249:5745b11f default
Show More
exc_tracking.py
273 lines | 7.8 KiB | text/x-python | PythonLexer
exceptions: allow error tracking and exception storage on vcsserver.
r491 # RhodeCode VCSServer provides access to different vcs backends via network.
source-code: updated copyrights to 2023
r1126 # Copyright (C) 2014-2023 RhodeCode GmbH
exceptions: allow error tracking and exception storage on vcsserver.
r491 #
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# 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 General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
exc-tracking: synced with ce
r1146 import io
exceptions: allow error tracking and exception storage on vcsserver.
r491 import os
import time
exc-tracking: use more rich style tracebacks.
r1144 import sys
exceptions: allow error tracking and exception storage on vcsserver.
r491 import datetime
import msgpack
import logging
import traceback
import tempfile
exc-tracking: use more rich style tracebacks.
r1144 import glob
exceptions: allow error tracking and exception storage on vcsserver.
r491
log = logging.getLogger(__name__)
# NOTE: Any changes should be synced with exc_tracking at rhodecode.lib.exc_tracking
global_prefix = 'vcsserver'
exc_store: allow to specify a custom path for exception store.
r519 exc_store_dir_name = 'rc_exception_store_v1'
exceptions: allow error tracking and exception storage on vcsserver.
r491
exc-tracking: use more rich style tracebacks.
r1144 def exc_serialize(exc_id, tb, exc_type, extra_data=None):
exceptions: allow error tracking and exception storage on vcsserver.
r491 data = {
exc-tracking: synced with ce
r1146 "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,
exceptions: allow error tracking and exception storage on vcsserver.
r491 }
exc-tracking: use more rich style tracebacks.
r1144 if extra_data:
data.update(extra_data)
exceptions: allow error tracking and exception storage on vcsserver.
r491 return msgpack.packb(data), data
def exc_unserialize(tb):
return msgpack.unpackb(tb)
exc-tracking: use more rich style tracebacks.
r1144 _exc_store = None
exceptions: allow error tracking and exception storage on vcsserver.
r491 def get_exc_store():
"""
Get and create exception store if it's not existing
"""
exc-tracking: use more rich style tracebacks.
r1144 global _exc_store
if _exc_store is not None:
# quick global cache
return _exc_store
exc_store: allow to specify a custom path for exception store.
r519 import vcsserver as app
exceptions: allow error tracking and exception storage on vcsserver.
r491
exc-tracking: synced with ce
r1146 exc_store_dir = (
app.CONFIG.get("exception_tracker.store_path", "") or tempfile.gettempdir()
)
exc_store: allow to specify a custom path for exception store.
r519 _exc_store_path = os.path.join(exc_store_dir, exc_store_dir_name)
exceptions: allow error tracking and exception storage on vcsserver.
r491
_exc_store_path = os.path.abspath(_exc_store_path)
if not os.path.isdir(_exc_store_path):
os.makedirs(_exc_store_path)
exc-tracking: synced with ce
r1146 log.debug("Initializing exceptions store at %s", _exc_store_path)
exc-tracking: use more rich style tracebacks.
r1144 _exc_store = _exc_store_path
exceptions: allow error tracking and exception storage on vcsserver.
r491 return _exc_store_path
exc-tracking: use more rich style tracebacks.
r1144 def get_detailed_tb(exc_info):
try:
exc-tracking: synced with ce
r1146 from pip._vendor.rich import (
traceback as rich_tb,
scope as rich_scope,
console as rich_console,
)
exc-tracking: use more rich style tracebacks.
r1144 except ImportError:
try:
exc-tracking: synced with ce
r1146 from rich import (
traceback as rich_tb,
scope as rich_scope,
console as rich_console,
)
exc-tracking: use more rich style tracebacks.
r1144 except ImportError:
return None
exc-tracking: synced with ce
r1146 console = rich_console.Console(width=160, file=io.StringIO())
exc-tracking: use more rich style tracebacks.
r1144
exc = rich_tb.Traceback.extract(*exc_info, show_locals=True)
tb_rich = rich_tb.Traceback(
trace=exc,
width=160,
extra_lines=3,
theme=None,
word_wrap=False,
show_locals=False,
exc-tracking: synced with ce
r1146 max_frames=100,
exc-tracking: use more rich style tracebacks.
r1144 )
# last_stack = exc.stacks[-1]
# last_frame = last_stack.frames[-1]
# if last_frame and last_frame.locals:
# console.print(
# rich_scope.render_scope(
# last_frame.locals,
# title=f'{last_frame.filename}:{last_frame.lineno}'))
console.print(tb_rich)
formatted_locals = console.file.getvalue()
return formatted_locals
exceptions: allow error tracking and exception storage on vcsserver.
r491
exc-tracking: use more rich style tracebacks.
r1144 def get_request_metadata(request=None) -> dict:
request_metadata = {}
if not request:
from pyramid.threadlocal import get_current_request
exc-tracking: synced with ce
r1146
exc-tracking: use more rich style tracebacks.
r1144 request = get_current_request()
dan
exc-store: add extra details for stored exceptions for vcsserver
r661
exc-tracking: use more rich style tracebacks.
r1144 # NOTE(marcink): store request information into exc_data
if request:
exc-tracking: synced with ce
r1146 request_metadata["client_address"] = getattr(request, "client_addr", "")
request_metadata["user_agent"] = getattr(request, "user_agent", "")
request_metadata["method"] = getattr(request, "method", "")
request_metadata["url"] = getattr(request, "url", "")
exc-tracking: use more rich style tracebacks.
r1144 return request_metadata
exc-tracking: synced with CE code
r1166 def format_exc(exc_info, use_detailed_tb=True):
exc-tracking: use more rich style tracebacks.
r1144 exc_type, exc_value, exc_traceback = exc_info
tb = "++ TRACEBACK ++\n\n"
tb += "".join(traceback.format_exception(exc_type, exc_value, exc_traceback, None))
exc-tracking: synced with CE code
r1166
exc-tracking: use more rich style tracebacks.
r1144 detailed_tb = getattr(exc_value, "_org_exc_tb", None)
exc-tracking: synced with CE code
r1166
dan
exc-store: add extra details for stored exceptions for vcsserver
r661 if detailed_tb:
ruff: code-cleanups
r1100 remote_tb = detailed_tb
py3: remove compat module usage
r1038 if isinstance(detailed_tb, str):
dan
exc-store: add extra details for stored exceptions for vcsserver
r661 remote_tb = [detailed_tb]
exc-tracking: synced with CE code
r1166
dan
exc-store: add extra details for stored exceptions for vcsserver
r661 tb += (
exc-tracking: use more rich style tracebacks.
r1144 "\n+++ BEG SOURCE EXCEPTION +++\n\n"
"{}\n"
"+++ END SOURCE EXCEPTION +++\n"
"".format("\n".join(remote_tb))
dan
exc-store: add extra details for stored exceptions for vcsserver
r661 )
exc-tracking: synced with CE code
r1166
dan
exc-store: add extra details for stored exceptions for vcsserver
r661 # Avoid that remote_tb also appears in the frame
del remote_tb
exc-tracking: synced with CE code
r1166
if use_detailed_tb:
locals_tb = get_detailed_tb(exc_info)
if locals_tb:
tb += f"\n+++ DETAILS +++\n\n{locals_tb}\n" ""
exc-tracking: use more rich style tracebacks.
r1144 return tb
def _store_exception(exc_id, exc_info, prefix, request_path=''):
"""
Low level function to store exception in the exception tracker
"""
extra_data = {}
extra_data.update(get_request_metadata())
exc_type, exc_value, exc_traceback = exc_info
tb = format_exc(exc_info)
dan
exc-store: add extra details for stored exceptions for vcsserver
r661
exceptions: allow error tracking and exception storage on vcsserver.
r491 exc_type_name = exc_type.__name__
exc-tracking: use more rich style tracebacks.
r1144 exc_data, org_data = exc_serialize(exc_id, tb, exc_type_name, extra_data=extra_data)
exc-tracking: synced with ce
r1146 exc_pref_id = f"{exc_id}_{prefix}_{org_data['exc_timestamp']}"
exceptions: allow error tracking and exception storage on vcsserver.
r491 exc_store_path = get_exc_store()
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)
exc-tracking: synced with ce
r1146 with open(stored_exc_path, "wb") as f:
exceptions: allow error tracking and exception storage on vcsserver.
r491 f.write(exc_data)
exc-tracking: synced with ce
r1146 log.debug("Stored generated exception %s as: %s", exc_id, stored_exc_path)
exceptions: allow error tracking and exception storage on vcsserver.
r491
exc-tracking: synced with ce
r1146 if request_path:
log.error(
'error occurred handling this request.\n'
'Path: `%s`, %s',
request_path, tb)
exceptions: allow error tracking and exception storage on vcsserver.
r491
vcsserver: log exceptions into the logs
r939
def store_exception(exc_id, exc_info, prefix=global_prefix, request_path=''):
exc_store: allow to specify a custom path for exception store.
r519 """
Example usage::
exc_info = sys.exc_info()
store_exception(id(exc_info), exc_info)
"""
exceptions: allow error tracking and exception storage on vcsserver.
r491 try:
exc-tracking: use more rich style tracebacks.
r1144 exc_type = exc_info[0]
exc_type_name = exc_type.__name__
exc-tracking: synced with ce
r1146 _store_exception(
exc_id=exc_id, exc_info=exc_info, prefix=prefix, request_path=request_path,
)
exc-tracking: use more rich style tracebacks.
r1144 return exc_id, exc_type_name
exceptions: allow error tracking and exception storage on vcsserver.
r491 except Exception:
exc-tracking: synced with ce
r1146 log.exception("Failed to store exception `%s` information", exc_id)
exceptions: allow error tracking and exception storage on vcsserver.
r491 # 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-tracking: synced with ce
r1146 exc_id = f"{exc_id}_{prefix}"
exceptions: allow error tracking and exception storage on vcsserver.
r491 else:
# search without a prefix
exc-tracking: synced with ce
r1146 exc_id = f"{exc_id}"
exceptions: allow error tracking and exception storage on vcsserver.
r491
exc-tracking: use more rich style tracebacks.
r1144 found_exc_id = None
exc-tracking: synced with ce
r1146 matches = glob.glob(os.path.join(exc_store_path, exc_id) + "*")
exc-tracking: use more rich style tracebacks.
r1144 if matches:
found_exc_id = matches[0]
exceptions: allow error tracking and exception storage on vcsserver.
r491
exc-tracking: use more rich style tracebacks.
r1144 return found_exc_id
exceptions: allow error tracking and exception storage on vcsserver.
r491
def _read_exception(exc_id, prefix):
exc_id_file_path = _find_exc_file(exc_id=exc_id, prefix=prefix)
if exc_id_file_path:
exc-tracking: synced with ce
r1146 with open(exc_id_file_path, "rb") as f:
exceptions: allow error tracking and exception storage on vcsserver.
r491 return exc_unserialize(f.read())
else:
exc-tracking: synced with ce
r1146 log.debug("Exception File `%s` not found", exc_id_file_path)
exceptions: allow error tracking and exception storage on vcsserver.
r491 return None
def read_exception(exc_id, prefix=global_prefix):
try:
return _read_exception(exc_id=exc_id, prefix=prefix)
except Exception:
exc-tracking: synced with ce
r1146 log.exception("Failed to read exception `%s` information", exc_id)
exceptions: allow error tracking and exception storage on vcsserver.
r491 # 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:
exc-tracking: synced with ce
r1146 log.exception("Failed to remove exception `%s` information", exc_id)
exceptions: allow error tracking and exception storage on vcsserver.
r491 # there's no way this can fail, it will crash server badly if it does.
pass
exc-tracking: use more rich style tracebacks.
r1144
def generate_id():
return id(object())