179 lines
4.6 KiB
Python
179 lines
4.6 KiB
Python
"""WizardKit: Debug Functions"""
|
|
# vim: sts=2 sw=2 ts=2
|
|
|
|
import inspect
|
|
import logging
|
|
import lzma
|
|
import os
|
|
import pickle
|
|
import platform
|
|
import re
|
|
import socket
|
|
import sys
|
|
import time
|
|
|
|
import requests
|
|
|
|
from wk.cfg.net import CRASH_SERVER
|
|
from wk.log import get_root_logger_path
|
|
|
|
# Classes
|
|
class Debug():
|
|
"""Object used when dumping debug data."""
|
|
def method(self):
|
|
"""Dummy method used to identify functions vs data."""
|
|
|
|
|
|
# STATIC VARIABLES
|
|
LOG = logging.getLogger(__name__)
|
|
DEBUG_CLASS = Debug()
|
|
METHOD_TYPE = type(DEBUG_CLASS.method)
|
|
|
|
|
|
# Functions
|
|
def generate_debug_report():
|
|
"""Generate debug report, returns str."""
|
|
platform_function_list = (
|
|
'architecture',
|
|
'machine',
|
|
'platform',
|
|
'python_version',
|
|
)
|
|
report = []
|
|
|
|
# Logging data
|
|
try:
|
|
log_path = get_root_logger_path()
|
|
except RuntimeError:
|
|
# Assuming logging wasn't started
|
|
pass
|
|
else:
|
|
report.append('------ Start Log -------')
|
|
report.append('')
|
|
with open(log_path, 'r', encoding='utf-8') as log_file:
|
|
report.extend(log_file.read().splitlines())
|
|
report.append('')
|
|
report.append('------- End Log --------')
|
|
|
|
# System
|
|
report.append('--- Start debug info ---')
|
|
report.append('')
|
|
report.append('[System]')
|
|
report.append(f' {"FQDN":<24} {socket.getfqdn()}')
|
|
for func in platform_function_list:
|
|
func_name = func.replace('_', ' ').capitalize()
|
|
func_result = getattr(platform, func)()
|
|
report.append(f' {func_name:<24} {func_result}')
|
|
report.append(f' {"Python sys.argv":<24} {sys.argv}')
|
|
report.append('')
|
|
|
|
# Environment
|
|
report.append('[Environment Variables]')
|
|
for key, value in sorted(os.environ.items()):
|
|
report.append(f' {key:<24} {value}')
|
|
report.append('')
|
|
|
|
# Done
|
|
report.append('---- End debug info ----')
|
|
return '\n'.join(report)
|
|
|
|
|
|
def generate_object_report(obj, indent=0):
|
|
"""Generate debug report for obj, returns list."""
|
|
report = []
|
|
attr_list = []
|
|
|
|
# Get attribute list
|
|
if hasattr(obj, '__slots__'):
|
|
attr_list = list(obj.__slots__)
|
|
else:
|
|
attr_list = [name for name in dir(obj) if not name.startswith('_')]
|
|
|
|
# Dump object data
|
|
for name in attr_list:
|
|
attr = getattr(obj, name)
|
|
|
|
# Skip methods
|
|
if isinstance(attr, METHOD_TYPE):
|
|
continue
|
|
|
|
# Add attribute to report (expanded if necessary)
|
|
if isinstance(attr, dict):
|
|
report.append(f'{name}:')
|
|
for key, value in attr.items():
|
|
report.append(f'{" "*(indent+1)}{key}: {str(value)}')
|
|
else:
|
|
report.append(f'{" "*indent}{name}: {str(attr)}')
|
|
|
|
# Done
|
|
return report
|
|
|
|
|
|
def save_pickles(obj_dict, out_path=None):
|
|
"""Save dict of objects using pickle."""
|
|
LOG.info('Saving pickles')
|
|
|
|
# Set path
|
|
if not out_path:
|
|
out_path = get_root_logger_path()
|
|
out_path = out_path.parent.joinpath('../debug').resolve()
|
|
|
|
# Save pickles
|
|
try:
|
|
for name, obj in obj_dict.copy().items():
|
|
if name.startswith('__') or inspect.ismodule(obj):
|
|
continue
|
|
with open(f'{out_path}/{name}.pickle', 'wb') as _f:
|
|
pickle.dump(obj, _f, protocol=pickle.HIGHEST_PROTOCOL)
|
|
except Exception:
|
|
LOG.error('Failed to save all the pickles', exc_info=True)
|
|
|
|
|
|
def upload_debug_report(report, compress=True, reason='DEBUG'):
|
|
"""Upload debug report to CRASH_SERVER as specified in wk.cfg.main."""
|
|
LOG.info('Uploading debug report to %s', CRASH_SERVER.get('Name', '?'))
|
|
headers = CRASH_SERVER.get('Headers', {'X-Requested-With': 'XMLHttpRequest'})
|
|
if compress:
|
|
headers['Content-Type'] = 'application/octet-stream'
|
|
|
|
# Check if the required server details are available
|
|
if not all(CRASH_SERVER.get(key, False) for key in ('Name', 'Url', 'User')):
|
|
msg = 'Server details missing, aborting upload.'
|
|
print(msg)
|
|
raise RuntimeError(msg)
|
|
|
|
# Set filename (based on the logging config if possible)
|
|
filename = 'Unknown'
|
|
try:
|
|
log_path = get_root_logger_path()
|
|
except RuntimeError:
|
|
# Assuming logging wasn't started
|
|
pass
|
|
else:
|
|
# Strip everything but the prefix
|
|
filename = re.sub(r'^(.*)_(\d{4}-\d{2}-\d{2}.*)', r'\1', log_path.name)
|
|
filename = f'{filename}_{reason}_{time.strftime("%Y-%m-%d_%H%M%S%z")}.log'
|
|
LOG.debug('filename: %s', filename)
|
|
|
|
# Compress report
|
|
if compress:
|
|
filename += '.xz'
|
|
xz_report = lzma.compress(report.encode('utf8'))
|
|
|
|
# Upload report
|
|
url = f'{CRASH_SERVER["Url"]}/{filename}'
|
|
response = requests.put(
|
|
url,
|
|
auth=(CRASH_SERVER['User'], CRASH_SERVER.get('Pass', '')),
|
|
data=xz_report if compress else report,
|
|
headers=headers,
|
|
timeout=60,
|
|
)
|
|
|
|
# Check response
|
|
if not response.ok:
|
|
raise RuntimeError('Failed to upload report')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
print("This file is not meant to be called directly.")
|