Initial work on trace formatting setup

This commit is contained in:
Jeremy Rifkin 2025-01-31 00:13:23 -06:00
parent 9077430b6a
commit ceb21428cc
No known key found for this signature in database
GPG Key ID: 19AA8270105E8EB4
6 changed files with 382 additions and 107 deletions

View File

@ -113,6 +113,7 @@ target_sources(
src/ctrace.cpp src/ctrace.cpp
src/exceptions.cpp src/exceptions.cpp
src/from_current.cpp src/from_current.cpp
src/formatting.cpp
src/options.cpp src/options.cpp
src/utils.cpp src/utils.cpp
src/demangle/demangle_with_cxxabi.cpp src/demangle/demangle_with_cxxabi.cpp

View File

@ -182,8 +182,6 @@ namespace cpptrace {
inline const_iterator cbegin() const noexcept { return frames.cbegin(); } inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
inline const_iterator cend() const noexcept { return frames.cend(); } inline const_iterator cend() const noexcept { return frames.cend(); }
private: private:
void print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
void print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
friend void print_terminate_trace(); friend void print_terminate_trace();
}; };

View File

@ -0,0 +1,66 @@
#ifndef CPPTRACE_FORMATTING_HPP
#define CPPTRACE_FORMATTING_HPP
#include <cpptrace/basic.hpp>
#include <memory>
#include <string>
#include <functional>
namespace cpptrace {
class CPPTRACE_EXPORT formatter {
class impl;
std::unique_ptr<impl> pimpl;
public:
formatter();
~formatter();
formatter(formatter&&) = default;
formatter(const formatter&);
formatter& operator=(formatter&&) = default;
formatter& operator=(const formatter&);
formatter& set_header(std::string);
enum class color_mode {
always,
none,
automatic,
};
formatter& set_color_mode(color_mode);
enum class address_mode {
raw,
object,
none,
};
formatter& set_address_mode(address_mode);
formatter& set_snippets(bool);
formatter& set_snippet_context(int);
formatter& include_column(bool);
formatter& set_filter(std::function<bool(const stacktrace_frame&)>);
std::string format(const stacktrace_frame&) const;
std::string format(const stacktrace_frame&, bool color) const;
std::string format(const stacktrace&) const;
std::string format(const stacktrace&, bool color) const;
void print(const stacktrace_frame&) const;
void print(const stacktrace_frame&, bool color) const;
void print(std::ostream&, const stacktrace_frame&) const;
void print(std::ostream&, const stacktrace_frame&, bool color) const;
void print(std::FILE*, const stacktrace_frame&) const;
void print(std::FILE*, const stacktrace_frame&, bool color) const;
void print(const stacktrace&) const;
void print(const stacktrace&, bool color) const;
void print(std::ostream&, const stacktrace&) const;
void print(std::ostream&, const stacktrace&, bool color) const;
void print(std::FILE*, const stacktrace&) const;
void print(std::FILE*, const stacktrace&, bool color) const;
};
CPPTRACE_EXPORT const formatter& get_default_formatter();
}
#endif

View File

