#include #include #include #include #include #include using namespace ::cpplogging; enum class parser_state { is_text, //!< current pos is inside normal text is_token_begin, //!< current pos is in token begin (except char '$') is_in_token, //!< current pos is in token (everything after '{') is_in_format, //!< current pos is in format (everything after ':') is_in_format_align, //!< current pos is in format align (expect char '-') is_in_format_zero, //!< current pos is in format align (expect char '0') is_in_format_dot, //!< current pos is in format dot (expect char '.') is_in_format_type, //!< current pos is in format type (expect char 's', 'f', x', 'X' or 'd') }; enum class value_token : uint8_t { unknown = 0, //!< unknown level, //!< log level uptime, //!< system uptime systime, //!< unix timestamp runtime, //!< runtime of the application sender, //!< sender of the log message sender_type, //!< type of sender thread, //!< thread id filename, //!< filename only filepath, //!< whole filepath line, //!< line number name, //!< name of the logger message, //!< log message newline, //!< write new line }; enum class value_format : uint8_t { unknown = 0, decimal, real, hex_lower, hex_upper, string, string_upper, }; struct format_item { const uint16_t magic { 0 }; //!< magic value to identify the item value_token token { value_token::unknown }; //!< value to print value_format format { value_format::unknown }; //!< format to print the value with uint8_t width { 0 }; //!< with of the value uint8_t precision { 0 }; //!< precision (for float values) bool right_align { false }; //!< use right alignment bool zero_fill { false }; //!< fill with zeros (if not than spaces) inline void reset() { memset(this, 0, sizeof(*this)); } inline std::string as_string() const { return std::string(reinterpret_cast(this), sizeof(*this)); } } __attribute__ ((packed)); static_assert(sizeof(format_item) == 8, "format_item has invalid size!"); struct op_less_invariant_string { inline bool operator()(const std::string& lhs, const std::string& rhs) const { auto c1 = lhs.c_str(); auto c2 = rhs.c_str(); auto l1 = lhs.size(); auto l2 = rhs.size(); while (l1 > 0 && l2 > 0 && std::tolower(*c1) == std::tolower(*c2)) { ++c1; ++c2; --l1; --l2; } return l1 > 0 && l2 > 0 ? std::tolower(*c1) < std::tolower(*c2) : l1 < l2; } }; struct invariant_string_map : public std::map { using map::map; inline value_token get_token(const std::string& token) const { auto it = find(token); return it == end() ? value_token::unknown : it->second; } }; static invariant_string_map value_token_map({ { "level", value_token::level }, { "uptime", value_token::uptime }, { "systime", value_token::systime }, { "runtime", value_token::runtime }, { "sender", value_token::sender }, { "sender_type", value_token::sender_type }, { "thread", value_token::thread }, { "filename", value_token::filename }, { "filepath", value_token::filepath }, { "line", value_token::line }, { "name", value_token::name }, { "message", value_token::message }, { "newline", value_token::newline }, }); consumer::format_type consumer::parse_format(const std::string& format) { consumer::format_type ret; parser_state state = parser_state::is_text; const char * s = format.c_str(); const char * e = s + format.size(); const char * c = s; const char * x = s; char * tmp; unsigned long long int u; format_item item; while (c <= e) { switch (*c) { /* if this is the EOF */ case '\0': if (s < c) { if (!ret.empty() && !ret.back().empty() && *ret.back().c_str() != '\0') ret.back() += std::string(s, cpplogging_convert_cast(c - s - 1)); else ret.emplace_back(s, c - s); } break; /* the format string starts with $ */ case '$': switch (state) { /* if we are 'in_text', we switch to 'is_token_begin' */ case parser_state::is_text: state = parser_state::is_token_begin; break; /* in an other case switch back to 'is_text' */ default: state = parser_state::is_text; item.reset(); break; } break; /* after '$' we expect '{' */ case '{': switch (state) { /* if we have seen '$' we are in 'is_token_begin' and switch to 'is_in_token' */ case parser_state::is_token_begin: if (s + 1 < c) { if (!ret.empty() && !ret.back().empty() && *ret.back().c_str() != '\0') ret.back() += std::string(s, cpplogging_convert_cast(c - s - 1)); else ret.emplace_back(s, c - s - 1); } s = c - 1; x = c + 1; state = parser_state::is_in_token; break; /* in an other case switch back to 'is_text' */ default: state = parser_state::is_text; item.reset(); break; } break; /* inside the token we have ':' to seperate the format */ case ':': switch (state) { /* if we are in 'is_in_token' we extract the token and switch to 'is_in_format' */ case parser_state::is_in_token: state = parser_state::is_in_format; item.token = value_token_map.get_token(std::string(x, cpplogging_convert_cast(c - x))); if (item.token == value_token::unknown) { state = parser_state::is_text; item.reset(); } x = c + 1; break; /* in an other case switch back to 'is_text' */ default: state = parser_state::is_text; item.reset(); break; } break; /* inside the format parsing we can have '-' to indicate right align */ case '-': switch (state) { /* if we are in 'is_in_format' we switch to 'is_in_format_align' */ case parser_state::is_in_format: state = parser_state::is_in_format_align; item.right_align = true; x = c + 1; break; /* in an other case switch back to 'is_text' */ default: state = parser_state::is_text; item.reset(); break; } break; /* inside the format parsing we can have '0' to indicate zero fill */ case '0': switch (state) { /* if we are in state 'is_in_format' or 'is_in_format_align' we switch to 'is_in_format_zero' */ case parser_state::is_in_format: case parser_state::is_in_format_align: state = parser_state::is_in_format_zero; item.zero_fill = true; x = c + 1; break; /* if we are in state 'is_in_format_zero' we ignore zeros, because they belong to the length */ case parser_state::is_in_format_zero: break; /* in an other case switch back to 'is_text' */ default: state = parser_state::is_text; item.reset(); break; } break; /* inside the format parsing we can have '.' to indicate the precision */ case '.': switch (state) { /* if we are in state 'is_in_format', 'is_in_format_align' or 'is_in_format_zero' we switch to 'is_in_format_dot' */ case parser_state::is_in_format: case parser_state::is_in_format_align: case parser_state::is_in_format_zero: u = std::strtoull(x, &tmp, 10); if (x == tmp || x > c) { /* if parsing the with failed, we switch back to 'is_text' */ state = parser_state::is_text; item.reset(); } else { item.width = cpplogging_convert_cast(u); x = c + 1; state = parser_state::is_in_format_dot; } break; /* in an other case switch back to 'is_text' */ default: state = parser_state::is_text; item.reset(); break; } break; /* if 'n' follows '$' we write a newline */ case 'n': switch (state) { case parser_state::is_token_begin: item.token = value_token::newline; ret.emplace_back(item.as_string()); item.reset(); state = parser_state::is_text; s = c + 1; break; default: break; } break; /* inside the format parsing we can have 's', 'f', 'd', 'x' or 'X' to indicate the format */ case 's': case 'S': case 'f': case 'd': case 'x': case 'X': switch (state) { /* if we are in state 'is_in_token' everything is fine */ case parser_state::is_in_token: break; /* if we are in state 'is_in_format', 'is_in_format_align' or 'is_in_format_zero' we switch to 'is_in_format_type' */ case parser_state::is_in_format: case parser_state::is_in_format_align: case parser_state::is_in_format_zero: case parser_state::is_in_format_dot: u = std::strtoull(x, &tmp, 10); if (x != c && (x == tmp || x > c)) { /* if parsing the with failed, we switch back to 'is_text' */ state = parser_state::is_text; item.reset(); } else { if (state == parser_state::is_in_format_dot) item.precision = cpplogging_convert_cast(u); else item.width = cpplogging_convert_cast(u); /* set the type and switch to 'is_in_format_type' */ x = c + 1; state = parser_state::is_in_format_type; switch(*c) { case 's': item.format = value_format::string; break; case 'S': item.format = value_format::string_upper; break; case 'f': item.format = value_format::real; break; case 'd': item.format = value_format::decimal; break; case 'x': item.format = value_format::hex_lower; break; case 'X': item.format = value_format::hex_upper; break; default: break; } } break; /* in an other case switch back to 'is_text' */ default: state = parser_state::is_text; item.reset(); break; } break; /* inside the token/format parsing '}' indicates the end */ case '}': switch (state) { /* if we are in 'is_in_token' we extract the token, add the item and switch to 'is_text' */ case parser_state::is_in_token: state = parser_state::is_text; item.token = value_token_map.get_token(std::string(x, cpplogging_convert_cast(c - x))); if (item.token == value_token::unknown) { state = parser_state::is_text; } else { ret.emplace_back(item.as_string()); } item.reset(); s = c + 1; break; /* if we are in 'is_in_format_type' we extract the token, add the item and switch to 'is_text' */ case parser_state::is_in_format_type: state = parser_state::is_text; ret.emplace_back(item.as_string()); item.reset(); s = c + 1; break; /* if we are in 'is_in_format', is_in_format_zero' or 'is_in_format_align' we extract the with, add the item and switch to 'is_text'*/ case parser_state::is_in_format: case parser_state::is_in_format_zero: case parser_state::is_in_format_align: case parser_state::is_in_format_dot: u = std::strtoull(x, &tmp, 10); s = c + 1; if (x != tmp && x <= c) { if (state == parser_state::is_in_format_dot) item.precision = cpplogging_convert_cast(u); else item.width = cpplogging_convert_cast(u); ret.emplace_back(item.as_string()); } item.reset(); state = parser_state::is_text; break; /* in an other case switch back to 'is_text' */ default: state = parser_state::is_text; item.reset(); break; } break; /* any non parsing relevant char */ default: switch (state) { /* in case 'is_in_format', 'is_in_format_align' and 'is_in_format_zero' we need to check if the current char is a digit, if not we switch to 'is_text' */ case parser_state::is_in_format: case parser_state::is_in_format_align: case parser_state::is_in_format_zero: if ( *c != '.' && (*c < '0' || *c > '9')) { state = parser_state::is_text; item.reset(); } break; /* any other case is fine */ default: break; } break; } ++c; } return ret; } static const auto start_time = std::chrono::steady_clock::now(); void consumer::write_formatted(const log_entry& e, const format_type& f, std::ostream& os) { using duration_f = std::chrono::duration>; using duration_u = std::chrono::duration>; static const std::string LogLevelUpperDebug("DEBUG", 5); static const std::string LogLevelUpperInfo ("INFO", 4); static const std::string LogLevelUpperWarn ("WARN", 4); static const std::string LogLevelUpperError("ERROR", 5); static const std::string LogLevelLowerDebug("debug", 5); static const std::string LogLevelLowerInfo ("info", 4); static const std::string LogLevelLowerWarn ("warn", 4); static const std::string LogLevelLowerError("error", 5); bool has_line_end = false; for (auto& x : f) { /* check if element is empty */ if (x.empty()) { continue; } /* check if element is normal item */ if (*x.c_str() != '\0') { os << x; continue; } /* setup the stream format */ const char * s; auto& i = *reinterpret_cast(x.c_str()); os << (i.right_align ? std::right : std::left) << std::setfill(i.zero_fill ? '0' : ' ') << std::setw(i.width) << std::setprecision(i.precision); switch (i.token) { /* level */ case value_token::level: has_line_end = false; switch (i.format) { case value_format::decimal: os << cpplogging_convert_cast(e.level); break; case value_format::string_upper: switch (e.level) { case log_level::debug: os << LogLevelUpperDebug; break; case log_level::info: os << LogLevelUpperInfo; break; case log_level::warn: os << LogLevelUpperWarn; break; case log_level::error: os << LogLevelUpperError; break; default: break; } break; default: switch (e.level) { case log_level::debug: os << LogLevelLowerDebug; break; case log_level::info: os << LogLevelLowerInfo; break; case log_level::warn: os << LogLevelLowerWarn; break; case log_level::error: os << LogLevelLowerError; break; default: break; } break; } break; /* uptime */ case value_token::uptime: has_line_end = false; switch (i.format) { case value_format::decimal: os << std::chrono::duration_cast(e.uptime.time_since_epoch()).count(); break; default: os << std::fixed << std::chrono::duration_cast(e.uptime.time_since_epoch()).count() << std::dec; break; } break; /* runtime */ case value_token::runtime: has_line_end = false; switch (i.format) { case value_format::decimal: os << std::chrono::duration_cast(e.uptime - start_time).count(); break; default: os << std::fixed << std::chrono::duration_cast(e.uptime - start_time).count() << std::dec; break; } break; /* systemtime */ case value_token::systime: has_line_end = false; switch (i.format) { case value_format::decimal: os << e.systime; break; default: os << std::fixed << e.systime << std::dec; break; } break; /* sender */ case value_token::sender: has_line_end = false; switch (i.format) { case value_format::decimal: os << reinterpret_cast(e.sender); break; case value_format::hex_lower: os << std::hex << reinterpret_cast(e.sender) << std::dec; break; default: os << std::hex << std::uppercase << reinterpret_cast(e.sender) << std::nouppercase << std::dec; break; } break; /* sender_type */ case value_token::sender_type: has_line_end = false; os << e.sender_type; break; /* thread */ case value_token::thread: has_line_end = false; switch (i.format) { case value_format::decimal: os << e.thread; break; case value_format::hex_lower: os << std::hex << e.thread << std::dec; break; default: os << std::hex << std::uppercase << e.thread << std::nouppercase << std::dec; break; } break; /* filename */ case value_token::filename: has_line_end = false; s = e.file; if (s) { auto tmp = strrchr(s, '/'); if (tmp) s = tmp + 1; } else s = "unknown"; os << s; break; /* filepath */ case value_token::filepath: has_line_end = false; os << (e.file ? e.file : "unknown"); break; /* line */ case value_token::line: has_line_end = false; switch (i.format) { case value_format::hex_lower: os << std::hex << e.line << std::dec; break; case value_format::hex_upper: os << std::hex << std::uppercase << e.line << std::nouppercase << std::dec; break; default: os << e.line; break; } break; /* name */ case value_token::name: if (!e.name.empty()) { os << e.name; has_line_end = (e.name.back() == '\n'); } break; /* message */ case value_token::message: if (!e.message.empty()) { os << e.message; has_line_end = (e.message.back() == '\n'); } break; /* newline */ case value_token::newline: has_line_end = true; os << std::endl; break; default: break; } } if (!has_line_end) os << std::endl; }