25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 

686 satır
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 = s;
  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. /* if we are in state 'is_in_format_zero' we ignore zeros, because they belong to the length */
  225. case parser_state::is_in_format_zero:
  226. break;
  227. /* in an other case switch back to 'is_text' */
  228. default:
  229. state = parser_state::is_text;
  230. item.reset();
  231. break;
  232. }
  233. break;
  234. /* inside the format parsing we can have '.' to indicate the precision */
  235. case '.':
  236. switch (state)
  237. {
  238. /* if we are in state 'is_in_format', 'is_in_format_align' or 'is_in_format_zero' we switch to 'is_in_format_dot' */
  239. case parser_state::is_in_format:
  240. case parser_state::is_in_format_align:
  241. case parser_state::is_in_format_zero:
  242. u = std::strtoull(x, &tmp, 10);
  243. if (x == tmp || x > c)
  244. {
  245. /* if parsing the with failed, we switch back to 'is_text' */
  246. state = parser_state::is_text;
  247. item.reset();
  248. }
  249. else
  250. {
  251. item.width = static_cast<uint8_t>(u);
  252. x = c + 1;
  253. state = parser_state::is_in_format_dot;
  254. }
  255. break;
  256. /* in an other case switch back to 'is_text' */
  257. default:
  258. state = parser_state::is_text;
  259. item.reset();
  260. break;
  261. }
  262. break;
  263. /* if 'n' follows '$' we write a newline */
  264. case 'n':
  265. switch (state)
  266. {
  267. case parser_state::is_token_begin:
  268. item.token = value_token::newline;
  269. ret.emplace_back(item.as_string());
  270. item.reset();
  271. state = parser_state::is_text;
  272. s = c + 1;
  273. break;
  274. default:
  275. break;
  276. }
  277. break;
  278. /* inside the format parsing we can have 's', 'f', 'd', 'x' or 'X' to indicate the format */
  279. case 's':
  280. case 'S':
  281. case 'f':
  282. case 'd':
  283. case 'x':
  284. case 'X':
  285. switch (state)
  286. {
  287. /* if we are in state 'is_in_token' everything is fine */
  288. case parser_state::is_in_token:
  289. break;
  290. /* if we are in state 'is_in_format', 'is_in_format_align' or 'is_in_format_zero' we switch to 'is_in_format_type' */
  291. case parser_state::is_in_format:
  292. case parser_state::is_in_format_align:
  293. case parser_state::is_in_format_zero:
  294. case parser_state::is_in_format_dot:
  295. u = std::strtoull(x, &tmp, 10);
  296. if (x != c && (x == tmp || x > c))
  297. {
  298. /* if parsing the with failed, we switch back to 'is_text' */
  299. state = parser_state::is_text;
  300. item.reset();
  301. }
  302. else
  303. {
  304. if (state == parser_state::is_in_format_dot)
  305. item.precision = static_cast<uint8_t>(u);
  306. else
  307. item.width = static_cast<uint8_t>(u);
  308. /* set the type and switch to 'is_in_format_type' */
  309. x = c + 1;
  310. state = parser_state::is_in_format_type;
  311. switch(*c)
  312. {
  313. case 's': item.format = value_format::string; break;
  314. case 'S': item.format = value_format::string_upper; break;
  315. case 'f': item.format = value_format::real; break;
  316. case 'd': item.format = value_format::decimal; break;
  317. case 'x': item.format = value_format::hex_lower; break;
  318. case 'X': item.format = value_format::hex_upper; break;
  319. default: break;
  320. }
  321. }
  322. break;
  323. /* in an other case switch back to 'is_text' */
  324. default:
  325. state = parser_state::is_text;
  326. item.reset();
  327. break;
  328. }
  329. break;
  330. /* inside the token/format parsing '}' indicates the end */
  331. case '}':
  332. switch (state)
  333. {
  334. /* if we are in 'is_in_token' we extract the token, add the item and switch to 'is_text' */
  335. case parser_state::is_in_token:
  336. state = parser_state::is_text;
  337. item.token = value_token_map.get_token(std::string(x, static_cast<size_t>(c - x)));
  338. if (item.token == value_token::unknown)
  339. {
  340. state = parser_state::is_text;
  341. }
  342. else
  343. {
  344. ret.emplace_back(item.as_string());
  345. }
  346. item.reset();
  347. s = c + 1;
  348. break;
  349. /* if we are in 'is_in_format_type' we extract the token, add the item and switch to 'is_text' */
  350. case parser_state::is_in_format_type:
  351. state = parser_state::is_text;
  352. ret.emplace_back(item.as_string());
  353. item.reset();
  354. s = c + 1;
  355. break;
  356. /* if we are in 'is_in_format', is_in_format_zero' or 'is_in_format_align'
  357. we extract the with, add the item and switch to 'is_text'*/
  358. case parser_state::is_in_format:
  359. case parser_state::is_in_format_zero:
  360. case parser_state::is_in_format_align:
  361. case parser_state::is_in_format_dot:
  362. u = std::strtoull(x, &tmp, 10);
  363. s = c + 1;
  364. if (x != tmp && x <= c)
  365. {
  366. if (state == parser_state::is_in_format_dot)
  367. item.precision = static_cast<uint8_t>(u);
  368. else
  369. item.width = static_cast<uint8_t>(u);
  370. ret.emplace_back(item.as_string());
  371. }
  372. item.reset();
  373. state = parser_state::is_text;
  374. break;
  375. /* in an other case switch back to 'is_text' */
  376. default:
  377. state = parser_state::is_text;
  378. item.reset();
  379. break;
  380. }
  381. break;
  382. /* any non parsing relevant char */
  383. default:
  384. switch (state)
  385. {
  386. /* in case 'is_in_format', 'is_in_format_align' and 'is_in_format_zero'
  387. we need to check if the current char is a digit, if not we switch to 'is_text' */
  388. case parser_state::is_in_format:
  389. case parser_state::is_in_format_align:
  390. case parser_state::is_in_format_zero:
  391. if ( *c != '.'
  392. && (*c < '0' || *c > '9'))
  393. {
  394. state = parser_state::is_text;
  395. item.reset();
  396. }
  397. break;
  398. /* any other case is fine */
  399. default:
  400. break;
  401. }
  402. break;
  403. }
  404. ++c;
  405. }
  406. return ret;
  407. }
  408. static const auto start_time = std::chrono::steady_clock::now();
  409. void consumer::write_formatted(const log_entry& e, const format_type& f, std::ostream& os)
  410. {
  411. using duration_f = std::chrono::duration<double, std::ratio<1>>;
  412. using duration_u = std::chrono::duration<unsigned long long int, std::ratio<1>>;
  413. static const std::string LogLevelUpperDebug("DEBUG", 5);
  414. static const std::string LogLevelUpperInfo ("INFO", 4);
  415. static const std::string LogLevelUpperWarn ("WARN", 4);
  416. static const std::string LogLevelUpperError("ERROR", 5);
  417. static const std::string LogLevelLowerDebug("debug", 5);
  418. static const std::string LogLevelLowerInfo ("info", 4);
  419. static const std::string LogLevelLowerWarn ("warn", 4);
  420. static const std::string LogLevelLowerError("error", 5);
  421. bool has_line_end = false;
  422. for (auto& x : f)
  423. {
  424. /* check if element is empty */
  425. if (x.empty())
  426. {
  427. continue;
  428. }
  429. /* check if element is normal item */
  430. if (*x.c_str() != '\0')
  431. {
  432. os << x;
  433. continue;
  434. }
  435. /* setup the stream format */
  436. const char * s;
  437. auto& i = *reinterpret_cast<const format_item*>(x.c_str());
  438. os << (i.right_align ? std::right : std::left)
  439. << std::setfill(i.zero_fill ? '0' : ' ')
  440. << std::setw(i.width)
  441. << std::setprecision(i.precision);
  442. switch (i.token)
  443. {
  444. /* level */
  445. case value_token::level:
  446. has_line_end = false;
  447. switch (i.format)
  448. {
  449. case value_format::decimal:
  450. os << static_cast<int>(e.level);
  451. break;
  452. case value_format::string_upper:
  453. switch (e.level)
  454. {
  455. case log_level::debug: os << LogLevelUpperDebug; break;
  456. case log_level::info: os << LogLevelUpperInfo; break;
  457. case log_level::warn: os << LogLevelUpperWarn; break;
  458. case log_level::error: os << LogLevelUpperError; break;
  459. default: break;
  460. }
  461. break;
  462. default:
  463. switch (e.level)
  464. {
  465. case log_level::debug: os << LogLevelLowerDebug; break;
  466. case log_level::info: os << LogLevelLowerInfo; break;
  467. case log_level::warn: os << LogLevelLowerWarn; break;
  468. case log_level::error: os << LogLevelLowerError; break;
  469. default: break;
  470. }
  471. break;
  472. }
  473. break;
  474. /* uptime */
  475. case value_token::uptime:
  476. has_line_end = false;
  477. switch (i.format)
  478. {
  479. case value_format::decimal:
  480. os << std::chrono::duration_cast<duration_u>(e.uptime.time_since_epoch()).count();
  481. break;
  482. default:
  483. os << std::fixed
  484. << std::chrono::duration_cast<duration_f>(e.uptime.time_since_epoch()).count()
  485. << std::dec;
  486. break;
  487. }
  488. break;
  489. /* runtime */
  490. case value_token::runtime:
  491. has_line_end = false;
  492. switch (i.format)
  493. {
  494. case value_format::decimal:
  495. os << std::chrono::duration_cast<duration_u>(e.uptime - start_time).count();
  496. break;
  497. default:
  498. os << std::fixed
  499. << std::chrono::duration_cast<duration_f>(e.uptime - start_time).count()
  500. << std::dec;
  501. break;
  502. }
  503. break;
  504. /* systemtime */
  505. case value_token::systime:
  506. has_line_end = false;
  507. switch (i.format)
  508. {
  509. case value_format::decimal:
  510. os << e.systime;
  511. break;
  512. default:
  513. os << std::fixed
  514. << e.systime
  515. << std::dec;
  516. break;
  517. }
  518. break;
  519. /* sender */
  520. case value_token::sender:
  521. has_line_end = false;
  522. switch (i.format)
  523. {
  524. case value_format::decimal:
  525. os << reinterpret_cast<uintptr_t>(e.sender);
  526. break;
  527. case value_format::hex_lower:
  528. os << std::hex
  529. << reinterpret_cast<uintptr_t>(e.sender)
  530. << std::dec;
  531. break;
  532. default:
  533. os << std::hex
  534. << std::uppercase
  535. << reinterpret_cast<uintptr_t>(e.sender)
  536. << std::nouppercase
  537. << std::dec;
  538. break;
  539. }
  540. break;
  541. /* sender_type */
  542. case value_token::sender_type:
  543. has_line_end = false;
  544. os << e.sender_type;
  545. break;
  546. /* thread */
  547. case value_token::thread:
  548. has_line_end = false;
  549. switch (i.format)
  550. {
  551. case value_format::decimal:
  552. os << e.thread;
  553. break;
  554. case value_format::hex_lower:
  555. os << std::hex
  556. << e.thread
  557. << std::dec;
  558. break;
  559. default:
  560. os << std::hex
  561. << std::uppercase
  562. << e.thread
  563. << std::nouppercase
  564. << std::dec;
  565. break;
  566. }
  567. break;
  568. /* filename */
  569. case value_token::filename:
  570. has_line_end = false;
  571. s = e.file;
  572. if (s)
  573. {
  574. auto tmp = strrchr(s, '/');
  575. if (tmp)
  576. s = tmp + 1;
  577. }
  578. else
  579. s = "unknown";
  580. os << s;
  581. break;
  582. /* filepath */
  583. case value_token::filepath:
  584. has_line_end = false;
  585. os << (e.file ? e.file : "unknown");
  586. break;
  587. /* line */
  588. case value_token::line:
  589. has_line_end = false;
  590. switch (i.format)
  591. {
  592. case value_format::hex_lower:
  593. os << std::hex
  594. << e.line
  595. << std::dec;
  596. break;
  597. case value_format::hex_upper:
  598. os << std::hex
  599. << std::uppercase
  600. << e.line
  601. << std::nouppercase
  602. << std::dec;
  603. break;
  604. default:
  605. os << e.line;
  606. break;
  607. }
  608. break;
  609. /* name */
  610. case value_token::name:
  611. if (!e.name.empty())
  612. {
  613. os << e.name;
  614. has_line_end = (e.name.back() == '\n');
  615. }
  616. break;
  617. /* message */
  618. case value_token::message:
  619. if (!e.message.empty())
  620. {
  621. os << e.message;
  622. has_line_end = (e.message.back() == '\n');
  623. }
  624. break;
  625. /* newline */
  626. case value_token::newline:
  627. has_line_end = true;
  628. os << std::endl;
  629. break;
  630. default:
  631. break;
  632. }
  633. }
  634. if (!has_line_end)
  635. os << std::endl;
  636. }