1#include "includes.h"
2#include "test_sink.h"
3
4using spdlog::memory_buf_t;
5using spdlog::details::to_string_view;
6
7// log to str and return it
8template<typename... Args>
9static std::string log_to_str(const std::string &msg, const Args &... args)
10{
11 std::ostringstream oss;
12 auto oss_sink = std::make_shared<spdlog::sinks::ostream_sink_mt>(oss);
13 spdlog::logger oss_logger("pattern_tester", oss_sink);
14 oss_logger.set_level(spdlog::level::info);
15
16 oss_logger.set_formatter(std::unique_ptr<spdlog::formatter>(new spdlog::pattern_formatter(args...)));
17
18 oss_logger.info(msg);
19 return oss.str();
20}
21
22TEST_CASE("custom eol", "[pattern_formatter]")
23{
24 std::string msg = "Hello custom eol test";
25 std::string eol = ";)";
26 REQUIRE(log_to_str(msg, "%v", spdlog::pattern_time_type::local, ";)") == msg + eol);
27}
28
29TEST_CASE("empty format", "[pattern_formatter]")
30{
31 REQUIRE(log_to_str("Some message", "", spdlog::pattern_time_type::local, "").empty());
32}
33
34TEST_CASE("empty format2", "[pattern_formatter]")
35{
36 REQUIRE(log_to_str("Some message", "", spdlog::pattern_time_type::local, "\n") == "\n");
37}
38
39TEST_CASE("level", "[pattern_formatter]")
40{
41 REQUIRE(log_to_str("Some message", "[%l] %v", spdlog::pattern_time_type::local, "\n") == "[info] Some message\n");
42}
43
44TEST_CASE("short level", "[pattern_formatter]")
45{
46 REQUIRE(log_to_str("Some message", "[%L] %v", spdlog::pattern_time_type::local, "\n") == "[I] Some message\n");
47}
48
49TEST_CASE("name", "[pattern_formatter]")
50{
51 REQUIRE(log_to_str("Some message", "[%n] %v", spdlog::pattern_time_type::local, "\n") == "[pattern_tester] Some message\n");
52}
53
54TEST_CASE("date MM/DD/YY ", "[pattern_formatter]")
55{
56 auto now_tm = spdlog::details::os::localtime();
57 std::stringstream oss;
58 oss << std::setfill('0') << std::setw(2) << now_tm.tm_mon + 1 << "/" << std::setw(2) << now_tm.tm_mday << "/" << std::setw(2)
59 << (now_tm.tm_year + 1900) % 1000 << " Some message\n";
60 REQUIRE(log_to_str("Some message", "%D %v", spdlog::pattern_time_type::local, "\n") == oss.str());
61}
62
63TEST_CASE("color range test1", "[pattern_formatter]")
64{
65 auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%v%$", spdlog::pattern_time_type::local, "\n");
66
67 memory_buf_t buf;
68 spdlog::fmt_lib::format_to(std::back_inserter(buf), "Hello");
69 memory_buf_t formatted;
70 std::string logger_name = "test";
71 spdlog::details::log_msg msg(logger_name, spdlog::level::info, spdlog::string_view_t(buf.data(), buf.size()));
72 formatter->format(msg, formatted);
73 REQUIRE(msg.color_range_start == 0);
74 REQUIRE(msg.color_range_end == 5);
75 REQUIRE(log_to_str("hello", "%^%v%$", spdlog::pattern_time_type::local, "\n") == "hello\n");
76}
77
78TEST_CASE("color range test2", "[pattern_formatter]")
79{
80 auto formatter = std::make_shared<spdlog::pattern_formatter>("%^%$", spdlog::pattern_time_type::local, "\n");
81 std::string logger_name = "test";
82 spdlog::details::log_msg msg(logger_name, spdlog::level::info, "");
83 memory_buf_t formatted;
84 formatter->format(msg, formatted);
85 REQUIRE(msg.color_range_start == 0);
86 REQUIRE(msg.color_range_end == 0);
87 REQUIRE(log_to_str("", "%^%$", spdlog::pattern_time_type::local, "\n") == "\n");
88}
89
90TEST_CASE("color range test3", "[pattern_formatter]")
91{
92 auto formatter = std::make_shared<spdlog::pattern_formatter>("%^***%$");
93 std::string logger_name = "test";
94 spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored");
95 memory_buf_t formatted;
96 formatter->format(msg, formatted);
97 REQUIRE(msg.color_range_start == 0);
98 REQUIRE(msg.color_range_end == 3);
99}
100
101TEST_CASE("color range test4", "[pattern_formatter]")
102{
103 auto formatter = std::make_shared<spdlog::pattern_formatter>("XX%^YYY%$", spdlog::pattern_time_type::local, "\n");
104 std::string logger_name = "test";
105 spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored");
106
107 memory_buf_t formatted;
108 formatter->format(msg, formatted);
109 REQUIRE(msg.color_range_start == 2);
110 REQUIRE(msg.color_range_end == 5);
111 REQUIRE(log_to_str("ignored", "XX%^YYY%$", spdlog::pattern_time_type::local, "\n") == "XXYYY\n");
112}
113
114TEST_CASE("color range test5", "[pattern_formatter]")
115{
116 auto formatter = std::make_shared<spdlog::pattern_formatter>("**%^");
117 std::string logger_name = "test";
118 spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored");
119 memory_buf_t formatted;
120 formatter->format(msg, formatted);
121 REQUIRE(msg.color_range_start == 2);
122 REQUIRE(msg.color_range_end == 0);
123}
124
125TEST_CASE("color range test6", "[pattern_formatter]")
126{
127 auto formatter = std::make_shared<spdlog::pattern_formatter>("**%$");
128 std::string logger_name = "test";
129 spdlog::details::log_msg msg(logger_name, spdlog::level::info, "ignored");
130 memory_buf_t formatted;
131 formatter->format(msg, formatted);
132 REQUIRE(msg.color_range_start == 0);
133 REQUIRE(msg.color_range_end == 2);
134}
135
136//
137// Test padding
138//
139
140TEST_CASE("level_left_padded", "[pattern_formatter]")
141{
142 REQUIRE(log_to_str("Some message", "[%8l] %v", spdlog::pattern_time_type::local, "\n") == "[ info] Some message\n");
143 REQUIRE(log_to_str("Some message", "[%8!l] %v", spdlog::pattern_time_type::local, "\n") == "[ info] Some message\n");
144}
145
146TEST_CASE("level_right_padded", "[pattern_formatter]")
147{
148 REQUIRE(log_to_str("Some message", "[%-8l] %v", spdlog::pattern_time_type::local, "\n") == "[info ] Some message\n");
149 REQUIRE(log_to_str("Some message", "[%-8!l] %v", spdlog::pattern_time_type::local, "\n") == "[info ] Some message\n");
150}
151
152TEST_CASE("level_center_padded", "[pattern_formatter]")
153{
154 REQUIRE(log_to_str("Some message", "[%=8l] %v", spdlog::pattern_time_type::local, "\n") == "[ info ] Some message\n");
155 REQUIRE(log_to_str("Some message", "[%=8!l] %v", spdlog::pattern_time_type::local, "\n") == "[ info ] Some message\n");
156}
157
158TEST_CASE("short level_left_padded", "[pattern_formatter]")
159{
160 REQUIRE(log_to_str("Some message", "[%3L] %v", spdlog::pattern_time_type::local, "\n") == "[ I] Some message\n");
161 REQUIRE(log_to_str("Some message", "[%3!L] %v", spdlog::pattern_time_type::local, "\n") == "[ I] Some message\n");
162}
163
164TEST_CASE("short level_right_padded", "[pattern_formatter]")
165{
166 REQUIRE(log_to_str("Some message", "[%-3L] %v", spdlog::pattern_time_type::local, "\n") == "[I ] Some message\n");
167 REQUIRE(log_to_str("Some message", "[%-3!L] %v", spdlog::pattern_time_type::local, "\n") == "[I ] Some message\n");
168}
169
170TEST_CASE("short level_center_padded", "[pattern_formatter]")
171{
172 REQUIRE(log_to_str("Some message", "[%=3L] %v", spdlog::pattern_time_type::local, "\n") == "[ I ] Some message\n");
173 REQUIRE(log_to_str("Some message", "[%=3!L] %v", spdlog::pattern_time_type::local, "\n") == "[ I ] Some message\n");
174}
175
176TEST_CASE("left_padded_short", "[pattern_formatter]")
177{
178 REQUIRE(log_to_str("Some message", "[%3n] %v", spdlog::pattern_time_type::local, "\n") == "[pattern_tester] Some message\n");
179 REQUIRE(log_to_str("Some message", "[%3!n] %v", spdlog::pattern_time_type::local, "\n") == "[pat] Some message\n");
180}
181
182TEST_CASE("right_padded_short", "[pattern_formatter]")
183{
184 REQUIRE(log_to_str("Some message", "[%-3n] %v", spdlog::pattern_time_type::local, "\n") == "[pattern_tester] Some message\n");
185 REQUIRE(log_to_str("Some message", "[%-3!n] %v", spdlog::pattern_time_type::local, "\n") == "[pat] Some message\n");
186}
187
188TEST_CASE("center_padded_short", "[pattern_formatter]")
189{
190 REQUIRE(log_to_str("Some message", "[%=3n] %v", spdlog::pattern_time_type::local, "\n") == "[pattern_tester] Some message\n");
191 REQUIRE(log_to_str("Some message", "[%=3!n] %v", spdlog::pattern_time_type::local, "\n") == "[pat] Some message\n");
192}
193
194TEST_CASE("left_padded_huge", "[pattern_formatter]")
195{
196 REQUIRE(log_to_str("Some message", "[%-300n] %v", spdlog::pattern_time_type::local, "\n") ==
197 "[pattern_tester ] Some message\n");
198
199 REQUIRE(log_to_str("Some message", "[%-300!n] %v", spdlog::pattern_time_type::local, "\n") ==
200 "[pattern_tester ] Some message\n");
201}
202
203TEST_CASE("left_padded_max", "[pattern_formatter]")
204{
205 REQUIRE(log_to_str("Some message", "[%-64n] %v", spdlog::pattern_time_type::local, "\n") ==
206 "[pattern_tester ] Some message\n");
207
208 REQUIRE(log_to_str("Some message", "[%-64!n] %v", spdlog::pattern_time_type::local, "\n") ==
209 "[pattern_tester ] Some message\n");
210}
211
212// Test padding + truncate flag
213
214TEST_CASE("paddinng_truncate", "[pattern_formatter]")
215{
216 REQUIRE(log_to_str("123456", "%6!v", spdlog::pattern_time_type::local, "\n") == "123456\n");
217 REQUIRE(log_to_str("123456", "%5!v", spdlog::pattern_time_type::local, "\n") == "12345\n");
218 REQUIRE(log_to_str("123456", "%7!v", spdlog::pattern_time_type::local, "\n") == " 123456\n");
219
220 REQUIRE(log_to_str("123456", "%-6!v", spdlog::pattern_time_type::local, "\n") == "123456\n");
221 REQUIRE(log_to_str("123456", "%-5!v", spdlog::pattern_time_type::local, "\n") == "12345\n");
222 REQUIRE(log_to_str("123456", "%-7!v", spdlog::pattern_time_type::local, "\n") == "123456 \n");
223
224 REQUIRE(log_to_str("123456", "%=6!v", spdlog::pattern_time_type::local, "\n") == "123456\n");
225 REQUIRE(log_to_str("123456", "%=5!v", spdlog::pattern_time_type::local, "\n") == "12345\n");
226 REQUIRE(log_to_str("123456", "%=7!v", spdlog::pattern_time_type::local, "\n") == "123456 \n");
227
228 REQUIRE(log_to_str("123456", "%0!v", spdlog::pattern_time_type::local, "\n") == "\n");
229}
230
231TEST_CASE("padding_truncate_funcname", "[pattern_formatter]")
232{
233 spdlog::sinks::test_sink_st test_sink;
234
235 const char *pattern = "%v [%5!!]";
236 auto formatter = std::unique_ptr<spdlog::formatter>(new spdlog::pattern_formatter(pattern));
237 test_sink.set_formatter(std::move(formatter));
238
239 spdlog::details::log_msg msg1{spdlog::source_loc{"ignored", 1, "func"}, "test_logger", spdlog::level::info, "message"};
240 test_sink.log(msg1);
241 REQUIRE(test_sink.lines()[0] == "message [ func]");
242
243 spdlog::details::log_msg msg2{spdlog::source_loc{"ignored", 1, "function"}, "test_logger", spdlog::level::info, "message"};
244 test_sink.log(msg2);
245 REQUIRE(test_sink.lines()[1] == "message [funct]");
246}
247
248TEST_CASE("padding_funcname", "[pattern_formatter]")
249{
250 spdlog::sinks::test_sink_st test_sink;
251
252 const char *pattern = "%v [%10!]";
253 auto formatter = std::unique_ptr<spdlog::formatter>(new spdlog::pattern_formatter(pattern));
254 test_sink.set_formatter(std::move(formatter));
255
256 spdlog::details::log_msg msg1{spdlog::source_loc{"ignored", 1, "func"}, "test_logger", spdlog::level::info, "message"};
257 test_sink.log(msg1);
258 REQUIRE(test_sink.lines()[0] == "message [ func]");
259
260 spdlog::details::log_msg msg2{spdlog::source_loc{"ignored", 1, "func567890123"}, "test_logger", spdlog::level::info, "message"};
261 test_sink.log(msg2);
262 REQUIRE(test_sink.lines()[1] == "message [func567890123]");
263}
264
265TEST_CASE("clone-default-formatter", "[pattern_formatter]")
266{
267 auto formatter_1 = std::make_shared<spdlog::pattern_formatter>();
268 auto formatter_2 = formatter_1->clone();
269 std::string logger_name = "test";
270 spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message");
271
272 memory_buf_t formatted_1;
273 memory_buf_t formatted_2;
274 formatter_1->format(msg, formatted_1);
275 formatter_2->format(msg, formatted_2);
276
277 REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2));
278}
279
280TEST_CASE("clone-default-formatter2", "[pattern_formatter]")
281{
282 auto formatter_1 = std::make_shared<spdlog::pattern_formatter>("%+");
283 auto formatter_2 = formatter_1->clone();
284 std::string logger_name = "test";
285 spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message");
286
287 memory_buf_t formatted_1;
288 memory_buf_t formatted_2;
289 formatter_1->format(msg, formatted_1);
290 formatter_2->format(msg, formatted_2);
291
292 REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2));
293}
294
295TEST_CASE("clone-formatter", "[pattern_formatter]")
296{
297 auto formatter_1 = std::make_shared<spdlog::pattern_formatter>("%D %X [%] [%n] %v");
298 auto formatter_2 = formatter_1->clone();
299 std::string logger_name = "test";
300 spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message");
301
302 memory_buf_t formatted_1;
303 memory_buf_t formatted_2;
304 formatter_1->format(msg, formatted_1);
305 formatter_2->format(msg, formatted_2);
306
307 REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2));
308}
309
310TEST_CASE("clone-formatter-2", "[pattern_formatter]")
311{
312 using spdlog::pattern_time_type;
313 auto formatter_1 = std::make_shared<spdlog::pattern_formatter>("%D %X [%] [%n] %v", pattern_time_type::utc, "xxxxxx\n");
314 auto formatter_2 = formatter_1->clone();
315 std::string logger_name = "test2";
316 spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message");
317
318 memory_buf_t formatted_1;
319 memory_buf_t formatted_2;
320 formatter_1->format(msg, formatted_1);
321 formatter_2->format(msg, formatted_2);
322
323 REQUIRE(to_string_view(formatted_1) == to_string_view(formatted_2));
324}
325
326class custom_test_flag : public spdlog::custom_flag_formatter
327{
328public:
329 explicit custom_test_flag(std::string txt)
330 : some_txt{std::move(txt)}
331 {}
332
333 void format(const spdlog::details::log_msg &, const std::tm &tm, spdlog::memory_buf_t &dest) override
334 {
335 if (some_txt == "throw_me")
336 {
337 throw spdlog::spdlog_ex("custom_flag_exception_test");
338 }
339 else if (some_txt == "time")
340 {
341 auto formatted = spdlog::fmt_lib::format("{:d}:{:02d}{:s}", tm.tm_hour % 12, tm.tm_min, tm.tm_hour / 12 ? "PM" : "AM");
342 dest.append(formatted.data(), formatted.data() + formatted.size());
343 return;
344 }
345 some_txt = std::string(padinfo_.width_, ' ') + some_txt;
346 dest.append(some_txt.data(), some_txt.data() + some_txt.size());
347 }
348 spdlog::details::padding_info get_padding_info()
349 {
350 return padinfo_;
351 }
352
353 std::string some_txt;
354
355 std::unique_ptr<custom_flag_formatter> clone() const override
356 {
357 return spdlog::details::make_unique<custom_test_flag>(some_txt);
358 }
359};
360// test clone with custom flag formatters
361TEST_CASE("clone-custom_formatter", "[pattern_formatter]")
362{
363 auto formatter_1 = std::make_shared<spdlog::pattern_formatter>();
364 formatter_1->add_flag<custom_test_flag>('t', "custom_output").set_pattern("[%n] [%t] %v");
365 auto formatter_2 = formatter_1->clone();
366 std::string logger_name = "logger-name";
367 spdlog::details::log_msg msg(logger_name, spdlog::level::info, "some message");
368
369 memory_buf_t formatted_1;
370 memory_buf_t formatted_2;
371 formatter_1->format(msg, formatted_1);
372 formatter_2->format(msg, formatted_2);
373
374 auto expected = spdlog::fmt_lib::format("[logger-name] [custom_output] some message{}", spdlog::details::os::default_eol);
375
376 REQUIRE(to_string_view(formatted_1) == expected);
377 REQUIRE(to_string_view(formatted_2) == expected);
378}
379
380//
381// Test source location formatting
382//
383
384#ifdef _WIN32
385static const char *const test_path = "\\a\\b\\c/myfile.cpp";
386#else
387static const char *const test_path = "/a/b//myfile.cpp";
388#endif
389
390TEST_CASE("short filename formatter-1", "[pattern_formatter]")
391{
392 spdlog::pattern_formatter formatter("%s", spdlog::pattern_time_type::local, "");
393 memory_buf_t formatted;
394 std::string logger_name = "logger-name";
395 spdlog::source_loc source_loc{test_path, 123, "some_func()"};
396 spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello");
397 formatter.format(msg, formatted);
398
399 REQUIRE(to_string_view(formatted) == "myfile.cpp");
400}
401
402TEST_CASE("short filename formatter-2", "[pattern_formatter]")
403{
404 spdlog::pattern_formatter formatter("%s:%#", spdlog::pattern_time_type::local, "");
405 memory_buf_t formatted;
406 std::string logger_name = "logger-name";
407 spdlog::source_loc source_loc{"myfile.cpp", 123, "some_func()"};
408 spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello");
409 formatter.format(msg, formatted);
410
411 REQUIRE(to_string_view(formatted) == "myfile.cpp:123");
412}
413
414TEST_CASE("short filename formatter-3", "[pattern_formatter]")
415{
416 spdlog::pattern_formatter formatter("%s %v", spdlog::pattern_time_type::local, "");
417 memory_buf_t formatted;
418 std::string logger_name = "logger-name";
419 spdlog::source_loc source_loc{"", 123, "some_func()"};
420 spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello");
421 formatter.format(msg, formatted);
422
423 REQUIRE(to_string_view(formatted) == " Hello");
424}
425
426TEST_CASE("full filename formatter", "[pattern_formatter]")
427{
428 spdlog::pattern_formatter formatter("%g", spdlog::pattern_time_type::local, "");
429 memory_buf_t formatted;
430 std::string logger_name = "logger-name";
431 spdlog::source_loc source_loc{test_path, 123, "some_func()"};
432 spdlog::details::log_msg msg(source_loc, "logger-name", spdlog::level::info, "Hello");
433 formatter.format(msg, formatted);
434
435 REQUIRE(to_string_view(formatted) == test_path);
436}
437
438TEST_CASE("custom flags", "[pattern_formatter]")
439{
440 auto formatter = std::make_shared<spdlog::pattern_formatter>();
441 formatter->add_flag<custom_test_flag>('t', "custom1").add_flag<custom_test_flag>('u', "custom2").set_pattern("[%n] [%t] [%u] %v");
442
443 memory_buf_t formatted;
444
445 spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, "some message");
446 formatter->format(msg, formatted);
447 auto expected = spdlog::fmt_lib::format("[logger-name] [custom1] [custom2] some message{}", spdlog::details::os::default_eol);
448
449 REQUIRE(to_string_view(formatted) == expected);
450}
451
452TEST_CASE("custom flags-padding", "[pattern_formatter]")
453{
454 auto formatter = std::make_shared<spdlog::pattern_formatter>();
455 formatter->add_flag<custom_test_flag>('t', "custom1").add_flag<custom_test_flag>('u', "custom2").set_pattern("[%n] [%t] [%5u] %v");
456
457 memory_buf_t formatted;
458
459 spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, "some message");
460 formatter->format(msg, formatted);
461 auto expected = spdlog::fmt_lib::format("[logger-name] [custom1] [ custom2] some message{}", spdlog::details::os::default_eol);
462
463 REQUIRE(to_string_view(formatted) == expected);
464}
465
466TEST_CASE("custom flags-exception", "[pattern_formatter]")
467{
468 auto formatter = std::make_shared<spdlog::pattern_formatter>();
469 formatter->add_flag<custom_test_flag>('t', "throw_me").add_flag<custom_test_flag>('u', "custom2").set_pattern("[%n] [%t] [%u] %v");
470
471 memory_buf_t formatted;
472 spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, "some message");
473 CHECK_THROWS_AS(formatter->format(msg, formatted), spdlog::spdlog_ex);
474}
475
476TEST_CASE("override need_localtime", "[pattern_formatter]")
477{
478 auto formatter = std::make_shared<spdlog::pattern_formatter>(spdlog::pattern_time_type::local, "\n");
479 formatter->add_flag<custom_test_flag>('t', "time").set_pattern("%t> %v");
480
481 {
482 memory_buf_t formatted;
483 spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, "some message");
484 formatter->format(msg, formatted);
485 REQUIRE(to_string_view(formatted) == "0:00AM> some message\n");
486 }
487
488 {
489 formatter->need_localtime();
490
491 auto now_tm = spdlog::details::os::localtime();
492 std::stringstream oss;
493 oss << (now_tm.tm_hour % 12) << ":" << std::setfill('0') << std::setw(2) << now_tm.tm_min << (now_tm.tm_hour / 12 ? "PM" : "AM")
494 << "> some message\n";
495
496 memory_buf_t formatted;
497 spdlog::details::log_msg msg(spdlog::source_loc{}, "logger-name", spdlog::level::info, "some message");
498 formatter->format(msg, formatted);
499 REQUIRE(to_string_view(formatted) == oss.str());
500 }
501}
502