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
25
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 int line_size = 2 * keep_size + placeholder.size();
369
370 while (line_size < width) {
371 if (keep_end > 0) {
372 --keep_end;
373 ++line_size;
374 } else break;
375 }
376
377 // Construct the result: start + placeholder + end
378 result = result.substr(0, keep_size) + placeholder + result.substr(keep_end);
379 }
380 break;
381 }
382 default:
383 // Standard truncation for other types
384 result = result.substr(0, width);
385 };
386 }
387
388 // Apply alignment and width
389 if (width > 0 && result.size() < static_cast<size_t>(width)) {
390 if (left_align) {
391 // Left alignment
392 oss << std::left << std::setw(width) << result;
393 } else if (center_align) {
394 // Center alignment
395 const int padding = (width - result.size()) / 2;
396 oss << std::string(padding, ' ') << result << std::string(width - padding - result.size(), ' ');
397 } else {
398 // Right alignment (default)
399 oss << std::right << std::setw(width) << result;
400 }
401 } else {
402 // If width not specified, simply append the result
403 oss << result;
404 }
405 }
406
407 private:
408
412 std::string remove_ansi_escape_codes(const std::string& input) const {
413 std::string result;
414 result.reserve(input.size());
415 bool in_escape_sequence = false;
416
417 for (size_t i = 0; i < input.size(); ++i) {
418 if (in_escape_sequence) {
419 if ((input[i] >= 'a' && input[i] <= 'z') || (input[i] >= 'A' && input[i] <= 'Z')) {
420 in_escape_sequence = false;
421 }
422 } else {
423 if (input[i] == '\033' && (i + 1) < input.size() && input[i + 1] == '[') {
424 in_escape_sequence = true;
425 ++i; // Skip '[' after '\033'
426 } else {
427 result += input[i]; // Append non-escape characters to the result
428 }
429 }
430 }
431
432 return result;
433 }
434 }; // FormatInstruction
435
439 public:
441
446 static std::vector<FormatInstruction> compile(
447 const std::string& pattern,
448 CompileContext context = CompileContext::Default) {
449 using FormatType = FormatInstruction::FormatType;
450 std::vector<FormatInstruction> instructions;
451 std::string buffer;
452 buffer.reserve(pattern.size());
453 bool strip_ansi = false;
454
455 for (size_t i = 0; i < pattern.size(); ++i) {
456 char c = pattern[i];
457
458 if (c == '%') {
459 if (!buffer.empty()) {
460 instructions.push_back(FormatInstruction(context, buffer, strip_ansi));
461 buffer.clear();
462 }
463
464 // Handling alignment, width, and truncation
465 bool left_align = false;
466 bool center_align = false;
467 bool truncate = false;
468 int width = 0;
469
470 // Check for alignment and width
471 while ((i + 1) < pattern.size() && (
472 std::isdigit(pattern[i + 1]) ||
473 pattern[i + 1] == '-' ||
474 pattern[i + 1] == '=')) {
475 char next = pattern[++i];
476 if (next == '-') {
477 left_align = true;
478 } else if (next == '=') {
479 center_align = true;
480 } else if (std::isdigit(next)) {
481 width = width * 10 + (next - '0');
482 }
483
484 // Check for truncation '!'
485 if ((i + 1) < pattern.size() && pattern[i + 1] == '!') {
486 truncate = true;
487 ++i;
488 break;
489 }
490 }
491
492 if ((i + 1) < pattern.size()) {
493 char next = pattern[++i];
494 switch (next) {
495 // Date and Time
496 case 'Y':
497 instructions.emplace_back(context, FormatType::Year, width, left_align, center_align, truncate, strip_ansi);
498 break;
499 case 'm':
500 if ((i + 1) < pattern.size() && pattern[i + 1] == 's') {
501 instructions.emplace_back(context, FormatType::MilliSecondTimeStamp, width, left_align, center_align, truncate, strip_ansi);
502 ++i; // Skip 's' after 'm'
503 break;
504 }
505 instructions.emplace_back(context, FormatType::Month, width, left_align, center_align, truncate, strip_ansi);
506 break;
507 case 'd':
508 instructions.emplace_back(context, FormatType::Day, width, left_align, center_align, truncate, strip_ansi);
509 break;
510 case 'H':
511 instructions.emplace_back(context, FormatType::Hour, width, left_align, center_align, truncate, strip_ansi);
512 break;
513 case 'M':
514 instructions.emplace_back(context, FormatType::Minute, width, left_align, center_align, truncate, strip_ansi);
515 break;
516 case 'S':
517 if ((i + 1) < pattern.size() && pattern[i + 1] == 'C') {
518 strip_ansi = true;
519 ++i; // Skip 'C' after 'S'
520 break;
521 }
522 instructions.emplace_back(context, FormatType::Second, width, left_align, center_align, truncate, strip_ansi);
523 break;
524 case 'e':
525 if ((i + 1) < pattern.size() && pattern[i + 1] == 'c') {
526 strip_ansi = false;
527 ++i; // Skip 'c' after 'e'
528 break;
529 }
530 instructions.emplace_back(context, FormatType::Millisecond, width, left_align, center_align, truncate, strip_ansi);
531 break;
532 case 'C':
533 instructions.emplace_back(context, FormatType::TwoDigitYear, width, left_align, center_align, truncate, strip_ansi);
534 break;
535 case 'c':
536 instructions.emplace_back(context, FormatType::DateTime, width, left_align, center_align, truncate, strip_ansi);
537 break;
538 case 'D':
539 instructions.emplace_back(context, FormatType::ShortDate, width, left_align, center_align, truncate, strip_ansi);
540 break;
541 case 'T':
542 case 'X':
543 instructions.emplace_back(context, FormatType::TimeISO8601, width, left_align, center_align, truncate, strip_ansi);
544 break;
545 case 'F':
546 instructions.emplace_back(context, FormatType::DateISO8601, width, left_align, center_align, truncate, strip_ansi);
547 break;
548 case 's':
549 if ((i + 1) < pattern.size() && pattern[i + 1] == 'c') {
550 strip_ansi = true;
551 ++i; // Skip 'c' after 's'
552 break;
553 }
554 case 'E':
555 if ((i + 1) < pattern.size() && pattern[i + 1] == 'C') {
556 strip_ansi = false;
557 ++i; // Skip 'C' after 'E'
558 break;
559 }
560 instructions.emplace_back(context, FormatType::TimeStamp, width, left_align, center_align, truncate, strip_ansi);
561 break;
562
563 // Weekday and Month Names
564 case 'b':
565 if ((i + 1) < pattern.size() && pattern[i + 1] == 's') {
566 instructions.emplace_back(context, FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
567 ++i; // Skip 's' after 'b'
568 break;
569 }
570 instructions.emplace_back(context, FormatType::AbbreviatedMonthName, width, left_align, center_align, truncate, strip_ansi);
571 break;
572 case 'B':
573 instructions.emplace_back(context, FormatType::FullMonthName, width, left_align, center_align, truncate, strip_ansi);
574 break;
575 case 'a':
576 instructions.emplace_back(context, FormatType::AbbreviatedWeekdayName, width, left_align, center_align, truncate, strip_ansi);
577 break;
578 case 'A':
579 instructions.emplace_back(context, FormatType::FullWeekdayName, width, left_align, center_align, truncate, strip_ansi);
580 break;
581
582 // Log Level
583 case 'l':
584 instructions.emplace_back(context, FormatType::LogLevel, width, left_align, center_align, truncate, strip_ansi);
585 break;
586 case 'L':
587 instructions.emplace_back(context, FormatType::ShortLogLevel, width, left_align, center_align, truncate, strip_ansi);
588 break;
589
590 // Thread
591 case 't':
592 instructions.emplace_back(context, FormatType::ThreadId, width, left_align, center_align, truncate, strip_ansi);
593 break;
594
595 // File and Function
596 case 'f':
597 if ((i + 1) < pattern.size() && pattern[i + 1] == 'f' && (i + 2) < pattern.size() && pattern[i + 2] == 'n') {
598 instructions.emplace_back(context, FormatType::FullFileName, width, left_align, center_align, truncate, strip_ansi);
599 i += 2; // Skip 'fn' after 'f'
600 break;
601 }
602 if ((i + 1) < pattern.size() && pattern[i + 1] == 'n') {
603 instructions.emplace_back(context, FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
604 ++i; // Skip 'n' after 'f'
605 break;
606 }
607 instructions.emplace_back(context, FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
608 break;
609 case 'g':
610 instructions.emplace_back(context, FormatType::FullFileName, width, left_align, center_align, truncate, strip_ansi);
611 break;
612 case '@':
613 instructions.emplace_back(context, FormatType::SourceFileAndLine, width, left_align, center_align, truncate, strip_ansi);
614 break;
615 case '#':
616 instructions.emplace_back(context, FormatType::LineNumber, width, left_align, center_align, truncate, strip_ansi);
617 break;
618 case '!':
619 instructions.emplace_back(context, FormatType::FunctionName, width, left_align, center_align, truncate, strip_ansi);
620 break;
621
622 // Color
623 case '^':
624 instructions.emplace_back(context, FormatType::StartColor, 0, false, false, false, strip_ansi);
625 break;
626 case '$':
627 instructions.emplace_back(context, FormatType::EndColor, 0, false, false, false, strip_ansi);
628 break;
629
630 // Message
631 case 'v':
632 instructions.emplace_back(context, FormatType::Message, width, left_align, center_align, truncate, strip_ansi);
633 break;
634
635 //
636 case 'N':
637 if ((i + 2) < pattern.size() && pattern[i + 1] == '(') {
638 size_t end = pattern.find(')', i + 2);
639 if (end == std::string::npos) {
640 ++i;
641 break;
642 }
643 std::string params = pattern.substr(i + 2, end - i - 2);
644 auto no_args_instructions = compile(params, CompileContext::NoArgsFallback);
645 instructions.insert(instructions.end(), no_args_instructions.begin(), no_args_instructions.end());
646 i = end;
647 }
648 break;
649
650 // Escape character or unknown
651 case '%':
652 default:
653 buffer += next; // Unrecognized symbols are recorded as text
654 break;
655 };
656 }
657 } else {
658 buffer += c;
659 }
660 }
661
662 if (!buffer.empty()) {
663 instructions.push_back(FormatInstruction(context, buffer, strip_ansi));
664 }
665 return instructions;
666 }
667 }; // PatternCompiler
668
669}; // namespace logit
670
671#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. 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.
@ 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:13
const std::string function
Function name.
Definition LogRecord.hpp:18
const int line
Line number in the source file.
Definition LogRecord.hpp:17
std::thread::id thread_id
ID of the logging thread.
Definition LogRecord.hpp:22
const LogLevel log_level
Log level (severity).
Definition LogRecord.hpp:14
const int64_t timestamp_ms
Timestamp in milliseconds.
Definition LogRecord.hpp:15
const std::string file
Source file name.
Definition LogRecord.hpp:16
const std::string format
Format string for the message.
Definition LogRecord.hpp:19
std::vector< VariableValue > args_array
Argument values for the log.
Definition LogRecord.hpp:21
const bool print_mode
Flag to determine whether arguments are printed in a raw format without special symbols.
Definition LogRecord.hpp:24
ValueType
Enumeration of possible value types for VariableValue.