Skip to content

Commit becedb1

Browse files
authored
Implement #259: add formatter option 'break_before_filename' (#265)
This PR adds support for a `break_before_filename` option on the formatter (see #259 for examples & more information). The following functions have been added to the API of the library: ```cpp class formatter { // ... // When printing the stacktrace, inserts a linebreak prior after the symbol name, // and inserts enough whitespace to align the filename with the symbol name. formatter& break_before_filename(bool do_break = true); // Formats an individual frame, with the given amount of extra whitespace added before the filename // in the event of a linebreak. This is needed because oftentimes an individual frame is printed // _after_ some identifier, such as the index of the frame within the trace, and if this information // is not taken into account, the filename will be misaligned. std::string format(const stacktrace_frame&, bool color, size_t filename_indent) const; // Same as above, but to a std::ostream& void print(std::ostream&, const stacktrace_frame&, bool color, size_t filename_indent) const; // Same as above, but with std::FILE* void print(std::FILE*, const stacktrace_frame&, bool color, size_t filename_indent) const; } ``` Unit tests were added in `test/unit/lib/formatting.cpp`.
1 parent 63aba56 commit becedb1

File tree

4 files changed

+187
-20
lines changed

4 files changed

+187
-20
lines changed

include/cpptrace/formatting.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ CPPTRACE_BEGIN_NAMESPACE
5656
formatter& filtered_frame_placeholders(bool);
5757
formatter& filter(std::function<bool(const stacktrace_frame&)>);
5858
formatter& transform(std::function<stacktrace_frame(stacktrace_frame)>);
59+
formatter& break_before_filename(bool do_break = true);
5960

6061
std::string format(const stacktrace_frame&) const;
6162
std::string format(const stacktrace_frame&, bool color) const;
63+
// The last argument is the indent to use for the filename, if break_before_filename is set
64+
std::string format(const stacktrace_frame&, bool color, size_t filename_indent) const;
6265

6366
std::string format(const stacktrace&) const;
6467
std::string format(const stacktrace&, bool color) const;
@@ -67,8 +70,12 @@ CPPTRACE_BEGIN_NAMESPACE
6770
void print(const stacktrace_frame&, bool color) const;
6871
void print(std::ostream&, const stacktrace_frame&) const;
6972
void print(std::ostream&, const stacktrace_frame&, bool color) const;
73+
// The last argument is the indent to use for the filename, if break_before_filename is set
74+
void print(std::ostream&, const stacktrace_frame&, bool color, size_t filename_indent) const;
7075
void print(std::FILE*, const stacktrace_frame&) const;
7176
void print(std::FILE*, const stacktrace_frame&, bool color) const;
77+
// The last argument is the indent to use for the filename, if break_before_filename is set
78+
void print(std::FILE*, const stacktrace_frame&, bool color, size_t filename_indent) const;
7279

7380
void print(const stacktrace&) const;
7481
void print(const stacktrace&, bool color) const;

src/formatting.cpp

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ CPPTRACE_BEGIN_NAMESPACE
6464
address_mode addresses = address_mode::raw;
6565
path_mode paths = path_mode::full;
6666
bool snippets = false;
67+
bool break_before_filename = false;
6768
int context_lines = 2;
6869
bool columns = true;
6970
symbol_mode symbols = symbol_mode::full;
@@ -106,13 +107,17 @@ CPPTRACE_BEGIN_NAMESPACE
106107
void transform(std::function<stacktrace_frame(stacktrace_frame)> transform) {
107108
options.transform = std::move(transform);
108109
}
110+
void break_before_filename(bool do_break) {
111+
options.break_before_filename = do_break;
112+
}
109113

110114
std::string format(
111115
const stacktrace_frame& frame,
112-
detail::optional<bool> color_override = detail::nullopt
116+
detail::optional<bool> color_override = detail::nullopt,
117+
size_t filename_indent = 0
113118
) const {
114119
std::ostringstream oss;
115-
print_internal(oss, frame, color_override.value_or(options.color == color_mode::always));
120+
print_internal(oss, frame, color_override.value_or(options.color == color_mode::always), filename_indent);
116121
return std::move(oss).str();
117122
}
118123

@@ -128,17 +133,19 @@ CPPTRACE_BEGIN_NAMESPACE
128133
void print(
129134
std::ostream& stream,
130135
const stacktrace_frame& frame,
131-
detail::optional<bool> color_override = detail::nullopt
136+
detail::optional<bool> color_override = detail::nullopt,
137+
size_t filename_indent = 0
132138
) const {
133-
print_internal(stream, frame, color_override);
139+
print_internal(stream, frame, color_override, filename_indent);
134140
stream << "\n";
135141
}
136142
void print(
137143
std::FILE* file,
138144
const stacktrace_frame& frame,
139-
detail::optional<bool> color_override = detail::nullopt
145+
detail::optional<bool> color_override = detail::nullopt,
146+
size_t filename_indent = 0
140147
) const {
141-
auto str = format(frame, color_override);
148+
auto str = format(frame, color_override, filename_indent);
142149
str += "\n";
143150
std::fwrite(str.data(), 1, str.size(), file);
144151
}
@@ -206,15 +213,15 @@ CPPTRACE_BEGIN_NAMESPACE
206213
return do_color;
207214
}
208215

