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
4
7
8#include <time_shield/time_conversions.hpp>
9#include <vector>
10#include <string>
11#include <sstream>
12#include <iomanip>
13
14namespace logit {
15
19
26
76
79 std::string static_text;
80
81 // Fields for alignment and width
82 int width = 0;
83 bool left_align = false;
84 bool center_align = false;
85 bool truncate = false;
86 bool strip_ansi = false;
87
94 const std::string& text,
95 bool strip_ansi)
97 };
98
110 int width = 0,
111 bool left = false,
112 bool center = false,
113 bool trunc = false,
114 bool strip_ansi = false) :
116 left_align(left), center_align(center),
118 };
119
125 template<class StreamType>
126 void apply(
127 StreamType& oss,
128 const LogRecord& record,
129 const time_shield::DateTimeStruct& dt) const {
130
132 !record.format.empty() ||
133 !record.args_array.empty())) return;
134
135 std::ostringstream temp_stream;
136 switch (type) {
137 // Text
139 temp_stream << static_text;
140 break;
141
142 // Date and Time
143 case FormatType::Year:
144 temp_stream << dt.year;
145 break;
147 temp_stream << std::setw(2) << std::setfill('0') << dt.mon;
148 break;
149 case FormatType::Day:
150 temp_stream << std::setw(2) << std::setfill('0') << dt.day;
151 break;
152 case FormatType::Hour:
153 temp_stream << std::setw(2) << std::setfill('0') << dt.hour;
154 break;
156 temp_stream << std::setw(2) << std::setfill('0') << dt.min;
157 break;
159 temp_stream << std::setw(2) << std::setfill('0') << dt.sec;
160 break;
162 temp_stream << std::setw(3) << std::setfill('0') << dt.ms;
163 break;
165 temp_stream << std::setw(2) << std::setfill('0') << (dt.year % 100);
166 break;
168 // Format equivalent to 'ctime'
169 char buffer[16];
170 temp_stream << time_shield::to_str(time_shield::day_of_week(dt.year, dt.mon, dt.day), time_shield::FormatType::SHORT_NAME);
171 temp_stream << " ";
172 temp_stream << time_shield::to_str(static_cast<time_shield::Month>(dt.mon), time_shield::FormatType::SHORT_NAME);
173 temp_stream << " ";
174 // day
175 snprintf(buffer, sizeof(buffer), "%2d ", dt.day);
176 temp_stream << buffer;
177 // time
178 snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d ", dt.hour, dt.min, dt.sec);
179 temp_stream << buffer;
180 // year
181 temp_stream << dt.year;
182 break;
183 }
185 temp_stream << std::setw(2) << std::setfill('0') << dt.mon << "/"
186 << std::setw(2) << std::setfill('0') << dt.day << "/"
187 << std::setw(2) << std::setfill('0') << (dt.year % 100);
188 break;
190 temp_stream
191 << std::setw(2) << std::setfill('0') << dt.hour << ":"
192 << std::setw(2) << std::setfill('0') << dt.min << ":"
193 << std::setw(2) << std::setfill('0') << dt.sec;
194 break;
196 temp_stream
197 << dt.year << "-"
198 << std::setw(2) << std::setfill('0') << dt.mon << "-"
199 << std::setw(2) << std::setfill('0') << dt.day;
200 break;
202 temp_stream << time_shield::ms_to_sec(record.timestamp_ms);
203 break;
205 temp_stream << record.timestamp_ms;
206 break;
207
208 // Weekday and Month Names
210 temp_stream << time_shield::to_str(static_cast<time_shield::Month>(dt.mon), time_shield::FormatType::SHORT_NAME);
211 break;
213 temp_stream << time_shield::to_str(static_cast<time_shield::Month>(dt.mon), time_shield::FormatType::FULL_NAME);
214 break;
216 temp_stream << time_shield::to_str(time_shield::day_of_week(dt.year, dt.mon, dt.day), time_shield::FormatType::SHORT_NAME);
217 break;
219 temp_stream << time_shield::to_str(time_shield::day_of_week(dt.year, dt.mon, dt.day), time_shield::FormatType::FULL_NAME);
220 break;
221
222 // Log Level
224 temp_stream << to_string(record.log_level);
225 break;
227 temp_stream << to_string(record.log_level, 1);
228 break;
229
230 // File and Function
232 std::string full_path = record.file;
233 size_t pos = full_path.find_last_of("/\\");
234 if (pos != std::string::npos) {
235 temp_stream << full_path.substr(pos + 1);
236 } else {
237 temp_stream << full_path;
238 }
239 break;
240 }
242 temp_stream << record.file;
243 break;
245 temp_stream << record.file << ":" << record.line;
246 break;
248 temp_stream << record.line;
249 break;
251 temp_stream << record.function;
252 break;
253
254 // Thread
256 temp_stream << record.thread_id;
257 break;
258
259 // Color
261 if (!strip_ansi) {
262 oss << get_log_level_color(record.log_level);
263 }
264 break;
266 if (!strip_ansi) {
268 }
269 break;
270
271 // Message
273 if (!record.format.empty()) {
274 if (record.args_array.empty()) {
275 temp_stream << record.format;
276 break;
277 }
278 using ValueType = VariableValue::ValueType;
279 for (size_t i = 0; i < record.args_array.size(); ++i) {
280 if (!record.print_mode && i) temp_stream << ", ";
281 const auto& arg = record.args_array[i];
282 switch (arg.type) {
283 case ValueType::STRING_VAL:
284 case ValueType::EXCEPTION_VAL:
285 case ValueType::ERROR_CODE_VAL:
286 case ValueType::ENUM_VAL:
287 case ValueType::DURATION_VAL:
288 case ValueType::TIME_POINT_VAL:
289 case ValueType::POINTER_VAL:
290 case ValueType::SMART_POINTER_VAL:
291 case ValueType::VARIANT_VAL:
292 case ValueType::OPTIONAL_VAL:
293 temp_stream << arg.to_string(record.format.c_str());
294 break;
295 default:
296 if (arg.is_literal) {
297 temp_stream << arg.name << ": " << arg.to_string(record.format.c_str());
298 } else {
299 temp_stream << arg.to_string(record.format.c_str());
300 }
301 break;
302 };
303 }
304 } else
305 if (!record.args_array.empty()) {
306 using ValueType = VariableValue::ValueType;
307 for (size_t i = 0; i < record.args_array.size(); ++i) {
308 if (!record.print_mode && i) temp_stream << ", ";
309 const auto& arg = record.args_array[i];
310 switch (arg.type) {
311 case ValueType::STRING_VAL:
312 case ValueType::EXCEPTION_VAL:
313 case ValueType::ERROR_CODE_VAL:
314 temp_stream << arg.to_string();
315 break;
316 case ValueType::ENUM_VAL:
317 if (arg.is_literal) {
318 if (record.print_mode) temp_stream << arg.to_string();
319 else temp_stream << arg.name << ": " << arg.to_string();
320 break;
321 }
322 temp_stream << arg.to_string();
323 break;
324 case ValueType::PATH_VAL:
325 case ValueType::DURATION_VAL:
326 case ValueType::TIME_POINT_VAL:
327 case ValueType::POINTER_VAL:
328 case ValueType::SMART_POINTER_VAL:
329 case ValueType::VARIANT_VAL:
330 case ValueType::OPTIONAL_VAL:
331 if (record.print_mode) temp_stream << arg.to_string();
332 else temp_stream << arg.name << ": " << arg.to_string();
333 break;
334 default:
335 if (arg.is_literal) {
336 if (record.print_mode) temp_stream << arg.to_string();
337 else temp_stream << arg.name << ": " << arg.to_string();
338 break;
339 }
340 temp_stream << arg.to_string();
341 break;
342 };
343 }
344 }
345 break;
346 };
347
348 // Get the string representation
349 std::string result = strip_ansi ? remove_ansi_escape_codes(temp_stream.str()) : temp_stream.str();
350
351 // Truncate if required
352 if (truncate && result.size() > static_cast<size_t>(width)) {
353 switch (type) {
354 // File and Function
359 const std::string placeholder = "..."; // Placeholder for omitted sections
360 int placeholder_size = static_cast<int>(placeholder.size());
361
362 // If the width is less than or equal to the placeholder size, return only the placeholder
363 if (width <= placeholder_size) {
364 result = placeholder.substr(0, width);
365 } else {
366 // Keep portions of the string from the beginning and end
367 size_t keep_size = (width - placeholder_size) / 2; // Portion to keep from each side
368 size_t keep_end = result.size() - keep_size;
369 int line_size = static_cast<int>(2 * keep_size + placeholder.size());
370
371 while (line_size < width) {
372 if (keep_end > 0) {
373 --keep_end;
374 ++line_size;
375 } else break;
376 }
377
378 // Construct the result: start + placeholder + end
379 result = result.substr(0, keep_size) + placeholder + result.substr(keep_end);
380 }
381 break;
382 }
383 default:
384 // Standard truncation for other types
385 result = result.substr(0, width);
386 };
387 }
388
389 // Apply alignment and width
390 if (width > 0 && result.size() < static_cast<size_t>(width)) {
391 if (left_align) {
392 // Left alignment
393 oss << std::left << std::setw(width) << result;
394 } else
395 if (center_align) {
396 // Center alignment
397 const int padding = static_cast<int>((width - result.size()) / 2);
398 oss << std::string(padding, ' ') << result << std::string(width - padding - result.size(), ' ');
399 } else {
400 // Right alignment (default)
401 oss << std::right << std::setw(width) << result;
402 }
403 } else {
404 // If width not specified, simply append the result
405 oss << result;
406 }
407 }
408
409 private:
410
414 std::string remove_ansi_escape_codes(const std::string& input) const {
415 std::string result;
416 result.reserve(input.size());
417 bool in_escape_sequence = false;
418
419 for (size_t i = 0; i < input.size(); ++i) {
420 if (in_escape_sequence) {
421 if ((input[i] >= 'a' && input[i] <= 'z') || (input[i] >= 'A' && input[i] <= 'Z')) {
422 in_escape_sequence = false;
423 }
424 } else {
425 if (input[i] == '\033' && (i + 1) < input.size() && input[i + 1] == '[') {
426 in_escape_sequence = true;
427 ++i; // Skip '[' after '\033'
428 } else {
429 result += input[i]; // Append non-escape characters to the result
430 }
431 }
432 }
433
434 return result;
435 }
436 }; // FormatInstruction
437
441 public:
443
448 static std::vector<FormatInstruction> compile(
449 const std::string& pattern,
450 CompileContext context = CompileContext::Default) {
451 using FormatType = FormatInstruction::FormatType;
452 std::vector<FormatInstruction> instructions;
453 std::string buffer;
454 buffer.reserve(pattern.size());
455 bool strip_ansi = false;
456
457 for (size_t i = 0; i < pattern.size(); ++i) {
458 char c = pattern[i];
459
460 if (c == '%') {
461 if (!buffer.empty()) {
462 instructions.push_back(FormatInstruction(context, buffer, strip_ansi));
463 buffer.clear();
464 }
465
466 // Handling alignment, width, and truncation
467 bool left_align = false;
468 bool center_align = false;
469 bool truncate = false;
470 int width = 0;
471
472 // Check for alignment and width
473 while ((i + 1) < pattern.size() && (
474 std::isdigit(pattern[i + 1]) ||
475 pattern[i + 1] == '-' ||
476 pattern[i + 1] == '=')) {
477 char next = pattern[++i];
478 if (next == '-') {
479 left_align = true;
480 } else if (next == '=') {
481 center_align = true;
482 } else if (std::isdigit(next)) {
483 width = width * 10 + (next - '0');
484 }
485
486 // Check for truncation '!'
487 if ((i + 1) < pattern.size() && pattern[i + 1] == '!') {
488 truncate = true;
489 ++i;
490 break;
491 }
492 }
493
494 if ((i + 1) < pattern.size()) {
495 char next = pattern[++i];
496 switch (next) {
497 // Date and Time
498 case 'Y':
499 instructions.emplace_back(context, FormatType::Year, width, left_align, center_align, truncate, strip_ansi);
500 break;
501 case 'm':
502 if ((i + 1) < pattern.size() && pattern[i + 1] == 's') {
503 instructions.emplace_back(context, FormatType::MilliSecondTimeStamp, width, left_align, center_align, truncate, strip_ansi);
504 ++i; // Skip 's' after 'm'
505 break;
506 }
507 instructions.emplace_back(context, FormatType::Month, width, left_align, center_align, truncate, strip_ansi);
508 break;
509 case 'd':
510 instructions.emplace_back(context, FormatType::Day, width, left_align, center_align, truncate, strip_ansi);
511 break;
512 case 'H':
513 instructions.emplace_back(context, FormatType::Hour, width, left_align, center_align, truncate, strip_ansi);
514 break;
515 case 'M':
516 instructions.emplace_back(context, FormatType::Minute, width, left_align, center_align, truncate, strip_ansi);
517 break;
518 case 'S':
519 if ((i + 1) < pattern.size() && pattern[i + 1] == 'C') {
520 strip_ansi = true;
521 ++i; // Skip 'C' after 'S'
522 break;
523 }
524 instructions.emplace_back(context, FormatType::Second, width, left_align, center_align, truncate, strip_ansi);
525 break;
526 case 'e':
527 if ((i + 1) < pattern.size() && pattern[i + 1] == 'c') {
528 strip_ansi = false;
529 ++i; // Skip 'c' after 'e'
530 break;
531 }
532 instructions.emplace_back(context, FormatType::Millisecond, width, left_align, center_align, truncate, strip_ansi);
533 break;
534 case 'C':
535 instructions.emplace_back(context, FormatType::TwoDigitYear, width, left_align, center_align, truncate, strip_ansi);
536 break;
537 case 'c':
538 instructions.emplace_back(context, FormatType::DateTime, width, left_align, center_align, truncate, strip_ansi);
539 break;
540 case 'D':
541 instructions.emplace_back(context, FormatType::ShortDate, width, left_align, center_align, truncate, strip_ansi);
542 break;
543 case 'T':
544 case 'X':
545 instructions.emplace_back(context, FormatType::TimeISO8601, width, left_align, center_align, truncate, strip_ansi);
546 break;
547 case 'F':
548 instructions.emplace_back(context, FormatType::DateISO8601, width, left_align, center_align, truncate, strip_ansi);
549 break;
550 case 's':
551 if ((i + 1) < pattern.size() && pattern[i + 1] == 'c') {
552 strip_ansi = true;
553 ++i; // Skip 'c' after 's'
554 break;
555 } else {
556 instructions.emplace_back(context, FormatType::TimeStamp, width, left_align, center_align, truncate, strip_ansi);
557 }
558 break;
559 case 'E':
560 if ((i + 1) < pattern.size() && pattern[i + 1] == 'C') {
561 strip_ansi = false;
562 ++i; // Skip 'C' after 'E'
563 break;
564 }
565 instructions.emplace_back(context, FormatType::TimeStamp, width, left_align, center_align, truncate, strip_ansi);
566 break;
567 // Weekday and Month Names
568 case 'b':
569 if ((i + 1) < pattern.size() && pattern[i + 1] == 's') {
570 instructions.emplace_back(context, FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
571 ++i; // Skip 's' after 'b'
572 break;
573 }
574 instructions.emplace_back(context, FormatType::AbbreviatedMonthName, width, left_align, center_align, truncate, strip_ansi);
575 break;
576 case 'B':
577 instructions.emplace_back(context, FormatType::FullMonthName, width, left_align, center_align, truncate, strip_ansi);
578 break;
579 case 'a':
580 instructions.emplace_back(context, FormatType::AbbreviatedWeekdayName, width, left_align, center_align, truncate, strip_ansi);
581 break;
582 case 'A':
583 instructions.emplace_back(context, FormatType::FullWeekdayName, width, left_align, center_align, truncate, strip_ansi);
584 break;
585
586 // Log Level
587 case 'l':
588 instructions.emplace_back(context, FormatType::LogLevel, width, left_align, center_align, truncate, strip_ansi);
589 break;
590 case 'L':
591 instructions.emplace_back(context, FormatType::ShortLogLevel, width, left_align, center_align, truncate, strip_ansi);
592 break;
593
594 // Thread
595 case 't':
596 instructions.emplace_back(context, FormatType::ThreadId, width, left_align, center_align, truncate, strip_ansi);
597 break;
598
599 // File and Function
600 case 'f':
601 if ((i + 1) < pattern.size() && pattern[i + 1] == 'f' && (i + 2) < pattern.size() && pattern[i + 2] == 'n') {
602 instructions.emplace_back(context, FormatType::FullFileName, width, left_align, center_align, truncate, strip_ansi);
603 i += 2; // Skip 'fn' after 'f'
604 break;
605 }
606 if ((i + 1) < pattern.size() && pattern[i + 1] == 'n') {
607 instructions.emplace_back(context, FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
608 ++i; // Skip 'n' after 'f'
609 break;
610 }
611 instructions.emplace_back(context, FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
612 break;
613 case 'g':
614 instructions.emplace_back(context, FormatType::FullFileName, width, left_align, center_align, truncate, strip_ansi);
615 break;
616 case '@':
617 instructions.emplace_back(context, FormatType::SourceFileAndLine, width, left_align, center_align, truncate, strip_ansi);
618 break;
619 case '#':
620 instructions.emplace_back(context, FormatType::LineNumber, width, left_align, center_align, truncate, strip_ansi);
621 break;
622 case '!':
623 instructions.emplace_back(context, FormatType::FunctionName, width, left_align, center_align, truncate, strip_ansi);
624 break;
625
626 // Color
627 case '^':
628 instructions.emplace_back(context, FormatType::StartColor, 0, false, false, false, strip_ansi);
629 break;
630 case '$':
631 instructions.emplace_back(context, FormatType::EndColor, 0, false, false, false, strip_ansi);
632 break;
633
634 // Message
635 case 'v':
636 instructions.emplace_back(context, FormatType::Message, width, left_align, center_align, truncate, strip_ansi);
637 break;
638
639 //
640 case 'N':
641 if ((i + 2) < pattern.size() && pattern[i + 1] == '(') {
642 size_t end = pattern.find(')', i + 2);
643 if (end == std::string::npos) {
644 ++i;
645 break;
646 }
647 std::string params = pattern.substr(i + 2, end - i - 2);
648 auto no_args_instructions = compile(params, CompileContext::NoArgsFallback);
649 instructions.insert(instructions.end(), no_args_instructions.begin(), no_args_instructions.end());
650 i = end;
651 }
652 break;
653
654 // Escape character or unknown
655 case '%':
656 default:
657 buffer += next; // Unrecognized symbols are recorded as text
658 break;
659 };
660 }
661 } else {
662 buffer += c;
663 }
664 }
665
666 if (!buffer.empty()) {
667 instructions.push_back(FormatInstruction(context, buffer, strip_ansi));
668 }
669 return instructions;
670 }
671 }; // PatternCompiler
672
673}; // namespace logit
674
675#endif // _LOGIT_PATTERN_COMPILER_HPP_INCLUDED
Compiler for log formatting patterns.
FormatInstruction::CompileContext CompileContext
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.
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:90
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.
@ 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
@ DateISO8601
F: Date in ISO 8601 format (Y-m-d)
@ 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:17
const std::string function
Function name.
Definition LogRecord.hpp:22
const int line
Line number in the source file.
Definition LogRecord.hpp:21
std::thread::id thread_id
ID of the logging thread.
Definition LogRecord.hpp:26
const LogLevel log_level
Log level (severity).
Definition LogRecord.hpp:18
const int64_t timestamp_ms
Timestamp in milliseconds.
Definition LogRecord.hpp:19
const std::string file
Source file name.
Definition LogRecord.hpp:20
const std::string format
Format string for the message.
Definition LogRecord.hpp:23
std::vector< VariableValue > args_array
Argument values for the log.
Definition LogRecord.hpp:25
const bool print_mode
Flag to determine whether arguments are printed in a raw format without special symbols.
Definition LogRecord.hpp:28
ValueType
Enumeration of possible value types for VariableValue.