@ -1,4 +1,5 @@
#include <cpptrace/cpptrace.hpp> #include <cpptrace/cpptrace.hpp>
#include <cpptrace/formatting.hpp>
#include <cstddef> #include <cstddef>
#include <cstdint> #include <cstdint>
@ -9,6 +10,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "cpptrace/basic.hpp"
#include "symbols/symbols.hpp" #include "symbols/symbols.hpp"
#include "unwind/unwind.hpp" #include "unwind/unwind.hpp"
#include "demangle/demangle.hpp" #include "demangle/demangle.hpp"
@ -129,41 +131,13 @@ namespace cpptrace {
return detail::get_frame_object_info(raw_address); return detail::get_frame_object_info(raw_address);
} }
static std::string frame_to_string(
bool color,
const stacktrace_frame& frame
) {
const auto reset = color ? RESET : "";
const auto green = color ? GREEN : "";
const auto yellow = color ? YELLOW : "";
const auto blue = color ? BLUE : "";
std::string str;
if(frame.is_inline) {
str += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
} else {
str += microfmt::format("{}0x{>{}:0h}{}", blue, 2 * sizeof(frame_ptr), frame.raw_address, reset);
}
if(!frame.symbol.empty()) {
str += microfmt::format(" in {}{}{}", yellow, frame.symbol, reset);
}
if(!frame.filename.empty()) {
str += microfmt::format(" at {}{}{}", green, frame.filename, reset);
if(frame.line.has_value()) {
str += microfmt::format(":{}{}{}", blue, frame.line.value(), reset);
if(frame.column.has_value()) {
str += microfmt::format(":{}{}{}", blue, frame.column.value(), reset);
}
}
}
return str;
}
std::string stacktrace_frame::to_string() const { std::string stacktrace_frame::to_string() const {
return to_string(false); return to_string(false);
} }
std::string stacktrace_frame::to_string(bool color) const { std::string stacktrace_frame::to_string(bool color) const {
return frame_to_string(color, *this); // return frame_to_string(color, *this);
return get_default_formatter().format(*this, color);
} }
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) { std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
@ -195,89 +169,34 @@ namespace cpptrace {
} }
void stacktrace::print() const { void stacktrace::print() const {
print(std::cerr, true); get_default_formatter().print(*this);
} }
void stacktrace::print(std::ostream& stream) const { void stacktrace::print(std::ostream& stream) const {
print(stream, true); get_default_formatter().print(stream, *this);
} }
void stacktrace::print(std::ostream& stream, bool color) const { void stacktrace::print(std::ostream& stream, bool color) const {
print(stream, color, true, nullptr); get_default_formatter().print(stream, *this, color);
} }
static void print_frame( namespace detail {
std::ostream& stream, const formatter& get_default_snippet_formatter() {
bool color, static formatter snippet_formatter = formatter{}.set_snippets(true);
unsigned frame_number_width, return snippet_formatter;
std::size_t counter,
const stacktrace_frame& frame
) {
std::string line = microfmt::format("#{<{}} {}", frame_number_width, counter, frame.to_string(color));
stream << line;
}
void stacktrace::print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
if(
color && (
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
)
) {
detail::enable_virtual_terminal_processing_if_needed();
}
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
std::size_t counter = 0;
if(frames.empty()) {
stream << "<empty trace>\n";
return;
}
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
print_frame(stream, color, frame_number_width, counter, frame);
if(newline_at_end || &frame != &frames.back()) {
stream << '\n';
}
counter++;
} }
} }
void stacktrace::print_with_snippets() const { void stacktrace::print_with_snippets() const {
print_with_snippets(std::cerr, true); detail::get_default_snippet_formatter().print(*this);
} }
void stacktrace::print_with_snippets(std::ostream& stream) const { void stacktrace::print_with_snippets(std::ostream& stream) const {
print_with_snippets(stream, true); detail::get_default_snippet_formatter().print(stream, *this);
} }
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const { void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
print_with_snippets(stream, color, true, nullptr); detail::get_default_snippet_formatter().print(stream, *this, color);
}
void stacktrace::print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
if(
color && (
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
)
) {
detail::enable_virtual_terminal_processing_if_needed();
}
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
std::size_t counter = 0;
if(frames.empty()) {
stream << "<empty trace>" << '\n';
return;
}
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
print_frame(stream, color, frame_number_width, counter, frame);
if(newline_at_end || &frame != &frames.back()) {
stream << '\n';
}
if(frame.line.has_value() && !frame.filename.empty()) {
stream << detail::get_snippet(frame.filename, frame.line.value(), 2, color);
}
counter++;
}
} }
void stacktrace::clear() { void stacktrace::clear() {
@ -289,13 +208,12 @@ namespace cpptrace {
} }
std::string stacktrace::to_string(bool color) const { std::string stacktrace::to_string(bool color) const {
std::ostringstream oss; return get_default_formatter().format(*this, color);
print(oss, color, false, nullptr);
return std::move(oss).str();
} }
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) { std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
return stream << trace.to_string(); get_default_formatter().print(stream, trace);
return stream;
} }
CPPTRACE_FORCE_NO_INLINE CPPTRACE_FORCE_NO_INLINE

294
src/formatting.cpp Normal file
View File

