1// Copyright(c) 2015-present, Gabi Melman & spdlog contributors.
2// Distributed under the MIT License (http://opensource.org/licenses/MIT)
3
4#pragma once
5
6#ifndef SPDLOG_HEADER_ONLY
7# include <spdlog/pattern_formatter.h>
8#endif
9
10#include <spdlog/details/fmt_helper.h>
11#include <spdlog/details/log_msg.h>
12#include <spdlog/details/os.h>
13#include <spdlog/fmt/fmt.h>
14#include <spdlog/formatter.h>
15
16#include <algorithm>
17#include <array>
18#include <chrono>
19#include <ctime>
20#include <cctype>
21#include <cstring>
22#include <iterator>
23#include <memory>
24#include <mutex>
25#include <string>
26#include <thread>
27#include <utility>
28#include <vector>
29
30namespace spdlog {
31namespace details {
32
33///////////////////////////////////////////////////////////////////////
34// name & level pattern appender
35///////////////////////////////////////////////////////////////////////
36
37class scoped_padder
38{
39public:
40 scoped_padder(size_t wrapped_size, const padding_info &padinfo, memory_buf_t &dest)
41 : padinfo_(padinfo)
42 , dest_(dest)
43 {
44 remaining_pad_ = static_cast<long>(padinfo.width_) - static_cast<long>(wrapped_size);
45 if (remaining_pad_ <= 0)
46 {
47 return;
48 }
49
50 if (padinfo_.side_ == padding_info::pad_side::left)
51 {
52 pad_it(remaining_pad_);
53 remaining_pad_ = 0;
54 }
55 else if (padinfo_.side_ == padding_info::pad_side::center)
56 {
57 auto half_pad = remaining_pad_ / 2;
58 auto reminder = remaining_pad_ & 1;
59 pad_it(half_pad);
60 remaining_pad_ = half_pad + reminder; // for the right side
61 }
62 }
63
64 template<typename T>
65 static unsigned int count_digits(T n)
66 {
67 return fmt_helper::count_digits(n);
68 }
69
70 ~scoped_padder()
71 {
72 if (remaining_pad_ >= 0)
73 {
74 pad_it(remaining_pad_);
75 }
76 else if (padinfo_.truncate_)
77 {
78 long new_size = static_cast<long>(dest_.size()) + remaining_pad_;
79 dest_.resize(static_cast<size_t>(new_size));
80 }
81 }
82
83private:
84 void pad_it(long count)
85 {
86 fmt_helper::append_string_view(string_view_t(spaces_.data(), static_cast<size_t>(count)), dest_);
87 }
88
89 const padding_info &padinfo_;
90 memory_buf_t &dest_;
91 long remaining_pad_;
92 string_view_t spaces_{" ", 64};
93};
94
95struct null_scoped_padder
96{
97 null_scoped_padder(size_t /*wrapped_size*/, const padding_info & /*padinfo*/, memory_buf_t & /*dest*/) {}
98
99 template<typename T>
100 static unsigned int count_digits(T /* number */)
101 {
102 return 0;
103 }
104};
105
106template<typename ScopedPadder>
107class name_formatter final : public flag_formatter
108{
109public:
110 explicit name_formatter(padding_info padinfo)
111 : flag_formatter(padinfo)
112 {}
113
114 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
115 {
116 ScopedPadder p(msg.logger_name.size(), padinfo_, dest);
117 fmt_helper::append_string_view(msg.logger_name, dest);
118 }
119};
120
121// log level appender
122template<typename ScopedPadder>
123class level_formatter final : public flag_formatter
124{
125public:
126 explicit level_formatter(padding_info padinfo)
127 : flag_formatter(padinfo)
128 {}
129
130 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
131 {
132 const string_view_t &level_name = level::to_string_view(msg.level);
133 ScopedPadder p(level_name.size(), padinfo_, dest);
134 fmt_helper::append_string_view(level_name, dest);
135 }
136};
137
138// short log level appender
139template<typename ScopedPadder>
140class short_level_formatter final : public flag_formatter
141{
142public:
143 explicit short_level_formatter(padding_info padinfo)
144 : flag_formatter(padinfo)
145 {}
146
147 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
148 {
149 string_view_t level_name{level::to_short_c_str(msg.level)};
150 ScopedPadder p(level_name.size(), padinfo_, dest);
151 fmt_helper::append_string_view(level_name, dest);
152 }
153};
154
155///////////////////////////////////////////////////////////////////////
156// Date time pattern appenders
157///////////////////////////////////////////////////////////////////////
158
159static const char *ampm(const tm &t)
160{
161 return t.tm_hour >= 12 ? "PM" : "AM";
162}
163
164static int to12h(const tm &t)
165{
166 return t.tm_hour > 12 ? t.tm_hour - 12 : t.tm_hour;
167}
168
169// Abbreviated weekday name
170static std::array<const char *, 7> days{{"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}};
171
172template<typename ScopedPadder>
173class a_formatter final : public flag_formatter
174{
175public:
176 explicit a_formatter(padding_info padinfo)
177 : flag_formatter(padinfo)
178 {}
179
180 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
181 {
182 string_view_t field_value{days[static_cast<size_t>(tm_time.tm_wday)]};
183 ScopedPadder p(field_value.size(), padinfo_, dest);
184 fmt_helper::append_string_view(field_value, dest);
185 }
186};
187
188// Full weekday name
189static std::array<const char *, 7> full_days{{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}};
190
191template<typename ScopedPadder>
192class A_formatter : public flag_formatter
193{
194public:
195 explicit A_formatter(padding_info padinfo)
196 : flag_formatter(padinfo)
197 {}
198
199 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
200 {
201 string_view_t field_value{full_days[static_cast<size_t>(tm_time.tm_wday)]};
202 ScopedPadder p(field_value.size(), padinfo_, dest);
203 fmt_helper::append_string_view(field_value, dest);
204 }
205};
206
207// Abbreviated month
208static const std::array<const char *, 12> months{{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"}};
209
210template<typename ScopedPadder>
211class b_formatter final : public flag_formatter
212{
213public:
214 explicit b_formatter(padding_info padinfo)
215 : flag_formatter(padinfo)
216 {}
217
218 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
219 {
220 string_view_t field_value{months[static_cast<size_t>(tm_time.tm_mon)]};
221 ScopedPadder p(field_value.size(), padinfo_, dest);
222 fmt_helper::append_string_view(field_value, dest);
223 }
224};
225
226// Full month name
227static const std::array<const char *, 12> full_months{
228 {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}};
229
230template<typename ScopedPadder>
231class B_formatter final : public flag_formatter
232{
233public:
234 explicit B_formatter(padding_info padinfo)
235 : flag_formatter(padinfo)
236 {}
237
238 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
239 {
240 string_view_t field_value{full_months[static_cast<size_t>(tm_time.tm_mon)]};
241 ScopedPadder p(field_value.size(), padinfo_, dest);
242 fmt_helper::append_string_view(field_value, dest);
243 }
244};
245
246// Date and time representation (Thu Aug 23 15:35:46 2014)
247template<typename ScopedPadder>
248class c_formatter final : public flag_formatter
249{
250public:
251 explicit c_formatter(padding_info padinfo)
252 : flag_formatter(padinfo)
253 {}
254
255 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
256 {
257 const size_t field_size = 24;
258 ScopedPadder p(field_size, padinfo_, dest);
259
260 fmt_helper::append_string_view(days[static_cast<size_t>(tm_time.tm_wday)], dest);
261 dest.push_back(' ');
262 fmt_helper::append_string_view(months[static_cast<size_t>(tm_time.tm_mon)], dest);
263 dest.push_back(' ');
264 fmt_helper::append_int(tm_time.tm_mday, dest);
265 dest.push_back(' ');
266 // time
267
268 fmt_helper::pad2(tm_time.tm_hour, dest);
269 dest.push_back(':');
270 fmt_helper::pad2(tm_time.tm_min, dest);
271 dest.push_back(':');
272 fmt_helper::pad2(tm_time.tm_sec, dest);
273 dest.push_back(' ');
274 fmt_helper::append_int(tm_time.tm_year + 1900, dest);
275 }
276};
277
278// year - 2 digit
279template<typename ScopedPadder>
280class C_formatter final : public flag_formatter
281{
282public:
283 explicit C_formatter(padding_info padinfo)
284 : flag_formatter(padinfo)
285 {}
286
287 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
288 {
289 const size_t field_size = 2;
290 ScopedPadder p(field_size, padinfo_, dest);
291 fmt_helper::pad2(tm_time.tm_year % 100, dest);
292 }
293};
294
295// Short MM/DD/YY date, equivalent to %m/%d/%y 08/23/01
296template<typename ScopedPadder>
297class D_formatter final : public flag_formatter
298{
299public:
300 explicit D_formatter(padding_info padinfo)
301 : flag_formatter(padinfo)
302 {}
303
304 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
305 {
306 const size_t field_size = 10;
307 ScopedPadder p(field_size, padinfo_, dest);
308
309 fmt_helper::pad2(tm_time.tm_mon + 1, dest);
310 dest.push_back('/');
311 fmt_helper::pad2(tm_time.tm_mday, dest);
312 dest.push_back('/');
313 fmt_helper::pad2(tm_time.tm_year % 100, dest);
314 }
315};
316
317// year - 4 digit
318template<typename ScopedPadder>
319class Y_formatter final : public flag_formatter
320{
321public:
322 explicit Y_formatter(padding_info padinfo)
323 : flag_formatter(padinfo)
324 {}
325
326 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
327 {
328 const size_t field_size = 4;
329 ScopedPadder p(field_size, padinfo_, dest);
330 fmt_helper::append_int(tm_time.tm_year + 1900, dest);
331 }
332};
333
334// month 1-12
335template<typename ScopedPadder>
336class m_formatter final : public flag_formatter
337{
338public:
339 explicit m_formatter(padding_info padinfo)
340 : flag_formatter(padinfo)
341 {}
342
343 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
344 {
345 const size_t field_size = 2;
346 ScopedPadder p(field_size, padinfo_, dest);
347 fmt_helper::pad2(tm_time.tm_mon + 1, dest);
348 }
349};
350
351// day of month 1-31
352template<typename ScopedPadder>
353class d_formatter final : public flag_formatter
354{
355public:
356 explicit d_formatter(padding_info padinfo)
357 : flag_formatter(padinfo)
358 {}
359
360 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
361 {
362 const size_t field_size = 2;
363 ScopedPadder p(field_size, padinfo_, dest);
364 fmt_helper::pad2(tm_time.tm_mday, dest);
365 }
366};
367
368// hours in 24 format 0-23
369template<typename ScopedPadder>
370class H_formatter final : public flag_formatter
371{
372public:
373 explicit H_formatter(padding_info padinfo)
374 : flag_formatter(padinfo)
375 {}
376
377 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
378 {
379 const size_t field_size = 2;
380 ScopedPadder p(field_size, padinfo_, dest);
381 fmt_helper::pad2(tm_time.tm_hour, dest);
382 }
383};
384
385// hours in 12 format 1-12
386template<typename ScopedPadder>
387class I_formatter final : public flag_formatter
388{
389public:
390 explicit I_formatter(padding_info padinfo)
391 : flag_formatter(padinfo)
392 {}
393
394 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
395 {
396 const size_t field_size = 2;
397 ScopedPadder p(field_size, padinfo_, dest);
398 fmt_helper::pad2(to12h(tm_time), dest);
399 }
400};
401
402// minutes 0-59
403template<typename ScopedPadder>
404class M_formatter final : public flag_formatter
405{
406public:
407 explicit M_formatter(padding_info padinfo)
408 : flag_formatter(padinfo)
409 {}
410
411 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
412 {
413 const size_t field_size = 2;
414 ScopedPadder p(field_size, padinfo_, dest);
415 fmt_helper::pad2(tm_time.tm_min, dest);
416 }
417};
418
419// seconds 0-59
420template<typename ScopedPadder>
421class S_formatter final : public flag_formatter
422{
423public:
424 explicit S_formatter(padding_info padinfo)
425 : flag_formatter(padinfo)
426 {}
427
428 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
429 {
430 const size_t field_size = 2;
431 ScopedPadder p(field_size, padinfo_, dest);
432 fmt_helper::pad2(tm_time.tm_sec, dest);
433 }
434};
435
436// milliseconds
437template<typename ScopedPadder>
438class e_formatter final : public flag_formatter
439{
440public:
441 explicit e_formatter(padding_info padinfo)
442 : flag_formatter(padinfo)
443 {}
444
445 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
446 {
447 auto millis = fmt_helper::time_fraction<std::chrono::milliseconds>(msg.time);
448 const size_t field_size = 3;
449 ScopedPadder p(field_size, padinfo_, dest);
450 fmt_helper::pad3(static_cast<uint32_t>(millis.count()), dest);
451 }
452};
453
454// microseconds
455template<typename ScopedPadder>
456class f_formatter final : public flag_formatter
457{
458public:
459 explicit f_formatter(padding_info padinfo)
460 : flag_formatter(padinfo)
461 {}
462
463 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
464 {
465 auto micros = fmt_helper::time_fraction<std::chrono::microseconds>(msg.time);
466
467 const size_t field_size = 6;
468 ScopedPadder p(field_size, padinfo_, dest);
469 fmt_helper::pad6(static_cast<size_t>(micros.count()), dest);
470 }
471};
472
473// nanoseconds
474template<typename ScopedPadder>
475class F_formatter final : public flag_formatter
476{
477public:
478 explicit F_formatter(padding_info padinfo)
479 : flag_formatter(padinfo)
480 {}
481
482 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
483 {
484 auto ns = fmt_helper::time_fraction<std::chrono::nanoseconds>(msg.time);
485 const size_t field_size = 9;
486 ScopedPadder p(field_size, padinfo_, dest);
487 fmt_helper::pad9(static_cast<size_t>(ns.count()), dest);
488 }
489};
490
491// seconds since epoch
492template<typename ScopedPadder>
493class E_formatter final : public flag_formatter
494{
495public:
496 explicit E_formatter(padding_info padinfo)
497 : flag_formatter(padinfo)
498 {}
499
500 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
501 {
502 const size_t field_size = 10;
503 ScopedPadder p(field_size, padinfo_, dest);
504 auto duration = msg.time.time_since_epoch();
505 auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration).count();
506 fmt_helper::append_int(seconds, dest);
507 }
508};
509
510// AM/PM
511template<typename ScopedPadder>
512class p_formatter final : public flag_formatter
513{
514public:
515 explicit p_formatter(padding_info padinfo)
516 : flag_formatter(padinfo)
517 {}
518
519 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
520 {
521 const size_t field_size = 2;
522 ScopedPadder p(field_size, padinfo_, dest);
523 fmt_helper::append_string_view(ampm(tm_time), dest);
524 }
525};
526
527// 12 hour clock 02:55:02 pm
528template<typename ScopedPadder>
529class r_formatter final : public flag_formatter
530{
531public:
532 explicit r_formatter(padding_info padinfo)
533 : flag_formatter(padinfo)
534 {}
535
536 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
537 {
538 const size_t field_size = 11;
539 ScopedPadder p(field_size, padinfo_, dest);
540
541 fmt_helper::pad2(to12h(tm_time), dest);
542 dest.push_back(':');
543 fmt_helper::pad2(tm_time.tm_min, dest);
544 dest.push_back(':');
545 fmt_helper::pad2(tm_time.tm_sec, dest);
546 dest.push_back(' ');
547 fmt_helper::append_string_view(ampm(tm_time), dest);
548 }
549};
550
551// 24-hour HH:MM time, equivalent to %H:%M
552template<typename ScopedPadder>
553class R_formatter final : public flag_formatter
554{
555public:
556 explicit R_formatter(padding_info padinfo)
557 : flag_formatter(padinfo)
558 {}
559
560 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
561 {
562 const size_t field_size = 5;
563 ScopedPadder p(field_size, padinfo_, dest);
564
565 fmt_helper::pad2(tm_time.tm_hour, dest);
566 dest.push_back(':');
567 fmt_helper::pad2(tm_time.tm_min, dest);
568 }
569};
570
571// ISO 8601 time format (HH:MM:SS), equivalent to %H:%M:%S
572template<typename ScopedPadder>
573class T_formatter final : public flag_formatter
574{
575public:
576 explicit T_formatter(padding_info padinfo)
577 : flag_formatter(padinfo)
578 {}
579
580 void format(const details::log_msg &, const std::tm &tm_time, memory_buf_t &dest) override
581 {
582 const size_t field_size = 8;
583 ScopedPadder p(field_size, padinfo_, dest);
584
585 fmt_helper::pad2(tm_time.tm_hour, dest);
586 dest.push_back(':');
587 fmt_helper::pad2(tm_time.tm_min, dest);
588 dest.push_back(':');
589 fmt_helper::pad2(tm_time.tm_sec, dest);
590 }
591};
592
593// ISO 8601 offset from UTC in timezone (+-HH:MM)
594template<typename ScopedPadder>
595class z_formatter final : public flag_formatter
596{
597public:
598 explicit z_formatter(padding_info padinfo)
599 : flag_formatter(padinfo)
600 {}
601
602 z_formatter() = default;
603 z_formatter(const z_formatter &) = delete;
604 z_formatter &operator=(const z_formatter &) = delete;
605
606 void format(const details::log_msg &msg, const std::tm &tm_time, memory_buf_t &dest) override
607 {
608 const size_t field_size = 6;
609 ScopedPadder p(field_size, padinfo_, dest);
610
611 auto total_minutes = get_cached_offset(msg, tm_time);
612 bool is_negative = total_minutes < 0;
613 if (is_negative)
614 {
615 total_minutes = -total_minutes;
616 dest.push_back('-');
617 }
618 else
619 {
620 dest.push_back('+');
621 }
622
623 fmt_helper::pad2(total_minutes / 60, dest); // hours
624 dest.push_back(':');
625 fmt_helper::pad2(total_minutes % 60, dest); // minutes
626 }
627
628private:
629 log_clock::time_point last_update_{std::chrono::seconds(0)};
630 int offset_minutes_{0};
631
632 int get_cached_offset(const log_msg &msg, const std::tm &tm_time)
633 {
634 // refresh every 10 seconds
635 if (msg.time - last_update_ >= std::chrono::seconds(10))
636 {
637 offset_minutes_ = os::utc_minutes_offset(tm_time);
638 last_update_ = msg.time;
639 }
640 return offset_minutes_;
641 }
642};
643
644// Thread id
645template<typename ScopedPadder>
646class t_formatter final : public flag_formatter
647{
648public:
649 explicit t_formatter(padding_info padinfo)
650 : flag_formatter(padinfo)
651 {}
652
653 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
654 {
655 const auto field_size = ScopedPadder::count_digits(msg.thread_id);
656 ScopedPadder p(field_size, padinfo_, dest);
657 fmt_helper::append_int(msg.thread_id, dest);
658 }
659};
660
661// Current pid
662template<typename ScopedPadder>
663class pid_formatter final : public flag_formatter
664{
665public:
666 explicit pid_formatter(padding_info padinfo)
667 : flag_formatter(padinfo)
668 {}
669
670 void format(const details::log_msg &, const std::tm &, memory_buf_t &dest) override
671 {
672 const auto pid = static_cast<uint32_t>(details::os::pid());
673 auto field_size = ScopedPadder::count_digits(pid);
674 ScopedPadder p(field_size, padinfo_, dest);
675 fmt_helper::append_int(pid, dest);
676 }
677};
678
679template<typename ScopedPadder>
680class v_formatter final : public flag_formatter
681{
682public:
683 explicit v_formatter(padding_info padinfo)
684 : flag_formatter(padinfo)
685 {}
686
687 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
688 {
689 ScopedPadder p(msg.payload.size(), padinfo_, dest);
690 fmt_helper::append_string_view(msg.payload, dest);
691 }
692};
693
694class ch_formatter final : public flag_formatter
695{
696public:
697 explicit ch_formatter(char ch)
698 : ch_(ch)
699 {}
700
701 void format(const details::log_msg &, const std::tm &, memory_buf_t &dest) override
702 {
703 dest.push_back(ch_);
704 }
705
706private:
707 char ch_;
708};
709
710// aggregate user chars to display as is
711class aggregate_formatter final : public flag_formatter
712{
713public:
714 aggregate_formatter() = default;
715
716 void add_ch(char ch)
717 {
718 str_ += ch;
719 }
720 void format(const details::log_msg &, const std::tm &, memory_buf_t &dest) override
721 {
722 fmt_helper::append_string_view(str_, dest);
723 }
724
725private:
726 std::string str_;
727};
728
729// mark the color range. expect it to be in the form of "%^colored text%$"
730class color_start_formatter final : public flag_formatter
731{
732public:
733 explicit color_start_formatter(padding_info padinfo)
734 : flag_formatter(padinfo)
735 {}
736
737 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
738 {
739 msg.color_range_start = dest.size();
740 }
741};
742
743class color_stop_formatter final : public flag_formatter
744{
745public:
746 explicit color_stop_formatter(padding_info padinfo)
747 : flag_formatter(padinfo)
748 {}
749
750 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
751 {
752 msg.color_range_end = dest.size();
753 }
754};
755
756// print source location
757template<typename ScopedPadder>
758class source_location_formatter final : public flag_formatter
759{
760public:
761 explicit source_location_formatter(padding_info padinfo)
762 : flag_formatter(padinfo)
763 {}
764
765 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
766 {
767 if (msg.source.empty())
768 {
769 ScopedPadder p(0, padinfo_, dest);
770 return;
771 }
772
773 size_t text_size;
774 if (padinfo_.enabled())
775 {
776 // calc text size for padding based on "filename:line"
777 text_size = std::char_traits<char>::length(msg.source.filename) + ScopedPadder::count_digits(msg.source.line) + 1;
778 }
779 else
780 {
781 text_size = 0;
782 }
783
784 ScopedPadder p(text_size, padinfo_, dest);
785 fmt_helper::append_string_view(msg.source.filename, dest);
786 dest.push_back(':');
787 fmt_helper::append_int(msg.source.line, dest);
788 }
789};
790
791// print source filename
792template<typename ScopedPadder>
793class source_filename_formatter final : public flag_formatter
794{
795public:
796 explicit source_filename_formatter(padding_info padinfo)
797 : flag_formatter(padinfo)
798 {}
799
800 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
801 {
802 if (msg.source.empty())
803 {
804 ScopedPadder p(0, padinfo_, dest);
805 return;
806 }
807 size_t text_size = padinfo_.enabled() ? std::char_traits<char>::length(msg.source.filename) : 0;
808 ScopedPadder p(text_size, padinfo_, dest);
809 fmt_helper::append_string_view(msg.source.filename, dest);
810 }
811};
812
813template<typename ScopedPadder>
814class short_filename_formatter final : public flag_formatter
815{
816public:
817 explicit short_filename_formatter(padding_info padinfo)
818 : flag_formatter(padinfo)
819 {}
820
821#ifdef _MSC_VER
822# pragma warning(push)
823# pragma warning(disable : 4127) // consider using 'if constexpr' instead
824#endif // _MSC_VER
825 static const char *basename(const char *filename)
826 {
827 // if the size is 2 (1 character + null terminator) we can use the more efficient strrchr
828 // the branch will be elided by optimizations
829 if (sizeof(os::folder_seps) == 2)
830 {
831 const char *rv = std::strrchr(filename, os::folder_seps[0]);
832 return rv != nullptr ? rv + 1 : filename;
833 }
834 else
835 {
836 const std::reverse_iterator<const char *> begin(filename + std::strlen(filename));
837 const std::reverse_iterator<const char *> end(filename);
838
839 const auto it = std::find_first_of(begin, end, std::begin(os::folder_seps), std::end(os::folder_seps) - 1);
840 return it != end ? it.base() : filename;
841 }
842 }
843#ifdef _MSC_VER
844# pragma warning(pop)
845#endif // _MSC_VER
846
847 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
848 {
849 if (msg.source.empty())
850 {
851 ScopedPadder p(0, padinfo_, dest);
852 return;
853 }
854 auto filename = basename(msg.source.filename);
855 size_t text_size = padinfo_.enabled() ? std::char_traits<char>::length(filename) : 0;
856 ScopedPadder p(text_size, padinfo_, dest);
857 fmt_helper::append_string_view(filename, dest);
858 }
859};
860
861template<typename ScopedPadder>
862class source_linenum_formatter final : public flag_formatter
863{
864public:
865 explicit source_linenum_formatter(padding_info padinfo)
866 : flag_formatter(padinfo)
867 {}
868
869 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
870 {
871 if (msg.source.empty())
872 {
873 ScopedPadder p(0, padinfo_, dest);
874 return;
875 }
876
877 auto field_size = ScopedPadder::count_digits(msg.source.line);
878 ScopedPadder p(field_size, padinfo_, dest);
879 fmt_helper::append_int(msg.source.line, dest);
880 }
881};
882
883// print source funcname
884template<typename ScopedPadder>
885class source_funcname_formatter final : public flag_formatter
886{
887public:
888 explicit source_funcname_formatter(padding_info padinfo)
889 : flag_formatter(padinfo)
890 {}
891
892 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
893 {
894 if (msg.source.empty())
895 {
896 ScopedPadder p(0, padinfo_, dest);
897 return;
898 }
899 size_t text_size = padinfo_.enabled() ? std::char_traits<char>::length(msg.source.funcname) : 0;
900 ScopedPadder p(text_size, padinfo_, dest);
901 fmt_helper::append_string_view(msg.source.funcname, dest);
902 }
903};
904
905// print elapsed time since last message
906template<typename ScopedPadder, typename Units>
907class elapsed_formatter final : public flag_formatter
908{
909public:
910 using DurationUnits = Units;
911
912 explicit elapsed_formatter(padding_info padinfo)
913 : flag_formatter(padinfo)
914 , last_message_time_(log_clock::now())
915 {}
916
917 void format(const details::log_msg &msg, const std::tm &, memory_buf_t &dest) override
918 {
919 auto delta = (std::max)(msg.time - last_message_time_, log_clock::duration::zero());
920 auto delta_units = std::chrono::duration_cast<DurationUnits>(delta);
921 last_message_time_ = msg.time;
922 auto delta_count = static_cast<size_t>(delta_units.count());
923 auto n_digits = static_cast<size_t>(ScopedPadder::count_digits(delta_count));
924 ScopedPadder p(n_digits, padinfo_, dest);
925 fmt_helper::append_int(delta_count, dest);
926 }
927
928private:
929 log_clock::time_point last_message_time_;
930};
931
932// Full info formatter
933// pattern: [%Y-%m-%d %H:%M:%S.%e] [%n] [%l] [%s:%#] %v
934class full_formatter final : public flag_formatter
935{
936public:
937 explicit full_formatter(padding_info padinfo)
938 : flag_formatter(padinfo)
939 {}
940
941 void format(const details::log_msg &msg, const std::tm &tm_time, memory_buf_t &dest) override
942 {
943 using std::chrono::duration_cast;
944 using std::chrono::milliseconds;
945 using std::chrono::seconds;
946
947 // cache the date/time part for the next second.
948 auto duration = msg.time.time_since_epoch();
949 auto secs = duration_cast<seconds>(duration);
950
951 if (cache_timestamp_ != secs || cached_datetime_.size() == 0)
952 {
953 cached_datetime_.clear();
954 cached_datetime_.push_back('[');
955 fmt_helper::append_int(tm_time.tm_year + 1900, cached_datetime_);
956 cached_datetime_.push_back('-');
957
958 fmt_helper::pad2(tm_time.tm_mon + 1, cached_datetime_);
959 cached_datetime_.push_back('-');
960
961 fmt_helper::pad2(tm_time.tm_mday, cached_datetime_);
962 cached_datetime_.push_back(' ');
963
964 fmt_helper::pad2(tm_time.tm_hour, cached_datetime_);
965 cached_datetime_.push_back(':');
966
967 fmt_helper::pad2(tm_time.tm_min, cached_datetime_);
968 cached_datetime_.push_back(':');
969
970 fmt_helper::pad2(tm_time.tm_sec, cached_datetime_);
971 cached_datetime_.push_back('.');
972
973 cache_timestamp_ = secs;
974 }
975 dest.append(cached_datetime_.begin(), cached_datetime_.end());
976
977 auto millis = fmt_helper::time_fraction<milliseconds>(msg.time);
978 fmt_helper::pad3(static_cast<uint32_t>(millis.count()), dest);
979 dest.push_back(']');
980 dest.push_back(' ');
981
982 // append logger name if exists
983 if (msg.logger_name.size() > 0)
984 {
985 dest.push_back('[');
986 fmt_helper::append_string_view(msg.logger_name, dest);
987 dest.push_back(']');
988 dest.push_back(' ');
989 }
990
991 dest.push_back('[');
992 // wrap the level name with color
993 msg.color_range_start = dest.size();
994 // fmt_helper::append_string_view(level::to_c_str(msg.level), dest);
995 fmt_helper::append_string_view(level::to_string_view(msg.level), dest);
996 msg.color_range_end = dest.size();
997 dest.push_back(']');
998 dest.push_back(' ');
999
1000 // add source location if present
1001 if (!msg.source.empty())
1002 {
1003 dest.push_back('[');
1004 const char *filename = details::short_filename_formatter<details::null_scoped_padder>::basename(msg.source.filename);
1005 fmt_helper::append_string_view(filename, dest);
1006 dest.push_back(':');
1007 fmt_helper::append_int(msg.source.line, dest);
1008 dest.push_back(']');
1009 dest.push_back(' ');
1010 }
1011 // fmt_helper::append_string_view(msg.msg(), dest);
1012 fmt_helper::append_string_view(msg.payload, dest);
1013 }
1014
1015private:
1016 std::chrono::seconds cache_timestamp_{0};
1017 memory_buf_t cached_datetime_;
1018};
1019
1020} // namespace details
1021
1022SPDLOG_INLINE pattern_formatter::pattern_formatter(
1023 std::string pattern, pattern_time_type time_type, std::string eol, custom_flags custom_user_flags)
1024 : pattern_(std::move(pattern))
1025 , eol_(std::move(eol))
1026 , pattern_time_type_(time_type)
1027 , need_localtime_(false)
1028 , last_log_secs_(0)
1029 , custom_handlers_(std::move(custom_user_flags))
1030{
1031 std::memset(&cached_tm_, 0, sizeof(cached_tm_));
1032 compile_pattern_(pattern_);
1033}
1034
1035// use by default full formatter for if pattern is not given
1036SPDLOG_INLINE pattern_formatter::pattern_formatter(pattern_time_type time_type, std::string eol)
1037 : pattern_("%+")
1038 , eol_(std::move(eol))
1039 , pattern_time_type_(time_type)
1040 , need_localtime_(true)
1041 , last_log_secs_(0)
1042{
1043 std::memset(&cached_tm_, 0, sizeof(cached_tm_));
1044 formatters_.push_back(details::make_unique<details::full_formatter>(details::padding_info{}));
1045}
1046
1047SPDLOG_INLINE std::unique_ptr<formatter> pattern_formatter::clone() const
1048{
1049 custom_flags cloned_custom_formatters;
1050 for (auto &it : custom_handlers_)
1051 {
1052 cloned_custom_formatters[it.first] = it.second->clone();
1053 }
1054 auto cloned = details::make_unique<pattern_formatter>(pattern_, pattern_time_type_, eol_, std::move(cloned_custom_formatters));
1055 cloned->need_localtime(need_localtime_);
1056#if defined(__GNUC__) && __GNUC__ < 5
1057 return std::move(cloned);
1058#else
1059 return cloned;
1060#endif
1061}
1062
1063SPDLOG_INLINE void pattern_formatter::format(const details::log_msg &msg, memory_buf_t &dest)
1064{
1065 if (need_localtime_)
1066 {
1067 const auto secs = std::chrono::duration_cast<std::chrono::seconds>(msg.time.time_since_epoch());
1068 if (secs != last_log_secs_)
1069 {
1070 cached_tm_ = get_time_(msg);
1071 last_log_secs_ = secs;
1072 }
1073 }
1074
1075 for (auto &f : formatters_)
1076 {
1077 f->format(msg, cached_tm_, dest);
1078 }
1079 // write eol
1080 details::fmt_helper::append_string_view(eol_, dest);
1081}
1082
1083SPDLOG_INLINE void pattern_formatter::set_pattern(std::string pattern)
1084{
1085 pattern_ = std::move(pattern);
1086 need_localtime_ = false;
1087 compile_pattern_(pattern_);
1088}
1089
1090SPDLOG_INLINE void pattern_formatter::need_localtime(bool need)
1091{
1092 need_localtime_ = need;
1093}
1094
1095SPDLOG_INLINE std::tm pattern_formatter::get_time_(const details::log_msg &msg)
1096{
1097 if (pattern_time_type_ == pattern_time_type::local)
1098 {
1099 return details::os::localtime(log_clock::to_time_t(msg.time));
1100 }
1101 return details::os::gmtime(log_clock::to_time_t(msg.time));
1102}
1103
1104template<typename Padder>
1105SPDLOG_INLINE void pattern_formatter::handle_flag_(char flag, details::padding_info padding)
1106{
1107 // process custom flags
1108 auto it = custom_handlers_.find(flag);
1109 if (it != custom_handlers_.end())
1110 {
1111 auto custom_handler = it->second->clone();
1112 custom_handler->set_padding_info(padding);
1113 formatters_.push_back(std::move(custom_handler));
1114 return;
1115 }
1116
1117 // process built-in flags
1118 switch (flag)
1119 {
1120 case ('+'): // default formatter
1121 formatters_.push_back(details::make_unique<details::full_formatter>(padding));
1122 need_localtime_ = true;
1123 break;
1124
1125 case 'n': // logger name
1126 formatters_.push_back(details::make_unique<details::name_formatter<Padder>>(padding));
1127 break;
1128
1129 case 'l': // level
1130 formatters_.push_back(details::make_unique<details::level_formatter<Padder>>(padding));
1131 break;
1132
1133 case 'L': // short level
1134 formatters_.push_back(details::make_unique<details::short_level_formatter<Padder>>(padding));
1135 break;
1136
1137 case ('t'): // thread id
1138 formatters_.push_back(details::make_unique<details::t_formatter<Padder>>(padding));
1139 break;
1140
1141 case ('v'): // the message text
1142 formatters_.push_back(details::make_unique<details::v_formatter<Padder>>(padding));
1143 break;
1144
1145 case ('a'): // weekday
1146 formatters_.push_back(details::make_unique<details::a_formatter<Padder>>(padding));
1147 need_localtime_ = true;
1148 break;
1149
1150 case ('A'): // short weekday
1151 formatters_.push_back(details::make_unique<details::A_formatter<Padder>>(padding));
1152 need_localtime_ = true;
1153 break;
1154
1155 case ('b'):
1156 case ('h'): // month
1157 formatters_.push_back(details::make_unique<details::b_formatter<Padder>>(padding));
1158 need_localtime_ = true;
1159 break;
1160
1161 case ('B'): // short month
1162 formatters_.push_back(details::make_unique<details::B_formatter<Padder>>(padding));
1163 need_localtime_ = true;
1164 break;
1165
1166 case ('c'): // datetime
1167 formatters_.push_back(details::make_unique<details::c_formatter<Padder>>(padding));
1168 need_localtime_ = true;
1169 break;
1170
1171 case ('C'): // year 2 digits
1172 formatters_.push_back(details::make_unique<details::C_formatter<Padder>>(padding));
1173 need_localtime_ = true;
1174 break;
1175
1176 case ('Y'): // year 4 digits
1177 formatters_.push_back(details::make_unique<details::Y_formatter<Padder>>(padding));
1178 need_localtime_ = true;
1179 break;
1180
1181 case ('D'):
1182 case ('x'): // datetime MM/DD/YY
1183 formatters_.push_back(details::make_unique<details::D_formatter<Padder>>(padding));
1184 need_localtime_ = true;
1185 break;
1186
1187 case ('m'): // month 1-12
1188 formatters_.push_back(details::make_unique<details::m_formatter<Padder>>(padding));
1189 need_localtime_ = true;
1190 break;
1191
1192 case ('d'): // day of month 1-31
1193 formatters_.push_back(details::make_unique<details::d_formatter<Padder>>(padding));
1194 need_localtime_ = true;
1195 break;
1196
1197 case ('H'): // hours 24
1198 formatters_.push_back(details::make_unique<details::H_formatter<Padder>>(padding));
1199 need_localtime_ = true;
1200 break;
1201
1202 case ('I'): // hours 12
1203 formatters_.push_back(details::make_unique<details::I_formatter<Padder>>(padding));
1204 need_localtime_ = true;
1205 break;
1206
1207 case ('M'): // minutes
1208 formatters_.push_back(details::make_unique<details::M_formatter<Padder>>(padding));
1209 need_localtime_ = true;
1210 break;
1211
1212 case ('S'): // seconds
1213 formatters_.push_back(details::make_unique<details::S_formatter<Padder>>(padding));
1214 need_localtime_ = true;
1215 break;
1216
1217 case ('e'): // milliseconds
1218 formatters_.push_back(details::make_unique<details::e_formatter<Padder>>(padding));
1219 break;
1220
1221 case ('f'): // microseconds
1222 formatters_.push_back(details::make_unique<details::f_formatter<Padder>>(padding));
1223 break;
1224
1225 case ('F'): // nanoseconds
1226 formatters_.push_back(details::make_unique<details::F_formatter<Padder>>(padding));
1227 break;
1228
1229 case ('E'): // seconds since epoch
1230 formatters_.push_back(details::make_unique<details::E_formatter<Padder>>(padding));
1231 break;
1232
1233 case ('p'): // am/pm
1234 formatters_.push_back(details::make_unique<details::p_formatter<Padder>>(padding));
1235 need_localtime_ = true;
1236 break;
1237
1238 case ('r'): // 12 hour clock 02:55:02 pm
1239 formatters_.push_back(details::make_unique<details::r_formatter<Padder>>(padding));
1240 need_localtime_ = true;
1241 break;
1242
1243 case ('R'): // 24-hour HH:MM time
1244 formatters_.push_back(details::make_unique<details::R_formatter<Padder>>(padding));
1245 need_localtime_ = true;
1246 break;
1247
1248 case ('T'):
1249 case ('X'): // ISO 8601 time format (HH:MM:SS)
1250 formatters_.push_back(details::make_unique<details::T_formatter<Padder>>(padding));
1251 need_localtime_ = true;
1252 break;
1253
1254 case ('z'): // timezone
1255 formatters_.push_back(details::make_unique<details::z_formatter<Padder>>(padding));
1256 need_localtime_ = true;
1257 break;
1258
1259 case ('P'): // pid
1260 formatters_.push_back(details::make_unique<details::pid_formatter<Padder>>(padding));
1261 break;
1262
1263 case ('^'): // color range start
1264 formatters_.push_back(details::make_unique<details::color_start_formatter>(padding));
1265 break;
1266
1267 case ('$'): // color range end
1268 formatters_.push_back(details::make_unique<details::color_stop_formatter>(padding));
1269 break;
1270
1271 case ('@'): // source location (filename:filenumber)
1272 formatters_.push_back(details::make_unique<details::source_location_formatter<Padder>>(padding));
1273 break;
1274
1275 case ('s'): // short source filename - without directory name
1276 formatters_.push_back(details::make_unique<details::short_filename_formatter<Padder>>(padding));
1277 break;
1278
1279 case ('g'): // full source filename
1280 formatters_.push_back(details::make_unique<details::source_filename_formatter<Padder>>(padding));
1281 break;
1282
1283 case ('#'): // source line number
1284 formatters_.push_back(details::make_unique<details::source_linenum_formatter<Padder>>(padding));
1285 break;
1286
1287 case ('!'): // source funcname
1288 formatters_.push_back(details::make_unique<details::source_funcname_formatter<Padder>>(padding));
1289 break;
1290
1291 case ('%'): // % char
1292 formatters_.push_back(details::make_unique<details::ch_formatter>('%'));
1293 break;
1294
1295 case ('u'): // elapsed time since last log message in nanos
1296 formatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::nanoseconds>>(padding));
1297 break;
1298
1299 case ('i'): // elapsed time since last log message in micros
1300 formatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::microseconds>>(padding));
1301 break;
1302
1303 case ('o'): // elapsed time since last log message in millis
1304 formatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::milliseconds>>(padding));
1305 break;
1306
1307 case ('O'): // elapsed time since last log message in seconds
1308 formatters_.push_back(details::make_unique<details::elapsed_formatter<Padder, std::chrono::seconds>>(padding));
1309 break;
1310
1311 default: // Unknown flag appears as is
1312 auto unknown_flag = details::make_unique<details::aggregate_formatter>();
1313
1314 if (!padding.truncate_)
1315 {
1316 unknown_flag->add_ch('%');
1317 unknown_flag->add_ch(flag);
1318 formatters_.push_back((std::move(unknown_flag)));
1319 }
1320 // fix issue #1617 (prev char was '!' and should have been treated as funcname flag instead of truncating flag)
1321 // spdlog::set_pattern("[%10!] %v") => "[ main] some message"
1322 // spdlog::set_pattern("[%3!!] %v") => "[mai] some message"
1323 else
1324 {
1325 padding.truncate_ = false;
1326 formatters_.push_back(details::make_unique<details::source_funcname_formatter<Padder>>(padding));
1327 unknown_flag->add_ch(flag);
1328 formatters_.push_back((std::move(unknown_flag)));
1329 }
1330
1331 break;
1332 }
1333}
1334
1335// Extract given pad spec (e.g. %8X, %=8X, %-8!X, %8!X, %=8!X, %-8!X, %+8!X)
1336// Advance the given it pass the end of the padding spec found (if any)
1337// Return padding.
1338SPDLOG_INLINE details::padding_info pattern_formatter::handle_padspec_(std::string::const_iterator &it, std::string::const_iterator end)
1339{
1340 using details::padding_info;
1341 using details::scoped_padder;
1342 const size_t max_width = 64;
1343 if (it == end)
1344 {
1345 return padding_info{};
1346 }
1347
1348 padding_info::pad_side side;
1349 switch (*it)
1350 {
1351 case '-':
1352 side = padding_info::pad_side::right;
1353 ++it;
1354 break;
1355 case '=':
1356 side = padding_info::pad_side::center;
1357 ++it;
1358 break;
1359 default:
1360 side = details::padding_info::pad_side::left;
1361 break;
1362 }
1363
1364 if (it == end || !std::isdigit(static_cast<unsigned char>(*it)))
1365 {
1366 return padding_info{}; // no padding if no digit found here
1367 }
1368
1369 auto width = static_cast<size_t>(*it) - '0';
1370 for (++it; it != end && std::isdigit(static_cast<unsigned char>(*it)); ++it)
1371 {
1372 auto digit = static_cast<size_t>(*it) - '0';
1373 width = width * 10 + digit;
1374 }
1375
1376 // search for the optional truncate marker '!'
1377 bool truncate;
1378 if (it != end && *it == '!')
1379 {
1380 truncate = true;
1381 ++it;
1382 }
1383 else
1384 {
1385 truncate = false;
1386 }
1387 return details::padding_info{std::min<size_t>(width, max_width), side, truncate};
1388}
1389
1390SPDLOG_INLINE void pattern_formatter::compile_pattern_(const std::string &pattern)
1391{
1392 auto end = pattern.end();
1393 std::unique_ptr<details::aggregate_formatter> user_chars;
1394 formatters_.clear();
1395 for (auto it = pattern.begin(); it != end; ++it)
1396 {
1397 if (*it == '%')
1398 {
1399 if (user_chars) // append user chars found so far
1400 {
1401 formatters_.push_back(std::move(user_chars));
1402 }
1403
1404 auto padding = handle_padspec_(++it, end);
1405
1406 if (it != end)
1407 {
1408 if (padding.enabled())
1409 {
1410 handle_flag_<details::scoped_padder>(*it, padding);
1411 }
1412 else
1413 {
1414 handle_flag_<details::null_scoped_padder>(*it, padding);
1415 }
1416 }
1417 else
1418 {
1419 break;
1420 }
1421 }
1422 else // chars not following the % sign should be displayed as is
1423 {
1424 if (!user_chars)
1425 {
1426 user_chars = details::make_unique<details::aggregate_formatter>();
1427 }
1428 user_chars->add_ch(*it);
1429 }
1430 }
1431 if (user_chars) // append raw chars found so far
1432 {
1433 formatters_.push_back(std::move(user_chars));
1434 }
1435}
1436} // namespace spdlog
1437