# -*- coding: UTF-8 -*- # # Copyright (C) 2020, Team Kodi # # 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, see . # pylint: disable=missing-docstring # # This is based on the metadata.tvmaze scrapper by Roman Miroshnychenko aka Roman V.M. """ Provides a context manager that writes extended debugging info in the Kodi log on unhandled exceptions """ from __future__ import absolute_import, unicode_literals import inspect from contextlib import contextmanager from platform import uname from pprint import pformat import xbmc from .utils import logger try: from typing import Text, Generator, Callable, Dict, Any # pylint: disable=unused-import except ImportError: pass def _format_vars(variables): # type: (Dict[Text, Any]) -> Text """ Format variables dictionary :param variables: variables dict :type variables: dict :return: formatted string with sorted ``var = val`` pairs :rtype: str """ var_list = [(var, val) for var, val in variables.items() if not (var.startswith('__') or var.endswith('__'))] var_list.sort(key=lambda i: i[0]) lines = [] for var, val in var_list: lines.append('{0} = {1}'.format(var, pformat(val))) return '\n'.join(lines) @contextmanager def debug_exception(logger_func=logger.error): # type: (Callable[[Text], None]) -> Generator[None] """ Diagnostic helper context manager It controls execution within its context and writes extended diagnostic info to the Kodi log if an unhandled exception happens within the context. The info includes the following items: - System info - Kodi version - Module path. - Code fragment where the exception has happened. - Global variables. - Local variables. After logging the diagnostic info the exception is re-raised. Example:: with debug_exception(): # Some risky code raise RuntimeError('Fatal error!') :param logger_func: logger function which must accept a single argument which is a log message. """ try: yield except Exception as exc: frame_info = inspect.trace(5)[-1] logger_func( '*** Unhandled exception detected: {} {} ***'.format(type(exc), exc)) logger_func('*** Start diagnostic info ***') logger_func('System info: {0}'.format(uname())) logger_func('OS info: {0}'.format( xbmc.getInfoLabel('System.OSVersionInfo'))) logger_func('Kodi version: {0}'.format( xbmc.getInfoLabel('System.BuildVersion'))) logger_func('File: {0}'.format(frame_info[1])) context = '' if frame_info[4] is not None: for i, line in enumerate(frame_info[4], frame_info[2] - frame_info[5]): if i == frame_info[2]: context += '{0}:>{1}'.format(str(i).rjust(5), line) else: context += '{0}: {1}'.format(str(i).rjust(5), line) logger_func('Code context:\n' + context) logger_func('Global variables:\n' + _format_vars(frame_info[0].f_globals)) logger_func('Local variables:\n' + _format_vars(frame_info[0].f_locals)) logger_func('**** End diagnostic info ****') raise exc