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