:param types.TracebackType|types.FrameType|StackSummary tb: traceback. if None, will use sys._getframe
    :param int|None limit: limit the traceback to this number of frames. by default, will look at sys.tracebacklimit
    :param dict[str]|None allLocals: if set, will update it with all locals from all frames
    :param dict[str]|None allGlobals: if set, will update it with all globals from all frames
    :param bool withTitle:
    :param bool|None with_color: output with ANSI escape codes for color
    :param bool with_vars: will print var content which are referenced in the source code line. by default enabled.
    :return: list of strings (line-based)
    :rtype: list[str]

def format_tb(tb=None, limit=None, allLocals=None, allGlobals=None, withTitle=False, with_color=None, with_vars=None):
    """
    :param types.TracebackType|types.FrameType|StackSummary tb: traceback. if None, will use sys._getframe
    :param int|None limit: limit the traceback to this number of frames. by default, will look at sys.tracebacklimit
    :param dict[str]|None allLocals: if set, will update it with all locals from all frames
    :param dict[str]|None allGlobals: if set, will update it with all globals from all frames
    :param bool withTitle:
    :param bool|None with_color: output with ANSI escape codes for color
    :param bool with_vars: will print var content which are referenced in the source code line. by default enabled.
    :return: list of strings (line-based)
    :rtype: list[str]
    """
    color = Color(enable=with_color)
    output = _Output(color=color)

    def format_filename(s):
        """
        :param str s:
        :rtype: str
        """
        base = os.path.basename(s)
        return (
            color('"' + s[:-len(base)], color.fg_colors[2]) +
            color(base, color.fg_colors[2], bold=True) +
            color('"', color.fg_colors[2]))

    format_py_obj = output.pretty_print
    if tb is None:
        # noinspection PyBroadException
        try:
            tb = get_current_frame()
            assert tb
        except Exception:
            output(color("format_tb: tb is None and sys._getframe() failed", color.fg_colors[1], bold=True))
            return output.lines

    def is_stack_summary(_tb):
        """
        :param StackSummary|object _tb:
        :rtype: bool
        """
        return isinstance(_tb, StackSummary)

    isframe = inspect.isframe
    if withTitle:
        if isframe(tb) or is_stack_summary(tb):
            output(color('Traceback (most recent call first):', color.fg_colors[0]))
        else:  # expect traceback-object (or compatible)
            output(color('Traceback (most recent call last):', color.fg_colors[0]))
    if with_vars is None and is_at_exit():
        # Better to not show __repr__ of some vars, as this might lead to crashes
        # when native extensions are involved.
        with_vars = False
        if withTitle:
            output("(Exclude vars because we are exiting.)")
    if with_vars is None:
        if any([f.f_code.co_name == "__del__" for f in iter_traceback()]):
            # __del__ is usually called via the Python garbage collector (GC).
            # This can happen and very random / non-deterministic places.
            # There are cases where it is not safe to access some of the vars on the stack
            # because they might be in a non-well-defined state, thus calling their __repr__ is not safe.
            # See e.g. this bug:
            # https://github.com/tensorflow/tensorflow/issues/22770
            with_vars = False
            if withTitle:
                output("(Exclude vars because we are on a GC stack.)")
    if with_vars is None:
        with_vars = True
    # noinspection PyBroadException
    try:
        if limit is None:
            if hasattr(sys, 'tracebacklimit'):
                limit = sys.tracebacklimit
        n = 0
        _tb = tb

        class NotFound(Exception):
            """
            Identifier not found.
            """

        def _resolve_identifier(namespace, keys):
            """
            :param dict[str] namespace:
            :param tuple[str] keys:
            :return: namespace[name[0]][name[1]]...
            """
            if keys[0] not in namespace:
                raise NotFound()
            obj = namespace[keys[0]]
            for part in keys[1:]:
                obj = getattr(obj, part)
            return obj

        # noinspection PyShadowingNames
        def _try_set(old, prefix, func):
            """
            :param None|str old:
            :param str prefix:
            :param func:
            :return: old
            """
            if old is not None:
                return old
            try:
                return add_indent_lines(prefix, func())
            except NotFound:
                return old
            except Exception as e:
                return prefix + "!" + e.__class__.__name__ + ": " + str(e)

        while _tb is not None and (limit is None or n < limit):
            if isframe(_tb):
                f = _tb
            elif is_stack_summary(_tb):
                if isinstance(_tb[0], ExtendedFrameSummary):
                    f = _tb[0].tb_frame
                else:
                    f = DummyFrame.from_frame_summary(_tb[0])
            else:
                f = _tb.tb_frame
            if allLocals is not None:
                allLocals.update(f.f_locals)
            if allGlobals is not None:
                allGlobals.update(f.f_globals)
            if hasattr(_tb, "tb_lineno"):
                lineno = _tb.tb_lineno
            elif is_stack_summary(_tb):
                lineno = _tb[0].lineno
            else:
                lineno = f.f_lineno
            co = f.f_code
            filename = co.co_filename
            name = co.co_name
            file_descr = "".join([
                '  ',
                color("File ", color.fg_colors[0], bold=True), format_filename(filename), ", ",
                color("line ", color.fg_colors[0]), color("%d" % lineno, color.fg_colors[4]), ", ",
                color("in ", color.fg_colors[0]), name])
            with output.fold_text_ctx(file_descr):
                if not os.path.isfile(filename):
                    alt_fn = fallback_findfile(filename)
                    if alt_fn:
                        output(
                            color("    -- couldn't find file, trying this instead: ", color.fg_colors[0]) +
                            format_filename(alt_fn))
                        filename = alt_fn
                source_code = get_source_code(filename, lineno, f.f_globals)
                if source_code:
                    source_code = remove_indent_lines(replace_tab_indents(source_code)).rstrip()
                    output("    line: ", color.py_syntax_highlight(source_code), color=color.fg_colors[0])
                    if not with_vars:
                        pass
                    elif isinstance(f, DummyFrame) and not f.have_vars_available:
                        pass
                    else:
                        with output.fold_text_ctx(color('    locals:', color.fg_colors[0])):
                            already_printed_locals = set()  # type: typing.Set[typing.Tuple[str,...]]
                            for token_str in grep_full_py_identifiers(parse_py_statement(source_code)):
                                splitted_token = tuple(token_str.split("."))
                                for token in [splitted_token[0:i] for i in range(1, len(splitted_token) + 1)]:
                                    if token in already_printed_locals:
                                        continue
                                    token_value = None
                                    token_value = _try_set(
                                        token_value, color("<local> ", color.fg_colors[0]),
                                        lambda: format_py_obj(_resolve_identifier(f.f_locals, token)))
                                    token_value = _try_set(
                                        token_value, color("<global> ", color.fg_colors[0]),
                                        lambda: format_py_obj(_resolve_identifier(f.f_globals, token)))
                                    token_value = _try_set(
                                        token_value, color("<builtin> ", color.fg_colors[0]),
                                        lambda: format_py_obj(_resolve_identifier(f.f_builtins, token)))
                                    token_value = token_value or color("<not found>", color.fg_colors[0])
                                    prefix = (
                                        '      %s ' % color(".", color.fg_colors[0], bold=True).join(token) +
                                        color("= ", color.fg_colors[0], bold=True))
                                    output(prefix, token_value)
                                    already_printed_locals.add(token)
                            if len(already_printed_locals) == 0:
                                output(color("       no locals", color.fg_colors[0]))
                else:
                    output(color('    -- code not available --', color.fg_colors[0]))
            if isframe(_tb):
                _tb = _tb.f_back
            elif is_stack_summary(_tb):
                _tb = StackSummary.from_list(_tb[1:])
                if not _tb:
                    _tb = None
            else:
                _tb = _tb.tb_next
            n += 1

    except Exception:
        output(color("ERROR: cannot get more detailed exception info because:", color.fg_colors[1], bold=True))
        import traceback
        for l in traceback.format_exc().split("\n"):
            output("   " + l)

    return output.lines