|
- #include <map>
- #include <chrono>
- #include <cstring>
- #include <iomanip>
-
- #include <cpplogging/manager/manager.h>
- #include <cpplogging/manager/consumer/consumer.h>
-
- 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<const char*>(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<std::string, value_token, op_less_invariant_string>
- {
- 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, static_cast<size_t>(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, static_cast<size_t>(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, static_cast<size_t>(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 = static_cast<uint8_t>(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 = static_cast<uint8_t>(u);
- else
- item.width = static_cast<uint8_t>(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, static_cast<size_t>(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 = static_cast<uint8_t>(u);
- else
- item.width = static_cast<uint8_t>(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<double, std::ratio<1>>;
- using duration_u = std::chrono::duration<unsigned long long int, std::ratio<1>>;
-
- 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<const format_item*>(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 << static_cast<int>(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<duration_u>(e.uptime.time_since_epoch()).count();
- break;
- default:
- os << std::fixed
- << std::chrono::duration_cast<duration_f>(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<duration_u>(e.uptime - start_time).count();
- break;
- default:
- os << std::fixed
- << std::chrono::duration_cast<duration_f>(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<uintptr_t>(e.sender);
- break;
- case value_format::hex_lower:
- os << std::hex
- << reinterpret_cast<uintptr_t>(e.sender)
- << std::dec;
- break;
- default:
- os << std::hex
- << std::uppercase
- << reinterpret_cast<uintptr_t>(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;
- }
|