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 FormatType {
22 // Text
24
25 // Date and Time
26 Year,
27 Month,
28 Day,
29 Hour,
30 Minute,
31 Second,
34 DateTime,
35 ShortDate,
38 TimeStamp,
40
41 // Weekday and Month Names
46
47 // Log Level
48 LogLevel,
50
51 // File and Function
52 FileName,
57
58 // Thread
59 ThreadId,
60
61 // Color
63 EndColor,
64
65 // Message
66 Message
67 };
68
70 std::string static_text;
71
72 // Fields for alignment and width
73 int width = 0;
74 bool left_align = false;
75 bool center_align = false;
76 bool truncate = false;
77 bool strip_ansi = false;
78
82 explicit FormatInstruction(const std::string& text, const bool& strip_ansi)
84 };
85
94 const FormatType& type,
95 const int& width = 0,
96 const bool& left = false,
97 const bool& center = false,
98 const bool& trunc = false,
99 const bool& strip_ansi = false) :
100 type(type), width(width),
101 left_align(left), center_align(center),
103 };
104
110 template<class StreamType>
111 void apply(
112 StreamType& oss,
113 const LogRecord& record,
114 const time_shield::DateTimeStruct& dt) const {
115 std::ostringstream temp_stream;
116 switch (type) {
117 // Text
119 temp_stream << static_text;
120 break;
121
122 // Date and Time
123 case FormatType::Year:
124 temp_stream << dt.year;
125 break;
127 temp_stream << std::setw(2) << std::setfill('0') << dt.mon;
128 break;
129 case FormatType::Day:
130 temp_stream << std::setw(2) << std::setfill('0') << dt.day;
131 break;
132 case FormatType::Hour:
133 temp_stream << std::setw(2) << std::setfill('0') << dt.hour;
134 break;
136 temp_stream << std::setw(2) << std::setfill('0') << dt.min;
137 break;
139 temp_stream << std::setw(2) << std::setfill('0') << dt.sec;
140 break;
142 temp_stream << std::setw(3) << std::setfill('0') << dt.ms;
143 break;
145 temp_stream << std::setw(2) << std::setfill('0') << (dt.year % 100);
146 break;
148 // Format equivalent to 'ctime'
149 char buffer[16];
150 temp_stream << time_shield::to_str(time_shield::day_of_week(dt.year, dt.mon, dt.day), time_shield::FormatType::SHORT_NAME);
151 temp_stream << " ";
152 temp_stream << time_shield::to_str(static_cast<time_shield::Month>(dt.mon), time_shield::FormatType::SHORT_NAME);
153 temp_stream << " ";
154 // day
155 snprintf(buffer, sizeof(buffer), "%2d ", dt.day);
156 temp_stream << buffer;
157 // time
158 snprintf(buffer, sizeof(buffer), "%.2d:%.2d:%.2d ", dt.hour, dt.min, dt.sec);
159 temp_stream << buffer;
160 // year
161 temp_stream << dt.year;
162 break;
163 }
165 temp_stream << std::setw(2) << std::setfill('0') << dt.mon << "/"
166 << std::setw(2) << std::setfill('0') << dt.day << "/"
167 << std::setw(2) << std::setfill('0') << (dt.year % 100);
168 break;
170 temp_stream
171 << std::setw(2) << std::setfill('0') << dt.hour << ":"
172 << std::setw(2) << std::setfill('0') << dt.min << ":"
173 << std::setw(2) << std::setfill('0') << dt.sec;
174 break;
176 temp_stream
177 << dt.year << "-"
178 << std::setw(2) << std::setfill('0') << dt.mon << "-"
179 << std::setw(2) << std::setfill('0') << dt.day;
180 break;
182 temp_stream << time_shield::ms_to_sec(record.timestamp_ms);
183 break;
185 temp_stream << record.timestamp_ms;
186 break;
187
188 // Weekday and Month Names
190 temp_stream << time_shield::to_str(static_cast<time_shield::Month>(dt.mon), time_shield::FormatType::SHORT_NAME);
191 break;
193 temp_stream << time_shield::to_str(static_cast<time_shield::Month>(dt.mon), time_shield::FormatType::FULL_NAME);
194 break;
196 temp_stream << time_shield::to_str(time_shield::day_of_week(dt.year, dt.mon, dt.day), time_shield::FormatType::SHORT_NAME);
197 break;
199 temp_stream << time_shield::to_str(time_shield::day_of_week(dt.year, dt.mon, dt.day), time_shield::FormatType::FULL_NAME);
200 break;
201
202 // Log Level
204 temp_stream << to_string(record.log_level);
205 break;
207 temp_stream << to_string(record.log_level, 1);
208 break;
209
210 // File and Function
212 std::string full_path = record.file;
213 size_t pos = full_path.find_last_of("/\\");
214 if (pos != std::string::npos) {
215 temp_stream << full_path.substr(pos + 1);
216 } else {
217 temp_stream << full_path;
218 }
219 break;
220 }
222 temp_stream << record.file;
223 break;
225 temp_stream << record.file << ":" << record.line;
226 break;
228 temp_stream << record.line;
229 break;
231 temp_stream << record.function;
232 break;
233
234 // Thread
236 temp_stream << record.thread_id;
237 break;
238
239 // Color
241 if (!strip_ansi) {
242 oss << get_log_level_color(record.log_level);
243 }
244 break;
246 if (!strip_ansi) {
248 }
249 break;
250
251 // Message
253 if (!record.format.empty()) {
254 if (record.args_array.empty()) {
255 temp_stream << record.format;
256 break;
257 }
258 using ValueType = VariableValue::ValueType;
259 for (size_t i = 0; i < record.args_array.size(); ++i) {
260 if (!record.print_mode && i) temp_stream << ", ";
261 const auto& arg = record.args_array[i];
262 switch (arg.type) {
263 case ValueType::STRING_VAL:
264 case ValueType::EXCEPTION_VAL:
265 temp_stream << arg.to_string(record.format.c_str());
266 break;
267 default:
268 if (arg.is_literal) {
269 temp_stream << arg.name << ": " << arg.to_string(record.format.c_str());
270 } else {
271 temp_stream << arg.to_string(record.format.c_str());
272 }
273 break;
274 };
275 }
276 } else
277 if (!record.args_array.empty()) {
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 temp_stream << arg.to_string();
286 break;
287 case ValueType::ENUM_VAL:
288 if (arg.is_literal) {
289 if (record.print_mode) temp_stream << arg.name;
290 else temp_stream << arg.name << ": " << arg.to_string();
291 break;
292 }
293 temp_stream << arg.to_string();
294 break;
295 default:
296 if (arg.is_literal) {
297 if (record.print_mode) temp_stream << arg.to_string();
298 else temp_stream << arg.name << ": " << arg.to_string();
299 break;
300 }
301 temp_stream << arg.to_string();
302 break;
303 };
304 }
305 }
306 break;
307 };
308
309 // Get the string representation
310 std::string result = strip_ansi ? remove_ansi_escape_codes(temp_stream.str()) : temp_stream.str();
311
312 // Truncate if required
313 if (truncate && result.size() > static_cast<size_t>(width)) {
314 result = result.substr(0, width);
315 }
316
317 // Apply alignment and width
318 if (width > 0 && result.size() < static_cast<size_t>(width)) {
319 if (left_align) {
320 // Left alignment
321 oss << std::left << std::setw(width) << result;
322 } else if (center_align) {
323 // Center alignment
324 const int padding = (width - result.size()) / 2;
325 oss << std::string(padding, ' ') << result << std::string(width - padding - result.size(), ' ');
326 } else {
327 // Right alignment (default)
328 oss << std::right << std::setw(width) << result;
329 }
330 } else {
331 // If width not specified, simply append the result
332 oss << result;
333 }
334 }
335
336 private:
337
341 std::string remove_ansi_escape_codes(const std::string& input) const {
342 std::string result;
343 result.reserve(input.size());
344 bool in_escape_sequence = false;
345
346 for (size_t i = 0; i < input.size(); ++i) {
347 if (in_escape_sequence) {
348 if ((input[i] >= 'a' && input[i] <= 'z') || (input[i] >= 'A' && input[i] <= 'Z')) {
349 in_escape_sequence = false;
350 }
351 } else {
352 if (input[i] == '\033' && (i + 1) < input.size() && input[i + 1] == '[') {
353 in_escape_sequence = true;
354 ++i; // Skip '[' after '\033'
355 } else {
356 result += input[i]; // Append non-escape characters to the result
357 }
358 }
359 }
360
361 return result;
362 }
363 }; // FormatInstruction
364
368 public:
369
373 static std::vector<FormatInstruction> compile(const std::string& pattern) {
374 std::cout << "-1" << std::endl;
375 using FormatType = FormatInstruction::FormatType;
376 std::vector<FormatInstruction> instructions;
377 std::string buffer;
378 buffer.reserve(pattern.size());
379 bool strip_ansi = false;
380
381 for (size_t i = 0; i < pattern.size(); ++i) {
382 char c = pattern[i];
383
384 if (c == '%') {
385 if (!buffer.empty()) {
386 instructions.push_back(FormatInstruction(buffer, strip_ansi));
387 buffer.clear();
388 }
389
390 // Handling alignment, width, and truncation
391 bool left_align = false;
392 bool center_align = false;
393 bool truncate = false;
394 int width = 0;
395
396 // Check for alignment and width
397 while ((i + 1) < pattern.size() && (
398 std::isdigit(pattern[i + 1]) ||
399 pattern[i + 1] == '-' ||
400 pattern[i + 1] == '=')) {
401 char next = pattern[++i];
402 if (next == '-') {
403 left_align = true;
404 } else if (next == '=') {
405 center_align = true;
406 } else if (std::isdigit(next)) {
407 width = width * 10 + (next - '0');
408 }
409
410 // Check for truncation '!'
411 if ((i + 1) < pattern.size() && pattern[i + 1] == '!') {
412 truncate = true;
413 ++i;
414 break;
415 }
416 }
417
418 if ((i + 1) < pattern.size()) {
419 char next = pattern[++i];
420 switch (next) {
421 // Date and Time
422 case 'Y':
423 instructions.emplace_back(FormatType::Year, width, left_align, center_align, truncate, strip_ansi);
424 break;
425 case 'm':
426 if ((i + 1) < pattern.size() && pattern[i + 1] == 's') {
427 instructions.emplace_back(FormatType::MilliSecondTimeStamp, width, left_align, center_align, truncate, strip_ansi);
428 ++i; // Skip 's' after 'm'
429 break;
430 }
431 instructions.emplace_back(FormatType::Month, width, left_align, center_align, truncate, strip_ansi);
432 break;
433 case 'd':
434 instructions.emplace_back(FormatType::Day, width, left_align, center_align, truncate, strip_ansi);
435 break;
436 case 'H':
437 instructions.emplace_back(FormatType::Hour, width, left_align, center_align, truncate, strip_ansi);
438 break;
439 case 'M':
440 instructions.emplace_back(FormatType::Minute, width, left_align, center_align, truncate, strip_ansi);
441 break;
442 case 'S':
443 if ((i + 1) < pattern.size() && pattern[i + 1] == 'C') {
444 strip_ansi = true;
445 ++i; // Skip 'C' after 'S'
446 break;
447 }
448 instructions.emplace_back(FormatType::Second, width, left_align, center_align, truncate, strip_ansi);
449 break;
450 case 'e':
451 if ((i + 1) < pattern.size() && pattern[i + 1] == 'c') {
452 strip_ansi = false;
453 ++i; // Skip 'c' after 'e'
454 break;
455 }
456 instructions.emplace_back(FormatType::Millisecond, width, left_align, center_align, truncate, strip_ansi);
457 break;
458 case 'C':
459 instructions.emplace_back(FormatType::TwoDigitYear, width, left_align, center_align, truncate, strip_ansi);
460 break;
461 case 'c':
462 instructions.emplace_back(FormatType::DateTime, width, left_align, center_align, truncate, strip_ansi);
463 break;
464 case 'D':
465 instructions.emplace_back(FormatType::ShortDate, width, left_align, center_align, truncate, strip_ansi);
466 break;
467 case 'T':
468 case 'X':
469 instructions.emplace_back(FormatType::TimeISO8601, width, left_align, center_align, truncate, strip_ansi);
470 break;
471 case 'F':
472 instructions.emplace_back(FormatType::DateISO8601, width, left_align, center_align, truncate, strip_ansi);
473 break;
474 case 's':
475 if ((i + 1) < pattern.size() && pattern[i + 1] == 'c') {
476 strip_ansi = true;
477 ++i; // Skip 'c' after 's'
478 break;
479 }
480 case 'E':
481 if ((i + 1) < pattern.size() && pattern[i + 1] == 'C') {
482 strip_ansi = false;
483 ++i; // Skip 'C' after 'E'
484 break;
485 }
486 instructions.emplace_back(FormatType::TimeStamp, width, left_align, center_align, truncate, strip_ansi);
487 break;
488
489 // Weekday and Month Names
490 case 'b':
491 if ((i + 1) < pattern.size() && pattern[i + 1] == 's') {
492 instructions.emplace_back(FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
493 ++i; // Skip 's' after 'b'
494 break;
495 }
496 instructions.emplace_back(FormatType::AbbreviatedMonthName, width, left_align, center_align, truncate, strip_ansi);
497 break;
498 case 'B':
499 instructions.emplace_back(FormatType::FullMonthName, width, left_align, center_align, truncate, strip_ansi);
500 break;
501 case 'a':
502 instructions.emplace_back(FormatType::AbbreviatedWeekdayName, width, left_align, center_align, truncate, strip_ansi);
503 break;
504 case 'A':
505 instructions.emplace_back(FormatType::FullWeekdayName, width, left_align, center_align, truncate, strip_ansi);
506 break;
507
508 // Log Level
509 case 'l':
510 instructions.emplace_back(FormatType::LogLevel, width, left_align, center_align, truncate, strip_ansi);
511 break;
512 case 'L':
513 instructions.emplace_back(FormatType::ShortLogLevel, width, left_align, center_align, truncate, strip_ansi);
514 break;
515
516 // Thread
517 case 't':
518 instructions.emplace_back(FormatType::ThreadId, width, left_align, center_align, truncate, strip_ansi);
519 break;
520
521 // File and Function
522 case 'f':
523 if ((i + 1) < pattern.size() && pattern[i + 1] == 'f' && (i + 2) < pattern.size() && pattern[i + 2] == 'n') {
524 instructions.emplace_back(FormatType::FullFileName, width, left_align, center_align, truncate, strip_ansi);
525 i += 2; // Skip 'fn' after 'f'
526 break;
527 }
528 if ((i + 1) < pattern.size() && pattern[i + 1] == 'n') {
529 instructions.emplace_back(FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
530 ++i; // Skip 'n' after 'f'
531 break;
532 }
533 instructions.emplace_back(FormatType::FileName, width, left_align, center_align, truncate, strip_ansi);
534 break;
535 case 'g':
536 instructions.emplace_back(FormatType::FullFileName, width, left_align, center_align, truncate, strip_ansi);
537 break;
538 case '@':
539 instructions.emplace_back(FormatType::SourceFileAndLine, width, left_align, center_align, truncate, strip_ansi);
540 break;
541 case '#':
542 instructions.emplace_back(FormatType::LineNumber, width, left_align, center_align, truncate, strip_ansi);
543 break;
544 case '!':
545 instructions.emplace_back(FormatType::FunctionName, width, left_align, center_align, truncate, strip_ansi);
546 break;
547
548 // Color
549 case '^':
550 instructions.emplace_back(FormatType::StartColor, 0, false, false, false, strip_ansi);
551 break;
552 case '$':
553 instructions.emplace_back(FormatType::EndColor, 0, false, false, false, strip_ansi);
554 break;
555
556 // Message
557 case 'v':
558 instructions.emplace_back(FormatType::Message, width, left_align, center_align, truncate, strip_ansi);
559 break;
560
561 // Escape character or unknown
562 case '%':
563 default:
564 buffer += next; // Unrecognized symbols are recorded as text
565 break;
566 };
567 }
568 } else {
569 buffer += c;
570 }
571 }
572
573 if (!buffer.empty()) {
574 instructions.push_back(FormatInstruction(buffer, strip_ansi));
575 }
576 return instructions;
577 }
578 }; // PatternCompiler
579
580}; // namespace logit
581
582#endif // _LOGIT_PATTERN_COMPILER_HPP_INCLUDED
#define LOGIT_DEFAULT_COLOR
Defines the default color for console output. If LOGIT_DEFAULT_COLOR is not defined,...
Compiler for log formatting patterns.
static std::vector< FormatInstruction > compile(const std::string &pattern)
Compiles a pattern string into a list of format instructions.
The primary namespace for the LogIt++ library.
std::string to_string(const LogLevel &level, const int &mode=0)
Convert LogLevel to a std::string representation.
Definition Enums.hpp:89
std::string get_log_level_color(const 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.
FormatInstruction(const FormatType &type, const int &width=0, const bool &left=false, const bool &center=false, const bool &trunc=false, const bool &strip_ansi=false)
Constructor for other types.
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)
FormatInstruction(const std::string &text, const bool &strip_ansi)
Constructor for static text.
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.