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