def get_exception_details(): from traceback import walk_tb, StackSummary, FrameSummary from time import strftime cla, exc, exc_traceback = sys.exc_info() exc_args = exc.__dict__["args"] if "args" in exc.__dict__ else "<no args>" ex_title = cla.__name__ + ": Exception:" + str(exc) + " - args:" + str( exc_args) msgs = [ ex_title, ] except_location = "" tb: list[FrameSummary] = StackSummary.extract(walk_tb(exc_traceback), limit=None, capture_locals=True) for frame in tb: local_vars_info = [] if frame.locals: for name, value in frame.locals.items(): if name == "self": continue local_vars_info.append(f'\t{name} = {value}') except_location += "\n" + frame.filename + ":" + str(frame.lineno) + " \n" + str(frame.name) + \ "\n<Args>:" + "\n".join(local_vars_info) msgs.insert(1, except_location) time = strftime("%Y-%m-%d %H:%M:%S") return time + " - " + ("".join(msgs))
def print_agen_stack(agen: AsyncGenerator, *, file: IO = sys.stderr, limit: int = DEFAULT_MAX_FRAMES, capture_locals: bool = False) -> None: """Print the stack trace for a currently running async generator.""" print(f'Stack for {agen!r} (most recent call last):', file=file) tb = Traceback.from_agen(agen, limit=limit) print_list( StackSummary.extract( cast(Generator, walk_tb(cast(TracebackType, tb))), limit=limit, capture_locals=capture_locals, ), file=file, )
def print_coro_stack(coro: Coroutine, *, file: IO = sys.stderr, limit: int = DEFAULT_MAX_FRAMES, capture_locals: bool = False) -> None: """Print the stack trace for a currently running coroutine.""" print(f'Stack for {coro!r} (most recent call last):', file=file) tb = Traceback.from_coroutine(coro, limit=limit) print_list( StackSummary.extract( cast(Generator, walk_tb(cast(TracebackType, tb))), limit=limit, capture_locals=capture_locals, ), file=file, )
def print_task_stack(task: asyncio.Task, *, file: IO = sys.stderr, limit: int = DEFAULT_MAX_FRAMES, capture_locals: bool = False) -> None: """Print the stack trace for an :class:`asyncio.Task`.""" print(f'Stack for {task!r} (most recent call last):', file=file) tb = Traceback.from_task(task, limit=limit) print_list( StackSummary.extract( cast(Generator, walk_tb(cast(TracebackType, tb))), limit=limit, capture_locals=capture_locals, ), file=file, )
def extract_log_tb(exc=None): tb = ''.join(StackSummary.extract(walk_tb(sys.exc_info()[-1])).format()) if exc.__cause__ is not None and isinstance(exc.__cause__, Traceback): tb = exc.__cause__.tb + tb return tb
def analyze_frame(trace_back, full_context: int = False) -> str: """ Read out variables' content surrounding the error line of code :param trace_back: A traceback object when exception occur :param full_context: Also export local variables that is not in the error line full_context == 0: export only variables that appear on the error line full_context == 1: export variables within the error function's scope full_context >= 2: export variables along the function's call stack up to `full_context` level :return: string of analyzed frame """ result = [] full_context = max(0, int(full_context)) # todo: add color bullet_1 = '|->' bullet_2 = '=>' multi_line_indent1 = 8 + len(bullet_1) multi_line_indent2 = 8 + len(bullet_2) stack_depth = get_traceback_depth(trace_back) with logging_disabled(): for idx, obj in enumerate(walk_tb(trace_back)): frame, _ = obj global_var = frame.f_globals local_var = frame.f_locals summary = StackSummary.extract([obj], capture_locals=True)[0] line = summary.line if not is_full_statement(summary.line): line = get_full_statement(summary.filename, summary.lineno) line = ' '.join(line) txt = [ f' File "{summary.filename}", line {summary.lineno}, in {summary.name}', f' {line}' ] # todo: dump all level to different file? parse_level = max(full_context - 1, 0) if idx + 1 < (stack_depth - parse_level): # don't parse variables for top levels txt.append('') result.append('\n'.join(txt)) continue # get value of variables on the error line identifiers = ID_PATTERN.findall(line) seen = set() outer = "(outer) " if idx else "" # ground level variables are not outer for sure for i in identifiers: if i in seen or i.endswith('.'): continue seen.add(i) spaces = multi_line_indent1 + len(i) if i in local_var: value = get_repr(local_var[i], spaces) txt.append(f' {bullet_1} {i} = {value}') elif i in global_var: spaces += len(outer) value = get_repr(global_var[i], spaces) txt.append(f' {bullet_1} {outer}{i} = {value}') elif '.' in i: # class attribute access spaces += len(outer) instance = i.split('.')[0] obj = local_var.get(instance, global_var.get(instance)) attribute = get_recur_attr(obj, i[len(instance) + 1:]) value = get_repr(attribute, spaces) scope = outer if instance in global_var else '' txt.append(f' {bullet_1} {scope}{i} = {value}') else: # reserved Keyword or non-identifier, eg. word inside the string pass # get value of other variables within local scope if full_context: other_local_var = set(local_var) - set(identifiers) if other_local_var: spaces = multi_line_indent2 txt.extend([ f' {bullet_2} {k} = {get_repr(v, spaces + len(k))}' for k, v in local_var.items() if k in other_local_var ]) txt.append('') result.append('\n'.join(txt)) return '\n'.join(result)