@ -0,0 +1,294 @@
#include <cpptrace/formatting.hpp>
#include <cpptrace/utils.hpp>
#include "utils/optional.hpp"
#include "utils/utils.hpp"
#include "snippets/snippet.hpp"
#include <memory>
#include <cstdio>
#include <string>
#include <functional>
#include <iostream>
#include <sstream>
namespace cpptrace {
class formatter::impl {
std::string header = "Stack trace (most recent call first):";
color_mode color = color_mode::automatic;
address_mode addresses = address_mode::raw;
bool snippets = false;
int context_lines = 2;
bool columns = true;
std::function<bool(const stacktrace_frame&)> filter = [] (const stacktrace_frame&) { return true; };
public:
void set_header(std::string header) {
this->header = std::move(header);
}
void set_color_mode(formatter::color_mode mode) {
this->color = mode;
}
void set_address_mode(formatter::address_mode mode) {
this->addresses = mode;
}
void set_snippets(bool snippets) {
this->snippets = snippets;
}
void set_snippet_context(int lines) {
this->context_lines = lines;
}
void include_column(bool columns) {
this->columns = columns;
}
void set_filter(std::function<bool(const stacktrace_frame&)> filter) {
this->filter = filter;
}
std::string format(const stacktrace_frame& frame, detail::optional<bool> color_override = detail::nullopt) const {
return frame_to_string(frame, color_override.value_or(color == color_mode::always));
}
std::string format(const stacktrace& trace, detail::optional<bool> 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<bool> color_override = detail::nullopt) const {
print(std::cout, frame, color_override);
}
void print(
std::ostream& stream,
const stacktrace_frame& frame,
detail::optional<bool> color_override = detail::nullopt
) const {
print_frame_internal(stream, frame, color_override);
}
void print(
std::FILE* file,
const stacktrace_frame& frame,
detail::optional<bool> 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<bool> color_override = detail::nullopt) const {
print(std::cout, trace, color_override);
}
void print(
std::ostream& stream,
const stacktrace& trace,
detail::optional<bool> color_override = detail::nullopt
) const {
print_internal(stream, trace, true, color_override);
}
void print(
std::FILE* file,
const stacktrace& trace,
detail::optional<bool> 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<bool> color_override) const {
bool do_color = color == color_mode::always || color_override.value_or(false);
if(
(color == color_mode::automatic || 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<bool> 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(!header.empty()) {
stream << header << '\n';
}
std::size_t counter = 0;
const auto& frames = trace.frames;
if(frames.empty()) {
stream << "<empty trace>\n";
return;
}
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
print_frame_internal(stream, frame, color, frame_number_width, counter);
if(newline_at_end || &frame != &frames.back()) {
stream << '\n';
}
if(frame.line.has_value() && !frame.filename.empty() && snippets) {
stream << detail::get_snippet(frame.filename, frame.line.value(), context_lines, color);
}
counter++;
}
}
void print_frame_internal(
std::ostream& stream,
const stacktrace_frame& frame,
bool color,
unsigned frame_number_width,
std::size_t counter
) const {
std::string line = microfmt::format("#{<{}} {}", frame_number_width, counter, frame_to_string(frame, color));
stream << line;
}
void print_frame_internal(
std::ostream& stream,
const stacktrace_frame& frame,
detail::optional<bool> color_override
) const {
bool do_color = should_do_color(stream, color_override);
maybe_ensure_virtual_terminal_processing(stream, do_color);
stream << frame_to_string(frame, do_color);
}
std::string frame_to_string(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 : "";
std::string str;
if(frame.is_inline) {
str += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
} else {
auto address = addresses == address_mode::raw ? frame.raw_address : frame.object_address;
str += microfmt::format("{}0x{>{}:0h}{}", blue, 2 * sizeof(frame_ptr), address, reset);
}
if(!frame.symbol.empty()) {
str += microfmt::format(" in {}{}{}", yellow, frame.symbol, reset);
}
if(!frame.filename.empty()) {
str += microfmt::format(" at {}{}{}", green, frame.filename, reset);
if(frame.line.has_value()) {
str += microfmt::format(":{}{}{}", blue, frame.line.value(), reset);
if(frame.column.has_value() && columns) {
str += microfmt::format(":{}{}{}", blue, frame.column.value(), reset);
}
}
}
return str;
}
};
formatter::formatter() : pimpl(std::make_unique<formatter::impl>()) {}
formatter::~formatter() = default;
formatter::formatter(const formatter& other) : pimpl(std::make_unique<formatter::impl>(*other.pimpl)) {}
formatter& formatter::operator=(const formatter& other) {
pimpl = std::make_unique<formatter::impl>(*other.pimpl);
return *this;
}
formatter& formatter::set_header(std::string header) {
pimpl->set_header(std::move(header));
return *this;
}
formatter& formatter::set_color_mode(color_mode mode) {
pimpl->set_color_mode(mode);
return *this;
}
formatter& formatter::set_address_mode(address_mode mode) {
pimpl->set_address_mode(mode);
return *this;
}
formatter& formatter::set_snippets(bool snippets) {
pimpl->set_snippets(snippets);
return *this;
}
formatter& formatter::set_snippet_context(int lines) {
pimpl->set_snippet_context(lines);
return *this;
}
formatter& formatter::include_column(bool columns) {
pimpl->include_column(columns);
return *this;
}
formatter& formatter::set_filter(std::function<bool(const stacktrace_frame&)> filter) {
pimpl->set_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;
}
}

View File

@ -1,5 +1,6 @@
#include <cpptrace/utils.hpp> #include <cpptrace/utils.hpp>
#include <cpptrace/exceptions.hpp> #include <cpptrace/exceptions.hpp>
#include <cpptrace/formatting.hpp>
#include <iostream> #include <iostream>
@ -27,12 +28,9 @@ namespace cpptrace {
CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() { CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
try { // try/catch can never be hit but it's needed to prevent TCO try { // try/catch can never be hit but it's needed to prevent TCO
generate_trace(1).print( formatter{}
std::cerr, .set_header("Stack trace to reach terminate handler (most recent call first):")
isatty(stderr_fileno), .print(std::cerr, generate_trace(1));
true,
"Stack trace to reach terminate handler (most recent call first):"
);
} catch(...) { } catch(...) {
if(!detail::should_absorb_trace_exceptions()) { if(!detail::should_absorb_trace_exceptions()) {
throw; throw;