1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
|
# -*- 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 <https://www.gnu.org/licenses/>.
# 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
|