209-
void print_internal(std::ostream& stream, const stacktrace_frame& input_frame, detail::optional<bool> color_override) const {
216+
void print_internal(std::ostream& stream, const stacktrace_frame& input_frame, detail::optional<bool> color_override, size_t col_indent) const {
210217
bool color = should_do_color(stream, color_override);
211218
maybe_ensure_virtual_terminal_processing(stream, color);
212219
detail::optional<stacktrace_frame> transformed_frame;
213220
if(options.transform) {
214221
transformed_frame = options.transform(input_frame);
215222
}
216223
const stacktrace_frame& frame = options.transform ? transformed_frame.unwrap() : input_frame;
217-
write_frame(stream, frame, color);
224+
write_frame(stream, frame, color, col_indent);
218225
}
219226

220227
void print_internal(std::ostream& stream, const stacktrace& trace, detail::optional<bool> color_override) const {
@@ -223,6 +230,7 @@ CPPTRACE_BEGIN_NAMESPACE
223230
write_trace(stream, trace, color);
224231
}
225232

233+
226234
void write_trace(std::ostream& stream, const stacktrace& trace, bool color) const {
227235
if(!options.header.empty()) {
228236
stream << options.header << '\n';
@@ -245,11 +253,12 @@ CPPTRACE_BEGIN_NAMESPACE
245253
counter++;
246254
continue;
247255
}
248-
microfmt::print(stream, "#{<{}} ", frame_number_width, counter);
256+
257+
size_t filename_indent = write_frame_number(stream, frame_number_width, counter);
249258
if(filter_out_frame) {
250259
microfmt::print(stream, "(filtered)");
251260
} else {
252-
write_frame(stream, frame, color);
261+
write_frame(stream, frame, color, filename_indent);
253262
if(frame.line.has_value() && !frame.filename.empty() && options.snippets) {
254263
auto snippet = detail::get_snippet(
255264
frame.filename,
@@ -270,29 +279,45 @@ CPPTRACE_BEGIN_NAMESPACE
270279
}
271280
}
272281

273-
void write_frame(std::ostream& stream, const stacktrace_frame& frame, color_setting color) const {
274-
write_address(stream, frame, color);
282+
/// Write the frame number, and return the number of characters written
283+
size_t write_frame_number(std::ostream& stream, unsigned int frame_number_width, size_t counter) const
284+
{
285+
microfmt::print(stream, "#{<{}} ", frame_number_width, counter);
286+
return 2 + frame_number_width;
287+
}
288+
289+
void write_frame(std::ostream& stream, const stacktrace_frame& frame, color_setting color, size_t col) const {
290+
col += write_address(stream, frame, color);
275291
if(frame.is_inline || options.addresses != address_mode::none) {
276292
stream << ' ';
293+
col += 1;
277294
}
278295
if(!frame.symbol.empty()) {
279296
write_symbol(stream, frame, color);
280297
}
281298
if(!frame.symbol.empty() && !frame.filename.empty()) {
282-
stream << ' ';
299+
if(options.break_before_filename) {
300+
microfmt::print(stream, "\n{<{}}", col, "");
301+
} else {
302+
stream << ' ';
303+
}
283304
}
284305
if(!frame.filename.empty()) {
285306
write_source_location(stream, frame, color);
286307
}
287308
}
288309

289-
void write_address(std::ostream& stream, const stacktrace_frame& frame, color_setting color) const {
310+
/// Write the address of the frame, return the number of characters written
311+
size_t write_address(std::ostream& stream, const stacktrace_frame& frame, color_setting color) const {
290312
if(frame.is_inline) {
291313
microfmt::print(stream, "{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
314+
return 2 * sizeof(frame_ptr) + 2;
292315
} else if(options.addresses != address_mode::none) {
293316
auto address = options.addresses == address_mode::raw ? frame.raw_address : frame.object_address;
294317
microfmt::print(stream, "{}0x{>{}:0h}{}", color.blue(), 2 * sizeof(frame_ptr), address, color.reset());
318+
return 2 * sizeof(frame_ptr) + 2;
295319
}
320+
return 0;
296321
}
297322

298323
void write_symbol(std::ostream& stream, const stacktrace_frame& frame, color_setting color) const {
@@ -399,13 +424,20 @@ CPPTRACE_BEGIN_NAMESPACE
399424
pimpl->transform(std::move(transform));
400425
return *this;
401426
}
427+
formatter& formatter::break_before_filename(bool do_break) {
428+
pimpl->break_before_filename(do_break);
429+
return *this;
430+
}
402431

403432
std::string formatter::format(const stacktrace_frame& frame) const {
404433
return pimpl->format(frame);
405434
}
406435
std::string formatter::format(const stacktrace_frame& frame, bool color) const {
407436
return pimpl->format(frame, color);
408437
}
438+
std::string formatter::format(const stacktrace_frame& frame, bool color, size_t filename_indent) const {
439+
return pimpl->format(frame, color, filename_indent);
440+
}
409441

410442
std::string formatter::format(const stacktrace& trace) const {
411443
return pimpl->format(trace);
@@ -445,12 +477,18 @@ CPPTRACE_BEGIN_NAMESPACE
445477
void formatter::print(std::ostream& stream, const stacktrace_frame& frame, bool color) const {
446478
pimpl->print(stream, frame, color);
447479
}
480+
void formatter::print(std::ostream& stream, const stacktrace_frame& frame, bool color, size_t filename_indent) const {
481+
pimpl->print(stream, frame, color, filename_indent);
482+
}
448483
void formatter::print(std::FILE* file, const stacktrace_frame& frame) const {
449484
pimpl->print(file, frame);
450485
}
451486
void formatter::print(std::FILE* file, const stacktrace_frame& frame, bool color) const {
452487
pimpl->print(file, frame, color);
453488
}
489+
void formatter::print(std::FILE* file, const stacktrace_frame& frame, bool color, size_t filename_indent) const {
490+
pimpl->print(file, frame, color, filename_indent);
491+
}
454492

455493
const formatter& get_default_formatter() {
456494
static formatter formatter;

test/unit/lib/formatting.cpp

Lines changed: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,21 @@ namespace {
2121

2222
#if UINTPTR_MAX > 0xffffffff
2323
#define ADDR_PREFIX "00000000"
24+
#define PADDING_TAG " "
2425
#define INLINED_TAG "(inlined) "
2526
#else
27+
#define PADDING_TAG ""
2628
#define ADDR_PREFIX ""
2729
#define INLINED_TAG "(inlined) "
2830
#endif
2931

30-
cpptrace::stacktrace make_test_stacktrace() {
32+
cpptrace::stacktrace make_test_stacktrace(size_t count = 1) {
3133
cpptrace::stacktrace trace;
32-
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "foo.cpp", "foo()", false});
33-
trace.frames.push_back({0x2, 0x1002, {30}, {40}, "bar.cpp", "bar()", false});
34-
trace.frames.push_back({0x3, 0x1003, {40}, {25}, "foo.cpp", "main", false});
34+
for(size_t i = 0; i < count; i++) {
35+
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "foo.cpp", "foo()", false});
36+
trace.frames.push_back({0x2, 0x1002, {30}, {40}, "bar.cpp", "bar()", false});
37+
trace.frames.push_back({0x3, 0x1003, {40}, {25}, "foo.cpp", "main", false});
38+
}
3539
return trace;
3640
}
3741

@@ -125,6 +129,124 @@ TEST(FormatterTest, NoAddresses) {
125129
);
126130
}
127131

132+
TEST(FormatterTest, BreakBeforeFilename) {
133+
auto formatter = cpptrace::formatter{}
134+
.break_before_filename(true);
135+
EXPECT_THAT(
136+
split(formatter.format(make_test_stacktrace()), "\n"),
137+
ElementsAre(
138+
"Stack trace (most recent call first):",
139+
"#0 0x" ADDR_PREFIX "00000001 in foo()",
140+
" " PADDING_TAG " at foo.cpp:20:30",
141+
"#1 0x" ADDR_PREFIX "00000002 in bar()",
142+
" " PADDING_TAG " at bar.cpp:30:40",
143+
"#2 0x" ADDR_PREFIX "00000003 in main",
144+
" " PADDING_TAG " at foo.cpp:40:25"
145+
)
146+
);
147+
}
148+
149+
TEST(FormatterTest, BreakBeforeFilenameNoAddresses) {
150+
auto formatter = cpptrace::formatter{}
151+
.break_before_filename(true)
152+
.addresses(cpptrace::formatter::address_mode::none);
153+
// Check that if no address is present, the filename indent is reduced
154+
EXPECT_THAT(
155+
split(formatter.format(make_test_stacktrace()), "\n"),
156+
ElementsAre(
157+
"Stack trace (most recent call first):",
158+
"#0 in foo()",
159+
" at foo.cpp:20:30",
160+
"#1 in bar()",
161+
" at bar.cpp:30:40",
162+
"#2 in main",
163+
" at foo.cpp:40:25"
164+
)
165+
);
166+
}
167+
168+
TEST(FormatterTest, BreakBeforeFilenameInlines) {
169+
auto formatter = cpptrace::formatter{}
170+
.break_before_filename(true);
171+
172+
// Check that indentation is computed correctly when elements are inlined
173+
auto trace = make_test_stacktrace();
174+
trace.frames[1].is_inline = true;
175+
EXPECT_THAT(
176+
split(formatter.format(trace), "\n"),
177+
ElementsAre(
178+
"Stack trace (most recent call first):",
179+
"#0 0x" ADDR_PREFIX "00000001 in foo()",
180+
" " PADDING_TAG " at foo.cpp:20:30",
181+
"#1 " INLINED_TAG " in bar()",
182+
" " PADDING_TAG " at bar.cpp:30:40",
183+
"#2 0x" ADDR_PREFIX "00000003 in main",
184+
" " PADDING_TAG " at foo.cpp:40:25"
185+
)
186+
);
187+
}
188+
189+
TEST(FormatterTest, BreakBeforeFilenameLongTrace) {
190+
// Check that indentation is computed correctly for longer traces (where the
191+
// frame number is padded)
192+
auto formatter = cpptrace::formatter{}
193+
.break_before_filename(true);
194+
195+
EXPECT_THAT(
196+
split(formatter.format(make_test_stacktrace(4)), "\n"),
197+
ElementsAre(
198+
"Stack trace (most recent call first):",
199+
"#0 0x" ADDR_PREFIX "00000001 in foo()",
200+
" " PADDING_TAG " at foo.cpp:20:30",
201+
"#1 0x" ADDR_PREFIX "00000002 in bar()",
202+
" " PADDING_TAG " at bar.cpp:30:40",
203+
"#2 0x" ADDR_PREFIX "00000003 in main",
204+
" " PADDING_TAG " at foo.cpp:40:25",
205+
"#3 0x" ADDR_PREFIX "00000001 in foo()",
206+
" " PADDING_TAG " at foo.cpp:20:30",
207+
"#4 0x" ADDR_PREFIX "00000002 in bar()",
208+
" " PADDING_TAG " at bar.cpp:30:40",
209+
"#5 0x" ADDR_PREFIX "00000003 in main",
210+
" " PADDING_TAG " at foo.cpp:40:25",
211+
"#6 0x" ADDR_PREFIX "00000001 in foo()",
212+
" " PADDING_TAG " at foo.cpp:20:30",
213+
"#7 0x" ADDR_PREFIX "00000002 in bar()",
214+
" " PADDING_TAG " at bar.cpp:30:40",
215+
"#8 0x" ADDR_PREFIX "00000003 in main",
216+
" " PADDING_TAG " at foo.cpp:40:25",
217+
"#9 0x" ADDR_PREFIX "00000001 in foo()",
218+
" " PADDING_TAG " at foo.cpp:20:30",
219+
"#10 0x" ADDR_PREFIX "00000002 in bar()",
220+
" " PADDING_TAG " at bar.cpp:30:40",
221+
"#11 0x" ADDR_PREFIX "00000003 in main",
222+
" " PADDING_TAG " at foo.cpp:40:25"
223+
)
224+
);
225+
}
226+
227+
TEST(FormatterTest, BreakBeforeFilenameColors) {
228+
// Check that indentation is computed correctly with colors enabled
229+
// (If microfmt is updated to count the number of characters printed,
230+
// it will need to _exclude_ colors for the purposes of computing
231+
// alignment)
232+
auto formatter = cpptrace::formatter{}
233+
.break_before_filename(true)
234+
.colors(cpptrace::formatter::color_mode::always);
235+
236+
EXPECT_THAT(
237+
split(formatter.format(make_test_stacktrace()), "\n"),
238+
ElementsAre(
239+
"Stack trace (most recent call first):",
240+
"#0 \x1B[34m0x" ADDR_PREFIX "00000001\x1B[0m in \x1B[33mfoo()\x1B[0m",
241+
" " PADDING_TAG " at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m20\x1B[0m:\x1B[34m30\x1B[0m",
242+
"#1 \x1B[34m0x" ADDR_PREFIX "00000002\x1B[0m in \x1B[33mbar()\x1B[0m",
243+
" " PADDING_TAG " at \x1B[32mbar.cpp\x1B[0m:\x1B[34m30\x1B[0m:\x1B[34m40\x1B[0m",
244+
"#2 \x1B[34m0x" ADDR_PREFIX "00000003\x1B[0m in \x1B[33mmain\x1B[0m",
245+
" " PADDING_TAG " at \x1B[32mfoo.cpp\x1B[0m:\x1B[34m40\x1B[0m:\x1B[34m25\x1B[0m"
246+
)
247+
);
248+
}
249+
128250
TEST(FormatterTest, PathShortening) {
129251
cpptrace::stacktrace trace;
130252
trace.frames.push_back({0x1, 0x1001, {20}, {30}, "/home/foo/foo.cpp", "foo()", false});

tools/symbol_tables/main.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,10 @@ void lookup_symbol(const options& options, cpptrace::frame_ptr address) {
7474
}
7575
}
7676
#elif IS_APPLE
77-
void dump_symbols(const std::filesystem::path&) {
77+
void dump_symbols(const options&) {
7878
fmt::println("Not implemented yet (TODO)");
7979
}
80-
void lookup_symbol(const std::filesystem::path&, cpptrace::frame_ptr) {
80+
void lookup_symbol(const options&, cpptrace::frame_ptr) {
8181
fmt::println("Not implemented yet (TODO)");
8282
}
8383
#else

0 commit comments

Comments
 (0)