LogIt++
Loading...
Searching...
No Matches
PatternCompiler.hpp
Go to the documentation of this file.
1#pragma once
2#ifndef _LOGIT_PATTERN_COMPILER_HPP_INCLUDED
3#define _LOGIT_PATTERN_COMPILER_HPP_INCLUDED
6
7#include <time_shield_cpp/time_shield.hpp>
8#include <vector>
9#include <string>
10#include <sstream>
11#include <iomanip>
12
13namespace logit {
14
18
21 enum class CompileContext {
22 Default,
24 };
25
28 enum class FormatType {
29 // Text
31
32 // Date and Time
33 Year,
34 Month,
35 Day,
36 Hour,
37 Minute,
38 Second,
41 DateTime,
42 ShortDate,
45 TimeStamp,
47
48 // Weekday and Month Names
53
54 // Log Level
55 LogLevel,
57
58 // File and Function
59 FileName,
64
65 // Thread
66 ThreadId,
67
68 // Color
70 EndColor,
71
72 // Message
73 Message
74 };
75
78 std::string static_text;
79
80 // Fields for alignment and width
81 int width = 0;
82 bool left_align = false;
83 bool center_align = false;
84 bool truncate = false;
85 bool strip_ansi = false;
86
93 const std::string& text,
94 bool strip_ansi)
96 };
97
109 int width = 0,
110 bool left = false,
111 bool center = false,
112 bool trunc = false,
113 bool strip_ansi = false) :
115 left_align(left), center_align(center),
117 };
118
124 template<class StreamType>
125 void apply(
126 StreamType& oss,
127 const LogRecord& record,
128 const time_shield::DateTimeStruct& dt) const {
129
131 !record.format.empty() ||
132 !record.args_array.empty())) return;
133
134 std::ostringstream temp_stream;
135 switch (type) {
136 // Text
138 temp_stream << static_text;
139 break;
140
141 // Date and Time
142 case FormatType::Year:
143 temp_stream << dt.year;
144 break;
146 temp_stream << std::setw(2) << std::setfill('0') << dt.mon;
147 break;
148 case FormatType::Day:
149 temp_stream << std::setw(2) << std::setfill('0') << dt.day;
150 break;
151 case FormatType::Hour:
152 temp_stream << std::setw(2) << std::setfill('0') << dt.hour;
153 break;
155 temp_stream << std::setw(2) << std::setfill('0') << dt.min;
156 break;
158 temp_stream << std::setw(2) << std::setfill('0') << dt.sec;
159 break;
161 temp_stream << std::setw(3) << std::setfill('0') << dt.ms;
162 break;
164 temp_stream << std::setw(2) << std::setfill('0') << (dt.year % 100);
165 break;
167 // Format equivalent to 'ctime'
168 char buffer[16];
169 temp_stream << time_shield::to_str(time_shield::day_of_week(dt.year, dt.mon, dt.day), time_shield::FormatType::SHORT_NAME);
170 temp_stream << " ";
171 temp_stream << time_shield::to_str(static_cast<time_shield::Month>(dt.mon), time_shield::FormatType::SHORT_NAME);
172 temp_stream << " ";
173 // day
174 snprintf(buffer, sizeof(buffer), "%2d ", dt.day);
175 temp_stream << buffer;
176 // time
177 snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d ", dt.hour, dt.min, dt.sec);
178 temp_stream << buffer;
179 // year
180 temp_stream << dt.year;
181 break;
182 }
184 temp_stream << std::setw(2) << std::setfill('0') << dt.mon << "/"
185 << std::setw(2) << std::setfill('0') << dt.day << "/"
186 << std::setw(2) << std::setfill('0') << (dt.year % 100);
187 break;
189 temp_stream
190 << std::setw(2) << std::setfill('0') << dt.hour << ":"
191 << std::setw(2) << std::setfill('0') << dt.min << ":"
192 << std::setw(2) << std::setfill('0') << dt.sec;
193 break;
195 temp_stream
196 << dt.year << "-"
197 << std::setw(2) << std::setfill('0') << dt.mon << "-"
198 << std::setw(2) << std::setfill('0') << dt.day;
199 break;
201 temp_stream << time_shield::ms_to_sec(record.timestamp_ms);
202 break;
204 temp_stream << record.timestamp_ms;
205 break;
206
207 // Weekday and Month Names
209 temp_stream << time_shield::to_str(static_cast<time_shield::Month>(dt.mon), time_shield::FormatType::SHORT_NAME);
210 break;
212 temp_stream << time_shield::to_str(static_cast<time_shield::Month>(dt.mon), time_shield::FormatType::FULL_NAME);
213 break;
215 temp_stream << time_shield::to_str(time_shield::day_of_week(dt.year, dt.mon, dt.day), time_shield::FormatType::SHORT_NAME);
216 break;
218 temp_stream << time_shield::to_str(time_shield::day_of_week(dt.year, dt.mon, dt.day), time_shield::FormatType::FULL_NAME);
219 break;
220
221 // Log Level
223 temp_stream << to_string(record.log_level);
224 break;
226 temp_stream << to_string(record.log_level, 1);
227 break;
228
229 // File and Function
231 std::string full_path = record.file;
232 size_t pos = full_path.find_last_of("/\\");
233 if (pos != std::string::npos) {
234 temp_stream << full_path.substr(pos + 1);
235 } else {
236 temp_stream << full_path;
237 }
238 break;
239 }
241 temp_stream << record.file;
242 break;
244 temp_stream << record.file << ":" << record.line;
245 break;
247 temp_stream << record.line;
248 break;
250 temp_stream << record.function;
251 break;
252
253 // Thread
255 temp_stream << record.thread_id;
256 break;
257
258 // Color
260 if (!strip_ansi) {
261 oss << get_log_level_color(record.log_level);
262 }
263 break;
265 if (!strip_ansi) {
267 }
268 break;
269
270 // Message
272 if (!record.format.empty()) {
273 if (record.args_array.empty()) {
274 temp_stream << record.format;
275 break;
276 }
277 using ValueType = VariableValue::ValueType;
278 for (size_t i = 0; i < record.args_array.size(); ++i) {
279 if (!record.print_mode && i) temp_stream << ", ";
280 const auto& arg = record.args_array[i];
281 switch (arg.type) {
282 case ValueType::STRING_VAL:
283 case ValueType::EXCEPTION_VAL:
284 case ValueType::ERROR_CODE_VAL:
285 case ValueType::ENUM_VAL:
286 case ValueType::DURATION_VAL:
287 case ValueType::TIME_POINT_VAL:
288 case ValueType::POINTER_VAL:
289 case ValueType::SMART_POINTER_VAL:
290 case ValueType::VARIANT_VAL:
291 case ValueType::OPTIONAL_VAL:
292 temp_stream << arg.to_string(record.format.c_str());
293 break;
294 default:
295 if (arg.is_literal) {
296 temp_stream << arg.name << ": " << arg.to_string(record.format.c_str());
297 } else {
298 temp_stream << arg.to_string(record.format.c_str());
299 }
300 break;
301 };
302 }
303 } else
304 if (!record.args_array.empty()) {
305 using ValueType = VariableValue::ValueType;
306 for (size_t i = 0; i < record.args_array.size(); ++i) {
307 if (!record.print_mode && i) temp_stream << ", ";
308 const auto& arg = record.args_array[i];
309 switch (arg.type) {
310 case ValueType::STRING_VAL:
311 case ValueType::EXCEPTION_VAL:
312 case ValueType::ERROR_CODE_VAL:
313 temp_stream << arg.to_string();
314 break;
315 case ValueType::ENUM_VAL:
316 if (arg.is_literal) {
317 if (record.print_mode) temp_stream << arg.to_string();
318 else temp_stream << arg.name << ": " << arg.to_string();
319 break;
320 }
321 temp_stream << arg.to_string();
322 break;
323 case ValueType::PATH_VAL:
324 case ValueType::DURATION_VAL:
325 case ValueType::TIME_POINT_VAL:
326 case ValueType::POINTER_VAL:
327 case ValueType::SMART_POINTER_VAL:
328 case ValueType::VARIANT_VAL:
329 case ValueType::OPTIONAL_VAL:
330 if (record.print_mode) temp_stream << arg.to_string();
331 else temp_stream << arg.name << ": " << arg.to_string();
332 break;
333 default:
334 if (arg.is_literal) {
335 if (record.print_mode) temp_stream << arg.to_string();
336 else temp_stream << arg.name << ": " << arg.to_string();
337 break;
338 }
339 temp_stream << arg.to_string();
340 break;
341 };
342 }
343 }
344 break;
345 };
346
347 // Get the string representation
348 std::string result = strip_ansi ? remove_ansi_escape_codes(temp_stream.str()) : temp_stream.str();
349
350 // Truncate if required
351 if (truncate && result.size() > static_cast<size_t>(width)) {
352 switch (type) {
353 // File and Function
358 const std::string placeholder = "..."; // Placeholder for omitted sections
359 int placeholder_size = static_cast<int>(placeholder.size());
360
361 // If the width is less than or equal to the placeholder size, return only the placeholder
362 if (width <= placeholder_size) {
363 result = placeholder.substr(0, width);
364 } else {
365 // Keep portions of the string from the beginning and end
366 size_t keep_size = (width - placeholder_size) / 2; // Portion to keep from each side
367 size_t keep_end = result.size() - keep_size;
368
369 // Construct the result: start + placeholder + end
370 result = result.substr(0, keep_size) + placeholder + result.substr(keep_end);
371 }
372 break;
373 }
374 default:
375 // Standard truncation for other types
376 result = result.substr(0, width);
377 };
378 }
379
380 // Apply alignment and width
381 if (width > 0 && result.size() < static_cast<size_t>(width)) {
382 if (left_align) {
383 // Left alignment
384 oss << std::left << std::setw(width) << result;
385 } else if (center_align) {
386 // Center alignment
387 const int padding = (width - result.size()) / 2;
388 oss << std::string(padding, ' ') << result << std::string(width - padding - result.size(), ' ');
389 } else {
390 // Right alignment (default)
391 oss << std::right << std::setw(width) << result;
392 }
393 } else {
394 // If width not specified, simply append the result
395 oss << result;
396 }
397 }
398
399 private:
400
404 std::string remove_ansi_escape_codes(const std::string& input) const {
405 std::string result;
406 result.reserve(input.size());
407 bool in_escape_sequence = false;
408
409 for (size_t i = 0; i < input.size(); ++i) {
410 if (in_escape_sequence) {
411 if ((input[i] >= 'a' && input[i] <= 'z') || (input[i] >= 'A' && input[i] <= 'Z')) {
412 in_escape_sequence = false;
413 }
414 } else {
415 if (input[i] == '\033' && (i + 1) < input.size() && input[i + 1] == '[') {
416 in_escape_sequence = true;
417 ++i; // Skip '[' after '\033'
418 } else {
419 result += input[i]; // Append non-escape characters to the result
420 }
421 }
422 }
423
424 return result;
425 }
426 }; // FormatInstruction
427
431 public:
433
438 static std::vector<FormatInstruction> compile(
439 const std::string& pattern,
440 CompileContext context = CompileContext::Default) {
441 using FormatType = FormatInstruction::FormatType;
442 std::vector<FormatInstruction> instructions;
443 std::string buffer;
444 buffer.reserve(pattern.size());
445 bool strip_ansi = false;
446
447 for (size_t i = 0; i < pattern.size(); ++i) {
448 char c = pattern[i];
449
450 if (c == '%') {
451 if (!buffer.empty()) {
452 instructions.push_back(FormatInstruction(context, buffer, strip_ansi));
453 buffer.clear();
454 }
455
456 // Handling alignment, width, and truncation
457 bool left_align = false;
458 bool center_align = false;
459 bool truncate = false;
460 int width = 0;
461
462 // Check for alignment and width
463 while ((i + 1) < pattern.size() && (
464 std::isdigit(pattern[i + 1]) ||
465 pattern[i + 1] == '-' ||
466 pattern[i + 1] == '=')) {
467 char next = pattern[++i];
468 if (next == '-') {
469 left_align = true;
470 } else if (next == '=') {
471 center_align = true;
472 } else if (std::isdigit(next)) {
473 width = width * 10 + (next - '0');
474 }
475
476 // Check for truncation '!'
477 if ((i + 1) < pattern.size() && pattern[i + 1] == '!') {
478 truncate = true;
479 ++i;
480 break;
481 }
482 }
483
484 if ((i + 1) < pattern.size()) {
485 char next = pattern[++i];
486 switch (next) {
487 // Date and Time
488 case 'Y':
489 instructions.emplace_back(context, FormatType::Year, width, left_align, center_align, truncate, strip_ansi);
490 break;
491 case 'm':
492 if ((i + 1) < pattern.size() && pattern[i + 1] == 's') {
493 instructions.emplace_back(context, FormatType::MilliSecondTimeStamp, width, left_align, center_align, truncate, strip_ansi);
494 ++i; // Skip 's' after 'm'
495 break;
496 }
497 instructions.emplace_back(context, FormatType::Month, width, left_align, center_align, truncate, strip_ansi);
498 break;
499 case 'd':
500 instructions.emplace_back(context, FormatType::Day, width, left_align, center_align, truncate, strip_ansi);
501 break;
502 case 'H':
503 instructions.emplace_back(context, FormatType::Hour, width, left_align, center_align, truncate, strip_ansi);
504 break;
505 case 'M':
506 instructions.emplace_back(context, FormatType::Minute, width, left_align, center_align, truncate, strip_ansi);
507 break;
508 case 'S':
509 if ((i + 1) < pattern.size() && pattern[i + 1] == 'C') {
510 strip_ansi = true;
511 ++i; // Skip 'C' after 'S'
512 break;
513 }
514 instructions.emplace_back(context, FormatType::Second, width, left_align, center_align, truncate, strip_ansi);
515 break;
516 case 'e':
517 if ((i + 1) < pattern.size() && pattern[i + 1] == 'c') {
518 strip_ansi = false;
519 ++i; // Skip 'c' after 'e'
520 break;
521 }
522 instructions.emplace_back(context, FormatType::Millisecond, width, left_align, center_align, truncate, strip_ansi);
523 break;
524 case 'C':
525 instructions.emplace_back(context, FormatType::TwoDigitYear, width, left_align, center_align, truncate, strip_ansi);
526 break;
527 case 'c':
528 instructions.emplace_back(context, FormatType::DateTime, width, left_align, center_align, truncate, strip_ansi);
529 break;
530 case 'D':
531 instructions.emplace_back(context, FormatType::ShortDate, width, left_align, center_align, truncate, strip_ansi);
532 break;
533 case 'T':
534 case 'X':
535 instructions.emplace_back(context, FormatType::TimeISO8601, width, left_align, center_align, truncate, strip_ansi);
536 break;
537 case 'F':
538 instructions.emplace_back(context, FormatType::DateISO8601, width, left_align, center_align, truncate, strip_ansi);
539 break;
540 case 's':
541 if ((i + 1) < pattern.size() && pattern[i + 1] == 'c') {
542 strip_ansi = true;
543 ++i; // Skip 'c' after 's'
544 break;
545 }
546 case 'E':
547 if ((i + 1) < pattern.size() && pattern[i + 1] == 'C') {
548 strip_ansi = false;
549 ++i; // Skip 'C' after 'E'
550 break;
551 }
552 instructions.emplace_back(context, FormatType::TimeStamp, width, left_align, center_align, truncate, strip_ansi);
553 break;
554
555 // Weekday and Month Names
556 case 'b':
557 if ((i + 1) < pattern.size() && pattern[i + 1] == 's') {
558 instructions.emplace_back(context, FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
559 ++i; // Skip 's' after 'b'
560 break;
561 }
562 instructions.emplace_back(context, FormatType::AbbreviatedMonthName, width, left_align, center_align, truncate, strip_ansi);
563 break;
564 case 'B':
565 instructions.emplace_back(context, FormatType::FullMonthName, width, left_align, center_align, truncate, strip_ansi);
566 break;
567 case 'a':
568 instructions.emplace_back(context, FormatType::AbbreviatedWeekdayName, width, left_align, center_align, truncate, strip_ansi);
569 break;
570 case 'A':
571 instructions.emplace_back(context, FormatType::FullWeekdayName, width, left_align, center_align, truncate, strip_ansi);
572 break;
573
574 // Log Level
575 case 'l':
576 instructions.emplace_back(context, FormatType::LogLevel, width, left_align, center_align, truncate, strip_ansi);
577 break;
578 case 'L':
579 instructions.emplace_back(context, FormatType::ShortLogLevel, width, left_align, center_align, truncate, strip_ansi);
580 break;
581
582 // Thread
583 case 't':
584 instructions.emplace_back(context, FormatType::ThreadId, width, left_align, center_align, truncate, strip_ansi);
585 break;
586
587 // File and Function
588 case 'f':
589 if ((i + 1) < pattern.size() && pattern[i + 1] == 'f' && (i + 2) < pattern.size() && pattern[i + 2] == 'n') {
590 instructions.emplace_back(context, FormatType::FullFileName, width, left_align, center_align, truncate, strip_ansi);
591 i += 2; // Skip 'fn' after 'f'
592 break;
593 }
594 if ((i + 1) < pattern.size() && pattern[i + 1] == 'n') {
595 instructions.emplace_back(context, FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
596 ++i; // Skip 'n' after 'f'
597 break;
598 }
599 instructions.emplace_back(context, FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
600 break;
601 case 'g':
602 instructions.emplace_back(context, FormatType::FullFileName, width, left_align, center_align, truncate, strip_ansi);
603 break;
604 case '@':
605 instructions.emplace_back(context, FormatType::SourceFileAndLine, width, left_align, center_align, truncate, strip_ansi);
606 break;
607 case '#':
608 instructions.emplace_back(context, FormatType::LineNumber, width, left_align, center_align, truncate, strip_ansi);
609 break;
610 case '!':
611 instructions.emplace_back(context, FormatType::FunctionName, width, left_align, center_align, truncate, strip_ansi);
612 break;
613
614 // Color
615 case '^':
616 instructions.emplace_back(context, FormatType::StartColor, 0, false, false, false, strip_ansi);
617 break;
618 case '$':
619 instructions.emplace_back(context, FormatType::EndColor, 0, false, false, false, strip_ansi);
620 break;
621
622 // Message
623 case 'v':
624 instructions.emplace_back(context, FormatType::Message, width, left_align, center_align, truncate, strip_ansi);
625 break;
626
627 //
628 case 'N':
629 if ((i + 2) < pattern.size() && pattern[i + 1] == '(') {
630 size_t end = pattern.find(')', i + 2);
631 if (end == std::string::npos) {
632 ++i;
633 break;
634 }
635 std::string params = pattern.substr(i + 2, end - i - 2);
636 auto no_args_instructions = compile(params, CompileContext::NoArgsFallback);
637 instructions.insert(instructions.end(), no_args_instructions.begin(), no_args_instructions.end());
638 i = end;
639 }
640 break;
641
642 // Escape character or unknown
643 case '%':
644 default:
645 buffer += next; // Unrecognized symbols are recorded as text
646 break;
647 };
648 }
649 } else {
650 buffer += c;
651 }
652 }
653
654 if (!buffer.empty()) {
655 instructions.push_back(FormatInstruction(context, buffer, strip_ansi));
656 }
657 return instructions;
658 }
659 }; // PatternCompiler
660
661}; // namespace logit
662
663#endif // _LOGIT_PATTERN_COMPILER_HPP_INCLUDED
Compiler for log formatting patterns.
static std::vector< FormatInstruction > compile(const std::string &pattern, CompileContext context=CompileContext::Default)
Compiles a pattern string into a list of format instructions.
#define LOGIT_DEFAULT_COLOR
Defines the default color for console output. If LOGIT_DEFAULT_COLOR is not defined,...
The primary namespace for the LogIt++ library.
std::string to_string(LogLevel level, int mode=0)
Convert LogLevel to a std::string representation.
Definition Enums.hpp:89
std::string get_log_level_color(LogLevel log_level)
Get the ANSI color code associated with a log level.
Definition Enums.hpp:131
Structure to store log formatting instructions.
FormatType type
The type of the format instruction.
int width
Width for formatting.
bool truncate
Truncation flag.
std::string static_text
Used only if type == StaticText.
bool strip_ansi
Removes ANSI escape codes (e.g., colors) if true.
bool center_align
Center alignment flag.
void apply(StreamType &oss, const LogRecord &record, const time_shield::DateTimeStruct &dt) const
Apply formatting considering alignment and width.
FormatType
Possible types of instructions for log formatting.
@ FullWeekdayName
A: Full weekday name
@ TimeStamp
s or E: Timestamp in seconds
@ FileName
f, fn, bs: Basename of the source file
@ SourceFileAndLine
%@: Source file and line number
@ StartColor
%^: Start of color range
@ AbbreviatedWeekdayName
a: Abbreviated weekday name
@ EndColor
$: End of color range
@ FullFileName
g, ffn: Full file path
@ DateISO8601
F: Date in ISO 8601 format (Y-m-d)
@ ShortDate
D: Short date (m/d/y)
@ MilliSecondTimeStamp
ms: Timestamp in milliseconds
@ AbbreviatedMonthName
b: Abbreviated month name
@ TimeISO8601
T or X: Time in ISO 8601 format (H:M:S)
CompileContext context
Compilation context, e.g., default or handling no-argument cases.
FormatInstruction(CompileContext context, const std::string &text, bool strip_ansi)
Constructor for static text.
CompileContext
Compilation context for handling special cases, such as when arguments are missing.
@ Default
Standard behavior without modifications.
@ NoArgsFallback
Handle a special pattern for cases with no arguments.
FormatInstruction(CompileContext context, FormatType type, int width=0, bool left=false, bool center=false, bool trunc=false, bool strip_ansi=false)
Constructor for other types.
bool left_align
Left alignment flag.
std::string remove_ansi_escape_codes(const std::string &input) const
Removes ANSI escape codes (including color codes and cursor movement) from a string.
Stores log metadata and content.
Definition LogRecord.hpp:15
const std::string function
Function name.
Definition LogRecord.hpp:20
const int line
Line number in the source file.
Definition LogRecord.hpp:19
std::thread::id thread_id
ID of the logging thread.
Definition LogRecord.hpp:24
const LogLevel log_level
Log level (severity).
Definition LogRecord.hpp:16
const int64_t timestamp_ms
Timestamp in milliseconds.
Definition LogRecord.hpp:17
const std::string file
Source file name.
Definition LogRecord.hpp:18
const std::string format
Format string for the message.
Definition LogRecord.hpp:21
std::vector< VariableValue > args_array
Argument values for the log.
Definition LogRecord.hpp:23
const bool print_mode
Flag to determine whether arguments are printed in a raw format without special symbols.
Definition LogRecord.hpp:26
ValueType
Enumeration of possible value types for VariableValue.