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