#include #include #include "utils/optional.hpp" #include "utils/utils.hpp" #include "snippets/snippet.hpp" #include #include #include #include #include #include namespace cpptrace { class formatter::impl { struct { std::string header = "Stack trace (most recent call first):"; color_mode color = color_mode::automatic; address_mode addresses = address_mode::raw; path_mode paths = path_mode::full; bool snippets = false; int context_lines = 2; bool columns = true; bool show_filtered_frames = true; std::function filter; } options; public: void header(std::string header) { options.header = std::move(header); } void colors(formatter::color_mode mode) { options.color = mode; } void addresses(formatter::address_mode mode) { options.addresses = mode; } void paths(path_mode mode) { options.paths = mode; } void snippets(bool snippets) { options.snippets = snippets; } void snippet_context(int lines) { options.context_lines = lines; } void columns(bool columns) { options.columns = columns; } void filtered_frame_placeholders(bool show) { options.show_filtered_frames = show; } void filter(std::function filter) { options.filter = filter; } std::string format(const stacktrace_frame& frame, detail::optional color_override = detail::nullopt) const { std::ostringstream oss; print_frame_inner(oss, frame, color_override.value_or(options.color == color_mode::always)); return std::move(oss).str(); } std::string format(const stacktrace& trace, detail::optional color_override = detail::nullopt) const { std::ostringstream oss; print_internal(oss, trace, false, color_override); return std::move(oss).str(); } void print(const stacktrace_frame& frame, detail::optional color_override = detail::nullopt) const { print(std::cout, frame, color_override); } void print( std::ostream& stream, const stacktrace_frame& frame, detail::optional color_override = detail::nullopt ) const { print_frame_internal(stream, frame, color_override); } void print( std::FILE* file, const stacktrace_frame& frame, detail::optional color_override = detail::nullopt ) const { auto str = format(frame, color_override); std::fwrite(str.data(), 1, str.size(), file); } void print(const stacktrace& trace, detail::optional color_override = detail::nullopt) const { print(std::cout, trace, color_override); } void print( std::ostream& stream, const stacktrace& trace, detail::optional color_override = detail::nullopt ) const { print_internal(stream, trace, true, color_override); } void print( std::FILE* file, const stacktrace& trace, detail::optional color_override = detail::nullopt ) const { auto str = format(trace, color_override); std::fwrite(str.data(), 1, str.size(), file); } private: bool stream_is_tty(std::ostream& stream) const { // not great, but it'll have to do return (&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno)); } void maybe_ensure_virtual_terminal_processing(std::ostream& stream, bool color) const { if(color && stream_is_tty(stream)) { detail::enable_virtual_terminal_processing_if_needed(); } } bool should_do_color(std::ostream& stream, detail::optional color_override) const { bool do_color = options.color == color_mode::always || color_override.value_or(false); if( (options.color == color_mode::automatic || options.color == color_mode::always) && (!color_override || color_override.unwrap() != false) && stream_is_tty(stream) ) { detail::enable_virtual_terminal_processing_if_needed(); do_color = true; } return do_color; } void print_internal(std::ostream& stream, const stacktrace& trace, bool newline_at_end, detail::optional color_override) const { bool do_color = should_do_color(stream, color_override); maybe_ensure_virtual_terminal_processing(stream, do_color); print_internal(stream, trace, newline_at_end, do_color); } void print_internal(std::ostream& stream, const stacktrace& trace, bool newline_at_end, bool color) const { if(!options.header.empty()) { stream << options.header << '\n'; } std::size_t counter = 0; const auto& frames = trace.frames; if(frames.empty()) { stream << "\n"; return; } const auto frame_number_width = detail::n_digits(static_cast(frames.size()) - 1); for(const auto& frame : frames) { if(options.filter && !options.filter(frame)) { if(!options.show_filtered_frames) { counter++; continue; } print_placeholder_frame(stream, frame_number_width, counter); } else { print_frame_internal(stream, frame, color, frame_number_width, counter); if(frame.line.has_value() && !frame.filename.empty() && options.snippets) { auto snippet = detail::get_snippet( frame.filename, frame.line.value(), options.context_lines, color ); if(!snippet.empty()) { stream << '\n'; stream << snippet; } } } if(newline_at_end || &frame != &frames.back()) { stream << '\n'; } counter++; } } void print_frame_internal( std::ostream& stream, const stacktrace_frame& frame, bool color, unsigned frame_number_width, std::size_t counter ) const { microfmt::print(stream, "#{<{}} ", frame_number_width, counter); print_frame_inner(stream, frame, color); } void print_placeholder_frame(std::ostream& stream, unsigned frame_number_width, std::size_t counter) const { microfmt::print(stream, "#{<{}} (filtered)", frame_number_width, counter); } void print_frame_internal( std::ostream& stream, const stacktrace_frame& frame, detail::optional color_override ) const { bool do_color = should_do_color(stream, color_override); maybe_ensure_virtual_terminal_processing(stream, do_color); print_frame_inner(stream, frame, do_color); } void print_frame_inner(std::ostream& stream, const stacktrace_frame& frame, bool color) const { const auto reset = color ? RESET : ""; const auto green = color ? GREEN : ""; const auto yellow = color ? YELLOW : ""; const auto blue = color ? BLUE : ""; if(frame.is_inline) { microfmt::print(stream, "{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)"); } else { auto address = options.addresses == address_mode::raw ? frame.raw_address : frame.object_address; microfmt::print(stream, "{}0x{>{}:0h}{}", blue, 2 * sizeof(frame_ptr), address, reset); } if(!frame.symbol.empty()) { microfmt::print(stream, " in {}{}{}", yellow, frame.symbol, reset); } if(!frame.filename.empty()) { microfmt::print( stream, " at {}{}{}", green, options.paths == path_mode::full ? frame.filename : detail::basename(frame.filename, true), reset ); if(frame.line.has_value()) { microfmt::print(stream, ":{}{}{}", blue, frame.line.value(), reset); if(frame.column.has_value() && options.columns) { microfmt::print(stream, ":{}{}{}", blue, frame.column.value(), reset); } } } } }; formatter::formatter() : pimpl(new impl) {} formatter::~formatter() { delete pimpl; } formatter::formatter(formatter&& other) : pimpl(detail::exchange(other.pimpl, nullptr)) {} formatter::formatter(const formatter& other) : pimpl(new impl(*other.pimpl)) {} formatter& formatter::operator=(formatter&& other) { if(pimpl) { delete pimpl; } pimpl = detail::exchange(other.pimpl, nullptr); return *this; } formatter& formatter::operator=(const formatter& other) { if(pimpl) { delete pimpl; } pimpl = new impl(*other.pimpl); return *this; } formatter& formatter::header(std::string header) { pimpl->header(std::move(header)); return *this; } formatter& formatter::colors(color_mode mode) { pimpl->colors(mode); return *this; } formatter& formatter::addresses(address_mode mode) { pimpl->addresses(mode); return *this; } formatter& formatter::paths(path_mode mode) { pimpl->paths(mode); return *this; } formatter& formatter::snippets(bool snippets) { pimpl->snippets(snippets); return *this; } formatter& formatter::snippet_context(int lines) { pimpl->snippet_context(lines); return *this; } formatter& formatter::columns(bool columns) { pimpl->columns(columns); return *this; } formatter& formatter::filtered_frame_placeholders(bool show) { pimpl->filtered_frame_placeholders(show); return *this; } formatter& formatter::filter(std::function filter) { pimpl->filter(std::move(filter)); return *this; } std::string formatter::format(const stacktrace_frame& frame) const { return pimpl->format(frame); } std::string formatter::format(const stacktrace_frame& frame, bool color) const { return pimpl->format(frame, color); } std::string formatter::format(const stacktrace& trace) const { return pimpl->format(trace); } std::string formatter::format(const stacktrace& trace, bool color) const { return pimpl->format(trace, color); } void formatter::print(const stacktrace& trace) const { pimpl->print(trace); } void formatter::print(const stacktrace& trace, bool color) const { pimpl->print(trace, color); } void formatter::print(std::ostream& stream, const stacktrace& trace) const { pimpl->print(stream, trace); } void formatter::print(std::ostream& stream, const stacktrace& trace, bool color) const { pimpl->print(stream, trace, color); } void formatter::print(std::FILE* file, const stacktrace& trace) const { pimpl->print(file, trace); } void formatter::print(std::FILE* file, const stacktrace& trace, bool color) const { pimpl->print(file, trace, color); } void formatter::print(const stacktrace_frame& frame) const { pimpl->print(frame); } void formatter::print(const stacktrace_frame& frame, bool color) const { pimpl->print(frame, color); } void formatter::print(std::ostream& stream, const stacktrace_frame& frame) const { pimpl->print(stream, frame); } void formatter::print(std::ostream& stream, const stacktrace_frame& frame, bool color) const { pimpl->print(stream, frame, color); } void formatter::print(std::FILE* file, const stacktrace_frame& frame) const { pimpl->print(file, frame); } void formatter::print(std::FILE* file, const stacktrace_frame& frame, bool color) const { pimpl->print(file, frame, color); } const formatter& get_default_formatter() { static formatter formatter; return formatter; } }