format.cpp 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. //
  2. // format.cpp
  3. // logger
  4. //
  5. // Created by Sam Jaffe on 8/21/16.
  6. //
  7. #include <cstdint>
  8. #include <cstring>
  9. #include <ctime>
  10. #include <iomanip>
  11. #include <iostream>
  12. #include <map>
  13. #include <stdexcept>
  14. #include <string>
  15. #include "format.hpp"
  16. #include "logger.hpp"
  17. #if defined( _WIN32 )
  18. # define NEWLINE "\r\n"
  19. #else
  20. # define NEWLINE "\n"
  21. #endif
  22. namespace logging {
  23. class format_parsing_exception : public std::logic_error {
  24. public:
  25. using std::logic_error::logic_error;
  26. };
  27. class unknown_format_specifier : public std::logic_error {
  28. public:
  29. using std::logic_error::logic_error;
  30. };
  31. }
  32. namespace logging {
  33. namespace {
  34. std::string fmt_time(struct timeval round, char const * const fmt) {
  35. struct tm time;
  36. gmtime_r(&round.tv_sec, &time);
  37. char buf[64] = {'\0'};
  38. std::strftime(buf, sizeof(buf), fmt, &time);
  39. return buf;
  40. }
  41. std::string fmt_time(struct timeval round, std::string const & fmt) {
  42. return fmt_time(round, fmt.c_str());
  43. }
  44. std::string fmt_time_with_milis(struct timeval round, std::string const & fmt) {
  45. char buf[64] = {'\0'};
  46. snprintf(buf, sizeof(buf), fmt.c_str(), round.tv_usec);
  47. return fmt_time(round, buf);
  48. }
  49. }
  50. using string_generator = std::function<std::string(logpacket const &)>;
  51. string_generator parse_date_format_string( char const * str ) {
  52. char const * const end = strchr(str, '}');
  53. if (end == nullptr)
  54. throw format_parsing_exception{"expected date-format specifier to terminate"};
  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"), 4, "%.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;
  77. if ( is_string("{ISO8601}")) {
  78. predef_format = "%%Y-%%m-%%d %%H:%%M:%%S,%.03d";
  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);
  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. throw unknown_format_specifier{std::string("unknown format character: '") + *next + "'"};
  110. }
  111. }
  112. format::generator parse_with_bounds( char const * & next ) {
  113. bool const is_left = *next == '-';
  114. auto align = is_left ? std::left : std::right;
  115. bool const trunc = *next == '.';
  116. if ( is_left || trunc ) ++next;
  117. string_generator gen;
  118. size_t chars = 0;
  119. int min = std::stoi( next, &chars );
  120. size_t max = 0xFFFFFFFF;
  121. next += chars;
  122. if ( trunc ) {
  123. max = min;
  124. min = 0;
  125. } else if ( *next == '.' ) {
  126. max = std::stoi( next + 1, &chars );
  127. next += chars + 1;
  128. }
  129. gen = handle( next );
  130. return [=](logpacket const & lp, std::ostream & out) {
  131. std::string str = gen( lp );
  132. if ( str.length() > max )
  133. str.erase(str.begin() + max);
  134. out << align << std::setw( min ) << str;
  135. };
  136. }
  137. format::generator convert( string_generator gen ) {
  138. return [=](logpacket const & lp, std::ostream & out) {
  139. out << gen( lp );
  140. };
  141. }
  142. format format::parse_format_string( std::string const & fmt ) {
  143. format out;
  144. char const * curr = fmt.c_str();
  145. char const * next = nullptr;
  146. char const * const end = curr + fmt.size();
  147. while ( ( next = std::strchr( curr, '%' ) + 1 )
  148. != nullptr ) {
  149. if ( end == next ) {
  150. throw format_parsing_exception{"expected format specifier, got end of string"}; // TODO
  151. }
  152. if ( curr < next - 1 ) {
  153. out.gen.push_back(convert(string_token({curr, next - 1})));
  154. }
  155. if ( is('d') ) {
  156. ++next;
  157. if ( is('{') ) curr = std::strchr(next, '}');
  158. out.gen.push_back(convert(date_token(next)));
  159. } else if ( is('n') ) {
  160. out.gen.push_back(convert(string_token(NEWLINE)));
  161. } else if ( is('%') ) {
  162. ++next;
  163. out.gen.push_back(convert(string_token("%")));
  164. } else if ( is('.') || is('-') ||
  165. 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. }
  179. namespace logging {
  180. format_point_t get_next_format_specifier( std::string const & interp, size_t pos ) {
  181. size_t next = pos;
  182. char const * str = interp.c_str();
  183. do {
  184. pos = interp.find("{}", next);
  185. next = pos + 2;
  186. } while ( pos > 0 && pos != std::string::npos &&
  187. ! strncmp(str + pos - 1, "{{}}", 4) );
  188. return { pos, next };
  189. }
  190. }