format.cxx 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. //
  2. // format.cpp
  3. // logger
  4. //
  5. // Created by Sam Jaffe on 8/21/16.
  6. //
  7. #include "logger/format.h"
  8. #include <cstdint>
  9. #include <cstring>
  10. #include <ctime>
  11. #include <iomanip>
  12. #include <iostream>
  13. #include <map>
  14. #include <stdexcept>
  15. #include <string>
  16. #include "common.h"
  17. #include "logger/exception.h"
  18. #include "logger/logger.h"
  19. #if defined( _WIN32 )
  20. # define NEWLINE "\r\n"
  21. #else
  22. # define NEWLINE "\n"
  23. #endif
  24. namespace logging {
  25. namespace {
  26. std::string fmt_time(struct timeval round, char const * const fmt) {
  27. struct tm time;
  28. // Supports localtime when requested, but you can't really test that
  29. if (strstr(fmt, "%z") || strstr(fmt, "%Z")) {
  30. localtime_r(&round.tv_sec, &time);
  31. } else {
  32. gmtime_r(&round.tv_sec, &time);
  33. }
  34. char buf[64] = {'\0'};
  35. std::strftime(buf, sizeof(buf), fmt, &time);
  36. return buf;
  37. }
  38. std::string fmt_time(struct timeval round, std::string const & fmt) {
  39. return fmt_time(round, fmt.c_str());
  40. }
  41. std::string fmt_time_with_milis(struct timeval round,
  42. std::string const & fmt) {
  43. char buf[64] = {'\0'};
  44. snprintf(buf, sizeof(buf), fmt.c_str(), round.tv_usec/1000);
  45. return fmt_time(round, buf);
  46. }
  47. }
  48. using string_generator = std::function<std::string(logpacket const &)>;
  49. string_generator parse_date_format_string( char const * str ) {
  50. char const * const end = strchr(str, '}');
  51. if (end == nullptr) {
  52. std::string error_msg{"expected date-format specifier to terminate"};
  53. throw format_parsing_exception{error_msg};
  54. }
  55. char const * const has_millis = strstr( str, "%_ms");
  56. std::string fmtstring{str, end};
  57. if ( has_millis && has_millis < end ) {
  58. size_t pos = 0;
  59. while ( (pos = fmtstring.find("%", pos)) != std::string::npos ) {
  60. fmtstring.replace(pos, 1, "%%");
  61. pos += 2;
  62. }
  63. fmtstring.replace(fmtstring.find("%%_ms"), 5, "%.03d");
  64. return [=](logpacket const & lp) {
  65. return fmt_time_with_milis(lp.time, fmtstring);
  66. };
  67. } else {
  68. return [=](logpacket const & lp) {
  69. return fmt_time(lp.time, fmtstring);
  70. };
  71. }
  72. }
  73. #define is( chr ) *next == chr
  74. #define is_string( str ) ! strncmp(next, str, strlen(str))
  75. string_generator date_token(char const * next) {
  76. std::string predef_format = "%%Y-%%m-%%d %%H:%%M:%%S,%.03d";
  77. if ( is_string("{ISO8601}")) {
  78. predef_format = "%%Y-%%m-%%dT%%H:%%M:%%S.%.03dZ";
  79. } else if (is_string("{ABSOLUTE}")) {
  80. predef_format = "%%H:%%M:%%S,%.04d";
  81. } else if (is_string("{DATE}")) {
  82. predef_format = "%%d %%b %%Y %%H:%%M:%%S,%.04d";
  83. } else if (is('{')) {
  84. return parse_date_format_string(next+1);
  85. }
  86. return [=](logpacket const & lp ){
  87. return fmt_time_with_milis(lp.time, predef_format);
  88. };
  89. }
  90. string_generator string_token(std::string str) {
  91. return [=]( logpacket const & ){
  92. return str;
  93. };
  94. }
  95. string_generator handle( char const * & next ) {
  96. if (is('c')) {
  97. return [](logpacket const & lp){
  98. return lp.logger;
  99. };
  100. } else if (is('p')) {
  101. return [](logpacket const & lp){
  102. return level_header(lp.level);
  103. };
  104. } else if (is('m')) {
  105. return [](logpacket const & lp){
  106. return lp.message;
  107. };
  108. } else {
  109. std::string error_msg{"unknown format character: '"};
  110. throw unknown_format_specifier{error_msg + *next + "'"};
  111. }
  112. }
  113. format::generator parse_with_bounds( char const * & next ) {
  114. bool const is_left = *next == '-';
  115. auto align = is_left ? std::left : std::right;
  116. bool const trunc = *next == '.';
  117. if ( is_left || trunc ) ++next;
  118. string_generator gen;
  119. size_t chars = 0;
  120. int min = std::stoi( next, &chars );
  121. size_t max = 0xFFFFFFFF;
  122. next += chars;
  123. if ( trunc ) {
  124. max = min;
  125. min = 0;
  126. } else if ( *next == '.' ) {
  127. max = std::stoi( next + 1, &chars );
  128. next += chars + 1;
  129. }
  130. gen = handle( next );
  131. return [=](logpacket const & lp, std::ostream & out) {
  132. std::string str = gen( lp );
  133. if ( str.length() > max )
  134. str.erase(str.begin() + max);
  135. out << align << std::setw( min ) << str;
  136. };
  137. }
  138. format::generator convert( string_generator gen ) {
  139. return [=](logpacket const & lp, std::ostream & out) {
  140. out << gen( lp );
  141. };
  142. }
  143. format format::parse_format_string( std::string const & fmt ) {
  144. format out;
  145. char const * curr = fmt.c_str();
  146. char const * next = nullptr;
  147. char const * const end = curr + fmt.size();
  148. while ((next = std::strchr(curr, '%')) != nullptr) {
  149. ++next;
  150. if (end == next) {
  151. std::string error_msg{"expected format specifier, got end of string"};
  152. throw format_parsing_exception{error_msg}; // TODO
  153. }
  154. if (curr < next-1) {
  155. out.gen.push_back(convert(string_token({curr, next - 1})));
  156. }
  157. if (is('d')) {
  158. ++next;
  159. out.gen.push_back(convert(date_token(next)));
  160. if (is('{')) next = std::strchr(next, '}');
  161. } else if (is('n')) {
  162. out.gen.push_back(convert(string_token(NEWLINE)));
  163. } else if (is('%')) {
  164. out.gen.push_back(convert(string_token("%")));
  165. } else if (is('.') || is('-') || isdigit( *next )) {
  166. parse_with_bounds( next );
  167. } else {
  168. handle( next );
  169. }
  170. curr = ++next;
  171. }
  172. if (curr < end) {
  173. out.gen.push_back(convert(string_token({curr, end})));
  174. }
  175. return out;
  176. }
  177. #undef is
  178. void format::process(logpacket const & pkt, std::ostream & os) const {
  179. for (auto func : gen) { func(pkt, os); }
  180. }
  181. std::string format::process(logpacket const & pkt) const {
  182. std::stringstream ss;
  183. process(pkt, ss);
  184. return ss.str();
  185. }
  186. }
  187. namespace logging {
  188. format_point_t get_next_format_specifier(std::string const & interp,
  189. size_t pos) {
  190. size_t next = interp.find("{}", pos);
  191. return { next, next + 2 };
  192. }
  193. }