No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.
 
 
 

690 líneas
26 KiB

  1. #include <map>
  2. #include <chrono>
  3. #include <cstring>
  4. #include <iomanip>
  5. #include <cpplogging/manager/manager.h>
  6. #include <cpplogging/manager/consumer/consumer.h>
  7. using namespace ::cpplogging;
  8. enum class parser_state
  9. {
  10. is_text, //!< current pos is inside normal text
  11. is_token_begin, //!< current pos is in token begin (except char '$')
  12. is_in_token, //!< current pos is in token (everything after '{')
  13. is_in_format, //!< current pos is in format (everything after ':')
  14. is_in_format_align, //!< current pos is in format align (expect char '-')
  15. is_in_format_zero, //!< current pos is in format align (expect char '0')
  16. is_in_format_dot, //!< current pos is in format dot (expect char '.')
  17. is_in_format_type, //!< current pos is in format type (expect char 's', 'f', x', 'X' or 'd')
  18. };
  19. enum class value_token
  20. : uint8_t
  21. {
  22. unknown = 0, //!< unknown
  23. level, //!< log level
  24. uptime, //!< system uptime
  25. systime, //!< unix timestamp
  26. runtime, //!< runtime of the application
  27. sender, //!< sender of the log message
  28. sender_type, //!< type of sender
  29. thread, //!< thread id
  30. filename, //!< filename only
  31. filepath, //!< whole filepath
  32. line, //!< line number
  33. name, //!< name of the logger
  34. message, //!< log message
  35. newline, //!< write new line
  36. };
  37. enum class value_format
  38. : uint8_t
  39. {
  40. unknown = 0,
  41. decimal,
  42. real,
  43. hex_lower,
  44. hex_upper,
  45. string,
  46. string_upper,
  47. };
  48. struct format_item
  49. {
  50. const uint16_t magic { 0 }; //!< magic value to identify the item
  51. value_token token { value_token::unknown }; //!< value to print
  52. value_format format { value_format::unknown }; //!< format to print the value with
  53. uint8_t width { 0 }; //!< with of the value
  54. uint8_t precision { 0 }; //!< precision (for float values)
  55. bool right_align { false }; //!< use right alignment
  56. bool zero_fill { false }; //!< fill with zeros (if not than spaces)
  57. inline void reset()
  58. { memset(this, 0, sizeof(*this)); }
  59. inline std::string as_string() const
  60. { return std::string(reinterpret_cast<const char*>(this), sizeof(*this)); }
  61. } __attribute__ ((packed));
  62. static_assert(sizeof(format_item) == 8, "format_item has invalid size!");
  63. struct op_less_invariant_string
  64. {
  65. inline bool operator()(const std::string& lhs, const std::string& rhs) const
  66. {
  67. auto c1 = lhs.c_str();
  68. auto c2 = rhs.c_str();
  69. auto l1 = lhs.size();
  70. auto l2 = rhs.size();
  71. while (l1 > 0 && l2 > 0 && std::tolower(*c1) == std::tolower(*c2))
  72. {
  73. ++c1;
  74. ++c2;
  75. --l1;
  76. --l2;
  77. }
  78. return l1 > 0 && l2 > 0
  79. ? std::tolower(*c1) < std::tolower(*c2)
  80. : l1 < l2;
  81. }
  82. };
  83. struct invariant_string_map
  84. : public std::map<std::string, value_token, op_less_invariant_string>
  85. {
  86. using map::map;
  87. inline value_token get_token(const std::string& token) const
  88. {
  89. auto it = find(token);
  90. return it == end()
  91. ? value_token::unknown
  92. : it->second;
  93. }
  94. };
  95. static invariant_string_map value_token_map({
  96. { "level", value_token::level },
  97. { "uptime", value_token::uptime },
  98. { "systime", value_token::systime },
  99. { "runtime", value_token::runtime },
  100. { "sender", value_token::sender },
  101. { "sender_type", value_token::sender_type },
  102. { "thread", value_token::thread },
  103. { "filename", value_token::filename },
  104. { "filepath", value_token::filepath },
  105. { "line", value_token::line },
  106. { "name", value_token::name },
  107. { "message", value_token::message },
  108. { "newline", value_token::newline },
  109. });
  110. consumer::format_type consumer::parse_format(const std::string& format)
  111. {
  112. consumer::format_type ret;
  113. parser_state state = parser_state::is_text;
  114. const char * s = format.c_str();
  115. const char * e = s + format.size();
  116. const char * c = s;
  117. const char * x;
  118. char * tmp;
  119. unsigned long long int u;
  120. format_item item;
  121. while (c <= e)
  122. {
  123. switch (*c)
  124. {
  125. /* if this is the EOF */
  126. case '\0':
  127. if (s < c)
  128. {
  129. if (!ret.empty() && !ret.back().empty() && *ret.back().c_str() != '\0')
  130. ret.back() += std::string(s, static_cast<size_t>(c - s - 1));
  131. else
  132. ret.emplace_back(s, c - s);
  133. }
  134. break;
  135. /* the format string starts with $ */
  136. case '$':
  137. switch (state)
  138. {
  139. /* if we are 'in_text', we switch to 'is_token_begin' */
  140. case parser_state::is_text:
  141. state = parser_state::is_token_begin;
  142. break;
  143. /* in an other case switch back to 'is_text' */
  144. default:
  145. state = parser_state::is_text;
  146. item.reset();
  147. break;
  148. }
  149. break;
  150. /* after '$' we expect '{' */
  151. case '{':
  152. switch (state)
  153. {
  154. /* if we have seen '$' we are in 'is_token_begin' and switch to 'is_in_token' */
  155. case parser_state::is_token_begin:
  156. if (s + 1 < c)
  157. {
  158. if (!ret.empty() && !ret.back().empty() && *ret.back().c_str() != '\0')
  159. ret.back() += std::string(s, static_cast<size_t>(c - s - 1));
  160. else
  161. ret.emplace_back(s, c - s - 1);
  162. }
  163. s = c - 1;
  164. x = c + 1;
  165. state = parser_state::is_in_token;
  166. break;
  167. /* in an other case switch back to 'is_text' */
  168. default:
  169. state = parser_state::is_text;
  170. item.reset();
  171. break;
  172. }
  173. break;
  174. /* inside the token we have ':' to seperate the format */
  175. case ':':
  176. switch (state)
  177. {
  178. /* if we are in 'is_in_token' we extract the token and switch to 'is_in_format' */
  179. case parser_state::is_in_token:
  180. state = parser_state::is_in_format;
  181. item.token = value_token_map.get_token(std::string(x, static_cast<size_t>(c - x)));
  182. if (item.token == value_token::unknown)
  183. {
  184. state = parser_state::is_text;
  185. item.reset();
  186. }
  187. x = c + 1;
  188. break;
  189. /* in an other case switch back to 'is_text' */
  190. default:
  191. state = parser_state::is_text;
  192. item.reset();
  193. break;
  194. }
  195. break;
  196. /* inside the format parsing we can have '-' to indicate right align */
  197. case '-':
  198. switch (state)
  199. {
  200. /* if we are in 'is_in_format' we switch to 'is_in_format_align' */
  201. case parser_state::is_in_format:
  202. state = parser_state::is_in_format_align;
  203. item.right_align = true;
  204. x = c + 1;
  205. break;
  206. /* in an other case switch back to 'is_text' */
  207. default:
  208. state = parser_state::is_text;
  209. item.reset();
  210. break;
  211. }
  212. break;
  213. /* inside the format parsing we can have '0' to indicate zero fill */
  214. case '0':
  215. switch (state)
  216. {
  217. /* if we are in state 'is_in_format' or 'is_in_format_align' we switch to 'is_in_format_zero' */
  218. case parser_state::is_in_format:
  219. case parser_state::is_in_format_align:
  220. state = parser_state::is_in_format_zero;
  221. item.zero_fill = true;
  222. x = c + 1;
  223. break;
  224. /* in an other case switch back to 'is_text' */
  225. default:
  226. state = parser_state::is_text;
  227. item.reset();
  228. break;
  229. }
  230. break;
  231. /* inside the format parsing we can have '.' to indicate the precision */
  232. case '.':
  233. switch (state)
  234. {
  235. /* if we are in state 'is_in_format', 'is_in_format_align' or 'is_in_format_zero' we switch to 'is_in_format_dot' */
  236. case parser_state::is_in_format:
  237. case parser_state::is_in_format_align:
  238. case parser_state::is_in_format_zero:
  239. u = std::strtoull(x, &tmp, 10);
  240. if (x == tmp || x > c)
  241. {
  242. /* if parsing the with failed, we switch back to 'is_text' */
  243. state = parser_state::is_text;
  244. item.reset();
  245. }
  246. else
  247. {
  248. item.width = static_cast<uint8_t>(u);
  249. x = c + 1;
  250. state = parser_state::is_in_format_dot;
  251. }
  252. break;
  253. /* in an other case switch back to 'is_text' */
  254. default:
  255. state = parser_state::is_text;
  256. item.reset();
  257. break;
  258. }
  259. break;
  260. /* if 'n' follows '$' we write a newline */
  261. case 'n':
  262. switch (state)
  263. {
  264. case parser_state::is_token_begin:
  265. item.token = value_token::newline;
  266. ret.emplace_back(item.as_string());
  267. item.reset();
  268. state = parser_state::is_text;
  269. s = c + 1;
  270. break;
  271. default:
  272. break;
  273. }
  274. break;
  275. /* inside the format parsing we can have 's', 'f', 'd', 'x' or 'X' to indicate the format */
  276. case 's':
  277. case 'S':
  278. case 'f':
  279. case 'd':
  280. case 'x':
  281. case 'X':
  282. switch (state)
  283. {
  284. /* if we are in state 'is_in_token' everything is fine */
  285. case parser_state::is_in_token:
  286. break;
  287. /* if we are in state 'is_in_format', 'is_in_format_align' or 'is_in_format_zero' we switch to 'is_in_format_type' */
  288. case parser_state::is_in_format:
  289. case parser_state::is_in_format_align:
  290. case parser_state::is_in_format_zero:
  291. case parser_state::is_in_format_dot:
  292. u = std::strtoull(x, &tmp, 10);
  293. if (x != c && (x == tmp || x > c))
  294. {
  295. /* if parsing the with failed, we switch back to 'is_text' */
  296. state = parser_state::is_text;
  297. item.reset();
  298. }
  299. else
  300. {
  301. if (state == parser_state::is_in_format_dot)
  302. item.precision = static_cast<uint8_t>(u);
  303. else
  304. item.width = static_cast<uint8_t>(u);
  305. /* set the type and switch to 'is_in_format_type' */
  306. x = c + 1;
  307. state = parser_state::is_in_format_type;
  308. switch(*c)
  309. {
  310. case 's': item.format = value_format::string; break;
  311. case 'S': item.format = value_format::string_upper; break;
  312. case 'f': item.format = value_format::real; break;
  313. case 'd': item.format = value_format::decimal; break;
  314. case 'x': item.format = value_format::hex_lower; break;
  315. case 'X': item.format = value_format::hex_upper; break;
  316. default: break;
  317. }
  318. }
  319. break;
  320. /* in an other case switch back to 'is_text' */
  321. default:
  322. state = parser_state::is_text;
  323. item.reset();
  324. break;
  325. }
  326. break;
  327. /* inside the token/format parsing '}' indicates the end */
  328. case '}':
  329. switch (state)
  330. {
  331. /* if we are in 'is_in_token' we extract the token, add the item and switch to 'is_text' */
  332. case parser_state::is_in_token:
  333. state = parser_state::is_text;
  334. item.token = value_token_map.get_token(std::string(x, static_cast<size_t>(c - x)));
  335. if (item.token == value_token::unknown)
  336. {
  337. state = parser_state::is_text;
  338. }
  339. else
  340. {
  341. ret.emplace_back(item.as_string());
  342. }
  343. item.reset();
  344. s = c + 1;
  345. break;
  346. /* if we are in 'is_in_format_type' we extract the token, add the item and switch to 'is_text' */
  347. case parser_state::is_in_format_type:
  348. state = parser_state::is_text;
  349. ret.emplace_back(item.as_string());
  350. item.reset();
  351. s = c + 1;
  352. break;
  353. /* if we are in 'is_in_format', is_in_format_zero' or 'is_in_format_align'
  354. we extract the with, add the item and switch to 'is_text'*/
  355. case parser_state::is_in_format:
  356. case parser_state::is_in_format_zero:
  357. case parser_state::is_in_format_align:
  358. case parser_state::is_in_format_dot:
  359. u = std::strtoull(x, &tmp, 10);
  360. s = c + 1;
  361. if (x != tmp && x <= c)
  362. {
  363. if (state == parser_state::is_in_format_dot)
  364. item.precision = static_cast<uint8_t>(u);
  365. else
  366. item.width = static_cast<uint8_t>(u);
  367. ret.emplace_back(item.as_string());
  368. }
  369. item.reset();
  370. state = parser_state::is_text;
  371. break;
  372. /* in an other case switch back to 'is_text' */
  373. default:
  374. state = parser_state::is_text;
  375. item.reset();
  376. break;
  377. }
  378. break;
  379. /* any non parsing relevant char */
  380. default:
  381. switch (state)
  382. {
  383. /* in case 'is_in_format', 'is_in_format_align' and 'is_in_format_zero'
  384. we need to check if the current char is a digit, if not we switch to 'is_text' */
  385. case parser_state::is_in_format:
  386. case parser_state::is_in_format_align:
  387. case parser_state::is_in_format_zero:
  388. if ( *c != '.'
  389. && (*c < '0' || *c > '9'))
  390. {
  391. state = parser_state::is_text;
  392. item.reset();
  393. }
  394. break;
  395. /* any other case is fine */
  396. default:
  397. break;
  398. }
  399. break;
  400. }
  401. ++c;
  402. }
  403. return ret;
  404. }
  405. static const auto start_time = std::chrono::steady_clock::now();
  406. void consumer::write_formatted(const log_entry& e, const format_type& f, std::ostream& os)
  407. {
  408. using duration_f = std::chrono::duration<double, std::ratio<1>>;
  409. using duration_u = std::chrono::duration<unsigned long long int, std::ratio<1>>;
  410. static const std::string LogLevelUpperDebug("DEBUG", 5);
  411. static const std::string LogLevelUpperInfo ("INFO", 4);
  412. static const std::string LogLevelUpperWarn ("WARN", 4);
  413. static const std::string LogLevelUpperError("ERROR", 5);
  414. static const std::string LogLevelLowerDebug("debug", 5);
  415. static const std::string LogLevelLowerInfo ("info", 4);
  416. static const std::string LogLevelLowerWarn ("warn", 4);
  417. static const std::string LogLevelLowerError("error", 5);
  418. bool has_line_end = false;
  419. for (auto& x : f)
  420. {
  421. /* check if element is empty */
  422. if (x.empty())
  423. {
  424. continue;
  425. }
  426. /* check if element is normal item */
  427. if (*x.c_str() != '\0')
  428. {
  429. os << x;
  430. continue;
  431. }
  432. /* setup the stream format */
  433. const char * s;
  434. auto& i = *reinterpret_cast<const format_item*>(x.c_str());
  435. os << (i.right_align ? std::right : std::left)
  436. << std::setfill(i.zero_fill ? '0' : ' ')
  437. << std::setw(i.width)
  438. << std::setprecision(i.precision);
  439. switch (i.token)
  440. {
  441. /* level */
  442. case value_token::level:
  443. has_line_end = false;
  444. switch (i.format)
  445. {
  446. case value_format::decimal:
  447. os << static_cast<int>(e.level);
  448. break;
  449. case value_format::string_upper:
  450. switch (e.level)
  451. {
  452. case log_level::debug: os << LogLevelUpperDebug; break;
  453. case log_level::info: os << LogLevelUpperInfo; break;
  454. case log_level::warn: os << LogLevelUpperWarn; break;
  455. case log_level::error: os << LogLevelUpperError; break;
  456. default: break;
  457. }
  458. break;
  459. default:
  460. switch (e.level)
  461. {
  462. case log_level::debug: os << LogLevelLowerDebug; break;
  463. case log_level::info: os << LogLevelLowerInfo; break;
  464. case log_level::warn: os << LogLevelLowerWarn; break;
  465. case log_level::error: os << LogLevelLowerError; break;
  466. default: break;
  467. }
  468. break;
  469. }
  470. break;
  471. /* uptime */
  472. case value_token::uptime:
  473. has_line_end = false;
  474. switch (i.format)
  475. {
  476. case value_format::decimal:
  477. os << std::chrono::duration_cast<duration_u>(e.uptime.time_since_epoch()).count();
  478. break;
  479. default:
  480. os << std::fixed
  481. << std::chrono::duration_cast<duration_f>(e.uptime.time_since_epoch()).count()
  482. << std::dec;
  483. break;
  484. }
  485. break;
  486. /* runtime */
  487. case value_token::runtime:
  488. has_line_end = false;
  489. switch (i.format)
  490. {
  491. case value_format::decimal:
  492. os << std::chrono::duration_cast<duration_u>(e.uptime - start_time).count();
  493. break;
  494. default:
  495. os << std::fixed
  496. << std::chrono::duration_cast<duration_f>(e.uptime - start_time).count()
  497. << std::dec;
  498. break;
  499. }
  500. break;
  501. /* systemtime */
  502. case value_token::systime:
  503. has_line_end = false;
  504. switch (i.format)
  505. {
  506. case value_format::decimal:
  507. os << e.systime;
  508. break;
  509. default:
  510. os << std::fixed
  511. << e.systime
  512. << std::dec;
  513. break;
  514. }
  515. break;
  516. /* sender */
  517. case value_token::sender:
  518. has_line_end = false;
  519. switch (i.format)
  520. {
  521. case value_format::decimal:
  522. os << reinterpret_cast<uintptr_t>(e.sender);
  523. break;
  524. case value_format::hex_lower:
  525. os << "0x"
  526. << std::hex
  527. << reinterpret_cast<uintptr_t>(e.sender)
  528. << std::dec;
  529. break;
  530. default:
  531. os << "0x"
  532. << std::hex
  533. << std::uppercase
  534. << reinterpret_cast<uintptr_t>(e.sender)
  535. << std::nouppercase
  536. << std::dec;
  537. break;
  538. }
  539. break;
  540. /* sender_type */
  541. case value_token::sender_type:
  542. has_line_end = false;
  543. os << e.sender_type;
  544. break;
  545. /* thread */
  546. case value_token::thread:
  547. has_line_end = false;
  548. switch (i.format)
  549. {
  550. case value_format::decimal:
  551. os << e.thread;
  552. break;
  553. case value_format::hex_lower:
  554. os << "0x"
  555. << std::hex
  556. << e.thread
  557. << std::dec;
  558. break;
  559. default:
  560. os << "0x"
  561. << std::hex
  562. << std::uppercase
  563. << e.thread
  564. << std::nouppercase
  565. << std::dec;
  566. break;
  567. }
  568. break;
  569. /* filename */
  570. case value_token::filename:
  571. has_line_end = false;
  572. s = e.file;
  573. if (s)
  574. {
  575. auto tmp = strrchr(s, '/');
  576. if (tmp)
  577. s = tmp + 1;
  578. }
  579. else
  580. s = "unknown";
  581. os << s;
  582. break;
  583. /* filepath */
  584. case value_token::filepath:
  585. has_line_end = false;
  586. os << (e.file ? e.file : "unknown");
  587. break;
  588. /* line */
  589. case value_token::line:
  590. has_line_end = false;
  591. switch (i.format)
  592. {
  593. case value_format::decimal:
  594. os << e.line;
  595. break;
  596. case value_format::hex_lower:
  597. os << "0x"
  598. << std::hex
  599. << e.line
  600. << std::dec;
  601. break;
  602. default:
  603. os << "0x"
  604. << std::hex
  605. << std::uppercase
  606. << e.line
  607. << std::nouppercase
  608. << std::dec;
  609. break;
  610. }
  611. break;
  612. /* name */
  613. case value_token::name:
  614. if (!e.name.empty())
  615. {
  616. os << e.name;
  617. has_line_end = (e.name.back() == '\n');
  618. }
  619. break;
  620. /* message */
  621. case value_token::message:
  622. if (!e.message.empty())
  623. {
  624. os << e.message;
  625. has_line_end = (e.message.back() == '\n');
  626. }
  627. break;
  628. /* newline */
  629. case value_token::newline:
  630. has_line_end = true;
  631. os << std::endl;
  632. break;
  633. default:
  634. break;
  635. }
  636. }
  637. if (!has_line_end)
  638. os << std::endl;
  639. }